摘要: 介绍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个数组参数设置函数。