摘要: 介紹CG的語法特點、特殊的語義及支持的特殊運算,簡單介紹CG工作的大致流程,重點講解如何封裝一個面向對象的CGShader類, 裏面封裝了Cg的核心函數,用這個類可以簡化CG的開發工作。
1. CG的語法、語義及特殊運算
與 C 的相同之處:CG具有類C的語法特點,有很多跟C一樣的基本數據類型和關鍵字,有相同的struct定義方式,也可以用#include包含文件,跟C & C++相同的註釋方式。
CG的特殊之處:Cg使用了C & C++中沒有的其他關鍵字,CG支持語義綁定,語義(semantic)其實就是Cg與傳統的圖形流水線通信的接口,Cg的Shader程序可以從固定流水線獲取一些屬性,如頂點的位置、顏色、紋理座標等,然後進行你想要的處理再反饋回去,達到修改固定流水線的目的,獲取屬性及反饋修改就是通過語義的綁定實現的。 語義分爲輸入和輸出語義,獲取屬性的接口就是輸入語義,修改反饋接口即爲輸出語義。語義綁定只有在Cg的入口函數的參數列表裏有效,在普通的內部函數內無效。輸入和輸出語義是不同的,雖然某些語義具有相同的名字,例如:頂點Shader的輸入參數,POSITION指應用程序傳入的頂點位置,而輸出參數使用POSITION語義就表示要反饋給硬件光柵器的裁剪空間位置。兩個語義都命名爲POSITION,而且都是表示一個位置,但每個位置語義所代表的位置是圖形流水線上不同階段的位置。如下語義綁定實例(爲輸出語義綁定):
struct Output {
float4 position : POSITION;
float4 color : COLOR;
};
CG的另一個特殊的地方就是支持向量類型及向量、矩陣運算。C & C ++中都爲標量類型,因爲向量運算是對頂點和片段進行處理不可缺少的,而且圖形處理器也內置了對向量數據類型的支持,所以Cg引入了向量類型數據,Cg裏的向量並不簡單是一個標量數組,而是壓縮數組(packed arrays),能夠提供更快速的向量操作。除了向量之外,Cg還天生支持矩陣類型。下面是一些向量、矩陣的例子:
float4 data = { 0.5, -2, 3, 3.14 }; // This is a vector with 4 component, initializing as in c
float4x4 matrix1; // Define a 4X4 matrix with 16 elements
易變(Varying)和統一(Uniform)變量:語義提供一種使用隨頂點而變化或隨片段而變化的值來初始化Cg程序參數的方法,因此Cg沒有取消了Varying,不像GLSL要用Varing關鍵字來限定易變變量,而Cg用語義綁定來表示。Cg裏跟其他Shading Language一樣也採用Uniform關鍵字來表示統一變量,統一變量就是需要從應用程序中傳入Cg程序中。
2. 應用Cg的大致工作流程
(1) 創建一個環境:CGContext ctx = cgCreateContext();
(2) 編譯一個程序:通過使用cgCreateProgram把一個Cg程序加入到一個環境裏,並編譯這個Cg程序
CGProgram program = cgCreateProgram(ctx, CG_SOURCE, programString, profile, "main", args);
(3) 載入一個程序: cgGLLoadProgram(program);
(4) 修改程序參數: 先獲得參數,CGParameter myParameter = cgGetNamedParameter(program, "myParamter"); 然後設置參數, 如: cgGLSetParameter4fv (myParameter, value);
(5) 執行程序: cgGLEnableProfile (profile); cgGLBindProgram (program); 在OpenGL中要禁止一個profile可以調用 cgGLDisableProfile(profile);
(6) 釋放資源: cgDestroyProgram (program); cgDestroyContext (context);
3. CGShader的封裝
該類把Cg的調用流程及參數設置進行了簡化並進行封裝,使用更直觀方便。其中參數設置的函數都做成模版成員函數。由於一般的編譯器對模版函數的定義與實現分離支持的不好,就把模版函數實現也放在頭文件裏。
頭文件( h file )如下:
#define CGSHADER_H
#include <map>
#include <cg/cg.h>
#include <cg/cgGL.h>
class String;
class CGShader
{
public:
CGShader(void);
CGShader(const CGShader& copy);
CGShader(const char* shaderName);
CGShader(bool ifVertexShader, const char* sourceStrOrFileName, const char* shaderName, const char* entry = "main",
bool ifFromFile = true, CGenum sourceType = CG_SOURCE, const char** args = NULL);
void operator = (const CGShader& copy);
void setShaderName(const char* shaderName);
void createShader(bool ifVertexShader, const char* sourceStrOrFileName, const char* entry = "main",
bool ifFromFile = true, CGenum sourceType = CG_SOURCE, const char** args = NULL);
void addParam(const char* paramName);
CGparameter getParamByName(const char* paramName);
/*------------Parameters setting------------*/
void setRowPriorMatrixf(const char* paramName, const float* mat);
void setRowPriorMatrixd(const char* paramName, const double* mat);
void setColPriorMatrixf(const char* paramName, const float* mat);
void setColPriorMatrixd(const char* paramName, const double* mat);
template<typename T>
void setParam1(const char* paramName, T x)
{
CGparameter var = getParamByName(paramName);
if(!cgIsParameter(var))
return;
if(typeid(x) == typeid(int))
cgSetParameter1i(var, x);
else if(typeid(x) == typeid(float))
cgSetParameter1f(var, x);
else if(typeid(x) == typeid(double))
cgSetParameter1d(var, x);
checkCGError(String("setParam1 ") + typeid(x).name());
}
template<typename T>
void setParam2(const char* paramName, T x, T y)
{
CGparameter var = getParamByName(paramName);
if(!cgIsParameter(var))
return;
if(typeid(x) == typeid(int))
cgSetParameter2i(var, x, y);
else if(typeid(x) == typeid(float))
cgSetParameter2f(var, x, y);
else if(typeid(x) == typeid(double))
cgSetParameter2d(var, x, y);
checkCGError(String("setParam2 ") + typeid(x).name());
}
template<typename T>
void setParam3(const char* paramName, T x, T y, T z)
{
CGparameter var = getParamByName(paramName);
if(!cgIsParameter(var))
return;
if(typeid(x) == typeid(int))
cgSetParameter3i(var, x, y, z);
else if(typeid(x) == typeid(float))
cgSetParameter3f(var, x, y, z);
else if(typeid(x) == typeid(double))
cgSetParameter3d(var, x, y, z);
checkCGError(String("setParam3 ") + typeid(x).name());
}
template<typename T>
void setParam4(const char* paramName, T x, T y, T z, T w)
{
CGparameter var = getParamByName(paramName);
if(!cgIsParameter(var))
return;
if(typeid(x) == typeid(int))
cgSetParameter4i(var, x, y, z, w);
else if(typeid(x) == typeid(float))
cgSetParameter4f(var, x, y, z, w);
else if(typeid(x) == typeid(double))
cgSetParameter4d(var, x, y, z, w);
checkCGError(String("setParam4 ") + typeid(x).name());
}
template<int n>
void setParamArrayi(const char* paramName, int (&arr)[n])
{
CGparameter var = getParamByName(paramName);
if(!cgIsParameter(var))
return;
switch(n){
case 1:
cgSetParameter1iv(var, arr);
break;
case 2:
cgSetParameter2iv(var, arr);
break;
case 3:
cgSetParameter3iv(var, arr);
break;
case 4:
cgSetParameter4iv(var, arr);
break;
}
checkCGError("setParamArray int");
}
template<int n>
void setParamArrayf(const char* paramName, float (&arr)[n])
{
CGparameter var = getParamByName(paramName);
if(!cgIsParameter(var))
return;
switch(n){
case 1:
cgSetParameter1fv(var, arr);
break;
case 2:
cgSetParameter2fv(var, arr);
break;
case 3:
cgSetParameter3fv(var, arr);
break;
case 4:
cgSetParameter4fv(var, arr);
break;
}
checkCGError("setParamArray float");
}
template<int n>
void setParamArrayd(const char* paramName, double (&arr)[n])
{
CGparameter var = getParamByName(paramName);
if(!cgIsParameter(var))
return;
switch(n){
case 1:
cgSetParameter1dv(var, arr);
break;
case 2:
cgSetParameter2dv(var, arr);
break;
case 3:
cgSetParameter3dv(var, arr);
break;
case 4:
cgSetParameter4dv(var, arr);
break;
}
checkCGError("setParamArray double");
}
/*------------------------------------------*/
void activate();
void deactivate();
void destroyShader();
static CGcontext getCGContext();
static void checkCGError(const String& situation);
private:
CGprogram cgProgram;
CGprofile cgProfile;
String shaderName;
std::map<String, CGparameter> params;
bool ifVertexShader;
static CGcontext context;
};
#endif
實現文件 (cpp file):
#include "GShader.h"
#include <typeinfo>
#pragma comment (lib, "cg.lib")
#pragma comment (lib, "cgGL.lib")
CGcontext CGShader::context = NULL;
CGShader::CGShader(void)
{
shaderName = NULL;
ifVertexShader = true;
}
CGShader::CGShader(const CGShader& copy)
{
cgProgram = copy.cgProgram;
cgProfile = copy.cgProfile;
params = copy.params;
shaderName = copy.shaderName;
ifVertexShader = copy.ifVertexShader;
}
CGShader::CGShader(const char* shaderName)
{
this->shaderName = shaderName;
ifVertexShader = true;
}
CGShader::CGShader(bool ifVertexShader, const char* sourceStrOrFileName, const char* shaderName, const char* entry /* ="main" */,
bool ifFromFile, CGenum sourceType /* = CG_SOURCE */, const char** args /* = NULL */)
{
createShader(ifVertexShader, sourceStrOrFileName, entry, ifFromFile, sourceType, args);
}
void CGShader::setShaderName(const char* shaderName)
{
if(shaderName)
this->shaderName = shaderName;
}
void CGShader::createShader(bool ifVertexShader, const char* sourceStrOrFileName, const char* entry /* ="main" */,
bool ifFromFile, CGenum sourceType /* = CG_SOURCE */, const char** args /* = NULL */)
{
if(!cgIsContext(context))
context = cgCreateContext();
if(ifVertexShader)
cgProfile = cgGLGetLatestProfile(CG_GL_VERTEX);
else
cgProfile = cgGLGetLatestProfile(CG_GL_FRAGMENT);
checkCGError("Get latest profile");
cgGLSetOptimalOptions(cgProfile);
checkCGError("Set optimal options for profile");
if(ifFromFile)
cgProgram = cgCreateProgramFromFile(context, sourceType, sourceStrOrFileName, cgProfile, entry, args);
else
cgProgram = cgCreateProgram(context, sourceType, sourceStrOrFileName, cgProfile, entry, args);
checkCGError("Create program");
cgGLLoadProgram(cgProgram);
checkCGError("Load program");
}
void CGShader::addParam(const char* paramName)
{
CGparameter var = cgGetNamedParameter(cgProgram, paramName);
if(cgIsParameter(var))
params[paramName] = var;
else
Logger::writeFatalErrorLog(String("Can't get the parameter: ") + paramName);
checkCGError("Get named parameter (in addParam function)");
}
CGparameter CGShader::getParamByName(const char* paramName)
{
CGparameter var;
std::map<String, CGparameter>::iterator it = params.find(paramName);
if(it != params.end())
var = it->second;
if(cgIsParameter(var))
return var;
Logger::writeErrorLog(String("Can't find parameter: ")+paramName);
return NULL;
}
void CGShader::activate()
{
cgGLBindProgram(cgProgram);
checkCGError("Bind program");
cgGLEnableProfile(cgProfile);
checkCGError("Enable profile");
}
void CGShader::deactivate()
{
cgGLDisableProfile(cgProfile);
}
void CGShader::destroyShader()
{
params.clear();
if(cgIsProgram(cgProgram)) cgDestroyProgram(cgProgram);
}
void CGShader::setRowPriorMatrixf(const char* paramName, const float* mat)
{
CGparameter var = getParamByName(paramName);
if(!cgIsParameter(var))
return;
cgSetMatrixParameterfr(var, mat);
checkCGError("Set row-prior matrix parameter");
}
void CGShader::setRowPriorMatrixd(const char* paramName, const double* mat)
{
CGparameter var = getParamByName(paramName);
if(!cgIsParameter(var))
return;
cgSetMatrixParameterdr(var, mat);
checkCGError("Set row-prior matrix parameter");
}
void CGShader::setColPriorMatrixf(const char* paramName, const float* mat)
{
CGparameter var = getParamByName(paramName);
if(!cgIsParameter(var))
return;
cgSetMatrixParameterfc(var, mat);
checkCGError("Set col-prior matrix parameter");
}
void CGShader::setColPriorMatrixd(const char* paramName, const double* mat)
{
CGparameter var = getParamByName(paramName);
if(!cgIsParameter(var))
return;
cgSetMatrixParameterdc(var, mat);
checkCGError("Set col-prior matrix parameter");
}
/*--------Static functions------------*/
CGcontext CGShader::getCGContext()
{
return context;
}
void CGShader::checkCGError(const String& situation)
{
CGerror error;
const char* errStr = cgGetLastErrorString(&error);
if(error != CG_NO_ERROR) {
Logger::writeErrorLog(String(situation) + ": " + errStr );
if(error == CG_COMPILER_ERROR) {
Logger::writeFatalErrorLog(cgGetLastListing(context));
}
}
}
說明: SetParam {1,2,3,4}系列函數用了一個類型形參可以用於int, float, double 型等的參數設置,用這四個函數代替了原來Cg中的12個對應的設置函數, 因爲int, float, double 可以有默認的類型互轉,頂多會有警告,這樣可以通過運行時動態判斷模版函數實例的實際類型來調用Cg中相對應的函數。而SetParamArray {i, f, d}不能用類似的動態判斷參數類型的方法用一個函數去封裝對應的幾個,應爲這幾個函數傳入的是數組,int, float ,double數組之間沒有默認的互轉,用動態轉換dynamic_cast也許可以解決這個問題 ,以後再試吧,現在先做成3個函數,每個函數用到一個非類型形參,即 template<int n>用於識別傳入函數的數組的長度,這樣可以實現一個函數設置不同長度的數組參數,這樣使得這3個模版函數可以替代Cg中的12個數組參數設置函數。