Thursday October 17, 2019
 home | about | contact | Donations
 The Maths of 3D You can't have 3D without a little maths...

Rasterization : Textured Triangle Basics

by bkenwright@xbdev.net

I woke up today, and thought..'I'll do the textured triangle tutorial that should be easy'... but you know how things are... little bugs just kept popping up.... the program crashing because of buffer accessing forbidden memory...offset values out by one etc.  But I fixed it!  It wasn't to beat me :)

Hopefully this tutorial will show you how easy it is to map a texture image to a triangle...and the power it gives you.  As textures can make your 3D world...or objects appear to be more complex than they really are.

Starting with the simple principle that each corner of the triangle has a corresponding (u,v) coordinate - so x0,y0 would have a u0,v0.... where u,v of 0,0 is the top left and 1,1 is the bottom right of the image.  Its then a matter of interpolating our values across the triangle surface...checking for edges and divide by zero's etc.

As usual, we'll split the triangle up into two important halves... the top half and the bottom half.  We start at the top and work our way down... when we reach the x2,y2 middle point...we carry on and draw the bottom.

For this simple demo, we arn't using any type of filtering...e.g. bi-linear etc...which takes into acount the neighbouring pixels and can generate sharper or smoother images.  Simple is good at this moment in time... we'll do more on that later.

 Download Demo Source Code    A simple program to demonstrate that how we interpolate a texture across a triangle...and to give you something to play with, is available to download.  The triangle is sizeable and uses the mouse cursor to move one of the triangle corners about.  As usual I've used a simple bitmap for the image....'snowman_robbery.jpg'...

The function which does all the work for us of interpolating the values across the triangles surface is shown below.  As usual the triangle is of two main parts...a top half and a bottom half.... I find that when your learning the code for the first time....just draw the top half of the triangle...and get a feel for the code.  The bottom half just continues from where you left off until its reached the bottom value of y2.

 Code: DownloadSourceCode ... void SWAP(float& a, float& b) {       float temp = a;       a = b;       b = temp; }   void MultiSwap(int &x0, int &y0, float &u0, float &v0,                      int &x1, int &y1, float &u1, float &v1,                      int &x2, int &y2, float &u2, float &v2 ) {       // Sort our points into order of y       // 0 top       // 2 middle       // 1 bottom       if( y1 < y0 )       {             SWAP(y1, y0);             SWAP(x1, x0);             SWAP(u1, u0);             SWAP(v1, v0);       }       if( y2 < y0 )       {             SWAP(y2, y0);             SWAP(x2, x0);             SWAP(u2, u0);             SWAP(v2, v0);       }       if( y1 < y2 )       {             SWAP(y2, y1);             SWAP(x2, x1);             SWAP(u2, u1);             SWAP(v2, v1);       }   }// End of MultiSwap(..)   void Draw_Textured_Triangle( unsigned int* pBits, int w, int h, int pitch,                                            int x0, int y0, float u0, float v0,                                            int x1, int y1, float u1, float v1,                                            int x2, int y2, float u2, float v2,                                            stImage* pImage ) {       // Sort our y values into order:       // y0 < y2 < y1       MultiSwap(x0,y0,u0,v0,   x1,y1,u1,v1,  x2,y2,u2,v2);         // Declare some variables that we'll use and where starting from y0 at the       // top of the triangle       float dxdy1    = (float)(x2-x0);       float dxdu1    = (u2-u0);       float dxdv1    = (v2-v0);         float dxdy2    = (float)(x1-x0);       float dxdu2    = (u1-v0);       float dxdv2    = (v1-v0);         float sdx,sdu,sdv;       float edx,edu,edv;       float pu, pv;       int x, y;         float dy1 = (float)(y2-y0);       float dy2 = (float)(y1-y0);         // Check for divide by zero's       if( y2-y0 != 0 )       {             dxdy1 /= dy1;             dxdu1 /= dy1;             dxdv1 /= dy1;       }         if( y1-y0 != 0 )       {             dxdy2 /= dy2;             dxdu2 /= dy2;             dxdv2 /= dy2;       }         float dxldy;  float dxrdy;       float dxldu;  float dxrdu;       float dxldv;  float dxrdv;         // Determine our left and right points for our x value gradient..       // e.g. the starting and ending line for our x inner loop       if( dxdy1 < dxdy2 )       {             dxldy = dxdy1;  dxrdy = dxdy2;             dxldu = dxdu1;  dxrdu = dxdu2;             dxldv = dxdv1;  dxrdv = dxdv2;       }       else       {             dxldy = dxdy2;  dxrdy = dxdy1;             dxldu = dxdu2;  dxrdu = dxdu1;             dxldv = dxdv2;  dxrdv = dxdv1;       }         // Initial starting x and ending x is sdx and edx which are x0,y0...the       // top of our triangle       sdx = (float)x0;  sdu=u0;  sdv=v0;       edx = (float)x0;  edu=u0;  edv=v0;       pu = u0; pv = v0;           float p_delta_u;       float p_delta_v;         for( y=(int)y0; y<= (int)y2; y++ )       {             p_delta_u = (edu - sdu);             p_delta_v = (edv - sdv);             if( edx - sdx != 0 )             {                   p_delta_u /= (float)(edx - sdx);                   p_delta_v /= (float)(edx - sdx);             }             pu=sdu;  pv=sdv;               for( x=(int)sdx; x<=(int)edx; x++ )             {                   int iwidth  = pImage->iWidth;                   int iheight = pImage->iHeight;                     int ww = (int)(pu*(iwidth-1));                   int hh = (int)(pv*(iheight-1));                     int pos = ww + hh*(iwidth);                   if( pos > (iwidth-1)*(iheight-1)) pos=(iwidth-1)*(iheight-1);                   if( pos < 0 ) pos=0;                   setpixel(pBits, w, h, pitch,                              x, y,                                pImage->pARGB[pos]);                     pu += p_delta_u;                   pv += p_delta_v;               }// End for loop x               sdx += dxldy;  sdu+=dxldu;  sdv+=dxldv;             edx += dxrdy;  edu+=dxrdu;  edv+=dxrdv;         }// End for loop y         ///////////////Now for the bottom of the triangle////////////////////         if( dxdy1 < dxdy2 )       {             dxldy = (float)(x1-x2);             dxldu = (u1-u2);             dxldv = (v1-v2);             if(y1-y2 != 0)             {                   dxldy /= (float)(y1-y2);                   dxldu /= (float)(y1-y2);                   dxldv /= (float)(y1-y2);             }// End inner if             sdx=(float)x2;  sdu=u2;   sdv=v2;       }       else       {             dxrdy = (float)(x1-x2);             dxrdu = (u1-u2);             dxrdv = (v1-v2);             if(y1-y2 != 0)             {                   dxrdy /= (float)(y1-y2);                   dxrdu /= (float)(y1-y2);                   dxrdv /= (float)(y1-y2);             }// End inner if             edx=(float)x2;  edu=u2;   edv=v2;       }// End of else         pu=u2;  pv=v2;           for( y=(int)y2; y<= (int)y1; y++ )       {             p_delta_u = (edu - sdu);             p_delta_v = (edv - sdv);             if( edx - sdx != 0 )             {                   p_delta_u /= (float)(edx - sdx);                   p_delta_v /= (float)(edx - sdx);             }             pu=sdu;  pv=sdv;               for( x=(int)sdx; x<=(int)edx; x++ )             {                   int iwidth  = pImage->iWidth;                   int iheight = pImage->iHeight;                     int ww = (int)(pu*(iwidth-1));                   int hh = (int)(pv*(iheight-1));                     int pos = ww + hh*(iwidth);                     if( pos > (iwidth-1)*(iheight-1)) pos=(iwidth-1)*(iheight-1);                   if( pos < 0 ) pos=0;                   setpixel(pBits, w, h, pitch,                              x, y,                                pImage->pARGB[pos]);                     pu += p_delta_u;                   pv += p_delta_v;               }// End for loop x               sdx += dxldy;  sdu+=dxldu;  sdv+=dxldv;             edx += dxrdy;  edu+=dxrdu;  edv+=dxrdv;         }// End for loop y   }// End of Draw_Textured_Triangle(..) ......

A word of optimisation.... the code isn't very optimised... I thought it made it a bit tricky for you to see how it was working.  Some optimisations would be to make things like the SetPixel(..) function inline...mix a bit of asm in the inner loops.... use the 'register' keyword maybe..  And calculate values that repeat once at the start and use them that way instead of calculating them again and again (e.g. y2-y1 );

 Visitor: 9534626  { 209.237.238.175 } Copyright (c) 2002-2017 xbdev.net - All rights reserved. Designated tutorial and software are the property of their respective owners.