






Study with the several resources on Docsity
Earn points by helping other students or get them with a premium plan
Prepare for your exams
Study with the several resources on Docsity
Earn points to download
Earn points by helping other students or get them with a premium plan
A step-by-step guide on how to implement a bump mapping effect using nvidia's cg programming language and opengl. The article covers the necessary bump mapping theory and presents the code for transforming light vectors and texture coordinates between object space and texture space. It also explains the use of cg vertex and fragment programs, and the setup process for cg.
Typology: Exams
1 / 12
This page cannot be seen from the preview
Don't miss anything!







Cg Bumpmapping Game
The page you are looking for is currently unavailable. The Web site might be experiencing technical difficulties, or you may need to adjust your browser settings
by Razvan Surdulescu
Get the demo, source, and documentation for this article here.
This article describes how to implement a simple and effective bump mapping effect using n VIDIA's Cg programming language and OpenGL.
Although the focus of the article is on Cg, a limited amount of bump mapping theory is necessary and presented first. Additional bump mapping references are listed at the end of the article.
The goal of bump mapping is to create the illusion of "ridges" and "valleys" on otherwise flat surfaces. example [1]:
The color of a surface is determined by the angle (dot product) between the normal vector of that surf the light vector:
Flat surface (^) Bump mapped surface
The light source is assumed to be placed "at infinity", and all rays from it are parallel to each other by they reach the surface.
On a flat surface, the normal vector is the same everywhere on that surface, so the color of that surfa the same everywhere.
If the normal vector could be perturbed at various points on that surface, it would yield areas that are lighter, thereby creating the illusion that parts of the surface are raised or lowered:
In order to compute the normal vector at every point on the surface, we need some additional informa whether points on that surface are "high" or "low". We use a height map to store this information (dar are "low", light areas are "high") [1]:
The normal vector at a point is defined as the cross product of two tangent vectors to the surface at th The points in the height map range from (0,0) to (width, height) in a two-dimensional system of coord
Consider the following triangle whose vertices (V1, V2, V3) are defined in object space and whose text coordinates (C1, C2, C3) are defined texture space [2]:
Let V4 be some point (in object space) that lies inside the triangle and let C4 be its corresponding text coordinate (in texture space). The vector (C4 - C1), in texture space, can be decomposed along T and T component be (C4 - C1) (^) T and let the B component be (C4 - C1) (^) B.
The vector (V4 - V1) is the sum of the T and B components of (C4 - C1):
It follows immediately that:
This is a system of two equations with two unknowns (T and B) that can be readily solved for T and B:
where
t1 = (C2 - C1) (^) T , t2 = (C3 - C1) (^) T b1 = (C2 - C1) (^) B , b2 = (C3 - C1) (^) B
By definition, the matrix that transforms vectors from texture space to object space has as columns th T, B, N. The inverse of this matrix transforms from object space to texture space. For example, this (in matrix will take the triangle normal (from object space) and map it to the Z axis (in texture space); si this (inverse) matrix will take the light vector (from object space) and transform it to texture space. A point, we can use η and the newly transformed light vector to compute the lighting value at every poin height map.
Cg Background
"Cg is a language for programming GPUs. Cg programs look a lot like C programs." [4]
The GPU stands for Graphics Processing Unit: it is a specialized integrated circuit that can perform com graphics computations. The two GPU operations that can be programmed via Cg are: vertex operation
fragment operations.
A Cg vertex program can perform certain computations on the GPU for every vertex defined in a mesh fragment program can perform certain computations on the GPU for every fragment (pixel or point on screen) in a mesh. Cg provides a number of graphics-specific primitives (such as vector dot- and cross products, matrix multiplications, etc.)
Any Cg program expects certain parameters as input and is required to produce certain parameters as For example, a Cg vertex program probably needs the position of the current vertex and some matrix and is required to produce the modified position of the current vertex as output. Similarly, a Cg fragm program probably needs the position of the current fragment and some color and/or texture paramete and is required to produce the color of the current fragment as output.
The parameters for a Cg program are of two types: varying and uniform. Varying parameters, as the n implies, vary for every graphics element drawn on the screen (for example, the vertex position is a va parameter to a vertex program). Uniform parameters are the same for the current batch of graphics e (for example, the ambient light color is a uniform parameter to a fragment program).
The vertex program always runs before the fragment program, and the output from the former can be directly into the latter.
Cg programs are "compiled" into "assembly" code that is specific to the targeted GPU and the associat software driver. For example, a vertex program written in Cg can be compiled and targeted to the Dire or to the n VIDIA (proprietary) OpenGL extensions. The Cg runtime can be instructed to silently select appropriate compiler target for the current platform.
A (syntactically and semantically) valid Cg program may fail to compile due to hardware limitations. Fo example, the program may require more registers (or combiner stages) than are available on the GPU case, you would need to modify the program, remove features from it, or buy better hardware.
For further information about the Cg language and supporting API, refer to the official n VIDIA docume [3].
Bump Mapping with Cg
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, o setup a global error callback function that is called whenever an error is encountered. For simplicity, w 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:
y) entry contains the corresponding η vector in the R, G, and B components. Since η is normaliz know that its components are all in the interval [-1, 1]; however, the R, G, and B components in are all between 0 and 255. In order to store η in the texture, we need to range compress it as fo
η = ((η + (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 its components between [0, 1]. Lastly, we multiply it by 255, to bring its components between [ suitable for storage in a texture. 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.
// 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 f declared to return a "float4" (a vector consisting of 4 floating point values: this is the required output 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 : TEXCOOR
colon indicates a binding semantic and the keyword is the target of that binding. For example, ": TEXC indicates that the parameter to the left of it will receive the values of the texture coordinates for the cu vertex from the first texture unit. Here is a listing of all binding semantics used in this program and th meaning:
Binding Semantic Meaning
TEXCOORD0 The texture coordinates in the first texture unit for the current vertex: glMultiTexCoord2fARB(GL_TEXTURE0_ARB,x, y);
The texture coordinates in the second texture unit for the current vertex:
Note that one parameter in the Cg program above does not have a binding semantic (the "ambientCol parameter). This uniform parameter will be set using the Cg API. First, we retrieve the symbolic param 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 cons
floating-point values (red, green, blue, and alpha), we are only interested in the color components. Th ending retrieves only the first 3 floating-point values, suitable for storage in a "float3" vector. You ca
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 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 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 ambien
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.
TEXCOORD1 glMultiTexCoord2fARB(GL_TEXTURE1_ARB,x, y);
The texture bound to the first texture unit: glActiveTextureARB(GL_TEXTURE0_ARB); glBindTexture(GL_TEXTURE_2D,handle);
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.
#include <Cg/cg.h> #include <Cg/cgGL.h>
void cgErrorCallback() { CGerror err = cgGetError();
if (err != CG_NO_ERROR) { cerr << "cgErrorCallback(): " << cgGetErrorString(err) << endl; exit(1); } }
void main(int argc, char* argv[]) { cgSetErrorCallback(cgErrorCallback);
CGcontext context = cgCreateContext();
CGprofile profile = cgGLGetLatestProfile(CG_GL_FRAGMENT); cgGLSetOptimalOptions(profile);
CGprogram program = cgCreateProgramFromFile( context, CG_SOURCE, [file name], profile, NULL, // entry point NULL); // arguments
cgGLLoadProgram(program);
cgGLBindProgram(program); cgGLEnableProfile(profile); }
#include <Cg/cg.h> #include <Cg/cgGL.h>
void draw() { // OpenGL lighting must be disabled since the pixel shader // program will compute the lighting value glDisable(GL_LIGHTING);
// The first texture unit contains the detail texture glActiveTextureARB(GL_TEXTURE0_ARB); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, [detail texture handle]);
// The second texture unit contains the normalmap texture glActiveTextureARB(GL_TEXTURE1_ARB); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, [normalmap texture handle]);
// Set the (fixed) ambient color value CGparameter ambientColorParameter = cgGetNamedParameter(program, "ambientColo
cgGLSetParameter3f(ambientColorParameter, [ambientr], [ambientg], [ambientb])
for every vertex in the triangle { // Bind the light vector to COLOR0 and interpolate // it across the edge glColor3f([lightx], [lighty], [lightz]);
// Bind the texture coordinates to TEXTURE0 and // interpolate them across the edge glMultiTexCoord2fARB(GL_TEXTURE0_ARB, [texturex], [texturey]);
// Bind the normalmap coordinates to TEXTURE1 and // interpolate them across the edge glMultiTexCoord2fARB(GL_TEXTURE1_ARB, [texturex], [texturey]);
// Specify the vertex coordinates glVertex3fv([vertexx], [vertexy], [vertexz]); } }
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.
// Compute diffuse factor float diffuse = dot(bumpNormalVectorFinal, lightVectorFinal);
return float4(diffuse * detailColor + ambientColor, 1.0); }
References
[1] n VIDIA "OpenGL SDK" [2] Eric Lengyel "Mathematics for 3D Game Programming & Computer Graphics" [3] n VIDIA "Cg" [4] Mark Kilgard "Cg in Two Pages" [5] Nate Robbins Windows port of Mark Kilgard’s "OpenGL Utility Toolkit" (GLUT) [6] Chad Austin "Corona" [7] NeHe "OpenGL Tutorials", Lesson 22 [8] chUmbaLum sOft "Milkshape 3D" [9] Dimitri van Heesch "Doxygen"
Discuss this article in the forums