hello_world_logo.gif (12859 bytes) maple_leaf.gif (1004 bytes)

This could be your company's banner advertisement

April 1999

Issue 2, Volume 1

About the Author

Hin Jang
is a computer science student at Ryerson Polytechnic University.

ryerson_crest.gif (2651 bytes)

Hello World!
Editorial
News
Comics
Events
 
Sponsors
 
Hello World! Teams:
  Editors
  Sponsorship
  Site Design
 
Member Universities
 
Past Issues
 
Contact Us
   
   

Site hosting provided by
smarttbutton.gif (2795 bytes)

   

Rendering Bézier Forms via Forward Differencing

-- Hin Jang

Forthcoming game engines will render, as its base primitive, curves and non-planar surfaces. One representation is the Bézier form, named after French mathematician Pierre Bézier. Herein is a very brief review of these entities primarily used in computer-aided geometric design for the past thirty years. Following this is an introduction to the method of evaluation known as forward differencing and its application to rendering curves and surfaces.

A Bézier curve of degree n is defined as

             n
            ----
   p(t)  =  \     pi Bi,n(t)
            /
            ----
            i=0

	  0 <= t <= 1

  

The points pi form the Bézier control polygon. The Berstein polynomials are

                  n!
   Bi,n(t)  =  ----------  (1 - t)n - i ti
               i!(n - i)!

  

With cubic curves (n = 3), for example,

   B0,3  =  -t3 + 3t2 - 3t + 1
   B1,3  =   3t3 - 6t2 + 3t
   B2,3  =  -3t3 + 3t2
   B3,3  =   t3

  

The expression for p(t) is then

   p(t)  =  (1 - t)3p0  +  3t(1 - t)2p1  +  3t2(1 - t)p2  +  t3p3

  

A tensor product Bézier surface p(s, t) of degree m by n is defined as

                m      n
               ----   ----
   p(s, t)  =  \      \      pij Bi,m(s) Bj,n(t)
               /      /
               ----   ----
               i=0    j=0

   0 <= s <= 1
   0 <= t <= 1

  

The points pi j = (x, y, z)ij form a Bézier control net, the vertices of the characteristic polyhedron that surrounds the surface. The Berstein polynomials Bi, m(s) and Bj, n(t) are defined as for Bézier curves. For a bicubic surface, the expression is

   p(s, t)  =  SPT

  where  
   S  =  [(1 - s)3  3s(1 - s)2  3s2(1 - s)  s3]

         | p00  p01  p02  p03 |
   P  =  | p10  p11  p12  p13 |
         | p20  p21  p22  p23 |
         | p30  p31  p32  p33 |

         | (1 - t)3   |
   T  =  | 3t(1 - t)2 |
         | 3t2(1 - t) |
         | t3         |

  

Varying the entries of the control net P deforms the underlying surface, and thus allowing a high degree of shape modification.

Foward Differencing

This method of evaulating a polynomial was known since the time of Isacc Newton and is well-suited for a variety of rendering contexts. Suppose

   y  =  f(x)

  

represents a function that is differentiable in an open interval containing x. The differential of y is

   dy  =  f'(x)dx

  

where dx is the differential of x. if f(x) is known, then f(x + k) can be approximated via differentials where

   f(x + k)  ~=  f(x) + dy  =  f(x) + f'(x)dx

   k > 0
  

Within the context of computer graphics programming, the forward difference Df(t) of a function f(t) is

   Df(t)  =  f(t + k) - f(t)

  so  
   f(t + k)  =  f(t) + Df(t)

  in iterative terms,  
   fn+1  =  fn + Dfn

  

Evaluating successive function values using the forward difference(s) is known as forward differencing.

Quadratic Bézier Curves

Bézier curves of degree two (i.e., quadratic) are the simplest in the family of parametric curves. Level-of-detail control involves, partially, the determination of the ideal resolution at which the following equation is evaluated for a given scene

   p(t)  =  (1 - t)2p0  +  2t(1 - t)p1  +  t2p2

  as derived from the general form  
             n
            ----
   p(t)  =  \     pi Bi,n(t)
            /
            ----
            i=0

   0 <= t <= 1

  with blending functions  
                  n!
   Bi,n(t)  =  ----------  (1 - t)n - i ti
               i!(n - i)!
   

  

Forward differencing or some hybrid of such a method will most likely be used evaluate the network of Bézier curves that populate a virtual environment. For a quadratic Bézier curve p(t), then,


    p(t)  =  (1 - t)2p0  +  2t(1 - t)p1  +  t2p2
          =  (t2 - 2t + 1)p0  +  (-2t2 + 2t)p1  +  t2p2

   Dp(t)   =  p(t + k)  -  p(t)
           =  ((t + k)2 - 2(t + k) + 1)p0  + (-2(t + k)2 + 2(t + k))p1  +
              (t + k)2p2  -  [(t2 - 2t + 1)p0  +  (-2t2 + 2t)p1  +  t2p2]
           =  (2tk + k2 - 2k)p0  +  (-4tk - 2k2 + 2k)p1  +  (2tk + k2)p2

   D2p(t)  =  Dp(t + k)  -  Dp(t)
           =  (2(t + k)k + k2 - 2k)p0  +  (-4(t + k)k - 2k2 + 2k)p1  +
              (2(t + k)k + k2)p2 - [(2tk + k2 - 2k)p0 + (-4tk - 2k2 + 2k)p1 +
              (2tk + k2)p2]
           =  2k2p0  -  4k2p1  +  2k2p2

  

at initial condition t = 0 with k = STEP = 0.1


     p(0)  =  p0

    Dp(0)  =  (k2 - 2k)p0  +  (-2k2 + 2k)p1  +  k2p2
           =  -0.19p0  +  0.18p1  +  0.01p2

   D2p(0)  =  2k2p0  -  4k2p1  +  2k2p2
           =  0.02p0  -  0.04p1  +  0.02p2


  

Thus, an implementation of forward differencing to render p(t) would be

   #include <stdio.h>
   #define STEP   0.1      // range = (0, 1]

   typedef struct point   Point;

   struct point {
      float x, y, z;
   };


   //
   // entries are valid for STEP equal to 0.1

   static float M[3][3] = {
      {  1.00,  0.00, 0.00 },
      { -0.19,  0.18, 0.01 },
      {  0.02, -0.04, 0.02 },
   };

   int main(int args, char *argv[]) {

      FILE    *fp;
      Point   p[3];      // control points
      int     i;

      if (args < 2) {
         fprintf(stderr, "usage: %s <filename>", argv[0]);
         exit(1);
      }

      if ((fp = fopen(argv[1], "r")) == NULL) {
         fprintf(stderr, "%s not found\n", argv[1]);
         exit(1);
      }

      i = 0;


      //
      // the call to fscanf() assumes the datafile consists of
      // three rows of three floating point numbers

      while (fscanf(fp, "%f %f %f", &p[i].x, &p[i].y, &p[i].z) != EOF)
         i++;

      Render_Quadratic_BezierCurve(p);

      return 0;
   }


   //
   // replace fprintf() with additional code or appropriate
   // function calls

   void Render_Quadratic_BezierCurve(Point p[3]) {

      float   x, y, z, dx, dy, dz, ddx, ddy, ddz, t;

      x   = p[0].x;
      y   = p[0].y;
      z   = p[0].z;

      dx  = M[1][0]*p[0].x  +  M[1][1]*p[1].x  +  M[1][2]*p[2].x; 
      dy  = M[1][0]*p[0].y  +  M[1][1]*p[1].y  +  M[1][2]*p[2].y; 
      dz  = M[1][0]*p[0].z  +  M[1][1]*p[1].z  +  M[1][2]*p[2].z; 

      ddx = M[2][0]*p[0].x  +  M[2][1]*p[1].x  +  M[2][2]*p[2].x; 
      ddy = M[2][0]*p[0].y  +  M[2][1]*p[1].y  +  M[2][2]*p[2].y; 
      ddz = M[2][0]*p[0].z  +  M[2][1]*p[1].z  +  M[2][2]*p[2].z; 

      fprintf(stdout, "(%f, %f, %f)\n", x, y, z);

      for (t = 0.0; t <= 1.0; t += STEP) {

         x += dx; dx += ddx;
         y += dy; dy += ddy;
         z += dz; dz += ddz;

         fprintf(stdout, "(%f, %f, %f)\n", x, y, z);

      }
   }

Although efficient, Render_Quadratic_BezierCurve( ) can be optimised further. Regardless of the geometry of the curve, p(t) is always evaluated at fixed increments of size STEP. ideally, however, we want

   || p(t)  -  p(t + k) ||  >>  0

   

Satisfying this condition for all sample points for all quadratic Bézier curves p(t) requires that k be a function of curvature. in other words, the curve is sampled at non-fixed intervals of the paramteric variable. an adaptive foward differencing algorithm evaluates p(t) at non-fixed intervals. To appreciate the efficiency of forward differencing consider the following table for one hundred samples (k = 0.01) of the quadratic Bézier curve p(t).

summary of arithmetic operations
method setup rendering
multiply add multiply add
direct evaluation 0 0 1300 700
forward differencing 18 12 0 600

The computational effort for transformation, rasterisation, and the loop increment and testing is not included in these figures. In general, forward differencing performs dn additions per sample for a d-dimensional Bézier curve of degree n.

Quadratic Bézier Surfaces

There are several ways to render a Bézier surface q(s, t), all of which are analogous to the one-parameter case: a simple curve. The setup, however, is more complex. Recall that the forward difference equations for the quadratic curve p(t) at initial condition t = 0 with k = STEP = 0.1 are


     p(0)  =  p0

    Dp(0)  =  (k2 - 2k)p0  +  (-2k2 + 2k)p1  +  k2p2
           =  -0.19p0  +  0.18p1  +  0.01p2

   D2p(0)  =  2k2p0  -  4k2p1  +  2k2p2
           =  0.02p0  -  0.04p1  +  0.02p2


  or, as a matrix product  

         |  1.00  0.00  0.00 | | p0 |
   D  =  | -0.19  0.18  0.01 | | p1 |
         |  0.02 -0.04  0.02 | | p2 |

  

The second and third rows of D are the forward differences. In the case of the quadratic surface q(s, t) with the same initial condition and step size in s and t


         |  1.00  0.00  0.00 | | p00 p01 p02 | | 1.00 -0.19  0.02 |
   D  =  | -0.19  0.18  0.01 | | p10 p11 p12 | | 0.00  0.18 -0.04 |
         |  0.02 -0.04  0.02 | | p20 p12 p22 | | 0.00  0.01  0.02 |

      =  S . P . T

  where P is the matrix of control points.  

The first row of matrix D now has the values p(0), Dp(0), D2p(0) in t. the other rows of D contain the first and second forward differences in s of the forward differences in the first row. The matrix T is the transpose of S. with matrix D defined as such, an algorithm to render q(s, t) is


   k = 1.0 / (maximum number of steps along t = 0 and s = 0)

   X = x-coordinates of the control points
   Y = y-coordinates of the control points
   Z = z-coordinates of the control points

   px = S * X * T
   py = S * Y * T
   pz = S * Z * T

   //
   // points along constant t, step size = k

   for (0.0 <= s <= 1.0) {

      Xd = forward differences for x-coordinate = first row of px
      Yd = forward differences for y-coordinate = first row of py
      Zd = forward differences for z-coordinate = first row of pz

      //
      // points along constant s, step size = k

      for (0.0 <= t <= 1.0) {

         generate points using differences Xd, Yd and Zd

      }

      update first and second rows of px
      update first and second rows of py
      update first and second rows of pz

   }

  

An actual implementation of this algorithm is shown below. The context in which the code was written values modularity higher than efficieny. As such, for real-time applications, a degree of optimisation is recommended. Note also that the order in which the points are generated does not lead to an efficient means to send triangle strips to the graphics subsystem. Modification of the inner loop is therefore required.

   //
   // written by hin: 19990114

   #include <stdio.h>

   #define STEP   0.1

   //
   // entries are valid for STEP = 0.1

   static float S[3][3] = {
      {  1.00,  0.00,  0.00 },
      { -0.19,  0.18,  0.01 },
      {  0.02, -0.04,  0.02 },
   };

   //
   // transpose of matrix S

   static float T[3][3] = {
      {  1.00, -0.19,  0.02 },
      {  0.00,  0.18, -0.04 },
      {  0.00,  0.01,  0.02 },
   };

   int main(int args, char *argv[]) {

      FILE    *fp;
      float   X[3][3], Y[3][3], Z[3][3];  // control points
      int     i, j;

      if (args < 2) {
         fprintf(stderr, "usage: %s <filename>", argv[0]);
         exit(1);
      }

      if ((fp = fopen(argv[1], "r")) == NULL) {
         fprintf(stderr, "%s not found\n", argv[1]);
         exit(1);
      }


      //
      // the call to fscanf() assumes the datafile consists of nine
      // (x, y, z) triplets in row major order

      for (j = 0; j < 3; j++)
         for (i = 0; i < 3; i++)
            (void)fscanf(fp, "%f %f %f", &X[i][j], &Y[i][j], &Z[i][j]);

      Render_Quadratic_BezierSurface(X, Y, Z);

      return 0;
   }


   //
   // replace fprintf() with additional code or appropriate
   // function calls.

   void Render_Quadratic_BezierSurface(float X[][3],
                                       float Y[][3],
                                       float Z[][3]) {

      register float   x, y, z, dx, dy, dz, ddx, ddy, ddz, s, t;
      float            xp[3][3], yp[3][3], zp[3][3], tp[3][3];

      MatrixMultiply(X, T,  tp);
      MatrixMultiply(S, tp, xp);

      MatrixMultiply(Y, T,  tp);
      MatrixMultiply(S, tp, yp);

      MatrixMultiply(Z, T,  tp);
      MatrixMultiply(S, tp, zp);

      for (s = 0.0; s <= 1.0; s += STEP) {

         x = xp[0][0];
         y = yp[0][0];
         z = zp[0][0];

         dx = xp[0][1];
         dy = yp[0][1];
         dz = zp[0][1];

         ddx = xp[0][2];
         ddy = yp[0][2];
         ddz = zp[0][2];

         for (t = 0.0; t <= 1.0; t += STEP) {

            // note:  any system that renders a surface is in
            //        fact sending triangle vertex data to the
            //        graphics subsystem.  the reason for this
            //        is purely a matter of resources.  to
            //        faithfully sample a continuous function on a
            //        closed interval would require extremely high
            //        sample densities and hence large data volumes
            //        and processing capacity.  it is therefore
            //        advantageous to reduce the sample density and
            //        compensate for the artifacts in favour of
            //        interactive rates of display.

            fprintf(stdout, "(%f, %f, %f)\n", x, y, z);

            x += dx; dx += ddx;
            y += dy; dy += ddy;
            z += dz; dz += ddz;

         }

         AddRows(xp);
         AddRows(yp);
         AddRows(zp);

      }
     
   }


   //
   // c = a * b

   void MatrixMultiply(float a[][3], float b[][3], float c[][3]) {

      c[0][0] = a[0][0]*b[0][0] + a[0][1]*b[1][0] + a[0][2]*b[2][0];
      c[0][1] = a[0][0]*b[0][1] + a[0][1]*b[1][1] + a[0][2]*b[2][1];
      c[0][2] = a[0][0]*b[0][2] + a[0][1]*b[1][2] + a[0][2]*b[2][2];
      c[1][0] = a[1][0]*b[0][0] + a[1][1]*b[1][0] + a[1][2]*b[2][0];
      c[1][1] = a[1][0]*b[0][1] + a[1][1]*b[1][1] + a[1][2]*b[2][1];
      c[1][2] = a[1][0]*b[0][2] + a[1][1]*b[1][2] + a[1][2]*b[2][2];
      c[2][0] = a[2][0]*b[0][0] + a[2][1]*b[1][0] + a[2][2]*b[2][0];
      c[2][1] = a[2][0]*b[0][1] + a[2][1]*b[1][1] + a[2][2]*b[2][1];
      c[2][2] = a[2][0]*b[0][2] + a[2][1]*b[1][2] + a[2][2]*b[2][2];

   }


   //
   // row0 += row1
   // row1 += row2

   void AddRows(float m[][3]) {

      m[0][0] += m[1][0];
      m[0][1] += m[1][1];
      m[0][2] += m[1][2];
      m[1][0] += m[2][0];
      m[1][1] += m[2][1];
      m[1][2] += m[2][2];

   }


further reading

Bernstein, S., Démonstration du théoreme de Weierstrass fondeé sur le calcul des probabilités. Harkov Soobs. Matem ob-va, 13:1-2, 1912

Bézier, P., Procédé de définition numérique des courbes et surfaces non mathématiques. Automatisme, XIII(5):189-196, 1968

Bézier, P., Mathematical and Practical Possibilities of UNISURF. In R. Barnhill and R. Riesenfeld, editors, Computer Aided Geometric Design, Academic Press, 127-152, 1974

Bézier, P., Essay de définition numérique des courbes et des surfaces expérimentales. Ph.D. dissertation, University of Paris VI, France, 1977

Boehm, W., "Generating the Bézier Points of B-spline Curves and Surfaces," Computer Aided Design, 13(6):365-366, 1981

Farin, G., Curves and Surfaces for Computer-Aided Geometric Design: A Practical Guide, Fourth Edition, Academic Press, San Diego, 1997

Foley, J.D., A. van Dam, S.K. Feiner, and J.F. Hughes, Computer Graphics Principles and Practice, Second Edition, Addison-Wesley, Reading, 488-491, 1990

Klassen, R.V., "Integer Forward Differencing of Cubic Polynomials: Analysis and Algorithms," ACM Transactions on Graphics, 10(2):152-181, April 1991

Klassen, R.V., "Exact Integer Hybrid Subdivision and Forward Differencing of Cubics," ACM Transactions on Graphics, 13(3):240-255, July 1994

Lien, S.L., M. Shantz, and V. Pratt, "Adaptive Forward Differencing for Rendering Curves and Surfaces," Computer Graphics, SIGGRAPH 1987 Proceedings, 21(4):111-117

Rappoport, A., "Renderings Curves and Surfaces with Hybrid Subdivision and Forward Differencing," ACM Transcations on Graphics, 10(4):323-341, October 1991

Shantz, M., and S.L. Chang, "Rendering Trimmed NURBS with Adaptive Forward Differencing," Computer Graphics, SIGGRAPH 1988 Proceedings, 22(4):189-196

Shao, L., and H. Zhou, "Curve Fitting with Bézier Cubics," Graphical Models and Image Processing, 58(3):223-232, May 1996

Vaishnav, H., and A. Rockwood, "Calculating Offsets of a Bézier Curve," ACM Proceedings on the Second Symposium on Solid Modeling, 491-492, 1993


Hin Jang (c) March 9, 1999 - permission to copy all or part of the material contained herein is granted, upon approval from the author via e-mail, provided the copies are not made or distributed for direct commerical advantage, the author's name and the title of the article and its date appear, and notice is given that copying is by permission of the author.

Columns
  Development
  Security
  Network Security
 
Feature Articles
  The 3D Chipset Wars

Active Server Pages

Dynamic HTML

Help Students Across Canada Go Higher!

The Core of Linux: Hacking the Kernel

An Introduction to PHP

Java's Magic Beans

Mac OS X Server: Unix for the Mac

Mentoring to a Multitude

MPEG-4: A Bird's Eye View

ATAPI Music CD Programming Interface in Linux

Snow Crash

Unified Modeling Language

Rendering Bézier Forms via Forward Differencing

April 1999

Issue 2, Volume 1

This could be your company's banner advertisement

hw_publegal.gif (2694 bytes)