Sponsored By

"Ups and Downs" of Bump Mapping with DirectX 6

Bump maps give the appearance of a three-dimensional texture on an object, without adding geometry to the underlying model. Find out how bump mapping works, see sample code for implementing the effect, and download two bump-mapping demo apps.

Kim Pallister, Blogger

June 4, 1999

24 Min Read
Game Developer logo in a gray background | Game Developer

Anyone who has spent some time 3D modeling in any of the popular modeling packages has probably already been exposed to bump mapping. In fact, artists not constrained by the limits of real-time 3D game engines use bump mapping quite extensively to add height to rivets, texture to brick walls, and otherwise add nooks to their crannies and crannies to their nooks.

Recent advances in consumer hardware and software bring us to the point where we can now begin to implement bump mapping in DirectX-based games. Coupled with procedural texturing techniques, AGP texturing, and today’s powerful processors, some very impressive effects are possible.

You may be asking, "I’ve never used a modeling package – what is bump mapping?" First, I should explain that the way bump mapping is implemented under DirectX 6 is not what purists would call "real" bump mapping. If you asked Jim Blinn to tell you about bump mapping (and he would know, as he invented it), he would likely tell you it’s different than what I’m about to present. Blinn would tell you that bump mapping adds surface detail (bumpiness) to objects in 3D scenes, without adding more geometry than already exists. It does so by varying the lighting of each pixel according to values in a bump map texture. As each pixel of a triangle is rendered, a lookup is done into a texture depicting the surface relief (a.k.a. the bump map). The values in the bump map are then used to perturb the normals for that pixel. The new bumped-and-wiggled normal is then used during the subsequent color calculation, and the end result is a flat surface which looks like it has bumps or depressions in it.

So, this sounds great to you, right? Now you want to know how to code it up in DirectX 6. Well, you can’t implement textbook bump mapping in games. At least not as Blinn or other graphics gurus would have you do it. There are a couple reasons for this. First, today’s 3D hardware doesn’t perform a per-pixel lighting calculation. Instead, it performs Gouraud shading, where colors are interpolated from values calculated at the vertex level. Second, even if the hardware did perform bump mapping, you still couldn’t feed the API the normals and lighting information. It’s sort of a chicken-and-egg situation: if the hardware doesn’t perform the lighting (or even know where the lights are), then it can’t use a bump map to modify the lighting.

Since we can’t do "textbook" bump mapping under DirectX 6 with today’s hardware, another method is needed. As it turns out, there are two. The first method uses multi-texture or multi-pass rendering to achieve an embossed effect using a bump map. There’s a second, more versatile, method of performing bump mapping under DirectX, called environment-map bump mapping, and it is supported by DirectX 6.

The Embossing Technique

The embossing technique is a good way to add detail to your scenes without necessarily upping your geometry requirements. Though it can't be used to do as much as the DirectX 6 bump mapping method, it has the advantage that it works on the bulk of today’s 3D hardware.

Embossing uses a height map to add the appearance of protuberance to the surface. This is done by brightening and darkening the surface in different areas to make it look like the lighting in the scene is adding highlights and shadows (see Figure 1). While there are numerous ways to achieve this effect, one that works on most hardware (that is, on hardware that only supports single textures and basic alpha blending) involves doing three passes.

Here’s how this method works. First you create a height map texture. This could be a bitmap generated by an artist, of say, rivets, or in some cases it could be generated algorithmically (for example, with a brick wall texture, you could just assume places of a very different color are mortar, and thus depressions). This height map should be scaled or clamped to fit within a 0-0.5 range (where 1.0 is full intensity, or 255,255,255 in the case of a 24-bit texture). (Of course, the heightmap could be authored from scratch in this range.) Make a copy of the height map, equal to 0.5 minus the height map value – in other words, an inverse of the height map. This removes the need for a signed add, which a lot of 3D hardware doesn’t support. (This could also be authored ahead of time, but would double texture storage requirements).

On the first pass, your engine should render the polygon using the height map as the texture (see figure 1A). Shift the UV texture coordinates of the "negative height map" so that the texture faces towards the light source (figure 1B), according to the angle between the light vector and the vertex normal. For a directional light, the shift is the same for the whole polygon, and you would use the polygon normal. For a point or spot light, you would do a different shift for each vertex. The degree to which a height map should be shifted depends on the type of texture. If your height map contains sharp slopes, then over shifting can ruin the effect. If the slopes in the height map are gentle, then undershifting will hide the effect.

The second pass requires that you shift the triangle’s UV coordinates toward toward the light source, and then render the triangle using the "negative height map" (figure 1B) as the texture (the inverse bump map), using additive blending. At this point the frame buffer will contain values from 0 to 1, where pixels with values of 0.5 represents no bump, pixels with values less than 0.5 represent depressions in the surface, and pixels with a value of greater than 0.5 are sections on the surface that are raised toward the light. The result should be a grey texture with shadows and highlights (figure 1C).

On the third pass, you render the polygon with the object’s base texture, using modulate 2X mode. The 2X mode is used to effectively "cancel out" the 0.5 base value from the second pass, because the "flat" areas of the triangle contain a 0.5 value now (half of full intensity), so using a simple modulate would darken the triangle. The 2X cancels this out, resulting in full intensity for 'flat' areas, and bumped areas tend towards white or black, depending on location (see figure 1E).Likewise, when the values in the frame buffer from 0 to 0.5 tend toward 0, and values from 0.5 to 1 tend toward saturation.

Figure 1 – Embossing illustration.The following images illustrate the embossingtechnique, in the order it is done.

Figure 1A - Bump map

Figure 1B - Inverse bump map (0.5 - bump map)

Figure 1C - Bump map + shifted inverse bump map.

Figure 1D - The object's base texture.

Figure 1E - Bump map + shifted, inverse bump map,modulated with the base texture, and doubled.

The amount to shift the UV coordinates during the second pass depends on your application, and there is no hard rule for determining how far the texture should be shifted. Height map textures with sharp rises will cause artifacts if they are overshifted. On the other hand, undershifting will result in hiding the effect in places where the height map textures have soft edges on them. The proper amount to shift the height map depend on your content and how dramatic or subtle you want the effect to be.

The shift of the UV coordinates can be done once per triangle for a directional or distant point light. In the case of a point-light source (a light relatively close to the triangle being rendered), you may wish to calculate the shift per vertex, to get a shift that varies across the face of the triangle. (The sample application allows you to toggle between the two)

To improve the performance of this technique, embossing can be accomplished using two passes on most multi-texture hardware. Better yet, embossing in one pass can be accomplished on several of the newer 3D cards (like the 3dfx Voodoo2, ATI Rage 128, 3D Labs Permedia3, Matrox G-400, and Nvidia Riva TNT), but unfortunately the implementation varies from card to card, and results aren’t always the same, either. If you are willing to implement the different code paths, though, the added performance may be worth it.

While the embossing method works well and it’s easy to implement, it also has a number of limitations. It is merely a lighting trick, so it does not affect things like reflection and environment maps. Also, it only works under monochrome lighting (although it may be possible using a fourth pass to change the light color). Finally, the programmer has to ensure that the inverse bump map is not shifted too far from its original position, as the embossing effect can be ruined, and it starts to look more like someone cut shapes out of the triangle with scissors

Listing 1 contains code for implementing the embossing method of bump mapping under DirectX 6. (The downloadable "Emboss" sample application also explains how to implement the same effect in two passes on multi-texture hardware. I have left the many different single pass methods as an exercise for the reader, but several of the different card vendors have sample implementations on their developer websites.

Listing 1. Code to implement the embossing technique. For sake of brevity, some of the renderstates and texturestagestates that are less relevant to the sample are omitted, but some of them, such as those to implement filtering, can be found in the sample code that accompanies this article.

// Rendering Pass #1

lpD3DDevice->SetTexture( 0, lpBumpTexture );

//ignore FB color, just use the output of the SRC (the triangle we are about to render)

lpD3DDevice->SetRenderState( D3DRENDERSTATE_ALPHABLENDENABLE, FALSE);

lpD3DDevice->SetRenderState( D3DRENDERSTATE_SRCBLEND, D3DBLEND_ONE);

lpD3DDevice->SetRenderState( D3DRENDERSTATE_DESTBLEND, D3DBLEND_ZERO);

//set up the bump texture

lpD3DDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1 );

lpD3DDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );

lpD3DDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE );

lpD3DDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_DISABLE );

lpD3DDevice->SetTextureStageState( 0, D3DTSS_ADDRESS, D3DTADDRESS_WRAP );

lpD3DDevice->SetTextureStageState( 1, D3DTSS_COLOROP, D3DTOP_DISABLE );

lpD3DDevice->SetTextureStageState( 1, D3DTSS_ALPHAOP, D3DTOP_DISABLE );

hr = lpD3DDevice->DrawPrimitive( D3DPT_TRIANGLELIST, D3DFVF_TLVERTEX, pVertices, 6, D3DDP_DONOTLIGHT);

 

// Rendering Pass #2

lpD3DDevice->SetTexture( 0, lpInvBumpTextureRaw );

//using the D3DBLEND_ONE for both SRC and DEST blend factors results in an

// additive blend. FB = (Src * 1) + (Dest * 1)

lpD3DDevice->SetRenderState( D3DRENDERSTATE_ALPHABLENDENABLE, TRUE );

lpD3DDevice->SetRenderState( D3DRENDERSTATE_SRCBLEND, D3DBLEND_ONE );

lpD3DDevice->SetRenderState( D3DRENDERSTATE_DESTBLEND, D3DBLEND_ONE );

// inverted bump texture

lpD3DDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1 );

lpD3DDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );

lpD3DDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE );

lpD3DDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_DISABLE );

hr = lpD3DDevice->DrawPrimitive( D3DPT_TRIANGLELIST, D3DFVF_TLVERTEX, pVertices, 6, D3DDP_DONOTLIGHT);

 

// Rendering Pass #3

lpD3DDevice->SetTexture( 0, lpBaseTexture );

//The default blending mode with the frame buffer (D3DRENDERSTATE_TEXTUREMAPBLEND) is modulate

// so by picking the src and dst blend factors below, we get:

// Framebuffer color = (SrcColor*DestColor) + (DestColor*SrcColor), or 2*Dst*Src

// this is why the heightmap needs to be from 0 to 0.5 only, and not above 0.5

lpD3DDevice->SetRenderState( D3DRENDERSTATE_ALPHABLENDENABLE, TRUE );

lpD3DDevice->SetRenderState( D3DRENDERSTATE_SRCBLEND, D3DBLEND_DESTCOLOR );

lpD3DDevice->SetRenderState( D3DRENDERSTATE_DESTBLEND, D3DBLEND_SRCCOLOR );

// base texture

lpD3DDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_MODULATE );

lpD3DDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );

lpD3DDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE );

lpD3DDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_DISABLE );

lpD3DDevice->SetTextureStageState( 0, D3DTSS_ADDRESS, D3DTADDRESS_WRAP );

 

hr = lpD3DDevice->DrawPrimitive( D3DPT_TRIANGLELIST, D3DFVF_TLVERTEX, pVertices, 6, D3DDP_DONOTLIGHT);

DirectX 6 Environment-Map Bump Mapping

DirectX implements bump mapping using a technique best described as "environment-map bump mapping". This method can only be used on systems with hardware that specifically supports it. (See Table 2 for information regarding which DirectX caps bits indicate support under Direct3D). In the coming months, there will be a few 3D accelerator cards that support this method of bump mapping in hardware. Those announced include the Matrox G-400 and the 3D Labs Permedia3, and it is likely that many others will follow. The wide variety of effects it can achieve are impressive. We’ll get into it some of those later, but first, let’s see how the feature works and how it is used.

In DirectX 6, bump mapping is implemented using two textures (not including the object’s base texture). The first is a bump map which contains slope information (you can think of it as a height differential map), rather than height information, as is the case with the embossing method. (The bump map is generated by comparing adjacent texels in the height map). Hardware that supports DirectX 6 bump mapping uses these values to perturb the texel coordinates used in the lookup into a second texture (as opposed to using the data to alter the polygon’s normals). The second texture is an environment map, which is rendered onto the original polygon using these perturbed UV coordinates. Figure 2B shows what this looks like, where the environment map in this case is the scene above the water, render to a texture).

By making the environment map a radial gradient lightmap (something like you'd get holding the airbrush tool in a paint package in one place and holding down the mouse button for a second), we can achieve the same effect as the earlier embossing technique. Additionally, we are not limited to a single light source, nor to monochrome lighting.

The technique is as follows. First, set up D3Dtexture2 surfaces for the object’s base texture and the environment map, and fill them with bitmap data as usual. Next, set up a D3Dtexture2 for the bump map, but specify a couple additional caps bits during the DirectDraw surface creation. If necessary, create a height differential map from a height map. (I say "if necessary" because the bump map could be stored this way already, but if a height map is used, it needs to be modified.) Creating a height differential map from a height map simply involves stepping through the bitmap, and comparing neighboring pixels to get the slope, as shown in Listing 2. When you have the height differential map created, assign it to the surface you want to be bump mapped . Then you should set up the Direct3D texture cascade for the three textures and for the bump mapping-related D3DtextureStageState variables. These would be:

Stage 0 – Set texture to base texture (as usual)

Stage 1 - Set texture to bump map

Stage 2 – Set texture to environment map

For Stage 1, The additional render state values that can optionally be set up are:

  • D3DTSS_BUMPENVMATXX (where XX is 00, 01, 10 or 11). This is a 2x2 "bump mapping matrix".

  • D3DTSS_BUMPENVSCALE and D3DTSS_BUMPENVOFFSET. These two values alter the effect of the luminance component of the bump map, if a bump map with luminance is being used. (The luminance aspect is explained below).

The only thing left to do in each frame is render the triangles. Listing 2 is a code snippet demonstrating this technique.

Listing 2. Code snippet showing DirectX 6 bump mapping.

 

//Pseudo code for creating a height differential map from a height map

//you would also have to handle a special case for the last row and column of the bitmap

for (y=0;y

for (x=0;x

HeightDifferentialMap(x,y).dU = HeightMap(x,y).dU - HeightMap(x+1,y).dU

HeightDifferentialMap(x,y).dU = HeightMap(x,y).dU - HeightMap(x,y+1).dU

Next

Next

//DEVCAPS bits check (assumes you got the CAPS already)

if( pd3dDevDescription->dwTextureOpCaps & D3DTEXOPCAPS_BUMPENVMAP )

DoSomething();//this device supports bump mapping

Else

DontDoAnything();//this device does not support bump mapping

 

//Setup of the bump map surface (assumes a direct draw surface descripton, ddsd, structure

// is set up already, and that pddsurface is a pointer to a DirectDrawSurface)

ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT;

ddsd.ddsCaps.dwCaps = DDSCAPS_TEXTURE | DDSCAPS_SYSTEMMEMORY;

ddsd.ddsCaps.dwCaps2 = 0L;

// Pix format for DuDv88 bumpmap, could also do DuDvL556 or 888

ddsd.ddpfPixelFormat.dwSize = sizeof(DDPIXELFORMAT);

ddsd.ddpfPixelFormat.dwFlags = DDPF_BUMPDUDV;

ddsd.ddpfPixelFormat.dwBumpBitCount = 16;

ddsd.ddpfPixelFormat.dwBumpDuBitMask = 0x000000ff;

ddsd.ddpfPixelFormat.dwBumpDvBitMask = 0x0000ff00;

ddsd.ddpfPixelFormat.dwBumpLuminanceBitMask = 0x00000000;

// Create the bumpmap's surface and texture objects

if( FAILED( pDD->CreateSurface( &ddsd, &(pddsurface), NULL ) ) )

return NULL;

if( FAILED(pddsurface ->QueryInterface( IID_IDirect3DTexture2, (VOID**)&pd3dtexBumpTexture ) ) )

//Set the Base Texture

lpD3DDevice->SetTexure(0, lpBaseTexture);

lpD3DDevice->SetTexureStageState(0, D3DTSS_TEXCOORDINDEX, 0);

lpD3DDevice->SetTexureStageState (0, D3DTSS_COLOROP, D3DTOP_MODULATE); //gouraud

lpD3DDevice->SetTexureStageState (0, D3DTSS_COLORARG1, D3DTA_TEXTURE);

lpD3DDevice->SetTexureStageState (0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);

lpD3DDevice->SetTexureStageState (0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);

lpD3DDevice->SetTexureStageState (0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);

 

// Set up the Bump Texture

lpD3DDevice->SetTexure(1, lpBumpTexture);

lpD3DDevice->SetTexureStageState...

lpD3DDevice->SetTexureStageState (1, D3DTSS_TEXCOORDINDEX, 0);

lpD3DDevice->SetTexureStageState (1, D3DTSS_COLOROP, D3DTOP_BUMPENVMAP);

lpD3DDevice->SetTexureStageState (1, D3DTSS_COLORARG1, D3DTA_TEXTURE);

lpD3DDevice->SetTexureStageState (1, D3DTSS_COLORARG2, D3DTA_CURRENT);

lpD3DDevice->SetTexureStageState (1, D3DTSS_BUMPENVMAT00, DWORD(1.0f));

lpD3DDevice->SetTexureStageState (1, D3DTSS_BUMPENVMAT01, DWORD(0.0f));

lpD3DDevice->SetTexureStageState (1, D3DTSS_BUMPENVMAT10, DWORD(0.0f));

lpD3DDevice->SetTexureStageState (1, D3DTSS_BUMPENVMAT11, DWORD(1.0f));

lpD3DDevice->SetTexureStageState (1, D3DTSS_BUMPENVLSCALE, DWORD(1.0f));

lpD3DDevice->SetTexureStageState (1, D3DTSS_BUMPENVLOFFSET, DWORD(0.0f));

// Set up the Environment map texture

lpD3DDevice->SetTexure(2, lpEnvMapTexture);

lpD3DDevice->SetTexureStageState (2, D3DTSS_ADDRESS, D3DTADDRESS_CLAMP);

lpD3DDevice->SetTexureStageState (2, D3DTSS_TEXCOORDINDEX, 1);

lpD3DDevice->SetTexureStageState (2, D3DTSS_COLOROP, D3DTOP_ADD);

lpD3DDevice->SetTexureStageState (2, D3DTSS_COLORARG1, D3DTA_TEXTURE);

lpD3DDevice->SetTexureStageState (2, D3DTSS_COLORARG2, D3DTA_CURRENT);

 

The bump-map texture can support several pixel formats, and all of these formats are new to DirectX 6. The pixel formats are specified during the surface enumeration and creation by using the DDPF_BUMPDUDV flag and optionally the DDPF_BUMPLUMINANCE flag. These flags make it possible to expose three texture formats: two 16 bits-per-pixel formats (DuDv 88 and 556DuDvL), and one 32 bits-per-pixel format (888DuDvL). Du and Dv can be thought of as specifying the slope of the bump map in the U and V direction. The luminance factor specifies how to modify the RGB output of the bumped environment map.

 

The 2x2 bump-mapping matrix is used to rotate and scale the UV coordinates used to do the lookup into environment map. The formula used to offset the UV coordinates is as follows:

[U’,V’] = [U,V] + [bump(u),bump(v)] * [ M00 M01 ]

[ M10 M11 ]

which boils down to:

U’ = U + (bump(u)*M00 + bump(v)*M10)

V’ = V + (bump(u)*M01 + bump(v)*M11)

Where U and V are the coordinates for the current pixel being rendered, bump(u), and bump(v) are the values fetched from the bump map (at U,V), and U’ and V’ are the resulting coordinates used to fetch texels from the environment map for rendering.

When using a texture pixel format for the bump map that contains a luminance component, the D3DTSS_BUMPENVSCALE and D3DTSS_BUMPENVOFFSET arguments are also taken into account. After the matrix multiplication above, the pixel color is modified as follows:

L’ = bumpmap(L) * BumpEnvScale + BumpEnvOffset

Stage output = EnvironmentMap(U’,V’) * L’ * Polygon diffuse color

Using Bump Mapping Effects

The fun begins when we start looking at how we can use bump-mapping techniques to achieve different special effects. Many of the values defined above can be animated, which can create some awesome effects. Some effects that bump maps can create include:

  • Run-time generation of environment maps. The map could be a previously rendered frame, a composite of several lightmaps (if you have more than one light source), or a procedural texture from some sort of noise function (like swirling clouds). Figure 2B illustrates this technique (the sample application was used to generate this screenshot) -- the environment map being bumped is an actual rendered scene.

  • Procedural generatation or modification of a bump map. This could be a sinusoidal function, or it could be used to create things like bullet holes that get composited into the bump map at run time. The sample code demonstrates this as well -- as the waves on the water are procedurally generated, the user can click and drag on the water to generate waves in the bump map that then are used to distort the reflection

  • Animating the BUMPENVMATXX matrix to rotate and scale the texel fetches into the environment map. This could affect the bumpiness of an entire object. Goosebumps could suddenly appear on a character and could be varied just by changing two values in the matrix (just two renderstate calls)

  • Animating the BUMPENVOFFSET and BUMPENVSCALE settings, to modify or animate the luminance of the bumped texel color. This could be used to make highlighted bumpy areas move around on an object.

  • Using filtering and/or MIP-map techniques on bump map textures or an environment map. Interesting effects could be achieved when a player got closer to a bump-mapped object, such as increasing the detail of the textures, so that small perturbations on the surface become more apparent as the player examines an object more closely.

Figure 2. DirectX 6 bump mapping illustration.

Figure 2A. Scene rendered to texture,which is then used as the water surface.[zoom]

Figure 2B. Scene rendered to texture, which is then used as environment mapand distorted with DirectX 6 bump mappingand a procedural bump map texture.[zoom]

Figure 2C. Same scene as 2B,rendered in wireframe.[zoom]

Now that we’ve looked at how to implement DirectX 6 bump mapping, let’s look at a few ideas for effects that could be possible (I say "could" because I haven't tried them all yet) with the above techniques. I have included two demonstration applications that you can download and try: one that shows the embossing technique <emboss.zip>, and another that demonstrates uses bump mapping to create a reflection on shimmering water surface <waterdemo.zip>.

Embossing. Using the embossing example presented earlier in this article, you could create the effect of a spotlight on an object, using a spotlight light map texture. However, unlike the embossing technique that used multiple textures, colored lights could be used instead. If you used this technique in conjunction with vertex lighting, you would likely need to play with the scale and fall-off of the lightmap to get it to match your global lighting model.

Water reflection. An animated, procedural sinusoidal bump map can create very realistic water without a lot of geometry (see Figure 1).

Water distortion. An effect similar to that used in the software-rendered version of Quake (where the entire frame buffer was animated in a sinusoidal pattern) could be implemented. First, you would render the 3D scene to a texture surface. Then using that texture as the environment map, render a couple of triangles to fill the frame buffer and use a sinusoidal patterned bump map to distort the view of the rendered scene, thus bending the entire scene according to the wave pattern.

Circus mirror and lens effects. Using a texture surface as the rendering target, render a scene (or a subset thereof) from the point of view of a mirror in your scene. Then use this rendered scene as the mirror's texture, and use bump mapping to perturb the reflection/refraction according to the values in your bump map. This way the mirror could be bent and warped, like a funhouse mirror, to distort the view of the scene.

Super Mario 64-style portals. An animated texture for the bump map could mimic the effect used in Mario 64 in which the portals that Mario jumped through rippled after he passed them. Nintendo create the effect in Mario 64 using additional geometry.

Better level-of-detail (LOD) transitions. Low LOD models can use bump mapping to hide their low polygon count. For example, if a building has a door handle and window sills that normally stick out from building itself, then when the viewer is far away from the building, the lower LOD version could simply be a cube. This low-detail version of the model could use bump mapping to make the door handle and window sills appear lighted or shadowed as lights moved about in the scene. As the player approaches, you could swap the low-detail geometry for the higher LOD, and turn off bump mapping.

Bullet holes, with Terminator 2-style melting effects. Bullet holes could be created in a surface by compositing in a pre-computed "bullet-hole bump map" onto the objects. Then, just like the silvery-surfaced bad guy in the movie Terminator 2, the bullet holes could melt away by animating the BUMPENVMATXX values toward 0.

Heat distortion rising off the ground. This effect could be done in a similar fashion to the water distortion effect earlier described, but applied to just a subsection of the scene. This may be an instance in which you would want to use a DuDvL format, where luminance in the bump map would taper off toward the top, making the rippling effect fade.

Rippling cloth/clothing/flags. By either procedurally rendering a bump map with an animation, or simply by animating the UV coordinates of a bump map, a character’s shirt could appear to ripple in the wind.

Flight simulator detail textures. This is a similar idea to detail texturing, in which blending a highly tiled texture onto a base texture adds detail -- grossly oversampled textures become more clearly defined as the viewer gets close to them. For example, imagine farm fields viewed from an altitude of 1,000 feet in a flight simulator. At that height, the fields look like a quilt. However, at a height of ten feet (say, when you’re about to crash into the field) the detail adds blades of wheat. Likewise, you could use bump mapping to show 20-foot waves on water from 1,000 feet up, and when the plane swoops down to ten feet in altitude (I never could keep that darned plane in the air), you can make out small ripples or rain drops.

Implementing the Techniques

Depending on your goals and the limitations of your game, some of the suggestions above will work, and some may not. It all depends on what effects you are trying to achieve, what your content will benefit from, and how your engine is structured to scale across different hardware. However, I hope that it points out that the DirectX 6 bump mapping technique available on several cards coming out this year offers an exciting opportunity for developers to add some really cool effects to their games.

Don’t forget what bump mapping was first developed for: adding detail without adding polygons. This is a boon in cases where the application has to scale across a range of systems, and where using multiple LOD models isn’t an option (e.g., the architecture in levels of some first-person shoot-style games). Both the embossing method and the environment-map bump-mapping approach can be used to add details to brick walls, grooved marble pillars, and riveted steel staircases.

Of course, you have to weigh the benefits of bump mapping (adding less triangles and achieving cool effects) against the drawbacks (writing code paths to detect and use different hardware, plus the additional fill rate that bump mapping requires), to see what makes sense for your application. The good news is that as faster multi-texturing graphics hardware comes to market in the near future, the latter drawback will become less of an issue. It is also worth noting that while multi-texturing performed in hardware tends to cause a performance hit on today’s hardware, the multi-pass approach is even more expensive. This is due to the read-modify-write accesses to the frame buffer, and it’s even more of an issue when it’s performed on older hardware, since older graphics chips typically have lower fill rates than today’s newer chips.

Developers writing to the PC platform deal with systems that vary widely in performance. Fortunately, both the bump mapping approaches described in this article help you scale your titles for various types of hardware without a lot of extra development effort and artwork. You just develop your game so that bump mapping features are switched off when the game detects a slower CPU and graphics card, and switch it on to add detail and perform effects on higher end systems. Bump mapping can make the graphics in your next title, pardon the pun, stand out.

Kim Pallister is a Technical Marketing Engineer and Processor Evangelist with Intel’s Developer Relations Group. He can be reached at [email protected]

Read more about:

Features

About the Author

Kim Pallister

Blogger

Kim Pallister is a Technical Marketing Engineer and Processor Evangelist with Intel's Developer Relations Group. He is currently focused on realtime 3D graphics technologies and game development. He can be reached at [email protected].

Daily news, dev blogs, and stories from Game Developer straight to your inbox

You May Also Like