Swizzling and Masking
如果你使用输入、常量、临时寄存器作为源寄存器,你可以彼此独立地swizzle .x,.y,.z,.w值。如果你使用输出、临时寄存器作为目标寄存器,你可以把.x,.y,.z,.w值用作
write masks。 下面是一些细节:
Swizzling (only source registers : vn,cn,rn)
例如:
1 mov R1, -R2.wxyz
目标寄存器为R1,R2为源寄存器(在指令语法中,源寄存器在目标寄存器的右边)。执行该mov指令后,会将R2中-w,-x,-y,-z的值分别赋给R1的x,y,z,w。
Masking (only destination registers : on,rn)
例如:
1 mov R1.xw, R2
使用R1为目标寄存器,R2位源寄存器。执行该mov指令后,会仅仅将R2中的x,w的值赋给R1。在目标寄存器中不支持swizzling和负数。
【对于编写顶点着色器的指导】
你需要记住这些:
1。有128条指令的限制。
2。每条指令的源寄存器不能超过一个常量寄存器(比如:add r0, c4, c3是错误的)
3。每条指令的源寄存器不能超过一个输入寄存器(比如:add r0, v1, v2是错误的)
4。没有类C的条件语句,但你可以用sge指令模仿r0 = (r1>=r2) ? r3 : r4
5。所有在顶点着色器中的值被固定在[0,1]
6。Every vertex shader must write at least to one component of oPos, or you will get an error message by the assembler.
有一些优化顶点着色器的方法:
1。当设置顶点着色器的常量数据时,尝试在一个SetVertexShaderConstant()的调用中设置所有数据。
2。使用一个mov指令之前停下来想想,或许你可以避免使用它。
3。选择多个指令运算,而不是单个指令运算。比如:
mad r4,r3,c9,r4
mov oD0,r4
==
mad oD0,r3,c9,r4
4。考虑优化之前,先移除像m4x4或者m3x3这样复杂的指令。
5。在着色器中的许多计算都可以被pulled outside并且用每个物体的形式表示,而不是每个顶点,而且可以把它们放入常量寄存器。如果你在做一些基于物体而不是基于顶点的计算,在CPU里进行,并把它作为常量上载到顶点着色器。
【编译顶点着色器】
Direct3D使用字节码(bytecodes),而OpenGL解析字符串。因此,Direct3D的开发者需要使用Assembler来assemble顶点着色器的资源。这可以帮助你更容易找到bug,也缩短了载入时间。
有三种不同的方法编译顶点着色器:
1。将顶点着色器资源写入一个独立的ASCII文件(比如 test.vsh),然后用顶点着色器Assembler将它编译进一个二进制文件(比如test.vso)。这个二进制文件将在游戏启动时被打开和读取。
使用这个方法,不是每个人都可以看到并且修改你的顶点着色器资源。
2。将顶点着色器资源写入一个独立的ASCII文件或者作为一个字符串写入你的*.cpp文件,当应用程序启动时,用D3DXAssemblerShader*()函数编译它。
3。将顶点着色器资源写入一个效果文件,当应用程序启动时打开这个效果文件。可以通过调用D3DXCreateEffectFromFile()读取效果文件来编译顶点着色器。
注:另一个方法是使用d3dtypes.h中展示的操作码构建你自己的顶点assembler/disassembler。
让我们回顾一下我们至今讲解过的知识,首先,我们通过D3DCAPS8::VertexShaderVersion检查设备是否支持顶点着色器,然后我们用D3DVSD_*宏声明一个顶点着色器。然后
我们用SetVertexShaderConstant()设置常量寄存器,然后写、编译顶点着色器。
现在,我们需要一个句柄去call顶点着色器。
【创建顶点着色器】
CreateVertexShader()函数被用来创建并且使一个顶点着色器生效。
1 HRESULT CreateVertexShader( 2 CONST DWORD* pDeclaration, 3 CONST DWORD* pFunction, 4 DWORD* pHandle, 5 DWORD Usage);
pDeclaration :顶点着色器声明的指针(它将顶点缓冲区映射到不同的顶点输入寄存器)。
pFunction : 通过D3DXAssembleShader() / D3DXAssembleShaderFromFile()使得顶点着色器指令被编译。
pHandle : 返回的顶点着色器的句柄。
Usage : 你可以使用D3DUSAGE_ SOFTWAREPROCESSING来强制使用软件进行顶点处理(当D3DRS_SOFTWAREVERTEXPROCESSING被设置为true时,它必须被使用)。但是使用GPU硬件处理速度更快。
【设置顶点着色器】
在DrawPrimitive*()调用之前,需要调用SetVetexShader()。这个函数动态地载入顶点着色器,用来设置使用哪个顶点着色器。你必须提供由CreateVertexShader()创建的顶点着色器句柄。
m_pd3dDevice->SetVertexShader( m_dwVertexShader );
顶点着色器可以被SetVertexShader()执行很多次,这个次数和顶点数一样多。
【释放顶点着色器资源】
当游戏终止或者设备(device)被改变,被顶点着色器使用的资源必须被释放掉,可以通过DeleteVertexShader()来执行。
1 if(m_pd3dDevice->m_dwVertexShader != 0xffffffff) 2 { 3 m_pd3dDevice->DeleteVertexShader(m_dwVertexShader); 4 m_pd3dDevice->m_dwVertexShader = 0xffffffff; 5 }
编写一个顶点着色器
1. Check for vertex shader support by checking the D3DCAPS8::VertexShaderVersion field.
2. Declare the vertex shader with the D3DVSD_* macros to map vertex buffer streams to
input registers.
3. Set the vertex shader constant registers with SetVertexShaderConstant().
Compile an already written vertex shader with D3DXAssembleShader*() (alternatively : could be precompiled using a shader assembler).
4. Create a vertex shader handle with CreateVertexShader().
5. Set a vertex shader with SetVertexShader() for a specific object.
6. Free vertex shader resources handled by the Direct3D engine with DeleteVertexShader().
【检查设备支持】
如果使用软件实现,则不需要检查设备支持。
这里跳过该步骤,因为directX 9以上就不需要检查设备支持了。
【声明顶点着色器】
声明顶点着色器意味着将顶点数据映射到特定的输入寄存器。因此,顶点着色器声明必须反映顶点缓冲区布局。
着色器声明:
1 DWORD dwDecl[] = 2 { 3 D3DVSD_STREAM(0), 4 D3DVSD_REG(0,D3DVSDT_FLOAT3), // D3DVSDE_POSITION,0 5 D3DVSD_END() 6 };
顶点缓冲区布局:
1 struct VERTEX 2 { 3 FLOAT x,y,z; 4 };
顶点格式声明:
1 #define D3DFVF_VERTEX (D3DFVF_XYZ)
位置的值将被存储进顶点缓冲区,并且会通过函数SetStreamSource()绑定到设备数据流端口。(可能是Higher-Order Surfaces(HOS)阶段,也可能是顶点着色器,取决于usage of HOS,可参考page 6中图片1中的Direct3D 管线)
【设置常量寄存器】
常量寄存器必须通过调用SetVertexShaderConstant()来进行填充。
下面的例子将材质颜色设置进常量寄存器c8.
1 FLOAT fMaterial[4]={0,1,0,0}; 2 m_pd3dDevice->SetVertexShaderConstant(8,fMaterial,1);
SetVertexShaderConstant的声明如下:
1 HRESULT SetVertexShaderConstant (DWORD Register, 2 CONST void* pConstantData, 3 DWORD ConstantCount);
第一个参数指要被使用的常量寄存器的数字编号。
第二个参数将128-bits的数据存储进该常量寄存器。
第三个参数是所要存储的128-bits数据的数量。比如,要将一个4x4矩阵存储进常量寄存器,
可以将第三个参数置为4:m_pd3dDevice->SetVertexShaderConstant(4, matTemp, 4); 如果这样的话,c4,c5,c6,c7都被用来存储这个矩阵。
【顶点着色器】
下面的例子中,顶点着色器被存储进一个字符数组中。
1 // reg c4-7 = WorldViewProj matrix 2 // reg c8 = constant color 3 // reg v0 = input register 4 5 const char BasicVertexShader[] = 6 "vs.1.1 " 7 "dp4 oPos.x, v0, c4 " 8 "dp4 oPos.y, v0, c5 " 9 "dp4 oPos.z, v0, c6 " 10 "dp4 oPos.w, v0, c7 " 11 "mov oD0, c8 ";
It is used inline in a constant char array. 这个顶点着色器遵循vs.1.1版本的实现规则。它用4个dp4指令将“整合在一起并且进行过转置”的World,View,Projection 矩阵转换到clip matrix(或者clip sapce),并且用mov指令将c8赋给一个oD0材质颜色。
1 D3DXMatrixRotationY( &m_matWorld, m_fTime * 1.5f ); 2 D3DXMATRIX matTemp; // set the clip matrix 3 D3DXMatrixTranspose( &matTemp , &(m_matWorld * m_matView * m_matProj) ); 4 m_pd3dDevice->SetVertexShaderConstant(4, matTemp, 4);
将物体绕Y轴旋转,需要调用D3DMatrixRotationY()。
它的实现就像这样:fRads代表你要旋转的角度。
1 VOID D3DMatrixRotationY(D3DMATRIX * mat, FLOAT fRads) 2 { 3 D3DXMatrixIdentity(mat); 4 mat._11 = cosf(fRads); 5 mat._13 = -sinf(fRads); 6 mat._31 = sinf(fRads); 7 mat._33 = cosf(fRads); 8 }= 9 cosf(fRads) 0 -sinf(fRads) 0 10 0 0 0 0 11 sinf(fRads) 0 cosf(fRads) 0 12 0 0 0 0
旋转之后,我们需要用D3DXMatrixTranspose()将矩阵转置。为什么必须将它转置呢?理由如下:一个4x4矩阵:
a b c d
e f g h
i j k l
m n o p
将一个向量vector通过该矩阵转换的公式为:将向量与矩阵相乘
dest.x = (v0.x * a) + (v0.y * e) + (v0.z * i) + (v0.w * m)
dest.y = (v0.x * b) + (v0.y * f) + (v0.z * j) + (v0.w * n)
dest.z = (v0.x * c) + (v0.y * g) + (v0.z * k) + (v0.w * o)
dest.w = (v0.x * d) + (v0.y * h) + (v0.z * l) + (v0.w * p)
但在着色器中,我们使用4个dp4的话:(在寄存器中,我们存储矩阵是一行一行地存储)
dest.x = (v0.x * a) + (v0.y * b) + (v0.z * c) + (v0.w * d)
dest.y = (v0.x * e) + (v0.y * f) + (v0.z * g) + (v0.w * h)
dest.z = (v0.x * i) + (v0.y * j) + (v0.z * k) + (v0.w * l)
dest.w = (v0.x * m) + (v0.y * n) + (v0.z * o) + (v0.w * p)
所以如果不进行转置是错误的。我们必须将矩阵进行转置为:
a e i m // c4
b f j n // c5
c g k o // c6
d h l p // c7
这样的话,dp4就会正确运算
dest.x = (v0.x * a) + (v0.y * e) + (v0.z * i) + (v0.w * m)
dest.y = (v0.x * b) + (v0.y * f) + (v0.z * j) + (v0.w * n)
dest.z = (v0.x * c) + (v0.y * g) + (v0.z * k) + (v0.w * o)
dest.w = (v0.x * d) + (v0.y * h) + (v0.z * l) + (v0.w * p)
或者
oPos.x = (v0.x * c4.x) + (v0.y * c4.y) + (v0.z * c4.z) + (v0.w * c4.w)
oPos.y = (v0.x * c5.x) + (v0.y * c5.y) + (v0.z * c5.z) + (v0.w * c5.w)
oPos.z = (v0.x * c6.x) + (v0.y * c6.y) + (v0.z * c6.z) + (v0.w * c6.w)
oPos.w = (v0.x * c7.x) + (v0.y * c7.y) + (v0.z * c7.z) + (v0.w * c7.w)
【编译顶点着色器】
被存储进字符数组的顶点着色器可以用下面的代码片段进行编译:
1 rc = D3DXAssembleShader( BasicVertexShader , sizeof(BasicVertexShader) -1, 2 0 , NULL , &pVS , &pErrors ); 3 if ( FAILED(rc) ) 4 { 5 OutputDebugString( "Failed to assemble the vertex shader, errors: " ); 6 OutputDebugString( (char*)pErrors->GetBufferPointer() ); 7 OutputDebugString( " " ); 8 }
D3DXAssembleShader()通过接口ID3DXBuffer在一个缓冲器对象(buffer object)中创建一个二进制版本的着色器。
D3DXAssembleShader()的声明:
1 HRESULT D3DXAssembleShader( 2 LPCVOID pSrcData, 3 UINT SrcDataLen, 4 DWORD Flags, 5 LPD3DXBUFFER* ppConstants, 6 LPD3DXBUFFER* ppCompiledShader, 7 LPD3DXBUFFER* ppCompilationErrors 8 );
第一个参数表示资源数据。
第二个参数表示数据的总大小(单位为字节byte)。
第三个参数有两个可选:
#define D3DXASM_DEBUG 1
#define D3DXASM_SKIPVALIDATION 2
第一个将调试信息作为注释插入着色器,第二个跳过合理检测。(The first one inserts debug info as comments into the shader and the second one skips validation. )
第四个参数,ID3DXBuffer接口可以得到顶点着色器的常量声明片段。为了忽略这个参数,这里将它设为NULL。
第五个参数为被编译的着色器。
第六个参数为存储在ID3DXBuffer接口的缓冲器对象的错误说明信息。
【创建顶点着色器】
代码如下:
1 rc = m_pd3dDevice->CreateVertexShader( dwDecl, (DWORD*)pVS->GetBufferPointer(), 2 &m_dwVertexShader, 0 ); 3 if ( FAILED(rc) ) 4 { 5 OutputDebugString( "Failed to create the vertex shader, errors: " ); 6 D3DXGetErrorStringA(rc,szBuffer,sizeof(szBuffer)); 7 OutputDebugString( szBuffer ); 8 OutputDebugString( " " ); 9 }
这个函数通过dwDecl得到顶点着色器声明,通过ID3DXBuffer接口得到二进制版本的着色器的指针。如果有错误发生,就会通过pVS->GetBufferPointer()得到错误信息。
D3DXGetErrorStringA()解释所有Direct3D和Direct3DX的HRESULTS并且返回一个szBuffer,里面存储着错误信息。
【设置顶点着色器】
1 m_pd3dDevice->SetVertexShader( m_dwVertexShader );
唯一需要提供的参数是顶点着色器的句柄。
这个函数执行的次数和顶点的次数一样。
【释放顶点着色器资源】
1 if ( m_dwVertexShader != 0xffffffff ) 2 { 3 m_pd3dDevice->DeleteVertexShader( m_dwVertexShader ); 4 m_dwVertexShader = 0xffffffff; 5 }
如果窗口大小或者设备改变,它一定会被调用。