void simpleTransform(float4 objectPosition: POSITION,
float4 color: COLOR,
float4 decalCoord: TEXCOORD0,
float4 lightMapCoord: TEXCOORD1,
out float4 clipPosition: POSITION,
out float4 oColor: COLOR,
out float4 oDecalCoord: TEXCOORD0,
out float4 oLightMapCoord: TEXCOORD1,
uniform float brightness,
uniform float4x4 modelViewProjection)
{
clipPosition = mul(modelViewProjection, objectPosition);
oColor = brightness * color;
oDecalCoord = decalCoord;
oLightMapCoord = lightMapCoord;
}
1.1 顶点程序的讲解
这个程序把一个顶点的对象空间位置通过一个包含世界、观察和投射串联转换的4x4矩阵进行转换。得出的结果向量是顶点在夹持空间的位置。每个顶点的颜色在输出前都通过一个浮点参数进行放缩。同样,两个纹理坐标集自然也传过去了。
Cg支持标量数据类型,比如float,但也很好地支持向量数据类型。float4表示包含四个浮点数的向量。float4x4表示一个矩阵。mul是一个标准库函数,完成矩阵和向量乘法。Cg提供像C++的函数重载;mul是一个重载函数,所以它可以用于向量和矩阵相乘的所有组合情况。
Cg提供和C一样的操作符。但是不像C,Cg的操作符可以接受和返回标量和向量。比如,标量brightness对向量color进行了放缩,就像你期望的。
在Cg中,用uniform修饰符声明一个参数表明它的值是由外部的数据源初始化的,而且在给定这批向量的处理中保持不变。从这点看来,Cg中的uniform修饰符和RenderMan中的uniform修饰符不同,但使用的场合一样。实际上,外部数据源是你的应用程序载入的一些OpenGL或Direct3D状态。比如,你的程序必须提供modelViewProjection矩阵和brightness标量。Cg运行库提供一个API把你程序的状态转化成编译的程序需要的适当的API状态。
跟在objectPosition、color、decalCoord和lightMapCoord参数后面的POSITION、COLOR、TEXCOORD0和TEXCOORD1标示符叫做输入语义。它们表明参数是怎么由每个顶点不同的数据初始化的。在OpenGL中,glVertex命令需要POSITION输入语义;glColor命令需要COLOR语义;glMultiTexCoord命令需要TEXCOORDn语义。
out修饰符表明clipPosition、oColor、oDecalCoord和oLightMapCoord参数是由这段程序输出的。跟在参数后面的语义就是输出语义。这些语义分别表明程序输出一个转换后的夹持空间位置和颜色量。同样,两个纹理坐标集也传递过去了。得出的结果顶点提供给图元汇编程序来生成一个光栅化的图元。
编译这个程序需要程序源代码、要编译的入口函数名和profile名(vs_1_1)。
然后Cg编译器可以把上面的Cg程序编译为下面的DirectX 8 vertex shader:
vs.1.1
mov oT0, v7
mov oT1, v8
dp4 oPos.x, c1, v0
dp4 oPos.y, c2, v0
dp4 oPos.z, c3, v0
dp4 oPos.w, c4, v0
mul oD0, c0.x, v5
Profile用来表明程序要被编译为用于哪个API运行环境的。同一个程序可以编译为用于DirectX 9 vertex shader profile(vs_2_0),多厂商OpenGL顶点程序扩展(arbvp1)或NVIDIA私有的OpenGL扩展(vp20和vp30)。
编译Cg程序的过程可以在你使用Cg的程序初始化的时候进行。Cg运行库包含Cg编译器,也以API的方式包含一个非常简单的过程,可以让你配置编译过的程序是用于OpenGL还是Direct3D的。
1.2 片元程序的讲解
除了写处理顶点的程序之外,你还可以写处理片元的程序。这儿有一个Cg片元程序:
float4 brightLightMapDecal(float4 color: COLOR,
float4 decalCoord: TEXCOORD0,
float4 lightMapCoord: TEXCOORD1,
uniform sampler2D decal,
uniform sampler2D lightMap) : COLOR
{
float4 d = tex2Dproj(decal, decalCoord);
float4 lm = tex2Dproj(lightMap, lightMapCoord);
return 2.0 * color * d * lm;
}
输入的参数是颜色插值和两个纹理坐标集,由它们的输入语义定义。
sampler2D类型对应于一个2D纹理单元。Cg标准库程例tex2Dproj可以进行2D纹理映射查找。两次tex2Dproj调用对decal和light map纹理进行取样,然后把结果分别赋值给局部变量d和lm。
这段程序把两个纹理结果,颜色插值和常数2.0相乘,得出RGBA颜色的结果。这段程序返回一个float4和返回值是COLOR的语义,也就是片元最后的颜色。
当要编译为arbfp1多厂商OpenGL片元profile时,Cg编译器把brightLightMapDecal转化成以下代码:
!!ARBfp1.0
PARAM c0 = {2, 2, 2, 2}; TEMP R0; TEMP R1; TEMP R2;
TXP R0, fragment.texcoord[0], texture[0], 2D;
TXP R1, fragment.texcoord[1], texture[1], 2D;
MUL R2, c0.x, fragment.color.primary;
MUL R0, R2, R0;
MUL result.color, R0, R1;
END
同一个程序可以编译为用于DirectX 8或9的profile(ps_1_3和ps_2_x)或NVIDIA私有的OpenGL扩展(fp20和fp30)。
2. 其他Cg的功能
2.1 从C来的特性
Cg提供结构体和数组,包含多维数组。Cg提供所有C的数学操作符(+、*、/等)。Cg提供一个布尔类型和那些与布尔类型相关的操作符(||、&&、!等)。Cg提供递增/递减(++/--)操作符,条件表达式操作符(?:),赋值表达式(+=等),甚至还有C逗号操作符。
Cg提供用户定义函数(除了已定义的标准库函数之外),但是不允许递归函数。Cg提供C的控制流结构的子集(do、while、for、if、break、continue);其他结构比如goto和switch在当前的Cg实现版本并不支持,但保留了必要的关键字。
就像C,Cg对数据类型的精度和范围并不严加限制。实际上,用于编译的profile的选择决定了每种数据类型具体的表现。float、half和double用来表示连续的值,理想的浮点数,但这依赖于profile。half指的是16位半精度浮点数据类型(NVIDIA的CineFX架构提供这样的数据类型)。int是整数,通常用于循环和索引。fixed是一个额外的数据类型,用来表示不是浮点数的定点的连续数据。
Cg提供#i nclude、#define、#ifdef等,和C的预处理一样。Cg支持C和C++的注释风格。
2.2 C没有的附加特性
Cg对向量数据类型提供内建的构造函数(类似于C++,但不是由用户自定义的):
float4 vec1 = float4(4.0, -2.0, 5.0, 3.0);
Swizzling是一种用来重组向量的组成或者构造更短或更长向量的方法。例子如下:
float2 vec2 = vec1.yx;// vec2 = (-2.0, 4.0)
float scalar = vec1.w;// scalar = 3.0
float3 vec3 = scalar.xxx;// vec3 = (3.0, 3.0, 3.0)
矩阵可以用更复杂的swizzling语法。向量和矩阵元素也可以用标准数组索引语法来访问。
写入掩码可以限制只给向量指定的部分赋值。例子如下:
vec1.xw = vec3;// vec1 = (3.0, -2.0, 5.0, 3.0)
使用.xyzw或.rgba后缀来组成swizzling和写入掩码。
Cg标准库包含大量的内建数学函数(abs、dot、log2、reflect、rsqrt等)和纹理访问函数(texCUBE、tex3Dproj等)。标准库广泛应用了函数重载(类似于C++)来支持不同长度的向量和不同的数据类型。你不必像在C里一样使用#i nclude来包含标准库程例的原型,Cg标准库程例都是自动生成原型的。
除了用于call-by-result传参的out修饰符之外,inout修饰符使参数既是call-by-value输入参数也是call-by-result输出参数。
关键字discard类似于return,但是不返回转换好的片元就终止处理了。
2.3 不支持的特性
Cg目前还不支持指针和位运算(但是,必要的C操作符和关键字为这些目的保留了)。Cg(目前)不支持联合体和函数变元。
Cg没有C++“大型的编程”特性,比如类、模板、操作符重载、异常处理和名字空间。
Cg标准库没有功能性的程例,比如字符串处理、文件输入/输出和内存配置,这些超出了Cg的专有领域。
但是,Cg保留了所有的C和C++关键字,这为从这些语言来的特性可以合入未来的Cg实现作保证。
3. Profile依赖
当你编译一个C或C++程序时,它不会因为程序过大(在一定前提之内)或因为这个程序的用途而无法通过编译。对于Cg,一个语法和语义都正确的程序可能仍然不能编译,因为这还受限于你编译这个程序的profile。
比如,目前当用顶点profile编译时,如果访问纹理就会产生一个错误。未来的顶点profile可能会允许纹理访问,但现在的顶点profile不行。其他错误更为固有。比如,一个片元profile不能以TEXCOORD0语义输出一个参数。其他错误可能是因为超出了当前的GPU能力范围,比如超过最大的指令数或可利用的纹理单元数目。
要知道,这些profile依赖错误并不反映出Cg语言的限制,而是目前Cg实现的限制或者你的目标GPU的潜在硬件限制。
4. 兼容性和移植性
NVIDIA的Cg实现和Microsoft的高级着色语言(HLSL)非常相似,因为它们是合作开发的。HLSL是和DirectX 9以及Windows操作系统整合在一起的。Cg则提供了多API(OpenGL、DirectX 8和DirectX 9)和多操作系统(Windows、Linux和Mac OS X)的支持。因为Cg对多厂商API有接口,Cg可以在多个厂商的GPU上运行。