Simple polygon rendering by Nathan Whitaker ------------------------------------------- About this document ------------------- I accept no responsibility for anything to do with this document etc.... I spend a lot of time on comp.graphics.algorithms and the rec.games.programmer newsgroups and have read many questions from many different beginners. I have noticed that these questions all seem to follow a common trend and the most widely asked question is how do I fill a polygon. In short I have created this document to answer the question. The polygon rendering techniques I am going to describe are very easy to code and understand but, are certainly not the fastest ( they were my first - hence the sloppy code ). I personally use other rendering methods for all the polygon primitives described below and all of my polygon code is in optimised assembler. This document was originally released as part of a zip file which contains an executable which demonstrates all the items in this document. Hit + and - on the numeric keypad to change the render type when in the demo. render types are Flat with Lambert, Gourard Lambert, Phong, affine textured, affine environment mapped, bump mapped with Lambert, environment & bump mapped with Lambert. The routines are old and are meant to demonstrate - not to impress. THESE POLYGON ROUTINES WORK ON THE ASSUMPTION THAT THE POLYGON IS CONVEX! ------------------------------------------------------------------------- Contact ------- Nathan Whitaker, Games Programmer, Reflections Software Email : Naif@rflect.demon.co.uk Contents -------- ( 1 ) Simple line draw algorithm - strange enough this is at the heart of our polygon primitives. ( 2 ) Flat shaded polygon rendering - converting the line draw routine. ( 3 ) Gourard shaded polygons. ( 4 ) Affine texture mapped polygons. ( 5 ) Perspective correct texture mapping. ( 6 ) Adding gourard shading to texture mapping. ( 7 ) Bump mapped polygons. ( 8 ) Environment mapping. ( 9 ) Phong shaded polygons. ( 1 ) Simple line draw algorithm ---------------------------------- There are many ways to render a polygon but, by far the easiest is to trace along the edges of the polygon storing the leftmost and rightmost x coorinate for each row of the polygon then filling horizontly between these points. To do this we need a routine which returns calculates points on a line so lets start with a simple line drawing algorithm which we will convert into a polygon edge tracer in section 2. I am going to describe one of the simplest yet fastest line drawing algorithms I know of. This line draw routine first of all calculates the x and y delta between the two line points:- deltax = x1 - x0; deltay = y1 - y0; Next the routine branches to one of two drawing cases based upon the largest absolute value of the two deltas. If the xdelta is the greater of the two we draw one pixel horizontally in the x direction of the line every loop iteration for a loop count of abs( dx ). You are correct in assuming that this will produce a horizontal line but, thats not what we want so we somehow need to know when we should move the y position up / down a row. We work out when to move a row by keeping a total which we will call error - every loop iteration we add the lesser delta - in this case we add abs( ydelta ). We then compare error with the greater delta and if error is larger we then move a row in the y direction and subtract the greater delta from the error. That basically is the line routine and in the case of dy > dx we step in the y direction one pixel every iteration and check when to move horizontally in the manner above. I have made a right mess of explaining this so I have enclosed source code for the line routine. void DrawLine( int x0, int y0, int x1, int y1, int color ) { int udx, udy, dx, dy, error, loop, xadd, yadd; dx = x1 - x0; // Work out x delta dy = y1 - y0; // Work out y delta udx = abs( dx ); // udx is the unsigned x delta udy = abs( dy ); // udy is the unsigned y delta if ( dx < 0 ) // Work out direction to step in the x direction. xadd = -1; else xadd = 1; if ( dy < 0 ) // Work out direction to step in the y direction. yadd = -1; else yadd = 1; error = 0; loop = 0; if ( udx > udy ) { do // Delta X > Delta Y { error += udy; if ( error >= udx ) // Time to move up / down? { error -= udx; y0 += yadd; } loop++; d_buffer[ ( y0 * 320 ) + x0 ] = color; // Plot pixel - d_buffer is my screen buffer. x0 += xadd; // Move horizontally. } while ( loop < udx ); // Repeat for x length of line. } else { do // Delta Y > Delta X { error += udx; if ( error >= udy ) // Time to move left / right? { error -= udy; x0 += xadd; // Move across. } loop++; d_buffer[ ( y0 * 320 ) + x0 ] = color; // Plot pixel. y0 += yadd; // Move up / down a row. } while ( loop < udy ); // Repeat for y length of line. } } ( 2 ) Flat shaded polygon rendering - converting the line draw routine. ------------------------------------------------------------------------- All that we need to do to turn our line drawing routine into a polygon edge tracer is to modify the part where we plot a pixel at x,y to store an x coordinate for that row. We are going to store these x coordinates ( there should be 2 for a convex poly ) in the structure below:- typedef struct { signed short x; } EDGE_LIST; EDGE_LIST poly_edge_list[ HEIGHT_OF_SCREEN ][ 2 ]; poly_edge_list is an array of screen height elements ( screen height as you should clip polys to the screen ) containing two x coordinates. What this means is for every y row of the polygon we have two x coordinates which we can quickly fill between to create a polygon. What this means for our edge tracer is that it should only store one x coordinate per row every time it is called. This is simple to implement as in the case of deltax > deltay we have a section of code which is entered only when we move a row.... if ( error >= udx ) // Time to move up / down? { error -= udx; y0 += yadd; } ....and in the case of deltay > deltax we increment / decrement the row every iteration anyway. Your modified line routine should resemble the code below. void DrawEdge( int x0, int y0, int x1, int y1 ) { int udx, udy, dx, dy, error, loop, yadd, max, temp; EDGE_LIST *left_edge_ptr, *right_edge_ptr; if ( x0 > x1 ) // Draw left - right always { temp = x0; x0 = x1; x1 = temp; temp = y0; y0 = y1; y1 = temp; } left_edge_ptr = &poly_edge_list[ y0 ][ 0 ]; right_edge_ptr = &poly_edge_list[ y0 ][ 1 ]; dx = x1 - x0; dy = y1 - y0; if ( dx || dy ) // Check coordinates are not equal { udx = abs( dx ); udy = abs( dy ); if ( dy < 0 ) yadd = -2; // One row is 2 elements left & right - hence 2 not 1 else yadd = 2; error = 0; loop = 0; if ( udx > udy ) { max = udx; if ( left_edge_ptr->x < 0 ) left_edge_ptr->x = x0; else if ( right_edge_ptr->x < 0 && x0 != left_edge_ptr->x ) right_edge_ptr->x = x0; do { error += udy; loop++; x0++; if ( error >= udx ) { left_edge_ptr += yadd; right_edge_ptr += yadd; error -= udx; if ( left_edge_ptr->x < 0 ) // edge_buffer initialsed to -1 left_edge_ptr->x = x0; else if ( x0 != left_edge_ptr->x ) right_edge_ptr->x = x0; } } while ( loop < max ); } else if ( udx < udy ) { max = udy + 1; do { if ( left_edge_ptr->x < 0 ) left_edge_ptr->x = x0; else if ( x0 != left_edge_ptr->x ) right_edge_ptr->x = x0; left_edge_ptr += yadd; right_edge_ptr += yadd; error += udx; if ( error >= udy ) { error -= udy; x0++; } loop++; } while ( loop < max ); } else // New case for delta x = delta y { max = udy + 1; do { if ( left_edge_ptr->x < 0 ) left_edge_ptr->x = x0; else if ( x0 != left_edge_ptr->x ) right_edge_ptr->x = x0; left_edge_ptr += yadd; right_edge_ptr += yadd; x0++; loop++; } while ( loop < max ); } } } You have probably noticed a few strange things about the code above heres an explanation: ( 1 ) The third case deltax = deltay has been added to prevent leaking at 45 degrees. ( 2 ) What does this do - if ( left_edge_ptr->x < 0 ) left_edge_ptr->x = x0; else if ( x0 != left_edge_ptr->x ) right_edge_ptr->x = x0; ???? If the slot 0 of the array is empty, set to x otherwise check slot 2 and if empty set to x. The whole array of slots is set to -1 ( empty ) at the start of each polygon. ( 3 ) Why are we drawing length + 1 and why is the order different in each case? This is due to the fact these were my first sloppy polygon routines and they were made in a hurry - they are there to prevent leaking. I guarantee that you will be sick of leaking by the end of your first polygon routine. Take a quick look at figure 1 below and come back...... Now I've explained the edge tracer conversion I will show you how you would render the polygon in figure 1. ( 1 ) Work out the height of the polygon - largest y - smallest y. Make vertex coordinates relative to smallest y e.g for every vertex y subtract smallest y. ( 2 ) Fill polygon height entries in poly_edge_list with -1 to indicate that they are empty slots. ( 3 ) Edge trace vertex 0 - vertex 1. Edge trace vertex 1 - vertex 2. Edge trace vertex 2 - vertex 3. Edge trace vertex 3 - vertex 0. ( 4 ) Loop through the poly_edge_list drawing horizontal lines of pixels between any slots that contain 2 x values. 2 /\ / \ / \ 1/ \ ! \ ! \ ! \ ! / 3 ! / ! / ! / ! / ! / ! / ! / ! / !/ 0 Figure 1 -------- Above is a really crap 4 vertex ascii polygon with the vertices labelled 0, 1, 2, 3. Finally this is the main core of a routine to draw a 4 point flat polygon. void PolyF4( int x0, int y0, int x1, int y1, int x2, int y2, int x3, int y3, char col ) { // set initial best values. signed short yloop, tempx, tempy, top = 4096, bot = -4096, height; if ( y0 < top ) // Get top and bottom values of polygon. top = y0; if ( y1 < top ) top = y1; if ( y2 < top ) top = y2; if ( y3 < top ) top = y3; if ( y0 > bot ) bot = y0; if ( y1 > bot ) bot = y1; if ( y2 > bot ) bot = y2; if ( y3 > bot ) bot = y3; height = bot - top; if ( height == 0 ) return; for ( yloop = 0; yloop < height; yloop++ ) { poly_edge_list[ yloop ][ 0 ].x = -1; poly_edge_list[ yloop ][ 1 ].x = -1; } y0 -= top; y1 -= top; y2 -= top; y3 -= top; DrawEdge( x0, y0, x1, y1 ); DrawEdge( x1, y1, x2, y2 ); DrawEdge( x2, y2, x3, y3 ); DrawEdge( x3, y3, x0, y0 ); for ( yloop = 0; yloop < height; yloop++ ) { if ( poly_edge_list[ yloop ][ 0 ].x > 0 ) // Check an x1 and an x2 exist. { // if ( poly_edge_list[ yloop ][ 1 ].x > 0 ) // DrawFRun( yloop + top, poly_edge_list[ yloop ][ 0 ].x, poly_edge_list[ yloop ][ 1 ].x, col ); } } } // ------------------------------------------------------------------------ This routine fills between the two edges in the set fill color. void DrawFRun( int y, int x0, int x1, char color ) { short loop; char *byte; if ( x0 == x1 ) d_buffer[ ( y * 320 ) + x0 ] = color; else if ( x0 < x1 ) { byte = &d_buffer[ ( y * 320 ) + x0 ]; for ( loop = x0; loop < x1; loop++ ) *byte++ = color; } else { byte = &d_buffer[ ( y * 320 ) + x1 ]; for ( loop = x1; loop < x0; loop++ ) *byte++ = color; } } ( 3 ) Gourard shaded polygons. -------------------------------- If you have read the code above and understood it gourard shading shouldn't be a problem. In this section I will tell you how to draw a gourard shaded polygon given a set of shade values for each vertex. I won't tell you how to calculate the shade at each vertex as I assume you know it - dot product with face normal and light source etc ( any good 3d doc will tell you this ). Given that you have a working flat polygon routine it isn't very hard to convert to a gourard shaded routine. In terms of shades I assume you have set up a progressive dark to light palette and you don't with to shade via a clut ( colour lookup table ), instead you want to shade directly between the specified colours. The first step to converting the flat shader to a gourard shader is to change our EDGE_LIST structure to include shade information like below:- typedef struct { signed short x; unsigned short col; } EDGE_LIST; Next we need to create a modified edge trace function which interpolates between the two vertex shades and stores the shade into the poly_edge_list ( col ). We interpolate between the shades by calculating a shade delta ( shade1 - shade0 ), multiplying this value by 256 ( for smoothing purposes ) and dividing the result by the largest delta...... void DrawGEdge( int x0, int y0, int x1, int y1, unsigned char c0, unsigned char c1 ) { int udx, udy, dx, dy, error, loop, yadd, max, temp; EDGE_LIST *left_edge_ptr, *right_edge_ptr; unsigned short coladd; unsigned short col; if ( x0 > x1 ) { temp = x0; x0 = x1; x1 = temp; temp = y0; y0 = y1; y1 = temp; temp = c0; c0 = c1; c1 = temp; } left_edge_ptr = &poly_edge_list[ y0 ][ 0 ]; right_edge_ptr = &poly_edge_list[ y0 ][ 1 ]; dx = x1 - x0; dy = y1 - y0; if ( dx || dy ) { udx = abs( dx ); udy = abs( dy ); if ( dy < 0 ) yadd = -2; else yadd = 2; error = 0; loop = 0; if ( udx > udy ) { max = udx; coladd = ( ( c1 - c0 ) * 256 ) / udx; col = c0 * 256; if ( left_edge_ptr->x < 0 ) { left_edge_ptr->x = x0; left_edge_ptr->col = c0; } else if ( right_edge_ptr->x < 0 && x0 != left_edge_ptr->x ) { right_edge_ptr->x = x0; right_edge_ptr->col = c0; } do { error += udy; col += coladd; loop++; x0++; if ( error >= udx ) { left_edge_ptr += yadd; right_edge_ptr += yadd; error -= udx; if ( left_edge_ptr->x < 0 ) { left_edge_ptr->x = x0; left_edge_ptr->col = col >> 8; } else if ( x0 != left_edge_ptr->x ) { right_edge_ptr->x = x0; right_edge_ptr->col = col >> 8; } } } while ( loop < max ); } else if ( udx < udy ) { max = udy + 1; coladd = ( ( c1 - c0 ) * 256 ) / udy; col = c0 * 256; do { if ( left_edge_ptr->x < 0 ) { left_edge_ptr->x = x0; left_edge_ptr->col = col >> 8; } else if ( x0 != left_edge_ptr->x ) { right_edge_ptr->x = x0; right_edge_ptr->col = col >> 8; } left_edge_ptr += yadd; right_edge_ptr += yadd; error += udx; if ( error >= udy ) { error -= udy; x0++; } loop++; col += coladd; } while ( loop < max ); } else { max = udy + 1; coladd = ( ( c1 - c0 ) * 256 ) / udy; col = c0 * 256; do { if ( left_edge_ptr->x < 0 ) { left_edge_ptr->x = x0; left_edge_ptr->col = col >> 8; } else if ( x0 != left_edge_ptr->x ) { right_edge_ptr->x = x0; right_edge_ptr->col = col >> 8; } left_edge_ptr += yadd; right_edge_ptr += yadd; x0++; loop++; col += coladd; } while ( loop < max ); } } } You have probably noticed how little actually changed from the original flat shader and to complete the gourard routine all you now need to do is change the horizontal filler loop. All we have to do the horizontal filler is to interpolate between the shades at each edge of the poly and fill with that shade - see below. void DrawGFRun( int y, int x0, int x1, unsigned char c0, unsigned char c1 ) { unsigned short coladd, col, temp; short loop, length; char *byte, val; if ( x0 == x1 ) d_buffer[ ( y * 320 ) + x0 ] = c0; else { if ( x0 > x1 ) { temp = x0; x0 = x1; x1 = temp; temp = c0; c0 = c1; c1 = temp; } length = x1 - x0; coladd = ( ( c1 - c0 ) * 256 ) / length; col = c0 * 256; byte = &d_buffer[ ( y * 320 ) + x0 ]; for ( loop = 0; loop <= length; loop++ ) { *byte++ = col >> 8; col += coladd; } } } ( 4 ) Affine texture mapped polygons. --------------------------------------- To convert the flat shader to be affine ( fitted ) texture mapped is just as simple as turning it into a gourard shader except we interpolate U and V instead of shade. I assume you know what U and V are but, in case you don't then in short they are X,Y positions in texture space. I won't bother explaining the procedure as its almost identical to the gourard shader - I'll merely present you with the code:- typedef struct { signed short x; unsigned short u, v; } EDGE_LIST; void DrawTEdge( int x0, int y0, int x1, int y1, int u0, int v0, int u1, int v1 ) { int udx, udy, dx, dy, error, loop, yadd, max, temp; unsigned short uadd, u, vadd, v; GEDGE *left_edge_ptr, *right_edge_ptr; if ( x0 > x1 ) { temp = x0; x0 = x1; x1 = temp; temp = y0; y0 = y1; y1 = temp; temp = u0; u0 = u1; u1 = temp; temp = v0; v0 = v1; v1 = temp; } left_edge_ptr = &poly_edge_list[ y0 ][ 0 ]; right_edge_ptr = &poly_edge_list[ y0 ][ 1 ]; dx = x1 - x0; dy = y1 - y0; if ( dx || dy ) { udx = abs( dx ); udy = abs( dy ); if ( dy < 0 ) yadd = -2; else yadd = 2; error = 0; loop = 0; if ( udx > udy ) { uadd = ( ( u1 - u0 ) * 256 ) / udx; vadd = ( ( v1 - v0 ) * 256 ) / udx; u = u0 * 256; v = v0 * 256; max = udx; if ( left_edge_ptr->x < 0 ) { left_edge_ptr->x = x0; left_edge_ptr->u = u0; left_edge_ptr->v = v0; } else if ( right_edge_ptr->x < 0 && x0 != left_edge_ptr->x ) { right_edge_ptr->x = x0; right_edge_ptr->u = u0; right_edge_ptr->v = v0; } do { error += udy; u += uadd; v += vadd; loop++; x0++; if ( error >= udx ) { left_edge_ptr += yadd; right_edge_ptr += yadd; error -= udx; if ( left_edge_ptr->x < 0 ) { left_edge_ptr->x = x0; left_edge_ptr->u = u >> 8; left_edge_ptr->v = v >> 8; } else if ( x0 != left_edge_ptr->x ) { right_edge_ptr->x = x0; right_edge_ptr->u = u >> 8; right_edge_ptr->v = v >> 8; } } } while ( loop < max ); } else if ( udx < udy ) { uadd = ( ( u1 - u0 ) * 256 ) / udy; vadd = ( ( v1 - v0 ) * 256 ) / udy; u = u0 * 256; v = v0 * 256; max = udy + 1; do { if ( left_edge_ptr->x < 0 ) { left_edge_ptr->x = x0; left_edge_ptr->u = u >> 8; left_edge_ptr->v = v >> 8; } else if ( x0 != left_edge_ptr->x ) { right_edge_ptr->x = x0; right_edge_ptr->u = u >> 8; right_edge_ptr->v = v >> 8; } left_edge_ptr += yadd; right_edge_ptr += yadd; error += udx; if ( error >= udy ) { error -= udy; x0++; } loop++; u += uadd; v += vadd; } while ( loop < max ); } else { uadd = ( ( u1 - u0 ) * 256 ) / udy; vadd = ( ( v1 - v0 ) * 256 ) / udy; u = u0 * 256; v = v0 * 256; max = udy + 1; do { if ( left_edge_ptr->x < 0 ) { left_edge_ptr->x = x0; left_edge_ptr->u = u >> 8; left_edge_ptr->v = v >> 8; } else if ( x0 != left_edge_ptr->x ) { right_edge_ptr->x = x0; right_edge_ptr->u = u >> 8; right_edge_ptr->v = v >> 8; } left_edge_ptr += yadd; right_edge_ptr += yadd; x0++; loop++; u += uadd; v += vadd; } while ( loop < max ); } } } // ------------------------------------------------------------------------ void DrawTRun( int y, int x0, int x1, unsigned char u0, unsigned char v0, unsigned char u1, unsigned char v1 ) { int x, xdiff, ydiff, index; unsigned short uadd, u, vadd, v, temp; short loop, length; char *byte, val; if ( x0 != x1 ) { if ( x0 > x1 ) { temp = x0; x0 = x1; x1 = temp; temp = u0; u0 = u1; u1 = temp; temp = v0; v0 = v1; v1 = temp; } length = x1 - x0; uadd = ( ( u1 - u0 ) * 256 ) / length; u = u0 * 256; vadd = ( ( v1 - v0 ) * 256 ) / length; v = v0 * 256; byte = &d_buffer[ ( y * 320 ) + x0 ]; for ( loop = 0; loop <= length; loop++ ) { // t_page is a 256 * 256 area of memory where my texture is loaded. *byte++ = t_page[ ( v & 0xff00 ) + ( u >> 8 ) ]; u += uadd; v += vadd; } } } ( 5 ) Perspective correct texture mapping. -------------------------------------------- I haven't wrote a perspective correct texture mapper using the slooowww edge tracing method but, it would be fairly simple to do. Store the data as in the affine mapper but, change the EDGE_LIST to store an interpolated 1 / Z value as well as the U and V values. You must also now interpolate between..... U0 * ( 1 / Z0 ) and U1 * ( 1 / Z1 ). V0 * ( 1 / Z0 ) and V1 * ( 1 / Z1 ). ...and store the above values into U, V. Finally when you come to drawing a horizontal run divide the interpolated U and V values by the interpolated 1 / Z value. You may have noticed that the 1 / Z calculation won't convert to the integer scheme we have been using but, don't worry - using the FPU is no longer considered voodoo and any good perspective texture mapper utilises the FPU. You may also have noticed that doing the above operations for every pixel in the fill loop will be slooowww. It is a good idea to calculate perspective corrected U and V values every ? pixels ( ? usually is 8 ) and interpolate ( normally - affine like ) between these ? pixel spans. This is a common technique and even has a nice long name - Bi Linear perspective correct texture mapping. ( 6 ) Adding gourard shading to texture mapping. -------------------------------------------------- This is simply a case of doing the changes to make the flat filler, gourard and also doing the changes to make the flat filler, affine texture mapped. You should now have the following structure:- typedef struct { signed short x; unsigned short u, v, col; } EDGE_LIST; Once you have changed your edge tracer to reflect the data above you will have to change your horizontal filler but, how you shade your texture is up to you. I suggest two ways - ( 1 ) If your texture is 16 colours then arrange your 256 colour palette like this. 0 - 15 = original 16 colours darkest shades. 16 - 31 = original 16 colours second darkest shades. ..... ..... ..... ..... 240 - 255 = orignal 16 colours brighest shades. Your filler's inner loop would look something like this:- for ( loop = 0; loop <= length; loop++ ) { shade = col >> 8; // shade is and'ed to get the appropriate shade offset in multiples of 16. // t_page values are 0 - 15. *byte++ = ( shade & 0xf0 ) + t_page[ ( ( v & 0xff00 ) + ( u >> 8 ) ]; u += uadd; v += vadd; col += coladd; } As in all the gourard shading code I have shown, I assume shades go anywhere in the region of 0 - 255. ( 2 ) You may want to create a clut ( colour lookup table ) - for every colour in the palette build a table of shade values which reflect the index of the closest colour for each shade ( am I speaking japenese again... ):- This is a clut of indexes to the 16 closest light to dark shades for each of the 256 colours..... char clut[ 256 ][ 16 ]; To use the clut above your filler's inner loop would look something like this:- for ( loop = 0; loop <= length; loop++ ) { shade = col >> 8; texel = t_page[ ( ( v & 0xff00 ) + ( u >> 8 ) ]; *byte++ = clut[ texel ][ shade >> 4 ]; u += uadd; v += vadd; col += coladd; } Of course you may want to use another method????????? ( 7 ) Bump mapped polygons. ----------------------------- I have to be honest and say that bump mapping really ticks me off..... the reason is lame artists. Myself and the other programmers here at Reflections spend all day, 5 days a week writing 'real' routines such as freescape car ai, 3d clipping routines, fast 3d collisions, realistic car simulations etc.... We regularly download demos over the internet and the bright flashing colours and beepy music attract a herd of artists... The artists watch on until usually half way through a demo a large light sourced bump mapped object spins into view and then it starts.... They all look at each other and start chatting about how we should employ the demo coders as they have programmed this monster of a routine and we ( Reflections coders ) are all crap... The annoying thing is that if these artists knew a single thing about programming they would realise that bump mapping is extremely simple to program. ---------------------------- So how do we do it??? Well, I think it would help if I quickly described how 2d bump mapping works....... Setup - First of all you create ( or in my case borrow ) an image which has been designed so that the colour value of a pixel represents a height. Second of all you create a nice palette which fades from dark to light. Third of all you create a predefined overlay representing your light source. This overlay usually takes the form of circle which fades from dark at the outer edges to light at the centre. Below is a sample piece of code to create the overlay. #define LIGHT_DIAMETER 256 // 256 shades in overlay char BumpOverlay[ LIGHT_DIAMETER * LIGHT_DIAMETER ]; int ypremultable[ 256 ]; void BuildBumpOverlay() { int x, y, x1, y1, dist; for ( y = 0; y < LIGHT_DIAMETER; y++ ) { for ( x = 0; x < LIGHT_DIAMETER; x++ ) { x1 = x - ( LIGHT_DIAMETER / 2 ); y1 = y - ( LIGHT_DIAMETER / 2 ); // Work out distance from center of light source. dist = sqrt( ( x1 * x1 ) + ( y1 * y1 ) ); // Closer to center = brighter. dist = ( LIGHT_DIAMETER / 2 ) - dist; if ( dist > 127 ) dist = 127; else if ( dist < 0 ) dist = 0; // Output values in the rance 0 - 254 BumpOverlay[ ( y * LIGHT_DIAMETER ) + x ] = dist * 2; } } // This table is precalculated to remove an expensive inner loop multiply operation. for ( y = 0; y < 256; y++ ) ypremultable[ y ] = y * LIGHT_DIAMETER; } Drawing - To draw the bump mapped picture we first define an x and y position for the light source then we draw 200 rows of 320 pixels performing the following calculations:- ( 1 ) For every pixel we calculate a pseudo x normal - we do this by saying the normal is the pixel to the right of the current pixel - the pixel to the left of the current pixel. We also calculate a pseudo y normal - similiarly we calculate it from the pixel below this pixel - the pixel above. ( 2 ) We add a base value and the distance on the respected axis from the center of the light source to each normal. ( 3 ) We adjust both normals so that they are in the 0 - 255 range. ( 4 ) We plot using the colour in the light overlay at x normal, y normal. void DrawBumpMap() { char *sp, *dp; int x, y, xdiff, ydiff; sp = t_page; // Height map dp = d_buffer; // Screen // Subtract light source center from x and y coordinates. for ( y = 0 - by; y < 200 - by; y++ ) { for ( x = 0 - bx; x < 320 - bx; x++, sp++ ) { // Get normals. xdiff = *( sp + 1 ) - *( sp - 1 ); ydiff = *( sp + 320 ) - *( sp - 320 ); // Add distance and base. xdiff = ( xdiff + x ) + 128; ydiff = ( ydiff + y ) + 128; // Keep in 0 - 255. if ( xdiff < 0 ) xdiff = 0; if ( xdiff > 255 ) xdiff = 255; if ( ydiff < 0 ) ydiff = 0; if ( ydiff > 255 ) ydiff = 255; // Set pixel to colour. *dp++ = BumpOverlay[ xdiff + ypremultable[ ydiff ] ]; } } } Anyway, enough of the 2d stuff.... As I mentioned earlier 3d bump mapping is soooooooooo simple. First of all take your textured gourard shader and add the following to the fill loop: for ( loop = 0; loop <= length; loop++ ) { // Get current U, V coordinate index = ( v & 0xff00 ) + ( u >> 8 ); // Perform the pseudo normal code - texture is 256 * 256. xdiff = t_page[ index + 1 ] - t_page[ index - 1 ]; ydiff = t_page[ index + 256 ] - t_page[ index - 256 ] ; // Add two normals together. xdiff = xdiff + ydiff; // Add light at this pixel. xdiff += col >> 8; // Limit range of value. if ( xdiff < 0 ) xdiff = 0; if ( xdiff > 255 ) xdiff = 255; *byte++ = xdiff; u += uadd; v += vadd; col += coladd; } The code above is slightly different to the 2d version - I just plugged it with similiar but, faster calculations. We can optimise the code above even more by precalculating the normals into a huge table - leaving us with the following. for ( loop = 0; loop <= length; loop++ ) { xdiff = precalc[ ( v & 0xff00 ) + ( u >> 8 ) ]; // Add light at this pixel. xdiff += col >> 8; // Limit range of value. if ( xdiff < 0 ) xdiff = 0; if ( xdiff > 255 ) xdiff = 255; *byte++ = xdiff; u += uadd; v += vadd; col += coladd; } precalc contains the following for each pixel:- precalc[ x + ( y * 256 ) ] = t_page[ x + ( y * 256 ) + 1 ] - t_page[ x + ( y * 256 ) - 1 ] + t_page[ x + ( ( y + 1 ) * 256 ) ] - t_page[ x + ( ( y - 1 ) * 256 ) ]; It must be noted that if you are clever about the range of the height values you can get rid of the xdiff > 255 check. ( 8 ) Environment mapping. ---------------------------- This is another simple effect if you code it the same way as demo coders. If you calculate a pseudo normal for each vertex ( as in gourard shading ) and you set each normal to a common unit length ( I use 4096 ) do something along the lines of the following to get the environment map U,V coordinates: Assume texture map is 256 * 256 and unit length is 4096 ( ONE ). // Get vertex numbers. v0 = poly_list[ 0 ]; v1 = poly_list[ 1 ]; v2 = poly_list[ 2 ]; v3 = poly_list[ 3 ]; // Get UV coordinates for each vertex in range of 0 - 255. uvinfo.u0 = ( point_normals[ v0 ].vx / 32 ) + 128; uvinfo.v0 = ( point_normals[ v0 ].vy / 32 ) + 128; uvinfo.u1 = ( point_normals[ v1 ].vx / 32 ) + 128; uvinfo.v1 = ( point_normals[ v1 ].vy / 32 ) + 128; uvinfo.u2 = ( point_normals[ v2 ].vx / 32 ) + 128; uvinfo.v2 = ( point_normals[ v2 ].vy / 32 ) + 128; uvinfo.u3 = ( point_normals[ v3 ].vx / 32 ) + 128; uvinfo.v3 = ( point_normals[ v3 ].vy / 32 ) + 128; All the code is doing is taking the x and y component of the normal and using it to calculate U, V coordinates in the range 0 - 255 ( my texture is 256 * 256 ). Simple......... ( 9 ) Phong shaded polygons. ------------------------------ Real phong shading involves calculating a normal and the amount of light for every pixel in the polygon. You would be right in assuming that demo coders are only human and they must be somehow cheating in the many phong shaded demos. It just so happens that if you have an environment mapping routine like above you can easily phong shade ( fake!!! ). What demo coders do is to precalculate a light overlay based upon the phong illumination model ( I haven't got the formula to hand ). This light overlay is almost identical in style to the light overlay that I showed you how to create in the bump mapping section. This overlay is simply used as a texture or a light index and when combined with the environment mapping produces fast and fake yet realistic looking phong shading.