vertex shader(4)

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 }

如果窗口大小或者设备改变,它一定会被调用。



原文地址:https://www.cnblogs.com/ll-10/p/5495871.html