之前我们学习了如何声明顶点着色器、如何设置常量寄存器中的常量。接下来我们学习如何写和编译一个顶点着色器程序。
在我们编译一个顶点着色器之前,首先需要写一个。
有17种不同的指令(instruction),它们的句法如下:
1. add
参数:dest,src1,src2
作用:src1+src2
2. dp3
参数:dest,src1,src2
作用:三个分量的点积(three-component dot product)。dest.x = dest.y = dest.z = dest.w = (src1.x*src2.x)+(src1.y*src2.y)+(src1.z*src2.z)
3. dp4
参数:dest,src1,src2
作用:四个分量的点积。(four-component dot product)dest.w = (src1.x*src2.x)+(src1.y*src2.y)+(src1.z*src2.z)+(src1.w*src2.w);
dest.x = dest.y = dest.z = dp4结果的标量
和mul的不同点:dp4产生的积是标量。mul是一个向量积(component by component vector product)。
4. dst
参数:dest,src1,src2
作用:dst这样计算:第一个源操作数(src1)被假定为vector(ignored,d*d,d*d,ignored),
第二个源操作数(src2)被假定为vector(ignored,1/d,ignored,1/d).
Calculate distance vector:
dest.x = 1
dest.y = src1.y*src2.y
dest.z = src1.z
dest.w = src2.w
dst在计算标准衰减(standard attenuation)是很有用的。
5. expp
参数:dest,src.w
作用:Exponential 10-bit precision
6. lit
参数:dest,src
作用:Calculate lighting coefficients from two dot products and a power.
7. logp
参数:dest,src.w
作用:Logarithm 10-bit precision
8. mad
参数:dest,src1,src2,src3
作用:dest = (src1*src2)+src3
9. max
参数:dest,src1,src2
作用:dest = (src1>=src2)?src1:src2)
10. min
参数:dest,src1,src2
作用:dest = (src1<src2)?src1:src2
11. mov
参数:dest,src
作用:
12. mul
参数:dest,src1,src2
作用:计算叉乘
13. nop
参数:none
作用:do nothing.
14. rcp
参数:dest,src.w
作用:
15. rsq
参数:dest,src
作用:
16. sge
参数:dest,src1,src2
作用:dest = (src1>=src2) ? 1 : 0
17. slt
参数:dest,src1,src2
作用:dest = (src1<src2) ? 1 : 0
ALU顶点着色器是一个操作quad-float data的多线程的向量处理器,它由两个功能单元组成。SIMD Vector Unit负责mov,mul,add,mad,dp3,dp4,dst,min,max,
slt,sge指令。 Special Function Unit负责rcp,rsq,lgp,expp,lit指令。大多数指令执行一个循环,在特殊环境下,rcp,rsq执行超过一个循环。They take only one slot in the vertex shader, but they actually take longer than one cycle to
execute when the result is used immediately because that leads to a register stall.
rsq被用在光照方程中规范化(normalize)向量。expp可以用于雾特效,处理噪音生成和粒子系统中粒子的行为,或者to implement a system for how
objects in a game are damaged.
lit指令被默认处理方向光(directional lights)。它计算漫反射和高光元素,基于N*L、N*H和specular power。 you can use an attenuation level separately with the result of lit by
using the dst instruction. This is useful for constructing attenuation factors for point and spot lights.
也有复杂的指令被顶点着色器支持,不应该用“宏”指这些复杂指令,因为它们不能被“C预处理宏”简单地代替。在使用这些指令前,你应该认真考虑。如果你使用它们,
你可能会丢失128-instructions和优化路径的控制。另一方面,Intel和AMD为它们的处理器提供软件效仿模式(software emulation mode),这可以优化一个m4x4复杂指令,
一些图形硬件也打开了优化m4x4的大门。所以,用在你的顶点着色器里,用m4x4代替调用dp4。如果你决定在你的着色器中使用m4x4指令,在那之后,你不应该在相同的数据上调用dp4,
因为这会在变换后的结果上有稍微不同。
-----------------------------------------------------------------------------------------------------
宏 | 参数 | 作用 | Clocks |
expp | dest, src1 | Provides exponential with full precision to at least 1/220 | 12 |
frc | dest, src1 | Returns fractional portion of each input component | 3 |
log | dest, src1 | Provides log2(x) with full float precision of at least 1/220 | 12 |
m3x2 | dest, src1, src2 | Computes the product of the input vector and a 3x2 matrix | 2 |
m3x3 | dest, src1, src2 | Computes the product of the input vector and a 3x3 matrix | 3 |
m3x4 | dest, src1, src2 | Computes the product of the input vector and a 3x4 matrix | 4 |
m4x3 | dest, src1, src2 | Computes the product of the input vector and a 4x3 matrix | 3 |
m4x4 | dest, src1, src2 | Computes the product of the input vector and a 4x4 matrix | 4 |
-----------------------------------------------------------------------------------------------------
你可以用这些指令执行所有变换和灯光操作。
现在,让我们来看看这些寄存器和指令如何运用在ALU顶点着色器中。在版本vs.1.1中,每个rasterizer(光栅)有16个输入寄存器,96个常量寄存器,12个临时寄存器(temporary),一个地址寄存器(address),13个输出寄存器。
每个寄存器可以处理4x32-bit值。每个32-bit值可以通过脚注x,y,z,w取得。为了访问这些寄存器分量,你必须在寄存器名字的后面添加.x,.y,.z,.w。
【使用输入寄存器】
你可以通过它们的名字v0-v15来访问这16个输入寄存器。
输入寄存器中一般提供的值有:
Position (x,y,z,w)
Diffuse color (r,g,b,a) — 0.0 到 1.0
Specular color (r,g,b,a) — 0.0 到 1.0
可达8个texture coordinates (each as s, t, r, q or u, v, w, q) 但一般是4个或者6个,取决于硬件的支持
Fog (f,*,*,*) — 用在雾方程的值
Point size (p,*,*,*)
你可以通过v0.x访问position的x分量。如果你需要知道RGBA diffuse color 的表示绿色的分量,你可以查看v1.y。你可以通过v7.x设置雾的值。其他分量,v7.y,v7.z,v7.w不被使用。
输入寄存器是只读的,每个指令只能访问(access)一个顶点输入寄存器。在输入寄存器中,未被特殊化的(即未被赋值)分量x,y,z设为0.0,w设为1.0。
下面的例子中,c0-c3和v0之间的点乘被存储在oPos中:
dp4 oPos.x , v0 , c0
dp4 oPos.y , v0 , c1
dp4 oPos.z , v0 , c2
dp4 oPos.w , v0 , c3
这个代码片段通常用在在World、View、Projection矩阵的结合体的帮助下,从 projection space 映射到 clip space。
因为给定的是单位向量,而两个单位向量的点积范围为[-1,1],所以oPos得到的值总在[-1,1]内。
你也可以使用: m4x4 oPos, v0, c0
不要忘记这个事实,你要至始至终的在你的顶点着色器中使用m4x4 或者 dp4, 不能既使用m4x4又使用dp4,根据前面讨论过的,dp4和m4x4作用的结果是有稍微区别的!
你也一定不要忘了每条指令只能使用一个输入寄存器。
所有在输入寄存器中的数据将贯穿顶点着色器程序的执行(即程序没结束前一直存在),甚至存在的时间更长,这意味着这些数据的生命周期甚至比顶点着色器的生命都长。
所以在下一个顶点着色器中reuse这些数据是可能的。
【使用常量寄存器】
常量寄存器中的数据一般包括:Matrix data、光照特性(如位置、衰减因子等)、当前时间、顶点插值数据、程序数据。有96个常量寄存器(or in the case of theRADEON 8500, 192个),所以可以存储96个quad-floats。
常量寄存器于顶点着色器是只读的,然而,应用程序可以对顶点着色器进行读写。常量寄存器保存它的数据的时间超过顶点着色器的生命周期,所以可以在下一个顶点着色器中reuse数据。所以app可以避免多余的SetVertexShaderConstant()的调用。读取过程中如果超过常量寄存器的range,将返回(0,0,0,0)。
你只能每个指令用一个常量寄存器,但你可以多次使用它。例如:
下面的指令是合法的:mul r5, c11, c11 // 表示c11和c11的积被存储在r5中
这条是非法的:add v0, c4, c3 //因为它一条指令使用了两个不同的寄存器
【使用寻址寄存器】
你可以用a0-an来访问寻址寄存器(如果着色器版本高于1.1,多个寻址寄存器可用)。在版本1.1中,只能使用a0来执行间接寻址操作,用来offset常量寄存器(也就是constant memory)。如:c[a0.x + n] ; // n表示基地址(base address),a0.x是地址偏移量(address offset)。
这儿是一个使用寻址寄存器的例子:
mov a0.x,r1.x
m4x3 r4,v0,c[a0.x + 9];
m3x3 r5,v3,c[a0.x + 9];
...
根据储存在临时寄存器r1.x的值,在指令m4x4,m3x3中使用不同的常量寄存器。记住,a0只存储整数,没有分数。而且,一个顶点着色器只能通过mov指令write to a0.x。
【使用临时寄存器】
你可以用r0-r11来访问12个临时寄存器。每个临时寄存器可以写一次读三次,因此,一个指令可以使用相同的临时寄存器三次。在写入临时寄存器之前,顶点着色器不能尝试从临时寄存器读取一个值。如果你尝试从一个尚未写入值的临时寄存器读取数据,当创建顶点着色器的时候(调用CreateVertexShader()),API将给你一个错误信息。
【使用输出寄存器】
你可以通过下面的寄存器名字访问13个只写输出寄存器。
一个例子:
dp4 oPos.x , v0 , c4 ; emit projected x position
dp4 oPos.y , v0 , c5 ; emit projected y position
dp4 oPos.z , v0 , c6 ; emit projected z position
dp4 oPos.w , v0 , c7 ; emit projected w position
mov oD0, v5 ; set the diffuse color
mov oT0, v2 ; outputs the texture coordinates to oT0 from input register v2