.
Tri-Linear MIP Mapping 26.april.1996 GFX

by Hin Jang

The two main problems with texture mapping are the following:

One way to avoid these problems is to use tri-linear MIP (multum in parvo — many things in a small place) mapping. This approach works by successively averaging the texels until a single value is generated. Given a 2n × 2n texture, where n is an integer and greater than zero, averaging the four texel regions yields a 2n-1 × 2n-1 image. The process continues recursively to generate sub-textures of decreasing resolution. The 20 × 20 = 1 × 1 texture is an average of all texels in the source image. These sub-textures are precomputed and stored with the original texture. The resulting texture pyramid requires 1.33 times the space of the original texture map.

   #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 texture
For 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.5
   
The 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 + b

to yield an implicit function in the form F(x, y) = ax + by + c = 0.

   d = F(x, y) = dy * x - dx * y + B * dx = 0

where 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. The initial value of d is given by
   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/2
Since (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:

All external (leaf) nodes contain either a polyhedron description or the root of a tiny BSP tree [6]. At level 0 of the bintree, select an x-value that satisfies at least one of the following criteria: At level 1 of the tree, select a y-value that satisfies the same criterion or criteria; and at level 2, a z-value. Continue this subdivision process (x,y,z,x,y,z...) until some predefined resolution has been reached. If you choose to split the environment, E, into equal sized volumes, you have, in a sense, subdivided E into cubes. Empty volumes, if any, should be merged with partially filled volumes.

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