The CodeDemon's Guide To Translucency

Version: 1.2
Date: November 1, 1998
Time: 11:15 PM PST
Copyright © 1998 by Jesse Towner


How This Document Is Organized

  1. Disclaimer
  2. Included Files
  3. Introduction
  4. Translucency Primer
  5. Preparing Oneself To Use Translucency
  6. Alpha Blending Demystified
  7. Applying Translucency
  8. Who Are You?
  9. How Can I Contact You?




1. Disclaimer

First of all, the methods for translucency described within this document may not be the only methods. The methods described are the ones that I have discovered, developed and explored on my own. Second of all, I am not responsible for what ever happens to you or your property (or anyone else or their property) because of the use of this package. Use at your own risk. Any trademark mentioned in this package is the property of their respectful owner. You have the right to distribute this package, so long as you distribute it in it's entirety, without modification. You have the right to use this package in the development of commercial/shareware/freeware products without royalties, so long as the author is given credit. This package is owned and maintained solely by the author, Jesse Towner.

2. Included Files

Download : trans.zip

You should have gotten the following files with this package:

Blend24.c- The Alpha Blending Map for 24bpp/32bpp video modes.
Color.c- Color and palette routines.
Color.h- Header file for color.c.
File_id.diz- Description file of the package.
Graphics.c- Contains the graphics initialization and drawing functions.
Graphics.h- Header for graphics.c.
Linkfile.wlk- Watcom link file.
Makefile- Watcom make file.
Pcx.c- Simple 256 color PCX file loader.
Pcx.h- Header for pcx.c.
Pic1.pcx- PCX file used in example program.
Readme.doc     - The file you are now reading.
Test.c- The source to the example program.
Test.exe- Compiled test program, pre-bound and compressed with PMODE/W.
Trans.c- Translucency table generation routine.
Trans.h- Header for trans.c.

NOTE!: When using the example program, push the ESC key to skip through each section and use the arrow keys to move the current object around.

3. Introduction

Welcome to my first tutorial package to do with computer programming. I have decided to start with color translucency as I've found that not very many people know how to do this. Hell, I've even talked to people that don't think it is possible to do, in software, running in an 8 bits per pixel graphics mode; however, just look at StarCraft, by Blizzard Entertainment, to prove to yourself that it is possible! Besides, translucency and various other lighting effects associated with it can make a great addition to your graphics library!

I assume you have knowledge of basic graphics programming such as drawing primitives, clearing the screen, drawing pictures, drawing sprites, that sort of thing. I also assume you can write code in C because that's what I'm writing the examples with. In fact, almost all of the code is in C except for some of the drawing routines, which I will provide in x86 Assembly Language using the inline assembler. Normally, I code all of the drawing functions in external assembly (including clipping), since I'm an ASM freak ;-). Also, I'm writing this code for Watcom v11.0 (sorry, the inline "_asm" statement isn't available in previous versions) DOS 32-bit Protected Mode. However, it should be very simple to port it to other platforms and compilers. I'm only going to be using Mode 13h, since it is the simplest video mode to code for. Personally, I use various other standards (don't you just love the amount of standards out there!) such as the VESA BIOS Extension and the 3Dfx Glide v2.0 SDK for my old DOS graphics library. But DOS is unfortunately dead as far as the software market is concerned. Sure, DOS is still a good learning platform, but if you're looking at getting a job as a PC computer programmer, you're going to have to learn Win32 coding at some point. The reason why I chose DOS as the platform for this package is because I wanted to use Mode 13h for simplicity. Anyway, if you're programming in Pascal, or even in BASIC, and you don't understand C, well I'm sorry, but you're out of luck... Once I started coding in C and C++, I could NOT turn back to Pascal or BASIC: the freedom that C/C++ offers makes Pascal and BASIC seem primitive, boring and just plain stupid. Advice from me to you, learn C and C++, and if you're going to get into serious optimizing, learn x86 and x87 assembly language programming.

Now, I bet you're itchin' to write some heavy duty code utilizing translucency (I am!), but first we're going to give ourselves a little background about translucency. So read on and enjoy!

4. Translucency Primer

Now it's time for some definitions and terminology. Many people are mixing up transparency with translucency and this REALLY bugs me. IT ESPECIALLY BUGS ME WHEN THE BIG-NAME GAME COMPANIES ARE MIXING IT UP CAUSING OTHERS TO USE THEIR DEFINITIONS INSTEAD OF THE ONES IN THE ENGLISH LANGUAGE! So I've taken the liberty to look them up for you. Here are the definitions straight from Webster's Third New International Dictionary that apply to the transmission of light.

Transparent (Transparency) Having the property of transmitting light without appreciable scattering so that bodies lying beyond are entirely visible.
Translucent (Translucency) Admitting and diffusing light so that objects beyond cannot be clearly distinguished: partly transparent.

By using 0.1% of your brain, you should be able to interpret this. If you can't then you shouldn't be reading this document =). Anyway, you will see that you use transparency when you draw sprites and you skip the zero pixels for example (or whatever your mask value is). Thus, the image behind where these 'zero' pixels should be is completely visible, and therefore the 'zero' pixels are considered transparent. Translucency is when you take two colors and you blend them, drawing the result. Therefore you would take the color of the background pixel that was already there you and blend it with the color of the pixel you want to draw. The end result would be a pixel that has a color somewhere in between the destination pixel color (the pixel that you drew) and the background / destination pixel color.

There are a few names that are given to translucency when applied to computer graphics, here they are:

Semi-Transparency Just another name for translucency; partial transparency.
Alpha Blending The blending of two or more colors in relationship to the value of the alpha. The lower the alpha value, the more apparent the destination color (the color of the object in the background) is in the result color. The higher the alpha, the more apparent the source color (the color of the object in foreground) is in the result color. The alpha value usually ranges from 0 to 255 (256 different variances of the resulting color are possible by blending two colors together) but it can range have a range of any value. This is the type of translucency most used nowadays (N64, Playstation, 3D Graphics Accelerators etc.). This will be the type of translucency that we will use as it provides the best results.
Color Addition Taking two colors, dividing their RGB values by a certain value (usually 2, the higher the value, the darker the resulting color is), and adding the two sets of RGB values together to form the result. This is the type of translucency the SNES used. Here's a formula, perhaps this will help:

R(result) = (R(1) + R(2)) / 2;
G(result) = (G(1) + G(2)) / 2;
B(result) = (B(1) + B(2)) / 2;

Where all of the R, G, and B values are greater than zero and lesser than the common limit (256 for 24 bits per pixel modes, 64 for VGA 6-bit precision palettes for 8 bits per pixel modes). As you can probably see, this is the exact same thing as taking the average of the two colors! Very simple and effective! However, the amount variances of the resulting color that you can get from blending two colors together this way is limited. We will not be using Color addition to perform our translucency effects in this document. However the above formula can be changed a bit to have a more effective range of colors. This type is only really suitable for hardware.

Color Subtraction As you may have guessed, this is just the backwards way of doing Color Addition. Here is the formula:

R(result) = (R(1) - R(2)) * 2;
G(result) = (G(1) - G(2)) * 2;
B(result) = (B(1) - B(2)) * 2;

Where all of the R, G, and B values are greater than zero and lesser than the common limit (256 for 24 bits per pixel modes, 64 for VGA 6-bit precision palettes for 8 bits per pixel modes). The SNES also used this type of translucency (Chrono Trigger, and Mario RPG used it I believe).

5. Preparing Oneself To Use Translucency

Before we delve into actually writing the translucency routines, we're going to need some routines to help us out a bit. And with routines comes structures. First of all, we're going to need a simple RGB structure which will hold the RGB values in a palette. Then we're going to need a 256 color palette type.


     /* The RGB color structure */
     typedef struct gfxRgb_t {
          unsigned char r;        /* Red color value */
          unsigned char g;        /* Green color value */
          unsigned char b;        /* Blue color value */
          unsigned char a;        /* Alignment byte (not used as an alpha) */
          } gfxRgb_t;

     /* 256 color palette */
     typedef gfxRgb_t gfxPalette_t[256];

As you can see, the palette is just an array of type gfxRgb_t. Now we're also going to need a special function that will determine the color in a palette closest to a set of RGB values (AKA RGB tuple). So you call the function passing the RGB values of the color you want and it returns the color value which is the closest match. It doesn't need to be fast, so we're just going to write something that gets the job. Also note that we will only be needing this for 256 color video modes since high/true color modes use direct color mapping.



int GFX_FindBestColor(gfxPalette_t pal, int r, int g, int b)
/***********************************************************************
* Function:    GFX_FindBestColor
* Parameters:  pal   - the 256 color palette to search in
*              r,g,b - the r, g, b values. They must range from 0 to 63.
* Returns:     The value of the closest matching color.
*
* Description: Finds the closest matching color in the given palette for
*              a set of RGB values.
***********************************************************************/
{
 static unsigned long colorDifLut[3*128];
 static int firstTime = 1;
 int i, i2, colorDif, lowest = INT_MAX, bestMatch = 0;

 /* If this is the first time that this function has been called, then
generate a little lookup table (AKA LUT) of weighted squares. Color matching
is done by taking the weighted square of the difference between a certain
color's RGB values and the RGB values of the color we are searching for. */

 if (firstTime == 1) {
     firstTime = 0;
     for (i=0; i<64; i++) {
          int i2 = i * i;
          colorDifLut[0  +i] = colorDifLut[0  +128-i] = i2 * (59 * 59);
          colorDifLut[128+i] = colorDifLut[128+128-i] = i2 * (30 * 30);
          colorDifLut[256+i] = colorDifLut[256+128-i] = i2 * (11 * 11);
          }
     }

 /* Now, search through the palette for the best color. */
 for (i=0; i<256; i++) {
     colorDif = colorDifLut[(pal[i].g - g) & 0x7F];
     if (colorDif < lowest) {
          colorDif += (colorDifLut+128)[(pal[i].r - r) & 0x7F];
          if (colorDif < lowest) {
               colorDif += (colorDifLut+256)[(pal[i].b - b) & 0x7F];
               if (colorDif < lowest) {
                    bestMatch = i;
                    if (colorDif == 0)
                         return bestMatch;
                    else
                         lowest = colorDif;
                    }
               }
          }
     }
}

There we are. As for palette access routines, I will assume you know how to do these. In the example code, I include some routines for accessing the VGA palette, such as GetPalette, SetPalette, GetColor, and SetColor. Now, I believe we can get started on our translucency code.

6. Alpha Blending Demystified

So, you want to learn how to use alpha blending, huh? Well, this is where you're going to learn how. There are two methods to perform alpha blending in software that I know of, alpha blending the color on the fly and using lookup tables. As you may have guessed, the lookup table method is the fastest. However, alpha blending on the fly is quite feasible, so long as you don't use a lot of it, and you have a somewhat fast computer. You can use both methods in all color depths, it's just based on how you set it up. But before we get into the different methods in depth, lets look at how alpha blending is done in general.

To blend two colors together, one must use I  N  T  E  R  P  O  L  A  T  I  O  N. To get an idea of how to, we are just going to interpolate two values, but then we will extend that to interpolate an entire RGB tuple with another. Anyway, lets call these two values "intensities", like the intensities of each vertex on a gouraud or smooth shaded polygon. To get an intensity value at a certain point on a polygon edge, you would interpolate the two intensities (the left intensity i1, and the right intensity i2) along the polygons edge based on the height delta. The same idea is used here too, we interpolate each color's RGB values along the alpha delta. The alpha delta is just the highest alpha value. In our example here, and in the source code provided, the alpha value ranges from 0-255. Therefore, 255 is the highest value, and thus is our alpha delta. So, the difference between the intensities is the dividend, the alpha delta is our divisor, and the resulting quotient is our interpolant.

INTERPOLANT = (i1 - i2) / (255);

But what do we do with the interpolant? Well, we just interpolate it to the position, or in our case, alpha, that we want. To do that, simply multiply the interpolant by the alpha value, which can range from 0 to 255! We then take the product of the two and add our base to it, i2, and we then have the resulting alpha blended value!

new_i = i2 + (INTERPOLANT * alpha);

So, when it comes to alpha blending two colors together, we simply extend the above rule to include the three RGB values of each color, blend them together, and we get the resulting RGB values of the blended color. Here's our formula, where r1, g1, and b1 are the first color's RGB values, r2, g2, and b2 are the second color's RGB values, and new_r, new_g, and new_b are RGB values of the resulting alpha blended color:


new_r = r2 + (((r1 - r2) / 255) * alpha);
new_g = g2 + (((g1 - g2) / 255) * alpha);
new_b = b2 + (((b1 - b2) / 255) * alpha);

Very simple, as one can see. This code would work fine and dandy if we were using floating point. But when it comes using floating point for alpha blending, it can be slow since we must convert the RGB values of each color to floating point before we can blend them and then we have to convert them back after to form the resulting color value. So instead, we're going to modify our code a bit to include a form of fixed point math.


new_r = r2 + ((((r1 - r2) * 256 / 255) * alpha) / 256);
new_g = g2 + ((((g1 - g2) * 256 / 255) * alpha) / 256);
new_b = b2 + ((((b1 - b2) * 256 / 255) * alpha) / 256);

Now we're going to flip our code around a bit so that the RGB values don't have to be signed.


new_r = ((r1*(alpha*256/255)) + (r2*(256-(n*256/255)))) / 256;
new_g = ((g1*(alpha*256/255)) + (g2*(256-(n*256/255)))) / 256;
new_b = ((b1*(alpha*256/255)) + (b2*(256-(n*256/255)))) / 256;

As you should notice, the above equation gives you the same results as the previous equations. You might think this equation to be a bit slow, but if your compiler is worth it's dime, the multiply's and divide's by 256 should be optimized to shifts of 8. Also, when we apply this equation to the alpha blend on the fly method, everything in the equation is constant, so that the only variables are the two sets of RGB values to blend together. Therefore, in the end, it results in only being a few ADDs, SHIFTs, ANDs and OR's after the compiler is through with it. This is not bad at all. So to blend two colors together, one must do the following:

  1. Obtain RGB values of each color to blend.
  2. Perform the equation on the two sets of RGB values.
  3. Take the resulting RGB values and convert them back into the color

7. Applying Translucency

Well, now that we know how alpha blending works, lets use it! Lets look at the Alpha Blending on the fly method first. This method works on the basis of special casing. What we are going to do is write a special cased routine for each alpha, for a resulting number of 256 different functions. This may sound very extreme and a waste of time, but let me finish. In reality, we only have to write one routine, then we let the compiler take care of the other 255 functions. Before we get into how these functions are created, lets look at how the functions are setup and called. So we have all of our 256 functions. What we do now is put pointer to them all into a lookup table or array. The function for alpha zero would the first function in the array, the function for alpha one would be the second in the array and so on. So we're going to need a structure to hold our functions.


     /* This is are Alpha Blend Map structure */
     typedef struct gfxAlphaBlendMap_t {
          unsigned int (* alpha[256])(unsigned int color1, unsigned int color2);
          } gfxAlphaBlendMap_t;

As you can see, our blending functions take two parameters, the two colors we want to blend together and it returns the blended color back two us. What each function must do is take the two colors, get the RGB values of each color, blend them together, and convert the resulting RGB values back into the proper color value at which point it returns it. In the example code, we aren't going to use this method, but I will provide an example Alpha Blend Map and a single putpixel routine in both C and x86 ASM for 24-bits per pixel video modes. Using this as a base, you should be able to make alpha blending maps for each color depth. You should take note that the 24-bits per pixel blending map can also be used for 32-bits per pixel modes, but the actual routines for drawing to a 32-bpp surface must be different. It all has to do with the fact that the alignment byte in a 32-bpp pixel isn't used for color, but only to align each pixel on a DWORD boundary. When compiling the blending map, in blend24.c, it will take a while to compile (maybe 20-30 secs, which really isn't that long), so don't restart your computer when you think it has hanged ;-).

Now, lets talk about the lookup table method, which will be used to blend colors in 8 bits per pixel modes. Take note that this isn't the only method to blend colors in 256 color modes, but it is probably the simplest and fastest method for performing it. Since we only have 256 colors at one time, we can generate a lookup table. This table is a bit big, but in today's world of computers, a table that's 64k in size is nothing when the user has 32MB (64MB or even 128MB) of RAM to waste. The problem with this method is that you can only calculate the colors for a single alpha at a time. Of course, you can have more than one lookup table (where each table is of a different alpha), but you're only limited to so many. Anyway, lets have a look at the table we will be putting our data into first...


     /* The Color Look-Up Table (CLUT) structure. This can be used for
        alpha blending using color indirection (256 color modes). It can
        also be used for lighting and other special effects, but I haven't
        included that in this package. Perhaps another time =).
     */

     typedef struct gfxClut_t {
          unsigned char data[256][256];     /* Pointer to CLUT color info. */
          } gfxClut_t;

Looking at the above definition, you should see that it is an array of 256 colors by 256 colors. In essence, all we have to do is calculate every possible color combination for a particular alpha. To get the blended color result once the table has been created, we just plug in the two color values we want to blend into the array, and the resulting indexed value will be the blended color we want. Another problem with this method is that once a table has been filled with data, the palette can't be changed or you will have to regenerate the CLUT (which takes a bit of time) or load it in from disk (or you could pre-load it in from disk, but remember, you've only got so much RAM to work with, unless of course, you have a few Gigs!). So, when using this method, try to use a single palette, (or at the very least, only a few palettes) or you may find your program spending most of it's initialization/loading time generating CLUT's. Back to alpha blending... What we need for a function to generate the CLUT's is something that will take the palette we want to generate the CLUT for, plus the alpha. Using those, it will find every possible combination for alpha blending the 256 colors together and it will store them in the CLUT. We're also going to use a simple callback function, which can be used as a progress indicator (this is illustrated in the test program). Here's the function listing...


void GFX_CreateTransTable(gfxClut_t * clut, gfxPalette_t pal, int alpha, 
	void (* callback)(int pos))
/************************************************************************
* Function:    GFX_CreateTransTable
* Parameters:  clut     - pointer to the clut to fill with translucency data
*              pal      - palette to use when creating the table
*              alpha    - the alpha value to use when creating the table
*              callback - this is the callback function
*
* Description: Creates a translucency clut that can be used for 256 video
*              modes. If 'callback' is not NULL, then it will call the
*              function 256 times over the time it takes to make the table.
*              This can be used to determine how much of the table has been
*              completed and how much is still left to do as it can take a
*              bit of time to calculate. This callback function can be
*              used as a progress indicator.
************************************************************************/
{
  int index, index2;
  gfxRgb_t c;

  for (index=0; index<256; index++) {
       for (index2=0; index2<256; index2++) {
            /* Mix the two colors together based on the alpha */
            c.r = ((int)pal[index].r * alpha / 255) +
                       ((int)pal[index2].r * (255 - alpha) / 255);

            c.g = ((int)pal[index].g * alpha / 255) +
                       ((int)pal[index2].g * (255 - alpha) / 255);

            c.b = ((int)pal[index].b * alpha / 255) +
                       ((int)pal[index2].b * (255 - alpha) / 255);

            /* Store the blended color into the translucency clut */
            clut->data[index][index2] = GFX_FindBestColor(pal, c.r, c.g, c.b);
            }

       /* Should we call the callback function? */
       if (callback)
            callback(index);
       }
}

Simple and elegant, just the way I like it! In the source code, we will use a global gfxClut_t pointer called 'gfxActiveClut' to point to the active CLUT. This way, we can use different CLUT's without a bunch of hassle and extra code. One last thing, in order to properly blend the colors together, one must do the following.

  1. Read in the destination color (the color that is already there).
  2. Take the source color (the color that we want to write out).
  3. Blend them together using the table: result = gfxActiveClut->[source][destination];
  4. Write out the resulting color.

That should be about it. If you're a bit confused, have a good look at the source code and that should clear things up. Hope this document helps you out at your graphics programming endeavors. If you find this stuff useful, and if you use it in the production of a commercial/shareware/freeware product, I would appreciate it if you gave me some credit for my work and efforts. If you have any suggestions or modifications for this package, then contact me through one of the methods below.

8. Who Are You?

My real name is Jesse Towner. I eat, drink, and sleep coding, but sometimes I manage to make it out of the house =). I'm currently working for Digital Vortex, Inc. (IDEA Software and F.A.S.T Projects). You just might be seeing our games on the shelves by year 2000. Well, time to hit the sack and dream about new graphics algorithms... See ya, and have a good hack at this stuff!

9. How Can You be Reached?

You can reach me via the following ways (note that the following info may change fairly soon, I'll update it when it does change):

  • URL: http://www.ideasoftware.com
  • E-MAIL: codedemon@rocketmail.com
  • PHONE: (250)-846-5088

Discuss this article in the forums


Date this article was posted to GameDev.net: 12/22/1999
(Note that this date does not necessarily correspond to the date the article was written)

See Also:
Alpha Blending

© 1999-2011 Gamedev.net. All rights reserved. Terms of Use Privacy Policy
Comments? Questions? Feedback? Click here!