Hardware Accelerated Spherical Environment Mapping using Texture Matrices

Contents

The Spheremap Demo

A Closer Look: Vertex Buffers

A Close Look: Direct 3D Texture Matrix

When you have removed the z-component check, all that's left to do in the main loop is generate texture coordinates. The vector [m11, m21, m31] is the local space +X direction in camera space and the vector [m12, m22, m32] is local space +Y direction in camera space. Recall that all normal vectors are points on a unit sphere, so the code generating the texture coordinates is effectively calculating the longitude and latitude coordinates of the normal vectors position on that sphere (or the cosines of them) by taking the dot product of the unit normal with the unit axes (see Figure 2a & 2b). The output of that calculation is scaled and biased so that the center of the sphere map is the origin:

Figure 2a: Normal vector

If we consider that the sphere map UV coordinate calculation requires two dot products, and a matrix*vector performs four dot products we should be able to perform the same calculation using a texture matrix. Direct3D supports 4x4 texture matrices at every texture stage so all we have to make a texture matrix that performs the same dot products as discussed above, also by carefully creating the texture matrix the scale and bias is automatically performed so the origin is in the center of the texture map. The required texture matrix looks like the following:

NOTE: In the above math, the vectors lsx and lsy are used in place of to represent the local space x and y axis in camera space - in other words, the local spacevectors respectively, transformed by the local*world matrices.

Next, specify the vertex normal as the first three elements of the input texture coordinate vector, and the forth element will automatically be set to its default of 1. The specified texture matrix will be applied to the texture coordinates (normal vector) and the resulting texture coordinated vector identical to that in the DirectX example.

Note: DirectX has no specific naming convention for the elements of a 4D texture coordinate so I will use the standard of. While performing standard 2D texture mapping 'r' component is equivalent to 'u', likewise the 's' component is 'v' and elements 't' and 'q' are unused.

The following code sets the above texture matrix at stage 0. This operation needs to be done any time either the world or local matrices change, as LocalToEyeMat = Local*World:

    D3DMATRIX tex = IdentityMatrix;
    tex_mat._11 = 0.5f*LocalToEyeMat._11;
    tex_mat._21 = 0.5f*LocalToEyeMat._21;
    tex_mat._31 = 0.5f*LocalToEyeMat._31;
    tex_mat._41 = 0.5f;
    tex_mat._12 = -0.5f*LocalToEyeMat._12;
    tex_mat._22 = -0.5f*LocalToEyeMat._22;
    tex_mat._32 = -0.5f*LocalToEyeMat._32;
    tex_mat._42 = -0.5f;

    3DDevice->SetTransform(D3DTRANSFORMSTATE_TEXTURE0,       &tex_mat);

There is one additional render state that needs to be set. You must tell Direct3D to apply the texture matrix and to use just the first two elements of the result:

    3DDevice->SetTextureStageStat ( 0,       D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2 );

Direct3D has no way of specifying that the untransformed normal should be used as input into the texture matrix. The quick fix for this is to create a flexible vertex that has a position, normal and a three-element texture coordinate, and when the buffer is filled, you copy each normal vector into the texture coordinate. Unfortunately, this also increases the size of each vertex by 12 bytes and consumes more bandwidth when processing the buffer. (In a basic vertex case, these extra 12 bytes increases the vertex size by 50%.) But the cost is worth it: you can perform the "normal" spherical environment mapping (as used in the Direct3D sample) with a static vertex buffer, using nothing more than the standard Direct3D pipeline. This is a big win with hardware, since cards like nVidia's GeForce and GeForce2 process the texture matrix in hardware without CPU intervention, allowing the vertex buffer to be stored in local video memory.

Note that both the Direct3D and texture matrix examples expect a unit scale in the local-to-camera space transform (local*world). If this isn't the case, the texture matrix must be scaled by the inverse of the scale factor. Additionally, the normal-vector texture coordinates are expected to be of unit length. If this technique is applied to dynamic geometry, then every time a normal is modified, the associated texture coordinate needs to be updated. Another shortcoming of the method discussed above is that only the original input normal vectors are considered when calculating the reflection which for most meshes is fine but when mesh skinning is applied there is a problem. When skinning a mesh in hardware each vertex (position and normal) is multiplied by a pair of world transforms, the final position and normal is calculated from a weighting applied to the results of these transforms. This skinned normal and position is not available outside of the graphics pipeline but to obtain a correct reflection we need to know what the skinned normal vector was but we have a problem, one solution would be to use the CPU to reskin the mesh but this is expensive.

________________________________________________________

A Close Look: Direct 3D Texture Matrix