💾 Archived View for gemini.spam.works › mirrors › textfiles › computers › DOCUMENTATION › gpoly.txt captured on 2022-06-12 at 06:33:21.
-=-=-=-=-=-=-
WGT Graphics Tutorial #2 Topic: Gouraud Shaded Polygons By Chris Egerter October 13, 1994 Contact me at: chris.egerter@homebase.com Introduction ------------ This series of tutorials describes a method of drawing filled polygons using 3 rendering techniques: Solid, Gouraud shading, and texture mapping. The code in this tutorial was written in Turbo C++ but can be ported to other graphics libraries and operating systems. I did not use the WGT functions in this one, so the wgtgfx.c file contains a few routines which are needed for the demos. I have decided to explain the method used for these routines since I had to discover them on my own, and think you can learn from the code. 1.0 - Shading Along a Line -------------------------- The idea behind shading is we want different shades of color along a surface. The simplest application of shading is along a horizontal line. Imagine the line is black at the left end, and white at the right end. A pixel in the middle will be a shade of grey. As the line is drawn from left to right, the color value starts at black and increases by a constant amount towards white. This constant value is determined by the number of colors between the endpoints and the length of the line. Specifically, the constant is equal to the number of colors divided by the number of pixels along the line. If the number of colors equals the number of pixels, the constant is 1, which makes perfect sense. Since we cannot deal with fractions of a color in computer graphics, we will only deal with the integer portion of the color value. 2.0 - Pseudo-code ----------------- Our basic shaded line routine looks like this: Calculate the step value Make a color variable equal to the left endpoint color. For x = x1 to x2 Put pixel on screen Add step value to the color End for 3.0 - Assembly Language Benefits -------------------------------- When dealing with 256 colors, you can fit the color value in one byte. We will use fixed point math to store the step value, that is, 1 byte to store the whole number, and 1 byte to store the fractional portion. By using 1 byte for the fraction, we can store the whole and fractional parts in one integer. This makes it easy in assembly language since we can put these values in a register, say AX, and access each portion individually with AH and AL. In C language you would need to shift the value right 8 times in order to get the whole value. This works out perfectly since we can add a step value to AX, and AH will always contain the color we want to put on the screen. You don't have to worry about the fractional portion carrying over 256 since it will already be added to the whole portion. 4.0 - Calculating the step value in 8.8 fixed point --------------------------------------------------- 8.8 means we are using 8 bits for the number before the decimal, and 8 bits for the fraction. You have to think of the fraction in hexadecimal since it will carry at 256 instead of the usual decimal system most people relate to (base 10). To make a step value with the whole portion in the upper byte, first we need to shift the colors to the left by 8 bits. This will put the color value in the high byte and leave the fraction as 0. Now to calculate the step value, divide it by the length. eg: step = (numcolors << 8) / length; 5.0 - Code segment 1 -------------------- We can write a more specific routine now: void shadedline (int x1, int firstcolor, int x2, int lastcolor, int y) { int length; int numcolors; int colorvalue; int step; int x; length = x2 - x1 + 1; if (length > 0) { numcolors = lastcolor - firstcolor + 1; colorvalue = firstcolor << 8; step = ((long)numcolors << 8) / (long)length; for (x = x1; x <= x2; x++) { drawpixel (x, y, colorvalue >> 8); colorvalue += step; } } } x1 is the left coordinate of the line, with firstcolor being the color of this point. x2 is the right coordinate of the line, with lastcolor being the color of this point. y is the y coordinate of the horizontal line (you only need one). drawpixel is a simple function which sets a single pixel to a color. It is defined as: void drawpixel (int x, int y, unsigned char col) { abuf [y * 320 + x] = col; } The above code is demonstrated in gshade1.c. 6.0 - Optimization Number 1 --------------------------- Calling drawpixel for each pixel is not very efficient. We know the pixels are one after the other, so it is useless to multiply the y coordinate by 320 every time when we can just move over one pixel. The following code shows how the drawpixel code has been simplified and put directly into the shadedline routine. void shadedline (int x1, int firstcolor, int x2, int lastcolor, int y) { int length; int numcolors; int colorvalue; int step; int x; unsigned char far * dest; /* Ptr to the screen */ length = x2 - x1 + 1; if (length > 0) { numcolors = lastcolor - firstcolor + 1; colorvalue = firstcolor << 8; step = ((long)numcolors << 8) / (long)length; dest = abuf + y * 320 + x1; /* Make a pointer to the first pixel */ for (x = x1; x <= x2; x++) { *dest++ = colorvalue >> 8; /* Draw the pixel and move to the next location in memory */ colorvalue += step; } } } The above code is demonstrated in gshade2.c. 7.0 - Optimization Number 2 (ASM) --------------------------------- The other bottleneck in the routine is the colorvalue being shifted right by 8 for every pixel. By using the assembly language registers mentioned earlier, we can take the high byte of colorvalue without shifting. The code below shows how inline assembly is used to speed up the routine: void shadedline (int x1, int firstcolor, int x2, int lastcolor, int y) { int length; int numcolors; int colorvalue; int step; int x; unsigned char far * dest; /* Ptr to the screen */ length = x2 - x1 + 1; if (length > 0) { numcolors = lastcolor - firstcolor + 1; colorvalue = firstcolor << 8; step = ((long)numcolors << 8) / (long)length; dest = abuf + y * 320 + x1; /* Make a pointer to the first pixel */ /* Begin assembly optimization */ if (length > 0) { asm { mov cx, word ptr length /* Set length */ les di, dest /* Set destination ptr */ mov ax, word ptr colorvalue /* Set color */ } shadedlineloop: ; asm { mov es:di, ah /* Move color to screen */ add ax, word ptr step /* Add to color */ inc di /* Move to next pixel */ dec cx /* Decrease length */ jnz shadedlineloop /* Repeat for all pixels */ } } } } The above code is demonstrated in gshade3.c. 8.0 - Combining The Shaded Line and Polygon Routines ---------------------------------------------------- The next question you may be asking is, "How do I know what colors to use at the endpoints when drawing a polygon?". In order to do this, we have to modify our polygon scan conversion routines I developed in tutorial #1. The point structure is defined as: typedef struct { int x,y; } point; Since each vertex now has a color associated with it, we will add another variable to this structure, called col. The point structure becomes the gpoint structure, which looks like this: typedef struct { int x,y; unsigned char col; } gpoint; When we converted the polygon into a list of x coordinates, we stored them into two arrays, startx and endx. Now we also need to store the color at both of these coordinates. Let's make two new arrays: int startcol[200]; int endcol[200]; When the list is created, we will have 2 x coordinates and a color value associated with each of them. This is all the information we need to call the shadedline routine. The polyline routine becomes the gpolyline routine, which also calculates the colors at the ends of each horizontal line. To do this, we use fixed point math, similar to the way we calculated the colors along the length of the line. This time we are adding the step value to the color for every pixel we move down, instead of across. void gpolyline (int x1, int y1, int col1, int x2, int y2, int col2) /* Calculates the coordinates of a line given two vertices (x1,y1) with color col1, and (x2,y2) with color col2. We will use fixed point math to speed things up. The x coordinate is multiplied by 256 and for each row, a constant m is added to x. This is a simplified version of a line algorithm because we only have to store 1 x coordinate for every y coordinate. The color value is increase by a step value based on the number of colors between the vertices and the distance between the y coordinates.