Swizzling and Masking

如果你使用输入、常量、临时寄存器作为源寄存器,你可以彼此独立地swizzle .x,.y,.z,.w值。如果你使用输出、临时寄存器作为目标寄存器,你可以把.x,.y,.z,.w值用作
write masks。 下面是一些细节:
Swizzling (only source registers : vn,cn,rn)
例如:

 mov R1, -R2.wxyz

目标寄存器为R1,R2为源寄存器(在指令语法中,源寄存器在目标寄存器的右边)。执行该mov指令后,会将R2中-w,-x,-y,-z的值分别赋给R1的x,y,z,w。
Masking (only destination registers : on,rn)
例如:

 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()函数被用来创建并且使一个顶点着色器生效。

 HRESULT CreateVertexShader(
CONST DWORD* pDeclaration,
CONST DWORD* pFunction,
DWORD* pHandle,
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()来执行。

 if(m_pd3dDevice->m_dwVertexShader != 0xffffffff)
{
m_pd3dDevice->DeleteVertexShader(m_dwVertexShader);
m_pd3dDevice->m_dwVertexShader = 0xffffffff;
}

编写一个顶点着色器

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以上就不需要检查设备支持了。

【声明顶点着色器】

声明顶点着色器意味着将顶点数据映射到特定的输入寄存器。因此,顶点着色器声明必须反映顶点缓冲区布局。

着色器声明:

 DWORD dwDecl[] =
{
D3DVSD_STREAM(),
D3DVSD_REG(,D3DVSDT_FLOAT3), // D3DVSDE_POSITION,0
D3DVSD_END()
};

顶点缓冲区布局:

 struct VERTEX
{
FLOAT x,y,z;
};

顶点格式声明:

 #define D3DFVF_VERTEX (D3DFVF_XYZ)

位置的值将被存储进顶点缓冲区,并且会通过函数SetStreamSource()绑定到设备数据流端口。(可能是Higher-Order Surfaces(HOS)阶段,也可能是顶点着色器,取决于usage of HOS,可参考page 6中图片1中的Direct3D 管线)

【设置常量寄存器】

常量寄存器必须通过调用SetVertexShaderConstant()来进行填充。
下面的例子将材质颜色设置进常量寄存器c8.

 FLOAT fMaterial[]={,,,};
m_pd3dDevice->SetVertexShaderConstant(,fMaterial,);

SetVertexShaderConstant的声明如下:

 HRESULT SetVertexShaderConstant (DWORD Register,
CONST void* pConstantData,
DWORD ConstantCount);

第一个参数指要被使用的常量寄存器的数字编号。
第二个参数将128-bits的数据存储进该常量寄存器。
第三个参数是所要存储的128-bits数据的数量。比如,要将一个4x4矩阵存储进常量寄存器,
可以将第三个参数置为4:m_pd3dDevice->SetVertexShaderConstant(4, matTemp, 4); 如果这样的话,c4,c5,c6,c7都被用来存储这个矩阵。

【顶点着色器】

下面的例子中,顶点着色器被存储进一个字符数组中。

 // reg c4-7 = WorldViewProj matrix
// reg c8 = constant color
// reg v0 = input register const char BasicVertexShader[] =
"vs.1.1 " \
"dp4 oPos.x, v0, c4 " \
"dp4 oPos.y, v0, c5 " \
"dp4 oPos.z, v0, c6 " \
"dp4 oPos.w, v0, c7 " \
"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材质颜色。

 D3DXMatrixRotationY( &m_matWorld, m_fTime * 1.5f );
D3DXMATRIX matTemp; // set the clip matrix
D3DXMatrixTranspose( &matTemp , &(m_matWorld * m_matView * m_matProj) );
m_pd3dDevice->SetVertexShaderConstant(, matTemp, );

将物体绕Y轴旋转,需要调用D3DMatrixRotationY()。
它的实现就像这样:fRads代表你要旋转的角度。

 VOID D3DMatrixRotationY(D3DMATRIX * mat, FLOAT fRads)
{
D3DXMatrixIdentity(mat);
mat._11 = cosf(fRads);
mat._13 = -sinf(fRads);
mat._31 = sinf(fRads);
mat._33 = cosf(fRads);
}=
cosf(fRads) -sinf(fRads) sinf(fRads) cosf(fRads)

旋转之后,我们需要用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)

【编译顶点着色器】

被存储进字符数组的顶点着色器可以用下面的代码片段进行编译:

 rc = D3DXAssembleShader( BasicVertexShader , sizeof(BasicVertexShader) -,
, NULL , &pVS , &pErrors );
if ( FAILED(rc) )
{
OutputDebugString( "Failed to assemble the vertex shader, errors:\n" );
OutputDebugString( (char*)pErrors->GetBufferPointer() );
OutputDebugString( "\n" );
}

D3DXAssembleShader()通过接口ID3DXBuffer在一个缓冲器对象(buffer object)中创建一个二进制版本的着色器。

D3DXAssembleShader()的声明:

 HRESULT D3DXAssembleShader(
LPCVOID pSrcData,
UINT SrcDataLen,
DWORD Flags,
LPD3DXBUFFER* ppConstants,
LPD3DXBUFFER* ppCompiledShader,
LPD3DXBUFFER* ppCompilationErrors
);

第一个参数表示资源数据。
第二个参数表示数据的总大小(单位为字节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接口的缓冲器对象的错误说明信息。

【创建顶点着色器】

代码如下:

 rc = m_pd3dDevice->CreateVertexShader( dwDecl, (DWORD*)pVS->GetBufferPointer(),
&m_dwVertexShader, );
if ( FAILED(rc) )
{
OutputDebugString( "Failed to create the vertex shader, errors:\n" );
D3DXGetErrorStringA(rc,szBuffer,sizeof(szBuffer));
OutputDebugString( szBuffer );
OutputDebugString( "\n" );
}

这个函数通过dwDecl得到顶点着色器声明,通过ID3DXBuffer接口得到二进制版本的着色器的指针。如果有错误发生,就会通过pVS->GetBufferPointer()得到错误信息。
D3DXGetErrorStringA()解释所有Direct3D和Direct3DX的HRESULTS并且返回一个szBuffer,里面存储着错误信息。

【设置顶点着色器】

 m_pd3dDevice->SetVertexShader( m_dwVertexShader );

唯一需要提供的参数是顶点着色器的句柄。
这个函数执行的次数和顶点的次数一样。

【释放顶点着色器资源】

 if ( m_dwVertexShader != 0xffffffff )
{
m_pd3dDevice->DeleteVertexShader( m_dwVertexShader );
m_dwVertexShader = 0xffffffff;
}

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

最新文章

  1. java中的拷贝(二)深克隆
  2. Jquery中的filter()详细说明和transition的用法
  3. [笔记]--在Windows下配置Git
  4. 使用C语言在Win控制台中实现指定位置输出
  5. 【转载】Hadoop历史服务器详解
  6. WTL 中CComboBoxEx显示不了的问题
  7. ORACLE表空间bigfile和smallfile
  8. poj1004
  9. oracle数据库单个数据文件的大小限制
  10. python教程6-3:排序
  11. Android setContentView方法解析(一)
  12. shell的date命令:使用方法,以及小时、分钟的计算
  13. react create-react-app 怎么添加sass
  14. Landsat数据下载方法小结
  15. window无法启动mongodb服务:系统找不到指定的文件错误的解决方法
  16. mysql大表设计以及优化
  17. 那种多空计算方法更正确呢?——从此图看应该是TEST005
  18. Unity3d Transform.forward和Vector3.forward的区别!
  19. java学习之路之javaSE基础1
  20. 7-将本地的javaweb项目部署到Linux服务器的一般操作

热门文章

  1. Linux多电脑ssh免密码登录
  2. WampServer的配置
  3. 实用符号Alt+小键盘快输
  4. 【mongodb】Mongodb初识
  5. C#类、方法的访问修饰符
  6. 如何安装nginx第三方模块
  7. 基于EFCore的数据Cache实现
  8. Java--mysql实现分页查询--分页显示
  9. mysql命令之二:查看mysql版本的四种方法
  10. Python中的Bunch模式