Shaders 102 – Code Integration

Overview

Today I am going to talk about how to integrate shaders into your code.  Starting with the layout of a shader program and how to integrate it in your code.

Basic Shader Code

Here is a vertex shader:

//vertex shader from chapter 3 of the Cg textbook

struct C3E2v_Output {
float4 position : POSITION;
float3 color : COLOR;
float2 texCoord : TEXCOORD0;
};

C3E2v_Output C3E2v_varying(
float2 position : POSITION,
float4 color : COLOR,
float2 texCoord : TEXCOORD0)
{
C3E2v_Output OUT;

OUT.position = float4(position,0,1);
OUT.color = color;
OUT.texCoord = texCoord;

return OUT;
}

Breakdown

The program first begins with an output structure as follows:

struct C3E2v_Output {
float4 position : POSITION;
float3 color : COLOR;
float2 texCoord : TEXCOORD0;
};

Since we know that this is the vertex shader, and it has to pass values to the rest of the graphics pipeline, this is structure is just some of the values that our shader will be using.  By defining an output structure we can manipulate the items inside it.  Basically this is like a variable declaration for a function where we would be outputting position, color and texture coordinates.

Last week we talked about how Cg has vectors and matrices integrated into their variable declaration.

  • float4 position : is essentially =[x,y,z,w] where w=1.  If this was written in C++ it would be float position[4]={x,y,z,w}
  • float3 color : is similar to the vector above, but this is used to represent our colour channels =[r,g,b]
  • float2 texCoord : is our U and V coordinates in our texture = [u,v]
While this program does not showcase the matrix declaration of Cg, here are some examples
  • float4x4 matrix1 : this is a four by four matrix with 16 elements
  • half3x2 matrix2 : this is a three by two matrix with 6 elements
  • fixed2x4 matrix3 : this is a two by four matrix with 8 elements
If you wanted to declare a matrix with some values, you would do it like this:
float2x3 = {1.0, 2.0,
            3.0, 4.0,
           5.0, 6.0}

 


Next we have our entry function:

C3E2v_Output C3E2v_varying(
float2 position : POSITION,
float4 color : COLOR,
float2 texCoord : TEXCOORD0)

This is what defines our fragment or vertex program.  This is similar to the main function in C/C++.  What this is telling us, is that our shader is taking in position, colour and texture coordinates.  We know that our structure above has the same parameters as our input, so we know that we are going to be manipulating these parameters and then outputting them.

Last we have our function body:

C3E2v_Output OUT;

OUT.position = float4(position,0,1);
OUT.color = color;
OUT.texCoord = texCoord;

return OUT;

First we start of by creating our structure object called OUT.  This code really doesn’t do much but set values in the structure equal to the inputs and output them to the next stage in the pipeline.  The interesting piece of code is the OUT.position = float4(position,0,1) part.  This takes the incoming position with only two incoming parameters (x,y) and converts it into a float4 by giving the last two variables a 0 and 1 value to get (x,y,0,1).

3D Graphics Application Integration

Creating Variables

So knowing how that code works is great, however implementing Cg code in your C++ code is where I normally spend most of my time working with shaders.  The actual shader code is fairly easy to work with, but integrating it in your Graphics Application is where the real pain is.  The Cg book doesn’t really cover this explicitly, however it does have examples in the API in your /Program Files/NVIDIA Corporation/Cg/examples/ folder.

To start there are a number of things you have to declare, I normally do these as global variables:

static CGcontext myCgContext;
static CGprofile myCgVertexProfile,
                 myCgFragmentProfile;
static CGprogram myCgVertexProgram,
                 myCgFragmentProgram;
static CGparameter myCgVertexParam_constantColor;

static const char *myProgramName = "Varying Parameter",
*myVertexProgramFileName = "C3E2v_varying.cg",
*myVertexProgramName = "C3E2v_varying",
*myFragmentProgramFileName = "C2E2f_passthru.cg",
*myFragmentProgramName = "C2E2f_passthru";

Obviously the names of these do not have to be the same as what I have written, but the idea is to teach you what each one is.

The first part of creating the CGcontext is the part I know the least about.  I believe it is the part where you initialise the shader program.  So just be sure to ALWAYS do this.

The next part is creating your vertex and fragment profile.  This is another thing to always do.

The next two parts are where you are given a lot of freedom.  The CGparameter will vary from program to program.  These are essentially parameters that you take in your graphics application and send to your shader.  constantColor is just a variable that we can send to our shader to replace the colour of every pixel or vertex.  Later on I will post on how we can send in parameters like diffuse light color, light position, attenuation parameters and much more.

The last part is the program names.  This is where you define your main function for each shader and the name of their file name.  Common names for each are fragment_passthru or vertex_passthru.

Initialise Shaders

The next step is where you physically create the shader program.  Somewhere in your glut loop you should create a initCg() void function where you place all your initialisations.  The book places everything in main, which I find to be stupid, so don’t do that.  It creates a lot of hard to read clutter.

myCgContext = cgCreateContext();
checkForCgError("creating context");
cgGLSetDebugMode(CG_FALSE);
cgSetParameterSettingMode(myCgContext, CG_DEFERRED_PARAMETER_SETTING);

myCgVertexProfile = cgGLGetLatestProfile(CG_GL_VERTEX);
cgGLSetOptimalOptions(myCgVertexProfile);
checkForCgError("selecting vertex profile");

myCgVertexProgram =
cgCreateProgramFromFile(
myCgContext,              // Cg runtime context
CG_SOURCE,                // Program in human-readable form
myVertexProgramFileName,  // Name of file containing program
myCgVertexProfile,        // Profile: OpenGL ARB vertex program
myVertexProgramName,      // Entry function name
NULL);                    // No extra compiler options;
checkForCgError("creating vertex program from file");
cgGLLoadProgram(myCgVertexProgram);
checkForCgError("loading verex program");

This code is fairly simple.  The beginning part creates the CgContext.  Then we create our vertex profile.  Lastly we tell the program where our Cg file is and the name of our entry function.  You would do something similar for the fragment shader.

The checkForCgError help your debug.  If anything goes wrong in the shader at that point, your cgError function will output an error code.

The other thing you can place in your initCg function is a GET_PARAM statement where you can pass variables to your shader program.

#define GET_PARAM(name) \
myCgVertexParam_##name = \
cgGetNamedParameter(myCgVertexProgram, #name); \
checkForCgError("could not get " #name " parameter");

GET_PARAM(modelViewProj);
GET_PARAM(globalAmbient);
GET_PARAM(eyePosition);

#define GET_PARAM2(varname, cgname) \
myCgVertexParam_##varname = \
cgGetNamedParameter(myCgVertexProgram, cgname); \
checkForCgError("could not get " cgname " parameter");

GET_PARAM2(material_Ke, "material.Ke");
GET_PARAM2(material_Ka, "material.Ka");
GET_PARAM2(material_Kd, "material.Kd");
GET_PARAM2(material_Ks, "material.Ks");
GET_PARAM2(material_shininess, "material.shininess");

The is an example of sending parameters directly to your entry function and to other structures you can make.  The first batch of code sends the modelViewProj matrix, GlobalAmbient colour and the eyePosition of the camera.  These would be items you list in your entry function input parameters.

The other batch of code is an example of you sending parameters to a structure called Material.  Material has emmissive, ambient, diffuse and specular lighting along with a shininess parameter.  This is helpful for creating virtual objects that reflect light differently so things can look like rubber or plastic.

 Enabling and Disabling

The last and simplest thing to do is to enable and disable your shaders during your OpenGL draw loop.

When you are drawing your objects, you need to bind your fragment and vertex shaders by:

//Enable Shaders
cgGLBindProgram(myCgVertexProgram);
checkForCgError("binding vertex program");
cgGLEnableProfile(myCgVertexProfile);
checkForCgError("enabling vertex profile");

cgGLBindProgram(myCgFragmentProgram);
checkForCgError("binding fragment program");
cgGLEnableProfile(myCgFragmentProfile);
checkForCgError("enabling fragment profile");

Once that is done you would go on with the rest of your draw calls and at the end of your draw loop you would disable them by:

// Disable Shaders
cgGLDisableProfile(myCgVertexProfile);
checkForCgError("disabling vertex profile");

cgGLDisableProfile(myCgFragmentProfile);
checkForCgError("disabling fragment profile");

Summary

That is pretty much a very basic description of how shaders are integrated into your C++ graphics application.

Thank you for reading,
– Moose

One thought on “Shaders 102 – Code Integration

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s