Tri-Linear MIP Mapping | 26.april.1996 | GFX |
by Hin Jang
The two main problems with texture mapping are the following:
#include <stdlib.h> #define MAX_LEVELS 8 #define MAX_RES 256 typedef unsigned char Mip_Level; typedef struct pixel { unsigned char r, g, b; } Pixel; typedef struct mipmap { Pixel *p; Mip_Level m; unsigned int res, texture_id; } MIP; void Create_MIPMapLevel(MIP *prev, MIP *new, Mip_Level level) { unsigned int i, ne, nw, sw, se, height, width, step; new->m = level; new->res = MAX_RES >> level; new->p = (Pixel *)calloc(new->res * new->res, sizeof(Pixel)); i = 0; nw = 0; sw = nw + prev->res; se = sw + 1; ne = nw + 1; height = width = 0; step = prev->res << 1; while (height++ < new->res) { while (width++ < new->res) { new->p[i].r = (prev->p[ne].r + prev->p[nw].r + prev->p[sw].r + prev->p[se].r) >> 2; new->p[i].g = (prev->p[ne].g + prev->p[nw].g + prev->p[sw].g + prev->p[se].g) >> 2; new->p[i].b = (prev->p[ne].b + prev->p[nw].b + prev->p[sw].b + prev->p[se].b) >> 2; nw += 2; sw += 2; se += 2; ne += 2; i++; } nw += step; sw += step; se += step; ne += step; } }The rendering algorithm selects the appropriately sized sub-texture to cover the designated screen space. If, for example, the texture needs to cover 1024 pixels, the 32 × 32 is chosen. In most instances, however, the texture pyramid will not have a sub-texture of such exact dimensions for a given screen space. Suppose a texture must occupy 2000 pixels; about halfway between 32 × 32 = 1024 and 64 × 64 = 4096. The algorithm will need to interpolate between two sub-textures. This is where the tri-linear part of mip-mapping comes into play.
+-----+-----+-----+-----+ +-----------+-----------+ | | | | | | | | | | | | | | | | +-----+-----+-----+-----+ | | | | | | | | | | | | | X | | | | X | | +-----+-----+-----+-----+ +-----------+-----------+ | | | | | | | | | | | | | | | | +-----+-----+-----+-----+ | | | | | | | | | | | | | | | | | | | +-----+-----+-----+-----+ +-----------+-----------+ 64 × 64 texture 32 × 32 textureFor the sample point X in the higher resolution texture (ie. 64 × 64), the algorithm bilinearly interpolates the four texels to get the correct colour value Ipix where
Ipix = (1 - v)[(1 - u)i0 + ui1] + v[(1 - u)i3 + ui2] 0 <= a <= 0.5 0 <= b <= 0.5The value of the sample in the lower resolution texture (ie. 32 × 32) is similarily defined by four texels. The third interpolation is between the two samples. This blending from high-res to the lower-res gives rise to tri-linear interpolation.
[1] Heckbert, P.S., "Filtering by Repeated Integration," Computer Graphics, SIGGRAPH 1986 Proceedings, 20(4):315-321[2] Williams, L., "Pyramidal Parametrics," Computer Graphics, SIGGRAPH 1983 Proceedings, 17(4):1-11
.
Digital Line Drawing | 03.july.1996 | GFX |
by Hin Jang
revised on 01.august.1999
A different formulation of the classic line-drawing algorithm developed by Jack Bresenham is described below [4, 6]. Assume the line's slope is between 0 and 1. Other slopes are handled by appropriate reflections about the principle axes.
For a selected pixel P at (xp, yp) the algorithm must choose between the pixel one increment to the right (called the east pixel, E) or the pixel one increment to the right and one increment up (called the northeast pixel, NE). The decision is based on the sign of the decision variable d which is updated by some value depending on the choice of the pixel. The value of d is derived from rearranging the slope-intercept form of the line
y = mx + bto yield an implicit function in the form F(x, y) = ax + by + c = 0.
d = F(x, y) = dy * x - dx * y + B * dx = 0where a = dy, b = -dx, and c = B * dx. The choice between pixel E and pixel NE depends on the sign of F(xp + 1, yp + 1/2). If d is greater than zero, pixel NE is chosen, otherwise pixel E is chosen.
dnew = F(xp + 2, yp + 3/2) = a(xp + 2) + b(yp + 3/2) + cbutdold = a(xp + 1) + b(yp + 1/2) + cSubtracting dold from dnew givesdnew = dold + a + bThe value of d is incremented by dy - dx.
dnew = F(xp + 2, yp + 1/2) = a(xp + 2) + b(yp + 1/2) + cSubtracting dold (as in CASE 1) from dnew givesdnew = dold + aThe value of d is therefore incremented by dy.
dinitial = F(x0 + 1, y0 + 1/2) = a(x0 + 1) + b(y0 + 1/2) + c = ax0 + by0 + c + a + b/2 = F(x0, y0) + a + b/2Since (x0, y0) is on the line and F(x0, y0) is zero on the line, dinitial = a + b/2 = dy - dx/2. The fraction is eliminated by redefining F by multiplying it by 2 as shown in here. Bresenham's line drawing algorithm is also available.
The above integer line-drawing routines generate only one pixel per inner loop. An obvious extension would be to generate more pixels to reduce inner loop overhead. Bresenham later developed a more efficient algorithm [2]. Computer graphics research yielded similar optimisations, one of which exploited the observation that some pixel sequences occur more often than others [3]. Within the past decade, two methods exploited parallelism in computer architecture. The double-step algorithm and its decendent the symmetric double-step algorithm, in particular, yield a theoretical speed-up by a factor of four as compared to the original Bresenham algorithm [5].
// let P1 = (x1, y1) // P2 = (x2, y2) // void Symmetric_DoubleStep(int x1, int y1, int x2, int y2) { int c, d, dx, dy, xend, leftover, incr1, incr2; dx = x2 - x1; dy = y2 - y1; xend = (int)((dx - 1) / 4); leftover = (dx - 1) % 4; incr2 = 4 * dy - 2 * dx; PutPixel(x1, y1); PutPixel(x2, y2); c = 2 * dy; incr1 = 2 * c; d = incr1 - dx; while (xend--) { x1++; x2--; if (d < 0) { // draw pattern one forward // draw pattern one backward PutPixel(x1++, y1); PutPixel(x1++, y1); PutPixel(x1, y1); PutPixel(x2--, y2); PutPixel(x2--, y2); PutPixel(x2, y2); d += incr1; } else { if (d < c) { if (d == 0) { // draw pattern two forward // draw pattern one backward PutPixel(x1++, y1); PutPixel(x1++, y1++); PutPixel(x1, y1); PutPixel(x2--, y2); PutPixel(x2--, y2); PutPixel(x2, y2); } else { // draw pattern two forward // draw pattern two backward PutPixel(x1++, y1); PutPixel(x1++, y1++); PutPixel(x1, y1); PutPixel(x2--, y2--); PutPixel(x2--, y2); PutPixel(x2, y2); } } else if (d == c) { // draw pattern three forward // draw pattern two backward PutPixel(x1++, y1++); PutPixel(x1++, y1); PutPixel(x1, y1); PutPixel(x2--, y2--); PutPixel(x2--, y2); PutPixel(x2, y2); } else { // draw pattern three forward // draw pattern three backward PutPixel(x1++, y1++); PutPixel(x1++, y1); PutPixel(x1, y1); PutPixel(x2--, y2); PutPixel(x2--, y2--); PutPixel(x1, y1); } d += incr2; } } if (leftover > 0) { PutPixel(x1++, y1); PutPixel(x1, y1); PutPixel(x2, y2); } }The pixel patterns are shown below. Forward patterns are followed from left to right in the direction toward P2(x2, y2). Backward patterns are followed from right to left in the direction toward P1(x1, y1).
| | | | | | | | | --+---+---+-- --+---+---+-- --+---+---+-- | | | | | | | | | --+---+---+-- --+---+---O-- --+---O---O-- | | | | | | | | | --O---O---O-- --O---O---+-- --O---+---+-- | | | | | | | | | pattern one pattern two pattern three
[1] Bresenham, J.E., "Algorithm for Computer Control of a Digital Plotter," IBM Systems Journal, 4(1):25-30, 1965[2] Bresenham, J.E., "Run Length Slice Algorithm for Incremental Lines," Fundemental Algorithms for Computer Graphics, R.A. Earnshaw, ed., Springer-Verlag, New York, 1985
[3] Gill, G.W., "N-Step Incremental Straight-Line Algorithms," IEEE Computer Graphics and Applications, 14(3):66-72, May 1994
[4] Pitteway, M.L.V., "Algorithm for Drawing Ellipses or Hyperbolae with a Digital Plotter," Computer Journal, 10(3):282-289, November 1967
[5] Rokne, J.G., B. Wyvill, and X. Wu, "Fast Line Scan-Conversion," ACM Transactions on Graphics, 9(4):376-388, October 1990
[6] Van Aken, J.R., and M. Novak, "Curve-Drawing Algorithms for Raster Displays," ACM Transactions on Graphics, 4(2):147-169, `April 1985
.
Optimising BSP Tree Traversal | 02.june.1996 | GFX |
by Hin Jang
The way in which the environment is partitioned is important with regard to the algorithm's efficiency of determining which objects reside within the view frustum. A common approach is to select a polygon as the partitioning plane. Each node of the tree will therefore contain a description of a plane. Although calculating the camera position with respect to a plane is a simple dot product test, these calculations may become rather costly for an environment consisting of several thousand polygons.
One method to increase the speed of tree traversal is assigning each partition plane as being parallel to one of the three principle axes. Using axis-aligned partitions eliminates the need of any dot product testing; you simply need to compare two numbers. The result of this subdivision is called a bintree [5]. All internal nodes of this tree will contain the following fields:
Building a tree in this way, while using it to help you in 3D culling, requires a variant of a preorder tree traversal. There is an added complexity to guarentee a front-to-back ordering of objects regardless of the camera's location. Given that object descriptions are at the leaves (external nodes), the following pseudocode outlines a preorder BSP tree traversal with proper subtree culling.
/* -------------------------------------------- recursive */ Traverse() { if (node_is_not_null) { if (node_is_a_leaf) visit_node() else { if (camera_in_front_of_partition) { if (view_frustum_intersects_partition) { Traverse(front_child) Traverse(back_child) } else Traverse(front_child) } else { if (view_frustum_intersects_partition) { Traverse(back_child) Traverse(front_child) } else Traverse(back_child) } } } } /* -------------------------------------------- iterative */ Traverse() { initialise_stack() push(root) while (stack_not_empty) { pop() if (node != NULL) { if (node_is_a_leaf) visit_node() else { if (camera_in_front_of_partition) { if (view_frustum_intersects_partition) push(back_child) push(front_child) } else { if (view_frustum_intersects_partition) push(front_child) push(back_child) } } } } }Because all partitions are axis-aligned, the 'camera_in_front_of_partition' test may be written as:
if (Camera->location[Node->axis] >= Node->origin)which is probably less expensive to compute than a dot product calculation in R3.
Any benefits of this bintree data structure, however, may be offset by an increased chance that axis-aligned partitions will yield more polygon splitting. This will cause an increase in the number of nodes that must be traversed and therefore more objects to be processed for any given scene.
[1] Fuchs, H., Z.M. Kedem and B.F. Naylor, "On Visible Surface Generation by a Priori Tree Structure," Computer Graphics, 14(3):124-133, July 1980[2] Miller, T., Hidden Surfaces: Combining BSP Trees with Graph-Based Algorithms, CS-96-15, Department of Computer Science, Brown University, April 1996
[3] Naylor, B.F., A Priori Based Technique for Determining Visibility Priority for 3-D Scenes, Ph.D. Dissertation, University of Texas at Dallas, May 1981
[4] Schumacker, R.A., B. Brand, M.G., Gilliland, and W.H. Sharp, "Study for Applying Computer-Generated Images to Visual Simulation," Technical Report AFHRL-TR-69-14, NTIS AD700375fR, U.S. Air Force Human Resources Lab., Air Force Systems Command, Brooks AFB, TX, September 1969
[5] Tamminen, M., and H. Samet, "Efficient Octree Conversion by Connectivity Labeling," Computer Graphics, SIGGRAPH 1984 Proceedings, 43-51
[6] Thibault, W.C., and B.F. Naylor, "Set Operations on Polyhedra Using Binary Space Partitioning Trees," Computer Graphics, 21(4):153-162, July 1987