Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
47 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

Bump Mapping with Cg

Cg Setup

Before using any of Cg's facilities, you need to perform a few global initialization steps.

The Cg API returns error codes after each operation. You can either test these error codes explicitly, or you can setup a global error callback function that is called whenever an error is encountered. For simplicity, we prefer the error callback approach:

void cgErrorCallback()
{
    CGerror err = cgGetError();

    if (err != CG_NO_ERROR) {
        cerr << "cgErrorCallback(): " << cgGetErrorString(err) << endl;
        exit(1);
    }
}

To instruct Cg to use this error callback, call this API:

cgSetErrorCallback(cgErrorCallback);

All Cg programs (and their data) are stored in a global Cg context (one per application/process):

CGcontext context = cgCreateContext();
In order to compile and load a Cg program, you need to specify whether the program is a vertex or a fragment program first (select and configure the appropriate profile). If your hardware/software supports multiple versions of such profiles, it is a good idea to select the latest (most advanced) one (as a way to "future-proof" your code and make sure that, as hardware evolves, it will continue to work):
CGprofile profile = cgGLGetLatestProfile(CG_GL_FRAGMENT);
cgGLSetOptimalOptions(profile);

The API "cgGLSetOptimalOptions" will setup the optimal compilation parameters for the selected profile. We are now ready to compile and load our Cg program (from "[file name]"):

CGprogram program = cgCreateProgramFromFile(
    context,
    CG_SOURCE,
    [file name],
    profile,
    NULL,    // entry point
    NULL);   // arguments

cgGLLoadProgram(program);

If the entry point is not specified, it defaults to "main" (the vertex or fragment program begins processing at the function "main"). The arguments parameter is an array of arguments passed directly to the compiler.

Once the Cg program has been compiled and loaded, it must be bound and enabled. The binding step is necessary since you can load multiple Cg programs in an application, but you can only use one as the current vertex or fragment program.

cgGLBindProgram(program);
cgGLEnableProfile(profile);

At this point, the Cg runtime has been properly setup and initialized and your Cg program is ready to receive input parameters, run and produce output.

See end of the article for a complete code listing of the code snippets described above.

Cg Implementation

We are now ready to implement the bump-mapping algorithm described above in Cg.

Let's collect and summarize all the parameters that are necessary for performing the bump mapping calculation for every pixel (fragment) of our mesh:

  1. The detail texture: this is a texture containing the colors for every point in our triangle. This parameter does not vary for the current triangle (it is a uniform parameter).
  2. The detail texture coordinates: these are the texture coordinates of the current vertex in our triangle. This parameter varies for each vertex of the current triangle (it is a varying parameter).
  3. The normal map texture: this is a texture of the same size as the height map described above, where (x, y) entry contains the corresponding η vector in the R, G, and B components. Since η is normalized, we know that its components are all in the interval [-1, 1]; however, the R, G, and B components in a texture are all between 0 and 255. In order to store η in the texture, we need to range compress it as follows:
    η = ((η + (1, 1, 1)) / 2) * 255
    
    We first add (1, 1, 1) to η in order to bring its components between [0, 2]. We then divide it by 2, to bring its components between [0, 1]. Lastly, we multiply it by 255, to bring its components between [0, 255]-suitable for storage in a texture. This is a uniform parameter.
  4. The normal map texture coordinates: there are the texture coordinates of the current vertex in our triangle (they are equal to the detail texture coordinates above). This is a varying parameter.
  5. The light vector: this is the vector connecting the light to the current vertex in our triangle. This is a varying parameter.
  6. The ambient color: this is the color of the triangle when it is facing away from the light (it is "in the dark"). This is a uniform parameter.

Here is what the Cg code looks like:

float4 main(float2 detailCoords : TEXCOORD0,
            float2 bumpCoords: TEXCOORD1,
            float3 lightVector : COLOR0,
            uniform float3 ambientColor,
            uniform sampler2D detailTexture : TEXUNIT0,
            uniform sampler2D bumpTexture : TEXUNIT1): COLOR
{
    float3 detailColor = tex2D(detailTexture, detailCoords).rgb;

    // Uncompress vectors ([0, 1] -> [-1, 1])
    float3 lightVectorFinal = 2.0 * (lightVector.rgb - 0.5);
    float3 bumpNormalVectorFinal = 2.0 * (tex2D(bumpTexture, bumpCoords).rgb - 0.5);

    // Compute diffuse factor
    float diffuse = dot(bumpNormalVectorFinal, lightVectorFinal);

    return float4(diffuse * detailColor + ambientColor, 1.0);
}

Let's look first at the program declaration and parameters.

The program consists of a single function, called "main"; this is the entry point into the program. The function is declared to return a "float4" (a vector consisting of 4 floating point values: this is the required output color from the fragment program). The function receives a number of parameters as input: some are tagged "uniform" (these are the uniform parameters) and some are not (these are the varying parameters).

Some parameters are followed by a colon a keyword (for example, "float2 detailCoords : TEXCOORD0"). The colon indicates a binding semantic and the keyword is the target of that binding. For example, ": TEXCOORD0" indicates that the parameter to the left of it will receive the values of the texture coordinates for the current vertex from the first texture unit. Here is a listing of all binding semantics used in this program and their meaning:

Binding Semantic Meaning
TEXCOORD0 The texture coordinates in the first texture unit for the current vertex:
glMultiTexCoord2fARB(GL_TEXTURE0_ARB,x, y);
TEXCOORD1 The texture coordinates in the second texture unit for the current vertex:
glMultiTexCoord2fARB(GL_TEXTURE1_ARB,x, y);
TEXUNIT0 The texture bound to the first texture unit:
glActiveTextureARB(GL_TEXTURE0_ARB);
glBindTexture(GL_TEXTURE_2D,handle);
TEXUNIT1 The texture bound to the second texture unit:
glActiveTextureARB(GL_TEXTURE1_ARB);
glBindTexture(GL_TEXTURE_2D,handle);
COLOR0 The color for the current vertex:
glColor3f(r,g,b);
COLOR The color (output) from the fragment program.

Note that one parameter in the Cg program above does not have a binding semantic (the "ambientColor" parameter). This uniform parameter will be set using the Cg API. First, we retrieve the symbolic parameter from the Cg program:

CGparameter ambientColorParameter = cgGetNamedParameter(program, "ambientColor");

Then we set its value:

cgGLSetParameter3f(ambientColorParameter, r, g, b);

Let's look now at the program implementation.

The first line retrieves the color from the detail texture and stores it into a vector:

float3 detailColor = tex2D(detailTexture, detailCoords).rgb;

Note the ".rgb" ending of the line: this is called a swizzle. Although every element in the texture consists of 4 floating-point values (red, green, blue, and alpha), we are only interested in the color components. The ".rgb" ending retrieves only the first 3 floating-point values, suitable for storage in a "float3" vector. You can re-order or duplicate the entries in the swizzle as you wish, for example: ".bgr", ".rrr", ".rrg" etc.

The following two lines perform the inverse of the range compress operation described above in order to retrieve the signed representation of the light and η vectors (note, again, the use of the swizzle operator):

float3 lightVectorFinal = 2.0 * (lightVector.rgb - 0.5);
float3 bumpNormalVectorFinal = 2.0 * (tex2D(bumpTexture, bumpCoords).rgb - 0.5);

We are now ready to compute the lighting value as the dot product between the light vector and η (we use the built-in Cg "dot" function)

float diffuse = dot(bumpNormalVectorFinal, lightVectorFinal);

Finally, we compute the output color as a combination between the detail texture color and the ambient color:

return float4(diffuse * detailColor + ambientColor, 1.0);

Since we return a vector consisting of 4 elements, we fill in the last one (the alpha value) with 1.0.



Appendices

Contents
  Bump Mapping Background
  Cg Background
  Bump Mapping with Cg
  Appendices

  Source code
  Printable version
  Discuss this article