Understanding vertex shaders
Absolutely minimal CG program for good fundamentals understanding
First, make sure you install CG!
// FILE: AbsoluteMinimalCGProgram.cpp
//////////////////////////////////////////
// ABSOLUTE MINIMAL CG PROGRAM: //
// BASIC DRAWING IN 2D USING CG //
// //
// This example discusses the absolute //
// basics of using CG with OpenGL. //
// //
// As a prerequisite, you should //
// already have experience with //
// OpenGL. If ya don't. . . I suggest //
// you get some practice, before //
// attempting to continue with this //
// series. //
// //
// You found this at bobobobo's weblog, //
// http://bobobobo.wordpress.com //
// //
// Creation date: Jan 21/08 //
// Last modified: Jan 23/08 //
// //
//////////////////////////////////////////
// Loosely based on Chapter 2 of "The Cg Tutorial" by Kilgard and Fernando,
// and the associated code files.
// The original Cg Tutorial code files are freely available for download at
// http://developer.nvidia.com/object/cg_tutorial_home.html
////////////////////////////////////////////////////////////////////
// To use this, get and install the Cg Toolkit and make sure you //
// get the examples that come with the Cg Toolkit to work first. //
////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>
#include <GL/glut.h>
// If having problems compiling, ('fatal error') see
// http://bobobobo.wordpress.com/2008/01/22/setting-up-cg-environment-variables/
#include <Cg/cg.h>
#include <Cg/cgGL.h>
// These link the Cg libraries.
#pragma comment( lib, "cg.lib" )
#pragma comment( lib, "cgGL.lib" )
// Ok, this package has 2 files:
// AbsoluteMinimalCGProgram.CPP: The C++ code file.
// vertexShader.CG: The vertex shader file.
// Take a real quick glance at both, up and down.
// ________ _____
// |___ ___| | _ |
// | | | | | |
// | | | |_| |
// |__| |_____| do.
//
// 1) One of the main ideas of this package is to compare
// OpenGL program behavior when our CG shader is on, and
// when there is no shader being used at all (classic OpenGL
// rendering).
// Go down to the #pragma region part that says
// "this code has no effect when the shader is on".
// Verify that claim is true. What other types of OpenGL
// function call appears to have no effect when the CG Shader is on?
// Change the values that are going into gluPerspective and gluLookAt()
// Change them to completely wild values. Take the eye
// really really far away so that the set of triangles is
// a mere speck. . . Then turn the shader on.
// Conclusion: IF YOU HAVE YOUR VERTEX SHADER ON, THEN ALL YOUR
// CODE that uses the OpenGL matrix stakcs __DOES NOT WORK ANYMORE__.
// !! HMM!! This is an important point. This should be very interesting to you. How
// are we going to make it look 3D without gluPerspective???
// Trust me, we will. . .
// 2) Play around with the drawing code a bit. Draw your own shapes.
// Get a feel for what shows up in the window, and what does not,
// when the shader is switched on.
// What are the max/min values of x,y and z that glVertex3f() can take?
//
#pragma region answer to 2
// ANSWER: x, y and z can only take values between
// [-1, +1] each if they are to show up in the final
// render when the shader is on.
#pragma endregion
//
// 3) Figure this out:
// What is the effect of z-value when
// the shader is on??? When does Z
// appear to have any affect on the final drawing?
// (try very large AND very small values of z!)
#pragma region answer to 3
// At this point, Z-value will appear to have NO EFFECT!
// To make the red square go in the back, put code like
#pragma endregion
// 4) Draw a huge red square BEHIND the triangles
// that appear when the shader is on.
// How can you make the red square appear behind all
// the triangles?
#pragma region answer to 4
// Use code like
/// glBegin( GL_QUADS ); glVertex3f(1,1,0);glVertex3f(-1,1,0);
/// glVertex3f(-1,-1,0);glVertex3f(1,-1,0); glEnd();
// _BEFORE_ the glBegin(); for the other triangles.
#pragma endregion
////////////////////////
// GLOBALS: Here we declare 3 global variables
// for use by our CG program. More detail
// when they're actually used.
CGcontext myCgContext; // Like a huge container for all of our CG stuff
CGprofile myCgVertexProfile; // A CGprofile contains a summary of the
// capabilities of the hardware that this
// CG program is going to run on.
CGprogram myCgVertexProgram; // And finally this global variable will be
// a reference to the program itself, once
// it has been compiled.
bool shaderOn = false; // This is a global var that we use to
// switch on and off the shader, so
// we can see the diff. You press spacebar.
// Below is a diagram that kind of shows in a big picture
// sort of way how the CGcontext, CGprofile and CGprogram exist.
/*
/----------------------------------\
/ \
/ \
/ \
/ \
\ CGprogram ----- USES ----> CGprofile /
\ /
\ /
\ /
\----------------------------------/
CGcontext
CGprogram USES CGprofile, when CGprogram is compiled.
CGcontext contains both CGprogram AND CGprofile.
*/
// Function that checks to see if CG is crying
// about some error. We run this function
// after every Cg command we execute from
// our C++ code.
void checkForCgError(const char *situation)
{
CGerror error;
const char *string = cgGetLastErrorString(&error);
if (error != CG_NO_ERROR) {
printf("%s: %s: %s\n",
"ERROR", situation, string);
if (error == CG_COMPILER_ERROR) {
printf("%s\n", cgGetLastListing(myCgContext));
}
exit(1);
}
}
////////////////////////
// display() function
// Once the line of code that says "glutMainLoop()" executes
// we'll be trapped cycling in this "display" function
// forever until someone hits the ESC key.
void display()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
if( shaderOn == true )
{
// !! Next 2 lines actually TURN ON THE VERTEX SHADER!!
cgGLBindProgram(myCgVertexProgram);
cgGLEnableProfile(myCgVertexProfile);
}
#pragma region this code has no effect when shader is on
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
gluPerspective( 45, 1.0, 0.4, 1000 );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
gluLookAt( 6.5, 0.0, 5.0,
0.0, 0.0, 0.0,
0.0, 1.0, 0.0 );
#pragma endregion
// Notice how we're always picking
// values between -1 and +1.
// That's important! Try drawing your own stuff.
glBegin(GL_TRIANGLES);
// First, a red triangle that goes from the middle to
// the top right corner.
glColor3d ( 1, 0, 0 );
glVertex3d( 0, 0, 0 );
glVertex3d( 0, 0.98, 0 );
glVertex3d( 0.98, 0.98, 0 );
// Second, a green triangle that goes from the middle to
// the top left corner
glColor3d ( 0, 1, 0 );
glVertex3d( 0, 0, 0 );
glVertex3d( 0, 0.98, 0 );
glVertex3d(-0.98, 0.98, 0 );
// Third, a blue triangle that goes from the middle to
// the bottom left corner
glColor3d ( 0, 0, 1 );
glVertex3d( 0, 0, 0 );
glVertex3d( 0, -0.98, 0 );
glVertex3d(-0.98, -0.98, 0 );
// Finally, a white triangle in the bottom right corner
glColor3d ( 1, 1, 1 );
glVertex3d( 0, 0, 0 );
glVertex3d( 0, -0.98, 0 );
glVertex3d( 0.98, -0.98, 0 );
glEnd();
if( shaderOn == true )
{
// Now that we are totally done drawing, we want to
// actually TURN OFF the shader.
cgGLDisableProfile(myCgVertexProfile); // !! SHADER OFF
}
glutSwapBuffers();
}
void keyboard(unsigned char c, int x, int y)
{
switch (c) {
case 27: /* Esc key */
/* Demonstrate proper deallocation of Cg runtime data structures.
Not strictly necessary if we are simply going to exit. */
cgDestroyProgram(myCgVertexProgram);
cgDestroyContext(myCgContext);
exit(0);
break;
case ' ': // spacebar
{
shaderOn = !shaderOn; // flip
char t[100];
sprintf( t, "First Cg program! Shader is %s", shaderOn ? "ON!" : "off." );
glutSetWindowTitle( t );
glutPostRedisplay(); // redraw screen
}
break;
}
}
int main(int argc, char **argv)
{
// 0. Initialize GLUT.
// Thanks Kilgard! You da bomb. :D.
#pragma region GLUT INIT
// If we weren't using GLUT, we'd have much
// more than 6 lines of code to write to get
// a window up in Windows.
glutInitWindowSize(600, 600);
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
glutInit(&argc, argv);
glutCreateWindow("First Cg program! Press spacebar to turn shader ON!");
glutDisplayFunc(display);
glutKeyboardFunc(keyboard);
#pragma endregion
glClearColor(0.1, 0.12, 0.3, 0.0); // dark background
#pragma region CG INIT
//////////////////////////////////////////////
//
// 1. CREATE the Cg CONTEXT.
//////////////////////////////////////////////
#pragma region create Cg context
// What's a "context"?
// When you're programming, the word comes up a lot.
// In Windows programming, you hear a lot about "device context."
// With OpenGL, you talk about the "rendering context."
// In plain English, a "context" is like a "circumstance."
// You usually need to know the "context" of a sentence
// to totally understand what it means.
// "Sorry, I should have held that."
// You'd need a context for that sentence for it
// to make any sense.
// In computing though, a context is a little more than
// just a situation.
// A "CONTEXT" in computing specifically is
// a COLLECTION of variables and ALL the data that represents
// the overall STATE of a program.
// One of the main jobs of your operating system is to
// handle "CONTEXT SWITCHES" -- ie to swap out
// a program and all of its associated variables from
// the CPU's registers, so another program can have
// a chance to run for microsecond or two.
// Read this: Context switch on Wikipedia
// So for Cg, a context will be a COLLECTION OF ALL
// THE ASSOCIATED INFORMATION THAT HAS TO DO WITH
// THIS CG SHADER.
#pragma endregion
// And so, now we shall create the context.
myCgContext = cgCreateContext();
checkForCgError("creating context");
////////////////////////////////////////
//
// 2. PICK A CG PROFILE TO USE
////////////////////////////////////////
#pragma region select a CG profile
// What's a CG PROFILE?
// So what's a PROFILE in CG? Its where
// CG programs go to show off the number
// of friends they have . . . :)
// But really, a CG PROFILE is kind of like
// a personality profile, only it tells the
// CG Compiler what assembly level instructions
// the present graphics hardware is capable of
// processing.
// The information about the PROFILE is used
// WHEN THE CG PROGRAM IS COMPILED.
// 'case ya didn't know, CG Programs are COMPILED
// AT RUN TIME. The CG CODE is normally __NOT__ compiled
// along with the rest of your C program
// when you first press F5 in Visual Studio.
// That's called Dynamic Compilation and it is
// a Good Thing.
// You CAN, however, statically compile your
// Cg program if you really want to.
// Different people running your CG program
// will be using different graphics cards to
// run it, each with different capabilities.
// My good ol' FX 5200, for example, has a much
// different set of capabilities than Nathan's
// 8800 card that he got for Christmas.
// Should the instructions generated by the Cg
// program to my old FX 5200 be exactly the same
// as the instructions generated to Nathan's 8800?
// No way!!
//
// As such, since Nathan's 8800 card CAN DO MORE
// THINGS than my FX 5200 can, the CG compiler
// should have a much different _attitude_ when its
// generating assembly code for my old FX 5200
// than it does for Nathan's 8800.
// THE PROFILE YOU USE DEPENDS ON:
// 1) The hardware you have available
// 2) The graphics API you are using (OpenGL or Direct3D).
#pragma endregion
myCgVertexProfile = cgGLGetLatestProfile(CG_GL_VERTEX);
cgGLSetOptimalOptions(myCgVertexProfile);
checkForCgError("selecting vertex profile");
////////////////////////////////////////
//
// 3. CREATE THE VERTEX PROGRAM, ASSOCIATING IT
// WITH YOUR CGcontext _and_ YOUR VERTEX PROFILE
////////////////////////////////////////
myCgVertexProgram =
cgCreateProgramFromFile(
myCgContext, /* Cg runtime context */
CG_SOURCE, /* Program in human-readable form */
"vertexShader.cg", /* Name of file containing program */
myCgVertexProfile, /* Profile: OpenGL ARB vertex program */
"vertexShaderFunction", /* Entry function name of vertex shader program */
NULL); /* No extra compiler options */
checkForCgError("creating vertex program from file");
////////////////////////////////////////
//
// 4. LOAD THE VERTEX PROGRAM IN TO THE GPU MEMORY
////////////////////////////////////////
cgGLLoadProgram(myCgVertexProgram);
checkForCgError("loading vertex program");
#pragma endregion
glutMainLoop(); // GOTO the display() function next
return 0;
}
/*
____ __ __ __ __ ___
/ _ \ / / / / / / \ \/ /
/ _/ / / / / / / / \ /
/ _/ \ / / / /__ / /__ / /
/_____//__/ /______//______/ /__/
*/
// FILE: vertexShader.cg
// This is our vertex shader program. It is a single function.
// vertexShaderFunction takes in vertex position and color,
// whose values originate in our OpenGL code.
// It outputs a new vertex position and color, which will be
// used to draw the final shapes to the screen.
/*
------------ ----------------------------- -------------
| INCOMING | => | vertexShaderFunction | => | OUTGOING |
| VERTEX | | | | VERTEX |
| with own | => | Like processing plant | => | final |
| position | | takes incoming position | | position |
| and color| => | and color values for each | => | and color |
------------ | vertex and does some | | of vertex |
| math on them. | -------------
-----------------------------
*/
void vertexShaderFunction (
float3 incomingPosition : POSITION, // : POSITION is a SEMANTIC (see note below)
float3 incomingColor : COLOR,
out float4 outgoingPosition : POSITION,
out float4 outgoingColor : COLOR // : COLOR is a SEMANTIC (see note below)
)
{
// compute outgoingPosition using the incomingPosition
// In this example, we'll just pass it through unmodified.
outgoingPosition.x = incomingPosition.x;
outgoingPosition.y = incomingPosition.y;
outgoingPosition.z = incomingPosition.z;
outgoingPosition.w = 1; // don't worry what w is YET, we'll get to that later.
// Here's a more advanced operation, to make color look cool.
float3 transformedColor = saturate( ( cosh( incomingColor ) ) * cos( incomingPosition.yxy ) );
outgoingColor = float4( transformedColor, 1 );
}
//////////////// WHAT IS THIS??? ////////////////////
// vertexShaderFunction is like a PROCESSING PLANT:
// This vertex shader takes IN two things:
// 1. It TAKES IN the raw vertex xyz position in space,
// as handed to it by OUR OpenGL code (incomingPosition)
//
// 2. It TAKES IN the raw vertex color, as handed to it
// by OUR OpenGL code (incomingColor)
// This vertex shader SPEWS OUT two things:
// 1. It SPITS OUT a NEW, FINAL POSITION for the
// vertex (variable outgoingPosition)
//
// 2. It SPITS OUT a NEW, FINAL COLOR for the
// vertex (in variable outgoingColor)
// This vertex shader will process EVERY SINGLE VERTEX
// IN OUR PROGRAM as it travels down the graphics pipeline.
// Something very important to realize about writing your
// own vertex shaders is, when you activate your vertex
// shader, YOU COMPLETELY BYPASS the regular OpenGL
// transformation and project matrices.
// So if you go to the C++ code and glRotatef, glTranslatef,
// and gluLookAt() to your heart's content, IT WILL HAVE
// ABSOLUTELY NO EFFECT on the final result you see here.
// That is because by writing and enabling your vertex
// shader program, you have effectively __TAKEN OVER__
// the graphics pipeline.
// It is now YOUR responsibility to take in x, y and z
// coordinates that the C++ program will feed in here,
// and TRANSFORM THEM TO GENERATE THE 3D LOOKING IMAGE.
// So if you're used to programming with OpenGL purely
// without using shaders, and you're used to using
// gluLookAt() and such things to get your scene to look
// the way you want, and gluPerspective() to get that
// sweet perspective view, you'll need to kiss those glMatrix
// functions goodbye for now.
// K-I-S-S ;).
// Ok, now that seems very complicated!!! How will we ever
// do that???
// Don't worry, we will.
// In order to make this digestable, we'll first start
// by totally ignoring the Z-component. We'll pretend
// that z doesn't exist and we'll totally draw in 2D.
// You're used to programming so that you write all this code
// to get the vertices into correct position BEFORE calling
// glVertex3f. Then the reality of it was, gluPerspective()
// and gluLookAt() took care of all the other stuff for you.
// Now we're doing things differently.
// The vertex shader we're writing makes changes to
// vertex position _AFTER_ the call of glVertex3f.
// is that the vertex shader will make changes to the
// vertices that you specified __AFTER__ the glVertex3f calls.
//
// This is totally NOT what you're used to, if you've never
// programmed shaders before.
// Also other points (that we'll explain in much more detail
// later) are that
// - when the shader is on, you TOTALLY BYPASS the regular
// OpenGL transformation matrices (glRotatef, glTranslatef
// HAVE NO EFFECT WHEN YOUR SHADER IS ON).
// - The reason the glRotatef and glTranslatef functions have
// no effect is, once you've activated your shader, YOU
// ARE IN FULL CONTROL OF THE GPU. When your shader is on,
// you become responsible to feed the rasterizer a set of
// points that have x values between [-1, +1] and
// y-values between [-1, +1]. Seems strange, but get used to the idea.
//
///////////////////
// COVERED HERE:
// 1. vector data types: float3 and float4
// 2. SEMANTICS
// 3. Keywords IN and OUT
//
// BEFORE YOU READ THIS
// Make sure you know EXACTLY WHAT A "function parameter" is.
// If you don't, you need to brush up on your C++ programming,
// specifically "functions, parameters and arguments"
// before you attempt to understand this, or it won't make
// any sense.
///////////////////
// 1. Vector data types: float3 and float4
//
// One of the absolute coolest things about working
// with CG is its treatment of vector values as
// first class, primitive types.
// The next tutorial will deal with vector data
// types and the operations that are defined for
// them in much more detail, but for now, just
// recognize that its just like working with
// real vectors.
///////////////////
// 2. SEMANTICS:
//
// Look at the first parameter to the vertexShaderFunction.
// float3 incomingPosition : POSITION
// The last word there in all caps ("POSITION") is what
// we call a SEMANTIC.
//
// What's a semantic? In plain English,
// 'semantic' just means the 'meaning' of something.
//
// We attach a SEMANTIC ("meaning") to each one
// of vertexShaderFunction()'s parameters
// precisely so that CG KNOWS WHAT EACH FUNCTION PARAMETER IS
// __TO BE USED FOR__.
//
// FIRST PARAM: incomingPosition
// The first parameter to "vertexShaderFunction"
// (the "incomingPosition" parameter) is given the
// POSITION semantic. This lets CG know that CG should
// pass in the vertex position as the first argument
// to this function.
// CG WILL AUTOMATICALLY FEED IN POSITION ___FOR YOU___. You don't
// have to do anything to pass the vertex position to the
// vertex shader OTHER THAN SPECIFY THE VERTICES IN THE OPENGL CODE.
// This takes a tad bit of getting used to, but its really natural.
// SECOND PARAM: incomingColor
// The second parameter ("incomingColor") to vertexShaderFunction() is given the
// COLOR semantic. This lets CG know that CG should
// pass in the vertex's COLOR as the second argument
// to this function. Again, this happens AUTOMATICALLY
// and there's nothing special you have to do to get it
// to happen other than specify a color in OpenGL (using
// glColor3f or something).
// So attaching the POSITION semantic to a function parameter
// identifies it to CG. Variable name doesn't matter.
///////////////////////////////
// 3. KEYWORD OUT:
//
// Take a look at the third parameter "outgoingPosition."
// In front, it has the special word 'OUT'.
// At the end, it also uses the semantic "POSITION."
// Combining that information, that tells CG that the
// variable "outgoingPosition" will be the FINAL OUTPUTTED
// POSITION of the vertex.
// Same goes for outgoingColor, except of course,
// outgoingColor will contain the final outputted color.
// Keyword OUT tells CG that those two parameters
// ARE the output values of this function.
// You can only have ONE PARAMETER that has the combination
// of OUT and POSITION applied to it, and ONE PARAMETER
// that has the combination of OUT and COLOR applied to it,
// since each vertex can only have ONE FINAL position and
// ONE FINAL color.
// Also it might take some getting used to the fact that
// this shader program acts as a processing plant that
// sort of takes in two FULL containers (incomingPosition
// and incomingColor) and it ALSO TAKES +IN+ two EMPTY
// containers (outgoingPosition and outgoingColor).
// The JOB of the vertexShaderFunction is to use the
// stuff provided in the incomingPosition and incomingColor
// containers and to FILL the outgoingPosition and outgoingColor
// containers appropriately.
// Programmatically, you've seen interfaces like this before
// if you've ever used the strcpy() function in C
// strcpy( char * dest, char * src );
// if we were to write this like a shader function is written,
// it would be:
// strcpy( out char * dest,
// char * src );
// ALSO as a final note, there is a keyword IN that you CAN use
//void vertexShaderFunction (
// IN float3 incomingPosition : POSITION, // keyword IN optional,
// IN float3 incomingColor : COLOR, // designate as inputs
//
// out float4 outgoingPosition : POSITION,
// out float4 outgoingColor : COLOR
// )
// AND finally, note that YOU COULD ALSO do this:
//void vertexShaderFunction (
// inout float4 pos : POSITION, // this var is both in and output
// inout float4 color : COLOR, // both in and output
// )
// { /* code */ }
// And that's all there is to the basics of a vertex shader.
// If you got most of that, you're ready for the next one! :).
////////////////////
// REVIEW POINTS:
// Its not the NAMING of the parameters to the function
// "incomingColor" and "outgoingColor" that tell CG what
// each variable does. Its the SEMANTIC and whether or
// not the variable uses the word OUT in front that tells
// CG what the variable is for. FOr all Cg cares, you could
// write
//void vertexShaderFunction(
// float3 hamburger : POSITION,
// float3 Fries : COLOR,
// out float4 milkshake : POSITION,
// out float4 coke : COLOR
// )
// and still the program would work the same, provided you
// make the appropriate changes to the function body as well.
// ONLY the ENTRY FUNCTION can use SEMANTICS.
// So if you write an additional helper function
// in the CG code file here, that's fine and dandy,
// but if the helper function has use of the POSITION
// or COLOR semantic, it will be to no effect. You'd
// have to pass those values manually yourself, if
// you wanted access to the in your helper function.
// Final note: The strangest thing you have to get
// used to is even though "vertexShaderFunction" is
// just a function, YOU never get a chance to CALL
// IT YOURSELF.
// Semantics are the ONLY WAY you tell CG what
// variables do what. YOU DO NOT get a chance
// to write a line of code like:
//
// vertexShaderFunction( v1, c1, v2, c2 );
// As such, you must provide instructions to CG:
// "What parameter does what?" before you even
// compile and run the program.
// Additional: What's an ENTRY FUNCTION?
// vertexShaderFunction (name of the shader function
// waaaaaaaaaay up at the top of this file) IS an entry function.
// Because vertexShaderFunction is where the program starts,
// vertexShaderFunction is called the ENTRY FUNCTION.
// Ok I'm done. Send any questions, comments,
// +/- fdback, or appreciations! to billy.baloop@gmail.com.
/*
____ __ __ __ __ ___
/ _ \ / / / / / / \ \/ /
/ _/ / / / / / / / \ /
/ _/ \ / / / /__ / /__ / /
/_____//__/ /______//______/ /__/
*/