這篇教程將介紹如果在openGL中使用CG(C for Graphic)語言。想在程序中使用CG,首先要下載並安裝 NVIDIA的Cg Toolkit。( http://developer.nvida.com/CgTutorial ) 安裝好後,在openGL程序中就可以添加cg.h和cgGL.h頭文件了。要在openGl程序中使用Cg,這兩個頭文件必不可少。
#include <Cg/cg.h> #include <Cg/cgGL.h> |
添加了頭文件後,再添加庫文件cg.lib cgGL.lib。Cg shader程序必須要有包含在Context中,Context是裝載shader的容器。所以首先要創建一個Context。
CGcontext myCgContext = cgCreateContext(); |
上面這個語句就創建了一個名叫myCgContext的Context,不管是vertex shader還是fragment shader,在編譯之前都放到這個Context裏。可以通過cgCreateProgramFromFile函數創建一個Cg程序,然後將Context和這個程序連接起來。
CGprogram myCgVertexProgram = cgCreateProgramFromFile( Context, /* Cg runtime context */ CG_SOURCE, /* shader程序的源代碼類型 */ programString, /* shader源代碼文件的名字 */ Profile, /* Profile: OpenGL ARB vertex program */ main, /* shader程序入口函數*/ args); /* 附加參數,一般爲NULL */ |
上面程序段中,CG_SOURCE和programString可以指定使用哪一種shader程序源代碼,如果使用後綴名爲.cg的文件作爲shader的源代碼文件,那麼這兩個參數分別設置爲CG_SOURCE和shader源程序代碼文件名即可。Cg也支持預編譯好的目標文件作爲shader程序的源文件,這時將這兩個參數設置爲CG_OBJECT和預編譯好的shader程序文件名即可。Profile參數指定使用哪一種版本的profile來編譯該shader程序,根據profile版本的不同,shader的能力有很大的差別。具體有哪些版本的profile,以及各個不同版本profile的詳細情況,大家可以參考http://developer.nvida.com/CgTutorial。
創建好Context,且鏈接了shader程序後,在openGL中還要使用函數cgGLLoadProgram加載該shader程序。
cgGLLoadProgram(CGprogram Program); |
上面的語句在openGL中加載一個創建好的shader程序。參數Program指定該程序是vertex shade程序還是fragment shader程序。
以上的工作都是爲了使用shader而做好了準備,下面就可以直接使用這些shader程序了。表現一個3D場景的時候,可能會使用很多不懂得物體。不同的物體可能有不同的物理屬性,所以我們可能希望不同的shader程序可以應用到不同的物體上。Cg提供了這樣的函數,函數cgGLBindProgram可以綁定一個shader程序到想要表現的物體上。綁定的時候只需要簡單的將要表現的物體的渲染代碼放到該函數語句後面即可。綁定後,再調用函數cgGLEnableProfile激活指定的profile來編譯shader程序。最後,shader程序使用完畢後,調用cgGLDisableProfile函數關閉當前profile。
cgGLBindProgram(Program); cgGLEnableProfile(Profile); // 渲染某物體 cgGLDisableProfile(Profile); |
上面的代碼簡單的展示了綁定,編譯shader程序並且用該shader程序渲染一個物體。
現在給出一個完整的代碼,在畫面上顯示一個圓環,通過vertex shader和 fragment shader來渲染它,在vertex shader裏幾乎沒做任何事情,只是將該圓環的本地座標轉換到剪裁座標,爲光柵化做好準備。然後將每個頂點在本地座標中的位置作爲頂點的顏色輸出傳入fragment shader,在fragment shader中沒有做任何工作,直接根據vertex shader中設定的頂點的顏色,通過插值計算各個像素的顏色並輸出。
下面分別是HelloCG.cpp,vertex shader 01vs.cg和fragment shader 01fs.cg的代碼。
HelloCG.cpp
#include <gl/glut.h> #include <cg/cg.h> #include <Cg/cgGL.h> #include <stdio.h>
int ww = 640, hh = 480;
void render(); void reshape(int w, int h);
static CGcontext myCgContext; static CGprofile myCgVertexProfile; static CGprofile myCgFragmentProfile; static CGprogram myCgVertexProgram; static CGprogram myCgFragmentProgram;
static const char *myProgramName = "Hello CG"; static const char *myVertexProgramFileName = "01vs.cg"; static const char *myVertexProgramName = "vs_main"; static const char *myFragmentProgramFileName = "01fs.cg"; static const char *myFragmentProgramName = "fs_main";
int main(int argc, char** argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH); glutInitWindowSize(ww,hh); glutCreateWindow(myProgramName);
glutDisplayFunc(render); glutReshapeFunc(reshape);
glEnable(GL_DEPTH_TEST);
myCgContext = cgCreateContext(); cgGLSetDebugMode(CG_FALSE); cgSetParameterSettingMode(myCgContext, CG_DEFERRED_PARAMETER_SETTING);
myCgVertexProfile = cgGLGetLatestProfile(CG_GL_VERTEX); cgGLSetOptimalOptions(myCgVertexProfile);
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 */
cgGLLoadProgram(myCgVertexProgram);
myCgFragmentProfile = cgGLGetLatestProfile(CG_GL_FRAGMENT); cgGLSetOptimalOptions(myCgFragmentProfile);
myCgFragmentProgram = cgCreateProgramFromFile( myCgContext, /* Cg runtime context */ CG_SOURCE, /* Program in human-readable form */ myFragmentProgramFileName, /* Name of file containing program */ myCgFragmentProfile, /* Profile: OpenGL ARB vertex program */ myFragmentProgramName, /* Entry function name */ NULL); /* No extra compiler options */ cgGLLoadProgram(myCgFragmentProgram);
glutMainLoop(); return 0; }
void reshape(int w, int h) { glMatrixMode(GL_PROJECTION); glLoadIdentity();
gluPerspective(45, (float)w/(float)h, 0.1, 100); glViewport(0,0,w,h);
ww = w; hh = h; }
void render() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glClearColor(.0f, .0f, .2f, 1.0f); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt(.0,.0,5.0, .0,.0,.0, .0,1.0,.0);
static float angle; glRotatef(angle, 0.0,1.0,0.0);
cgGLBindProgram(myCgVertexProgram); cgGLEnableProfile(myCgVertexProfile);
cgGLBindProgram(myCgFragmentProgram); cgGLEnableProfile(myCgFragmentProfile);
//將ModelViewProjection矩陣傳入shader CGparameter mvp = cgGetNamedParameter(myCgVertexProgram, "MVP"); cgGLSetStateMatrixParameter(mvp, CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY);
glutSolidTorus(0.3,1.0,30,30);
cgGLDisableProfile(myCgVertexProfile); cgGLDisableProfile(myCgFragmentProfile);
angle += 0.5; if(angle >=360) angle = 0.0f;
glutSwapBuffers(); glutPostRedisplay(); } |
01vs.cg
struct output { float4 position : POSITION; float4 color : COLOR; };
output vs_main( float4 position : POSITION, float4 color : COLOR, uniform float4x4 MVP ) { output OUT; OUT.position = mul(MVP, position); OUT.color = position;
return OUT; } |
01fs.cg
float4 fs_main( float4 color : COLOR ) : COLOR { return color; } |
下面來解釋一下這兩個shader程序。在01vs.cg中,struct是Cg的關鍵字,和C語言是一樣的,申明一個結構體。然後改shader的入口函數vs_main返回這樣的一個結構體。在結構體中,申明瞭position和color,分別表示要在vertex shader中輸出頂點的位置和顏色。要注意的是,這裏的冒號是Cg的一個新語法,它表示“語義”。它緊跟在一個變量後,表示對該變量進行說明。大寫字母POSITION和COLOR都是Cg的關鍵字。它們表示變量position和color分別作爲頂點位置和顏色傳入fragment shader。有了POSITION和COLOR的說明,在隨後的渲染管線中,要用到頂點位置和顏色的時候就可以分別使用這兩個變量。當然GPU也會自動將這兩個變量作爲頂點位置和顏色來使用。mul是Cg內置函數,表示矩陣的乘法,變量MVP是float4x4的類型,該類型表示一個4*4的矩陣。矩陣和向量的乘法就可以使用函數mul來實現。
同樣地,在fragment shader 01fs.cg中,shader入口函數fs_main返回類型是一個有4維向量,該函數有個傳入參數float4 color:COLOR,這裏的語義COLOR就表示這個變量是從vertex shader裏傳來的要作爲顏色使用的變量。在函數fs_main的後面也有個語義COLOR,它表示該shader程序返回的4維向量要作爲顏色來是使用。
整個HelloCG程序很簡單,如果在代碼中有不懂得部分,特別是對爲什麼要傳入ModelViewProjection矩陣等不明白的同學,那麼說明你對圖形管線的各個階段還不瞭解,建議這些同學多看看圖形學方面的書籍,也可以參考我的OpenGL圖形管線和座標變換這篇文章。