Bump Mapping with CgCg SetupBefore 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): 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):CGcontext context = cgCreateContext(); 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 ImplementationWe 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:
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:
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.
|
|