Bump mapping simulates high-detail surface irregularities, not by using additional geometry but by perturbing the normals. Because normals are the driving force behind most rendering equations, the shading is locally modified, giving the illusion of bumps, wrinkles, and so on to the viewer.
Bump mapping was introduced by Blinn in the late 1970s and is now an extremely popular technique in offline renderers. Slowly, more and more games are incorporating it as well, as hardware manufacturers keep inventing simpler, more powerful ways to specify bump mapping. In this section, we will review the three most popular methods along with some specific code to generate bump mapping in DirectX.
Emboss Bump Mapping
Embossing an image is a processing operation that enhances edges directionally, giving them a raised look. Emboss bump mapping tries to achieve the same results in real time by using several texturing passes. Specifically, the classic algorithm uses three passes. Here's the complete rundown:
We can reduce this to two steps by encoding the bump map in the alpha channel, so the algorithm is as follows:
As you may have guessed, the blending between the RGB and the alpha channel must be a multiply, so we implement Lambert's equation for diffuse lighting. The following DirectX 9 example uses this approach:
m_pd3dDev->SetTexture( 0, m_pEmbossTexture ); m_pd3dDev->SetTextureStageState( 0, D3DTSS_TEXCOORDINDEX, 0 ); m_pd3dDev->SetTextureStageState(0,D3DTSS_COLOROP,D3DTOP_MODULATE ); m_pd3dDev->SetTextureStageState(0,D3DTSS_COLORARG1,D3DTA_TEXTURE); m_pd3dDev->SetTextureStageState(0,D3DTSS_COLORARG2,D3DTA_DIFFUSE ); m_pd3dDev->SetTextureStageState(0,D3DTSS_ALPHAOP,D3DTOP_SELECTARG1); m_pd3dDev->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE ); m_pd3dDev->SetTexture( 1, m_pEmbossTexture ); m_pd3dDev->SetTextureStageState(1, D3DTSS_TEXCOORDINDEX, 1 ); m_pd3dDev->SetTextureStageState(1,D3DTSS_COLOROP,D3DTOP_SELECTARG2); m_pd3dDev->SetTextureStageState(1, D3DTSS_COLORARG1,D3DTA_TEXTURE); m_pd3dDev->SetTextureStageState(1, D3DTSS_COLORARG2,D3DTA_CURRENT); m_pd3dDev->SetTextureStageState(1,D3DTSS_ALPHAOP, D3DTOP_ADDSIGNED ); m_pd3dDev->SetTextureStageState(1,D3DTSS_ALPHAARG1, D3DTA_TEXTURE | D3DTA_COMPLEMENT ); m_pd3dDev->SetTextureStageState(1,D3DTSS_ALPHAARG2,D3DTA_CURRENT ); m_pd3dDev->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE ); m_pd3dDev->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA ); m_pd3dDev->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ZERO ); m_pObject->Render( m_pd3dDevice );
Notice how we set the texture stage 1 to do an add while we are complementing the map.
The complex part of any emboss bump mapping algorithm is how to compute the light direction, and thus shift the u,v texture values to simulate bumps. We must begin by placing a coordinate system at the vertex and expressing the light direction in terms of those coordinates. The first vector we will use is the normal at the vertex. The second vector is a vector along either the u or v texture coordinate axes, tangent to the surface. The third vector, dubbed the binormal, is just the cross product between the normal and tangent vector. These three vectors conform a basis matrix, which can be used to convert the light direction to local coordinates. This new vector is said to be in the object's tangent space. By scaling it (depending on the amount of bumps we want to simulate), we can add its X and Y components to the u,v pairs, and thus generate a nice looking bump map effect.
Dot3 Bump Mapping
The Dot3 bump mapping method starts not with a heightfield as embossed bump mapping did, but with a normal map: a regular bitmap that encodes normals instead of RGB triplets. Then, to render the bump maps, we transform the light position to the tangent space, as we did with embossing. We then have one light vector for each vertex in the mesh. To render a triangle, the light vectors for each one of the three vertices are interpolated. Notice how this is not a color interpolation but a light vector interpolation. Then, the bump map is combined with the interpolated light value for a given pixel. These are combined with a dot product as in the classic Lambert equation. This dot product is implemented as a special texture blending function.
Here is the sample code to perform Dot3 bump mapping on Direct3D 9:
// we compute a light vector D3DXVECTOR3 m_vLight; point p(0.5,1,0.5); p.normalize(); m_vLight=p.x; m_vLight=p.y; m_vLight=p.z; // encode the vector into a double word DWORD dwFactor = VectortoRGBA( &m_vLight, 10.0f ); // set the computed factor so the texture unit uses it d3d_device->SetRenderState( D3DRS_TEXTUREFACTOR, dwFactor ); // set the texture (here goes the normal map) texture->activate(0); // operation: dot3 d3d_device->SetTextureStageState(0,D3DTSS_COLOROP, D3DTOP_DOTPRODUCT3); // first argument: the normal map d3d_device->SetTextureStageState(0,D3DTSS_COLORARG1, D3DTA_TEXTURE); // second argument: the factor d3d_device->SetTextureStageState(0,D3DTSS_COLORARG2, D3DTA_TFACTOR);
Dot3 bump mapping has an advantage over embossing in that results look remarkably better. Additionally, Dot3 can handle specular highlights on the bump maps, whereas emboss only supports the diffuse component of lighting. Additionally, the code is remarkably simple.