Bump Mapping



Introduction

A bump map of an image is one whose intensity is a function of the intensity variation in the source image. This mapping is done at the pixel level of the images. Generating a bump map of an image should be simple once you have access to the input image. That would constitute a basic bump-mapping program. To enhance this, we could add a real time display routine to display the bump map, add a light source, which can be moved by the user etc. These and more have been accomplished in the bump-mapping program.

Working

As mentioned before, the source image has to be first loaded. The user is prompted to enter the source image file name.  For simplicity, only BMP format images are supported. The BMP file handling functions are wrapped inside a class called the Cbitmap. There are member functions in the class to load the image from the file to the memory in a 24 bits per pixel memory model Cbitmap::loadtomem( ) and also a function Cbitmap::loadytomem( ) to load to a memory block the saturation value of the image. This function is used here since for calculating the bump map, saturation value of each pixel is required. The bmpcodec library has been designed to handle all types of BMP format files with exception to some rarely seen variations. The highlight of the library being the ability to handle RLE compressed images and converting all the types of BMP files to the common 24-bits per pixel format while loading to memory. Note that the saturation map of the image is in the 8-bits per pixel format i.e. the saturation value range is 0 to 255.

Now we get to the actual task of generating the bump map. Firstly, some variables have to be initialized. The light map litemap[ ] has to be setup. For simplicity, let us consider a circular shaped light map with highest intensity at the center and receding outwards. This can be generated by the following code:

float nX, nY, nZ ;

for ( int y = 0 ; y < 512 ; y++ )

for ( int x = 0 ; x < 512 ; x++ )

{

nX = ( x - 256 ) / 256.0 ;

nY = ( y - 256 ) / 256.0 ;

nZ = 1 - sqrt ( nX * nX + nY * nY ) ;

if ( nZ < 0 ) 

nZ = 0 ;

litemap [ x + y * 512 ] = ( 511< ? ( nZ * 382 + nZ * nZ * nZ * nZ * nZ * nZ * nZ * nZ * nZ * 128 ) ) ;

} ;

 

After the light map, we need to load the palette that will be used while displaying the bump map. The .pal files should be loaded to the array palette[ ]. The loadpalette( ) function does this. The extra bit of code in this function is because the .pal file contains 256 RGB entries while our palette requires 512 entries. Hence, the 256 colors are dithered to twice as much.

With everything initialized, we can start generating the bump map with all the frills mentioned in the introduction. The Bump( ) function is called continuously until the user interrupts with a key press. In the bump routine, the simplified code looks something like this:

void Bump( )

{

int nx, ny ;

int rx, ry ;

int index ;

int i = 640 * 1 ;

mouse_info mi = get_m_info( ) ;

 

for ( int y = 1 ; y < 478 ; y++ )

for ( int x = 0 ; x < 640 ; x++ )

{

nx = hmap [ i + 1 ] - hmap [ i - 1 ] ;

ny = hmap [ i + 640 ] - hmap [ i - 640 ] ;

 

rx = x - mi.x ;

ry = y - mi.y ;

 

nx -= rx ;

ny -= ry ;

nx += 256 ;

ny += 256 ;

if ( nx > 511 || nx < 0 ) nx = 511 ;

if (ny>511 || ny < 0 ) ny = 511 ;

index = litemap [ nx + ny * 256 ] ;

 

putpixel ( x, y, palette[index].red, palette[index].green, palette[index].blue ) ;

i++ ;

} ;

}

In general terms, what the function does is, it calculates the difference between the intensities (saturation) of the adjacent horizontal and vertical pixels of every pixel. This value is then mapped onto the light map to get the intensity value of the pixel. Next, this intensity value is displayed on the screen using the palette.

If each pixel were to be written directly like this, it would result in a lot of flicker and poor performance. Hence a technique called double buffering, commonly used in computer games, is used here. The image to be drawn is not drawn directly to the screen, but to a memory block similar in dimensions to the screen. When all the drawing is complete, this memory block is transferred in one stretch to the screen reducing flicker and making animation smooth. The 'buffer.cpp' contains the Cscreenbuffer class, which handles the double buffer and its related functions. To use this class, an object of the Cscreenbuffer is created and allocated memory. All the drawings can be done in this buffer using the functions available such as the Cscreenbuffer::setpixel( ), Cscreenbuffer::bar( ), etc. After completing the drawing of the frame the buffer is transferred to the screen by using the Cscreenbuffer::refresh( ) function.

For displaying the image a true color graphics mode is required. Hence, we switch to a SVGA (Super VGA) mode. There are many hundreds of different and incompatible SVGA cards on the market today, but you won't need to write special code for each one because you can use the standard VESA interface instead. This is a software API that was designed by the Video Electronics Standards Association. It is usually implemented either as a loadable TSR utility or as part of the BIOS ROM on your video card. VESA allows you to do things like setting graphics modes and displaying images without needing to know the hardware details for every graphics chipset: this is obviously a good thing if you want your program to work across a range of different machines! The functions required to use the VESA SVGA mode are in the 'vesa.cpp' file. There are different functions that are necessary to detect VESA, set a required mode, close the mode etc. But the functions required to get the SVGA mode up and going are the vbe_initialize( ) and the vbe_close( ). All the drawing functions appear between these two statements.

It should be noted that the VESA implementations are buggy and are not uniform across all video cards. Hence, no software using VESA SVGA mode can claim to run successfully on all video cards. This program uses the VESA 1.2 specification functions for display. However, these were functions designed for 16-bit real mode interface. With newer 32-bit processors, VESA specifications were also upgraded to 2.0 and 3.0 supporting features like linear frame buffer and protected mode bank switching. These functions offer superior performance and massive speed up when used in programs. However, the support for these functions also become smaller among video cards and the program cannot be targeted towards a wider audience. Hence, at the cost of speed, this program is aimed towards a bigger audience. However, it is a relatively simpler task to make the program use the latest functions if your hardware supports it.

Some places where you would get VESA specifications are:

VBE 2.0 spec - ftp://x2ftp.oulu.fi/pub/msdos/programming/specs/vbe20.zip

The official VESA specification, version 2.0.

VESA 1.2 spec - ftp://x2ftp.oulu.fi/pub/msdos/programming/specs/vesasp12.zip

An earlier version of the VESA specification. This has been replaced by the document above, but can be a useful reference if you want to make sure that your code will work across a wide range of different VESA driver versions.

DJGPP specific usage - http://www.delorie.com/djgpp/ug

Conclusion

The above explanation describes briefly the skeleton of the bump-mapping program. Any other feature available in the program is just a tweak in the code or an add-on for fun. I would desist from explaining all that because it would deviate from the main agenda of introducing bump mapping.

Download