Environment Mapping Explained

This sample uses high-level shader language (HLSL) and effects to apply a cubic environment map using a pixel shader. A vertex shader is also used trivially to transform the vertex data to projection space. The shaders and the helper functions are written in HLSL. An effect encapsulates the sampler state, the texture state, and a technique that invokes the vertex and pixel shaders.

How an Environment Map Works

An environment map, sometimes referred to as a reflection map, is a method for applying environment reflections to a curved surface. A cubic environment map, is a texture map that contains six 2D textures, one for each direction from a point (see Cubic Environment Mapping). By applying an environment map, an object can be made to appear like it has a highly smooth surface (such as an object made out of metal or glass). This example illustrates the difference between two objects: one object lit with normal diffuse lighting, and one that has a environment map applied.

Figure 1. Standard ambient/diffuse lighting Figure 2. With an environment map and identical lighting

The skull in Figure 1 appears to have a flat finish; there are no reflections from the environment and no glossy or specular highlights. In contrast, Figure 2 contains an environment map that makes the skull appear to have a highly reflective, glossy surface.

How This Example Works

An environment map relies on a reflection vector to sample the texture. The reflection vector leaves the point being textured at an angle form the normal that is equivalent to the angle between the view vector to the point. This is shown below.

Generating the Reflection Vector

The reflection vector will be used as a lookup (in other words, as texture coordinates) for the texture sampling. The vertex shader calculates the reflection vector with the help of the CalcReflectionVector HLSL function as shown here.

// Generate a reflection vector to lookup in a reflection cube-map
Out.CubeTexcoord = CalcReflectionVector(EyeToVertex, Normal);

The helper function uses the reflect - HLSL HLSL intrinsic function to generate a reflection vector from the view vector and the vertex normal. The last thing the helper function does is normalize the resulting vector by calling the normalize - HLSL intrinsic function.

float3 CalcReflectionVector(float3 ViewToPos, float3 Normal)
{
    return normalize(reflect(ViewToPos, Normal));
}

You may want to test your normals to see that they are correct, by simply returning the normal with this function. This will produce a texture image on your object that is incorrect, but it will help verify the normal data. Once this works, add the reflect and normalize intrinsic functions to create correct environmental mapping.

Environment Map Lookup

The environment map lookup is a cubic texture sample. The texture sample requires the input texture coordinate (passed into the helper function by CubeTexcoord), and the sampler that we declared earlier. The sampling is done with the help of the HLSL intrinsic function texCUBE - HLSL, which takes the 3D coordinates, and returns a 4D color value.

float4 CubeMapLookup(float3 CubeTexcoord)
{
    return 0.25 * texCUBE(EnvironmentSampler, CubeTexcoord);
}

Lastly, once the texture is sampled and a color is returned, this will be added to the existing pixel color in the pixel shader. The 0.25 factor is an arbitrary number chose to scale down the influence of the sampled color. This is done so that the appearance of the surface is not completely reflective.

Technique

In the render loop, an application sets the current technique ID3DXEffect::SetTechnique. This technique contians a single pass which calls a vs_2_0 vertex shader and a ps_2_0 pixel shader.

technique TEnvironmentMap
{
    pass P0
    {
        VertexShader = compile vs_2_0 VS();
        PixelShader = compile ps_2_0 PS();
    }
}

Pixel Shader

The pixel shader is also very simple, thanks to the CubeMapLookup helper function. The pixel shader is passed in the output of the vertex shader, and then returns a single color, marked by the COLOR semantic just after the functions argument list.

The first line of the pixel shader sums the diffuse and specular color to make the final color. The second line adds an additional color that is returned by the CubeMapLookup helper function; this is the cubic environment map sample that results from looking up the texture coordinates that were calculated by the vertex shader.

struct VS_OUTPUT
{
    float4 Pos  : POSITION;
    float4 Diff : COLOR0;
    float4 Spec : COLOR1;
    float3 VolTexcoord : TEXCOORD0;
    float3 CubeTexcoord : TEXCOORD1;
};
float4 PS ( VS_OUTPUT In) : COLOR
{
    float4 OutColor;
    
    OutColor = In.Diff + In.Spec;

    OutColor += CubeMapLookup(In.CubeTexcoord);

    return OutColor;
}