前言算法
1.HLSL入门编程
1.1什么是着色器windows
1.2什么是HLSL数组
1.3怎么写HLSL着色器缓存
1.4怎么用HLSL着色器数据结构
2.顶点着色器框架
2.1可编程数据流模型ide
2.2顶点声明模块化
2.3用顶点着色器实现渐变更画函数
3.像素着色器
3.1多纹理化
3.2多纹理效果的像素着色器
3.3应用程序
4.HLSL Effect(效果框架)
4.1Effect代码结构
4.2用Effect实现多纹理化效果
结语
参考资料
本教程针对HLSL(High Level Shading Language)初学者,从应用的角度对HLSL、顶点着色器、像素着色器和Effect效果框架进行了介绍,教程中去掉了对HLSL语法等一些细节内容的讨论,力求帮助读者尽量快地理解HLSL编程的概念,掌握HLSL编程的方法。
教程中部分阐述直接引用了其余文档,这是由于这些文档表述之精要,已经达到了不能更改的地步,这里表示感谢。
本文档版权为做者全部,非商业用途可无偿使用,转载请注明出处。
1.1什么是着色器
DirectX使用管道技术(pipeline)进行图形渲染,其构架以下:
图1.1 Direct3D Graphics Pipeline
以前咱们使用管道的步骤以下:
1. 设定顶点、图元、纹理等数据信息;
2. 设定管道状态信息;
² 渲染状态
经过SetRenderState方法设定渲染状态;
另外,使用如下方法设置变换、材质和光照:
SetTransform
SetMaterial
SetLight
LightEnable
² 取样器状态
经过SetSamplerState方法设定取样器状态;
² 纹理层状态
经过SetTextureStageState设定纹理层状态;
3. 渲染;
这部分交由D3D管道按照以前的设定自行完成,这部分操做是D3D预先固定的,因此这种管道技术被称为固定功能管道(fixed function pipeline);
固定功能管道给咱们编程提供了必定的灵活性,可是仍有不少效果难以经过这种方式实现,好比:
1. 在渲染过程当中,咱们要求y坐标值大于10的顶点要被绘制到坐标值(0,0,0)的地方,在以前的固定功能管道中,顶点被绘制的位置是在第1步即被设定好的,不可能在渲染过程当中进行改变,因此是不可行的;
2. 谋顶点在纹理贴图1上映射为点A,在纹理贴图2上映射为点B,咱们要求该顶点颜色由A、B共同决定,即:
定点颜色 = A点色彩值*0.7 + B点色彩值*0.3
这在固定管道编程中也是不可行的。
以上两个问题均可以由可编程管道(pragrammable pipeline)来解决。
可编程管线容许用户自定义一段能够在GPU上执行的程序,代替固定管道技术中的Vertex Processing和Pixel Processing阶段(参照图1.1),从而在使咱们在编程中达到更大的灵活性。其中替换Vertex Processing的部分叫作Vertex Shader(顶点着色器),替换Pixel Proccessing的部分叫作Pixel Shader(像素着色器),这就是咱们所说的着色器Shader。
1.2什么是HLSL
Direct8.x中,着色器是经过低级着色汇编语言来编写的,这样的程序更像是汇编式的指令集合,因为其效率低、可读性差、版本限制等缺点,迫切要求出现一门更高级的着色语言。到了Direct3D9,HLSL(High Level Shading Language,高级渲染语言)应运而生了。
HLSL的语法很是相似于C和C++,学习起来是很方便的。
1.3怎么写HLSL着色器
咱们能够直接把HLSL着色器代码做为一长串字符串编写进咱们的应用程序源文件中,可是,更加方便和模块化的方法是把着色器的代码从应用程序代码中分离出来。所以,咱们将着色器代码单独保存为文本格式,而后在应用程序中使用特定函数将其加载进来。
下面是一个完整的HLSL着色器程序代码,咱们把它保存在BasicHLSL.txt中。该着色器完成顶点的世界变换、观察变换和投影变幻,并将顶点颜色设定为指定的颜色。
//
// BasicHLSL.txt
//
//
// Global variable
//
matrix WVPMatrix;
vector color;
//
// Structures
//
struct VS_INPUT
{
vector position : POSITION;
};
struct VS_OUTPUT
{
vector position : POSITION;
vector color : COLOR;
};
//
// Functions
//
VS_OUTPUT SetColor(VS_INPUT input)
{
VS_OUTPUT output = (VS_OUTPUT)0;
output.position = mul(input.position, WVPMatrix);
output.color = color;
return output;
}
下面就针对上述代码讲解一下HLSL着色器程序的编写:
1.3.1全局变量
代码中声明了两个全局变量:
matrix WVPMatrix;
vector color;
变量WVPMatrix是一个矩阵类型,它包含了世界、观察、投影的合矩阵,用于对顶点进行坐标变换;
变量color是一个向量类型,它用于设定顶点颜色;
代码中并无对全局变量进行初始化,这是由于咱们对全局变量的初始化过程将在应用程序中进行,全局变量在应用程序中赋值而在着色器程序中使用,这是应用程序和着色器通讯的关键所在。具体赋值过程将在后续部分讲述。
1.3.2输入输出
² 输入输出结构
程序中定义了两个输入输出结构VS_INPUT和VS_OUTPUT
struct VS_INPUT
{
vector position : POSITION;
};
struct VS_OUTPUT
{
vector position : POSITION;
vector color : COLOR;
};
自定义的结构能够采用任意名称,结构不过是一种组织数据的方式,并非强制的,你也能够不使用,而将本程序的输入改成:
vector position : POSITION;
² 标志符
用于输入输出的变量采用用一种特殊的声明方式:
Type VariableName : Semantic
这个特殊的冒号语法表示一个语义,冒号后面的标志符用来指定变量的用途,如
vector position : POSITION;
其中,POSITION标志符代表该变量表示顶点位置,另外还有诸如COLOR、NORMAL等不少表示其余意义的标志符。
本节所说的输入输出实际上是指着色器代码和编译器、GPU之间的通讯,和应用程序是无关的,因此这些变量不须要在应用程序中进行赋值,标志符告诉编译器各个输入输出变量的用途(顶点位置、法线、颜色等),这是着色器代码和编译器、GPU之间通讯的关键。
1.3.3入口函数
程序中还定义了一个函数SetColor:
OUTPUT SetColor(INPUT input)
{
VS_OUTPUT output = (VS_OUTPUT)0;
output.position = mul(input.position, WVPMatrix);
output.color = color;
return output;
}
1. 该函数以input和output类型做为输入输出;
2. 使全局变量WVPMatrix和input.position相乘,以完成顶点的世界、观察、投影变换,并把结果赋值到output.position;
output.position = mul(input.position, WVPMatrix);
3. 将全局变量color的值赋给output.color;
output.color = color;
4. 在同一个着色器代码文件中,能够有多个用户自定义函数,所以在应用程序中须要指定一个入口函数,至关于windows程序的WinMain函数,本程序只包含SetColor一个函数并且它将被作为入口函数使用。
1.3.4总结
至此,一个HLSL着色器编写完毕,渲染过程当中,当一个顶点被送到着色器时:
1. 全局变量WVPMatrix、color将在应用程序中被赋值;
2. 入口函数SetColor被调用编译器根据标志符将顶点信息填充到VS_INPUT中的各个字段;
3. SetColor函数中,首先定义一个VS_OUTPUT信息,以后根据WVPMatrix和color变量完成顶点的坐标变换和颜色设定操做,最后函数返回VS_OUTPUT结构;
4. 编译器将会再次根据标志符把返回的VS_OUTPUT结构中的各字段映射为顶点相应的信息。
5. 顶点被送往下一个流程接受进一步处理。
上述过程当中,全局变量在应用程序中赋值而在着色器程序中使用,这是应用程序和着色器通讯的关键所在;标志符告诉编译器各个输入输出变量的用途(顶点位置、法线、颜色等),这是着色器代码和编译器、GPU之间通讯的关键。我的认为这是着色器中最为精义的地方:)
1.4怎么用HLSL着色器
应用程序中对HLSL着色器的使用分为如下步骤:
1. 加载(称为编译更为稳当)着色器代码;
2. 建立(顶点/像素)着色器;
3. 对着色器中的变量进行赋值,完成应用程序和着色器之间的通讯。
4. 把着色器设定到渲染管道中;
本例使用的着色器是一个顶点着色器,所以咱们将经过顶点着色器的使用来说解着色器的使用过程,像素着色器的使用过程与此大同小异,两者之间仅有些微差异。
1.4.1声明全局变量
IDirect3DVertexShader9* BasicShader = 0; //顶点着色器指针
ID3DXConstantTable* BasicConstTable = 0; //常量表指针
D3DXHANDLE WVPMatrixHandle = 0;
D3DXHANDLE ColorHandle = 0;
ID3DXMesh* Teapot = 0; //指向程序中D3D茶壶模型的指针
1.4.2编译着色器
经过D3DXCompileShaderFromFile函数从应用程序外部的文本文件BasicHLSL.txt中编译一个着色器:
//编译后的着色器代码将被放在一个buffer中,能够经过ID3DXBuffer接口对其进行访问,以后的着色器将从这里建立
ID3DXBuffer* shaderBuffer = 0;
//用于接受错误信息
ID3DXBuffer* errorBuffer = 0;
//编译着色器代码
D3DXCompileShaderFromFile("BasicHLSL.txt", //着色器代码文件名
0,
0,
"SetColor", //入口函数名称
"vs_1_1", //顶点着色器版本号
D3DXSHADER_DEBUG,// Debug模式编译
&shaderBuffer, //指向编译后的着色器代码的指针
&errorBuffer,
&BasicConstTable); //常量表指针
1.4.3建立着色器
应用程序经过CreateVertexShader建立一个顶点着色器,注意使用了上一步获得的shaderBuffer:
g_pd3dDevice->CreateVertexShader((DWORD*)shaderBuffer->GetBufferPointer(), &BasicShader);
1.4.3对着色器中的变量进行赋值
1.3.4节说到着色器的全局变量在应用程序中赋值而在着色器程序中使用,这是应用程序和着色器通讯的关键所在,这里就具体说明赋值过程。
着色器中的全局变量在编译后都被放在一个叫常量表的结构中,咱们可使用ID3DXConstantTable接口对其进行访问,参照1.4.1中编译着色器函数D3DXCompileShaderFromFile的最后一个参数,该参数即返回了指向常量表的指针。
对一个着色器中变量进行赋值的步骤以下:
1. 经过变量名称获得指向着色器变量的句柄;
还记得在BasicHLSL.x着色器文件中咱们声明的两个全局变量吗:
matrix WVPMatrix;
vector color;
咱们在应用程序中相应的声明两个句柄:
D3DXHANDLE WVPMatrixHandle = 0;
D3DXHANDLE ColorHandle = 0;
而后经过变量名获得分别获得对应的两个句柄:
WVPMatrixHandle = BasicConstTable->GetConstantByName(0, "WVPMatrix");
ColorHandle = BasicConstTable->GetConstantByName(0, "color");
2. 经过句柄对着色器变量进行赋值;
咱们能够先设置各变量为默认值:
BasicConstTable->SetDefaults(g_pd3dDevice);
以后,可使用ID3DXConstantTable::SetXXX函数对各个变量进行赋值:
HRESULT SetXXX(
LPDIRECT3DDEVICE9 pDevice,
D3DXHANDLE hConstant,
XXX value
);
其中XXX表明变量类型,例如Matrix类型的变量就要使用SetMatrix函数赋值,而Vector类型的则要使用SetVector来赋值。
1.4.4把着色器设定到渲染管道中
这里咱们使用SetVertexShader方法把顶点着色器设定到渲染管道中:
g_pd3dDevice->SetVertexShader(BasicShader);
1.4.5整个渲染过程以下
在渲染过程当中,咱们设定顶点的变换坐标和颜色值,渲染代码以下:
g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
D3DCOLOR_XRGB(153,153,153), 1.0f, 0 );
//开始渲染
g_pd3dDevice->BeginScene();
//获得世界矩阵、观察矩阵和投影矩阵
D3DXMATRIX matWorld, matView, matProj;
g_pd3dDevice->GetTransform(D3DTS_WORLD, &matWorld);
g_pd3dDevice->GetTransform(D3DTS_VIEW, &matView);
g_pd3dDevice->GetTransform(D3DTS_PROJECTION, &matProj);
D3DXMATRIX matWVP = matWorld * matView * matProj;
//经过句柄对着色器中的WVPMatrix变量进行赋值
BasicConstTable->SetMatrix(g_pd3dDevice, WVPMatrixHandle, &matWVP);
D3DXVECTOR4 color(1.0f, 1.0f, 0.0f, 1.0f);
//经过句柄对着色器中的color变量进行赋值,这里咱们赋值为黄色
BasicConstTable->SetVector(g_pd3dDevice, ColorHandle, &color);
//把顶点着色器设定到渲染管道中
g_pd3dDevice->SetVertexShader(BasicShader);
//绘制模型子集
Teapot->DrawSubset(0);
//渲染完毕
g_pd3dDevice->EndScene();
g_pd3dDevice->Present(NULL, NULL, NULL, NULL);
编译运行程序,运行效果如图1.2所示,这里咱们将顶点颜色设置为黄色,若是读者在渲染过程当中不断变换对着色器变量color的赋值,你将会获得一个色彩不断变幻的D3D茶壶。
D3DXVECTOR4 color(1.0f, 1.0f, 0.0f, 1.0f); //读者能够尝试改变颜色值
BasicConstTable->SetVector(g_pd3dDevice, ColorHandle, &color);
图1.2 着色器效果
顶点着色器(vertex shader)是一个在显卡的GPU上执行的程序,它替换了固定功能管道(fixed function pipeline)中的变换(transformation)和光照(lighting)阶段(这不是百分之百的正确,由于顶点着色器能够被Direct3D运行时(Direct3D runtime)以软件模拟,若是硬件不支持顶点着色器的话)。图2.1说明了管线中顶点着色器替换的部件。
图2.1
因为顶点着色器是咱们(在HLSL中)写的一个自定义程序,所以咱们在图形效果方面得到了极大的自由性。咱们再也不受限于Direct3D的固定光照算法。此外,应用程序操纵顶点位置的能力也有了多样性,例如:布料仿真,粒子系统的点大小操纵,还有顶点混合/变形。此外,咱们的顶点数据结构更自由了,而且能够在可编程管线中包含比在固定功能管线中多的多的数据。
正如做者所在群的公告所说,“拍照不在于你对相机使用的熟练程度,而是在于你对艺术的把握。”以前的介绍使读者对着色器的编写和使用都有了必定的了解,下面咱们将把重心从介绍如何使用着色器转到如何实现更高级的渲染效果上来。
2.1可编程数据流模型
DirectX 8.0引入了数据流的概念,能够这样理解数据流(图2.2):
图2.2
程序中使用IDirect3DDevice9::SetStreamSource方法把一个顶点缓存绑定到一个设备数据流。
2.2顶点声明
该小节对顶点声明的描述绝大多数都取自翁云兵的《着色器和效果》,该文对顶点声明的描述是我所见到最详尽最透彻的,这里向做者表示敬意:)
到如今为止,咱们已经使用自由顶点格式(flexible vertex format,FVF)来描述顶点结构中的各份量。可是,在可编程管线中,咱们的顶点数据能够包含比用FVF所能表达的多的多的数据。所以,咱们一般使用更具表达性的而且更强有力的顶点声明(vertex declaration)。
注意:咱们仍然能够在可编程管线中使用FVF——若是咱们的顶点格式能够这样描述。无论怎样,这只是为了方便,由于FVF会在内部被转换为一个顶点声明。
2.2.1 描述顶点声明
咱们将一个顶点声明描述为一个D3DVERTEXELEMENT9结构的数组。D3DVERTEXELEMENT9数组中的每一个元素描述了一个顶点的份量。因此,若是你的顶点结构有三个份量(例如:位置、法线、颜色),那么其相应的顶点声明将会被一个含3个元素的D3DVERTEXELEMENT9结构数组描述。
D3DVERTEXELEMENT9结构定义以下:
typedef struct _D3DVERTEXELEMENT9 {
BYTE Stream;
BYTE Offset;
BYTE Type;
BYTE Method;
BYTE Usage;
BYTE UsageIndex;
} D3DVERTEXELEMENT9;
² Stream——指定关联到顶点份量的流;
² Offset——偏移,按字节,相对于顶点结构成员的顶点份量的开始。例如,若是顶点结构是:
struct Vertex
{
D3DXVECTOR3 pos;
D3DXVECTOR3 normal;
};
……pos份量的偏移是0,由于它是第一个份量;normal份量的偏移是12,由于sizeof(pos) == 12。换句话说,normal份量以Vertex的第12个字节为开始。
² Type——指定数据类型。它能够是D3DDECLTYPE枚举类型的任意成员;完整列表请参见文档。经常使用类型以下:
D3DDECLTYPE_FLOAT1——浮点数值
D3DDECLTYPE_FLOAT2——2D浮点向量
D3DDECLTYPE_FLOAT3——3D浮点向量
D3DDECLTYPE_FLOAT4——4D浮点向量
D3DDECLTYPE_D3DCOLOR—D3DCOLOR类型,它扩展为RGBA浮点颜色向量(r, g, b, a),其每一份量都是归一化到区间[0, 1]了的。
² Method——指定网格化方法。咱们认为这个参数是高级的,所以咱们使用默认值,标识为D3DDECLMETHOD_DEFAULT。
² Usage——指定已计划的对顶点份量的使用。例如,它是否准备用于一个位置向量、法线向量、纹理坐标等,有效的用途标识符(usage identifier)是D3DDECLUSAGE枚举类型的:
typedef enum _D3DDECLUSAGE {
D3DDECLUSAGE_POSITION = 0, // Position.
D3DDECLUSAGE_BLENDWEIGHTS = 1, // Blending weights.
D3DDECLUSAGE_BLENDINDICES = 2, // Blending indices.
D3DDECLUSAGE_NORMAL = 3, // Normal vector.
D3DDECLUSAGE_PSIZE = 4, // Vertex point size.
D3DDECLUSAGE_TEXCOORD = 5, // Texture coordinates.
D3DDECLUSAGE_TANGENT = 6, // Tangent vector.
D3DDECLUSAGE_BINORMAL = 7, // Binormal vector.
D3DDECLUSAGE_TESSFACTOR = 8, // Tessellation factor.
D3DDECLUSAGE_POSITIONT = 9, // Transformed position.
D3DDECLUSAGE_COLOR = 10, // Color.
D3DDECLUSAGE_FOG = 11, // Fog blend value.
D3DDECLUSAGE_DEPTH = 12, // Depth value.
D3DDECLUSAGE_SAMPLE = 13 // Sampler data.
} D3DDECLUSAGE;
其中,D3DDECLUSAGE_PSIZE类型用于指定一个顶点的点的大小。它用于点精灵,所以咱们能够基于每一个顶点控制其大小。一个D3DDECLUSAGE_POSITION成员的顶点声明意味着这个顶点已经被变换,它通知图形卡不要把这个顶点送到顶点处理阶段(变形和光照)。
² UsageIndex——用于标识多个相同用途的顶点份量。这个用途索引是位于区间[0, 15]间的一个整数。例如,假设咱们有三个用途为D3DDECLUSAGE_NORMAL的顶点份量。咱们能够为第一个指定用途索引为0,为第二个指定用途索引为1,而且为第三个指定用途索引为2。按这种方式,咱们能够经过其用途索引标识每一个特定的法线。
例:假设咱们想要描述的顶点格式由两个数据流组成,第一个数据流包含位置、法线、纹理坐标3个份量,第二个数据流包含位置和纹理坐标2个份量,顶点声明能够指定以下:
D3DVERTEXELEMENT9 decl[] =
{
//第一个数据流,包含份量位置、法线、纹理坐标
{ 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT,D3DDECLUSAGE_
POSITION, 0 },
{ 0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_
NORMAL, 0 },
{ 0, 24, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_
TEXCOORD, 0 },
//第一个数据流,包含份量位置、纹理坐标
{ 1, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_
POSITION, 1 },
{ 1, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_
NORMAL, 1 },
D3DDECL_END()
};
D3DDECL_END宏用于初始化D3DVERTEXELEMENT9数组的最后一个顶点元素。
2.2.2建立顶点声明
CreateVertexDeclaration函数用于建立顶点声明,decl为指向上一小节定义的D3DVERTEXELEMENT9数组的指针,函数返回IDirect3DVertexDeclaration9指针g_Decl;
IDirect3DVertexDeclaration9 *g_Decl = NULL;
g_pd3dDevice->CreateVertexDeclaration(decl ,&g_Decl);
2.2.3设置顶点声明
g_pd3dDevice->SetVertexDeclaration(g_Decl);
至此,可编程数据流模型、顶点声明介绍完毕,在下面的例子中读者将会有更连贯的理解。
2.3用顶点着色器实现渐变更画
2.3.1渐变更画(Morphing)
Morphing渐变是20世纪90年代出现的一种革命性的计算机图形技术,该技术使得动画序列平滑且易于处理,即便在低档配置的计算机系统上也能正常运行。
渐变是指随时间的变化把一个形状改变为另外一个形状。对咱们而言,这些形状就是Mesh网格模型。渐变网格模型的处理就是以时间轴为基准,逐渐地改变网格模型顶点的坐标,从一个网格模型的形状渐变到另一个。请看图2.3:
图2.3
咱们在程序中使用两个网格模型——源网格模型和目标网格模型,设源网格模型中顶点1的坐标为A(Ax,Ay,Az),目标网格模型中对应顶点1的坐标为B(Bx,By,Bz),要计算渐变过程当中时间点t所对应的顶点1的坐标C(Cx,Cy,Cz),咱们使用以下方法:
T为源网格模型到目标网格模型渐变所花费的所有时间,获得时间点t占整个过程T的比例为:
S = t / T
那么顶点1在t时刻对应的坐标C为:
C = A * (1-S)+ B * S
这样,在渲染过程当中咱们根据时间不断调整S的值,就获得了从源网格模型(形状一)到目标网格模型(形状二)的平滑过渡。
接下来将在程序里使用顶点着色器实现咱们的渐变更画。
2.3.2渐变更画中的顶点声明
程序中,咱们设定一个顶点对应两个数据流,这两个数据流分别包含了源网格模型的数据和目标网格模型的数据。渲染过程当中,咱们在着色器里根据两个数据流中的顶点数据以及时间值肯定最终的顶点信息。
个数据流包含份量以下:
源网格模型数据流:顶点位置、顶点法线、纹理坐标;
目标网格模型数据流:顶点位置、顶点法线;
注意目标网格模型数据流没有包含纹理坐标,由于纹理对于两个网格模型都是同样的,因此仅使用源网格模型的纹理就能够了。
顶点声明指定以下:
D3DVERTEXELEMENT9 decl[] =
{
//源网格模型数据流,包含份量位置、法线、纹理坐标
{ 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT,D3DDECLUSAGE_
POSITION, 0 },
{ 0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_
NORMAL, 0 },
{ 0, 24, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_
TEXCOORD, 0 },
//目标网格模型数据流,包含份量位置、纹理坐标
{ 1, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_
POSITION, 1 },
{ 1, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_
NORMAL, 1 },
D3DDECL_END()
};
2.3.3渐变更画中的顶点着色器
下面给出顶点着色器源码,代码存储于vs.txt中,该顶点着色器根据源网格模型数据流和目标网格模型数据流中的信息以及时间标尺值计算出顶点最终位置信息,并对顶点作了坐标变换和光照处理。代码中给出了详细的注释,帮助读者理解。
//全局变量
//世界矩阵、观察矩阵、投影矩阵的合矩阵,用于顶点的坐标变换
matrix WVPMatrix;
//光照方向
vector LightDirection;
//存储2.3.1小节提到的公式S = t / T中的时间标尺S值
//注意到Scalar是一个vector类型,咱们在Scalar.x中存储了S值,Scalar.y中存储的则是(1-S)值
vector Scalar;
//输入
struct VS_INPUT
{
//对应源网格模型数据流中的顶点份量:位置、法线、纹理坐标
vector position : POSITION;
vector normal : NORMAL;
float2 uvCoords : TEXCOORD;
//对应目标网格模型数据流中的顶点份量:位置、法线
vector position1 : POSITION1;
vector normal1 : NORMAL1;
};
//输出
struct VS_OUTPUT
{
vector position : POSITION;
vector diffuse : COLOR;
float2 uvCoords : TEXCOORD;
};
//入口函数
VS_OUTPUT Main(VS_INPUT input)
{
VS_OUTPUT output = (VS_OUTPUT)0;
//顶点最终位置output.position取决于源网格模型数据流中位置信息input.position和目标网格模型数据流中位置信息input.position1以及时间标尺Scalar的值
//对应2.3.1小节中的公式C = A * (1-S)+ B * S
output.position = input.position*Scalar.x + input.position1*Scalar.y;
//顶点坐标变换操做
output.position = mul(output.position, WVPMatrix);
//计算顶点最终法线值
vector normal = input.normal*Scalar.x + input.normal1*Scalar.y;
//逆光方向与法线的点积,得到漫射色彩
output.diffuse = dot((-LightDirection), normal);
//存储纹理坐标
output.uvCoords = input.uvCoords;
return output;
}
以上是本例用到的顶点着色器,在接下来的应用程序中,咱们将给三个着色器全局变量赋值:
² WVPMatrix;
世界矩阵、观察矩阵、投影矩阵的合矩阵,用于顶点的坐标变换;
² LightDirection
光照方向;
² Scalar
存储2.3.1小节提到的公式S = t / T中的时间标尺S值;
注意到Scalar是一个vector类型,咱们在Scalar.x中存储了S值,Scalar.y中存储的则是(1-S)值;
2.3.4应用程序
咱们在应用程序中执行如下操做:
下面是应用程序代码:
…
/*********************声明变量*****************/
//两个指向LPD3DXMESH的指针,分别用于存储源网格模型和目标网格模型;
LPD3DXMESH g_SourceMesh;
LPD3DXMESH g_TargetMesh;
//顶点声明指针
IDirect3DVertexDeclaration9 *g_Decl = NULL;
//顶点着色器
IDirect3DVertexShader9 *g_VS = NULL;
//常量表
ID3DXConstantTable* ConstTable = NULL;
//常量句柄
D3DXHANDLE WVPMatrixHandle = 0;
D3DXHANDLE ScalarHandle = 0;
D3DXHANDLE LightDirHandle = 0;
…
/***************程序初始化*****************/
//加载源、目标网格模型
Load_Meshes();
//顶点声明
D3DVERTEXELEMENT9 MorphMeshDecl[] =
{
//1st stream is for source mesh - position, normal, texcoord
{ 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },
{ 0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 0 },
{ 0, 24, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 },
//2nd stream is for target mesh - position, normal
{ 1, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 1 },
{ 1, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 1 },
D3DDECL_END()
};
//建立顶点着色器
ID3DXBuffer* shader = NULL;
ID3DXBuffer* errorBuffer = NULL;
D3DXCompileShaderFromFile("vs.txt",
0,
0,
"Main", // entry point function name
"vs_1_1",
D3DXSHADER_DEBUG,
&shader,
&errorBuffer,
&ConstTable);
if(errorBuffer)
{
::MessageBox(0, (char*)errorBuffer->GetBufferPointer(), 0, 0);
ReleaseCOM(errorBuffer);
}
//建立顶点着色器
g_pd3dDevice->CreateVertexShader((DWORD*)shader->GetBufferPointer(), &g_VS);
//建立顶点声明
g_pd3dDevice->CreateVertexDeclaration(MorphMeshDecl ,&g_Decl);
//获得各常量句柄
WVPMatrixHandle = ConstTable->GetConstantByName(0, "WVPMatrix");
ScalarHandle = ConstTable->GetConstantByName(0, "Scalar");
LightDirHandle = ConstTable->GetConstantByName(0, "LightDirection");
//为着色器全局变量LightDirection赋值
ConstTable->SetVector(g_pd3dDevice, LightDirHandle, &D3DXVECTOR4(0.0f, -1.0f, 0.0f, 0.0f));
//设置各着色器变量为默认值
ConstTable->SetDefaults(g_pd3dDevice);
…
/*******************渲染*******************/
g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
D3DCOLOR_XRGB(153,153,153), 1.0f, 0 );
g_pd3dDevice->BeginScene();
//为着色器全局变量WVPMatrix赋值
D3DXMATRIX matWorld, matView, matProj;
g_pd3dDevice->GetTransform(D3DTS_WORLD, &matWorld);
g_pd3dDevice->GetTransform(D3DTS_VIEW, &matView);
g_pd3dDevice->GetTransform(D3DTS_PROJECTION, &matProj);
D3DXMATRIX matWVP;
matWVP = matWorld * matView * matProj;
ConstTable->SetMatrix(g_pd3dDevice, WVPMatrixHandle, &matWVP);
//为着色器全局变量Scalar赋值,注意程序中获取时间标尺值Scalar的方法
float DolphinTimeFactor = (float)(timeGetTime() % 501) / 250.0f;
float Scalar =
(DolphinTimeFactor<=1.0f)?DolphinTimeFactor:(2.0f-DolphinTimeFactor);
ConstTable->SetVector(g_pd3dDevice,ScalarHandle,&D3DXVECTOR4(1.0f-Scalar, Scalar, 0.0f, 0.0f));
//设置顶点着色器和顶点声明
g_pd3dDevice->SetVertexShader(g_VS);
g_pd3dDevice->SetVertexDeclaration(g_Decl);
//绑定目标网格模型的定点缓存到第二个数据流中
IDirect3DVertexBuffer9 *pVB = NULL;
g_TargetMesh->GetVertexBuffer(&pVB);
g_pd3dDevice->SetStreamSource(1, pVB, 0,
D3DXGetFVFVertexSize(g_TargetMesh->GetFVF()));
ReleaseCOM(pVB);
//绑定源网格模型的顶点缓存到第一个数据流中
g_SourceMesh->GetVertexBuffer(&pVB);
g_pd3dDevice->SetStreamSource(0, pVB, 0,
D3DXGetFVFVertexSize(g_TargetMesh->GetFVF()));
ReleaseCOM(pVB);
//绘制Mesh网格模型
DrawMesh(g_SourceMesh, g_pMeshTextures0, g_VS, g_Decl);
g_pd3dDevice->EndScene();
g_pd3dDevice->Present( NULL, NULL, NULL, NULL );
…
2.3.5对应用程序的一点说明
程序中咱们使用SetStreamSource方法把源网格模型和目标网格模型中的顶点缓存分别绑定到两个设备数据流,可是Direct3D对数据流中的数据的真正引用只有在调用诸如DrawPrimitive、DrawIndexedPrimitive之类的绘制方法时才发生,所以在绘制Mesh网格模型时咱们不能再使用传统的DrawSubmit方法,而是使用了DrawIndexedPrimitive,下面就如何调用DrawIndexedPrimitive绘制Mesh模型进行说明,该部份内容和HLSL着色器关系不大,在这里列出仅仅是为了你们理解程序的完整性,读者彻底能够跳过本节不看。
使用DrawIndexedPrimitive绘制Mesh模型的步骤以下:
1. 加载网格模型后使用OptimizeInPlace方法对Mesh进行优化;
2. 一旦优化了网格模型,你就能够查询ID3DXMesh对象,获得一个D3DXATTRIBUTERANGE数据类型的数组,咱们称之为属性列表,该数据类型被定义以下:
typedef struct_D3DXATTRIBUTERANGE{
DWORD AttribId; //子集编号
DWORD FaceStart; //这两个变量用于圈定本子集中的多边形
DWORD FaceCount;
DWORD VertexStart; //这两个变量用于圈定本子集中的顶点
DWORD VertexCount;
} D3DXATTRIBUTERANGE;
咱们属性列表中的每一项都表明一个被优化后Mesh的一个子集,D3DXATTRIBUTERANGE结构的各字段描述了该子集的信息。
1. 获得属性数据后,咱们就调用DrawIndexedPrimitive方法能够精美地渲染子集了。
下面是绘制Mesh模型的程序代码:
在Load_Meshes()函数的最后,咱们使用OptimizeInPlace方法对源网格模型和目标网格模型进行优化,其余加载材质和纹理的操做和以前同样,相信你们可以理解:
…
//优化源网格模型
g_SourceMesh->OptimizeInplace(D3DXMESHOPT_ATTRSORT, NULL, NULL, NULL, NULL);
…
//优化目标网格模型
g_TargetMesh->OptimizeInplace(D3DXMESHOPT_ATTRSORT, NULL, NULL, NULL, NULL);
…
在Draw_Mesh()函数中,渲染模型,注意程序是如何配合属性表调用DrawIndexedPrimitive方法进行绘制的:
…
//分别获得指向Mesh模型顶点缓存区和索引缓存区的指针
IDirect3DVertexBuffer9 *pVB = NULL;
IDirect3DIndexBuffer9 *pIB = NULL;
pMesh->GetVertexBuffer(&pVB);
pMesh->GetIndexBuffer(&pIB);
//获得Mesh模型的属性列表
DWORD NumAttributes;
D3DXATTRIBUTERANGE *pAttributes = NULL;
pMesh->GetAttributeTable(NULL, &NumAttributes);
pAttributes = new D3DXATTRIBUTERANGE[NumAttributes];
pMesh->GetAttributeTable(pAttributes, &NumAttributes);
//设置顶点着色器和顶点声明
g_pd3dDevice->SetVertexShader(pShader);
g_pd3dDevice->SetVertexDeclaration(pDecl);
//设置数据流
g_pd3dDevice->SetStreamSource(0, pVB, 0, D3DXGetFVFVertexSize(pMesh->GetFVF()));
g_pd3dDevice->SetIndices(pIB);
//遍历属性列表并配合其中的信息调用DrawIndexPrimitive绘制各个子集
for(DWORD i=0;i<NumAttributes;i++)
{
if(pAttributes[i].FaceCount)
{
//Get material number
DWORD MatNum = pAttributes[i].AttribId;
//Set texture
g_pd3dDevice->SetTexture(0, pTextures[MatNum]);
//Draw the mesh subset
g_pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0,
pAttributes[i].VertexStart,
pAttributes[i].VertexCount,
pAttributes[i].FaceStart * 3,
pAttributes[i].FaceCount);
}
}
//Free resources
ReleaseCOM(pVB);
ReleaseCOM(pIB);
delete [] pAttributes;
…
编译运行程序,效果如图2.4所示,你将看到屏幕上白色的海豚上下翻腾,同时感觉到顶点着色器为渲染效果所带来的巨大改善。
图2.4
像素着色器是在对每一个像素进行光栅化处理期间在图形卡的GPU上执行的程序。(不像顶点着色器,Direct3D不会以软件模拟像素着色器的功能。)它实际上替换了固定功能管线的多纹理化阶段(the multitexturing stage),并赋予咱们直接操纵单独的像素和访问每一个像素的纹理坐标的能力。这种对像素和纹理坐标的直接访问使咱们能够达成各类特效,例如:多纹理化(multitexturing)、每像素光照(per pixel lighting)、景深(depth of field)、云状物模拟(cloud simulation)、焰火模拟(fire simulation)、混杂阴影化技巧(sophisticated shadowing technique)。
像素着色器的编写、使用和顶点着色器大同小异,有了以前的基础,不用太过于详细的介绍相信读者也能理解,下面使用像素着色器实现多纹理化。
3.1多纹理化
简单的说,多纹理化就是使用多个纹理贴图混合后进行渲染,如图3.1,渲染过程当中,从纹理1和纹理2中分别采样,获得的颜色值依据必定规则进行组合获得纹理3,这就是多纹理化。
图3.1
3.2多纹理效果的像素着色器
下面是像素着色器的代码,该代码存储于ps.txt中,该像素着色器根据输入的两套纹理坐标对对应的纹理贴图进行采样,根据必定比例Scalar混合后输出像素颜色。
//全局变量
//存储颜色混合的比例值s,其中
//Scalar.x = s
//Scalar.y = 1-s
vector Scalar;
//纹理
texture Tex0;
texture Tex1;
//纹理采样器
sampler Samp0 =
sampler_state
{
Texture = <Tex0>;
MipFilter = LINEAR;
MinFilter = LINEAR;
MagFilter = LINEAR;
};
sampler Samp1 =
sampler_state
{
Texture = <Tex1>;
MipFilter = LINEAR;
MinFilter = LINEAR;
MagFilter = LINEAR;
};
//输入两套纹理坐标
struct PS_INPUT
{
float2 uvCoords0 : TEXCOORD0;
float2 uvCoords1 : TEXCOORD1;
};
//输出像素颜色
struct PS_OUTPUT
{
float4 Color : COLOR0;
};
//入口函数
PS_OUTPUT PS_Main(PS_INPUT input)
{
PS_OUTPUT output = (PS_OUTPUT)0;
//分别对两个纹理进行采样按照比例混合后输出颜色值
output.Color = tex2D(Samp0, input.uvCoords0)*Scalar.x + tex2D(Samp1, input.uvCoords1)*Scalar.y;
return output;
}
整个程序很容易理解,程序中涉及到着色器的纹理和采样,是咱们第一次接触的内容,下面给于说明。
3.2.1HLSL采样器和纹理
和vector、matrix同样,采样器sample和纹理texture也是HLSL语言的一种类型,HLSL着色器使用采样器对指定纹理进行采样,获得采样后的颜色值以供处理。
它们的用法以下:
//声明一个纹理变量
texture g_texture;
//定义采样器
sampler g_samp =
sampler_state
{
//关联到纹理
Texture = <g_texture>;
//设置采样器状态
MipFilter = LINEAR;
MinFilter = LINEAR;
MagFilter = LINEAR;
};
//调用HLSL内置函数tex2D取得颜色值,参数一为采样器,参数二为纹理坐标
vector Color = tex2D(g_samp, uvCoords);
更多HLSL采样器和纹理的内容请参见DirectX文档。
以上是本例用到的像素着色器,在接下来的应用程序中,咱们将给三个着色器全局变量赋值:
² Scalar
存储颜色混合的比例值s,其中Scalar.x = s, Scalar.y = 1-s;
² Samp0
第一层纹理采样器;
² Samp1
第二层纹理采样器;
像素着色器的输入结构中咱们设定了一个顶点对应两套纹理坐标,读者能够留意一下应用程序中对应的顶点格式的定义。
3.3应用程序
程序中咱们首先建立一个四边形,而后使用像素着色器进行纹理混合后对其进行渲染。下面是应用程序代码:
…
/*********************顶点格式定义*****************/
struct CUSTOMVERTEX
{
//定点位置坐标
float x,y,z;
//两套纹理坐标;
float tu0, tv0;
float tu1, tv1;
};
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ | D3DFVF_TEX2)
…
/*********************声明变量*****************/
//顶点着色器
LPDIRECT3DPIXELSHADER9 pixelShader = 0;
//常量表
ID3DXConstantTable* pixelConstTable = 0;
//常量句柄
D3DXHANDLE ScalarHandle = 0;
D3DXHANDLE Samp0Handle = 0;
D3DXHANDLE Samp1Handle = 0;
//常量描述结构
D3DXCONSTANT_DESC Samp0Desc;
D3DXCONSTANT_DESC Samp1Desc;
//四边形顶点缓存
LPDIRECT3DVERTEXBUFFER9 quadVB = NULL;
//两个纹理
LPDIRECT3DTEXTURE9 quadTexture0 = NULL;
LPDIRECT3DTEXTURE9 quadTexture1 = NULL;
…
/********************初始化应用程序*****************/
//建立四边形顶点模型
CUSTOMVERTEX quad[] =
// x y z tu0 tv0 tu1 tv1
{{-3.0f, -3.0f, 10.0f, 0.0f, 1.0f, 0.0f, 1.0f},
{ -3.0f, 3.0f, 10.0f, 0.0f, 0.0f, 0.0f, 0.0f},
{ 3.0f, -3.0f, 10.0f, 1.0f, 1.0f, 1.0f, 1.0f},
{ 3.0f, 3.0f, 10.0f, 1.0f, 0.0f, 1.0f, 0.0f}};
//建立顶点缓存
void *ptr = NULL;
g_pd3dDevice->CreateVertexBuffer(sizeof(quad),
D3DUSAGE_WRITEONLY,
0,
D3DPOOL_MANAGED,
&quadVB,
NULL);
quadVB->Lock(0, 0, (void**)&ptr, 0);
memcpy((void*)ptr, (void*)quad, sizeof(quad));
quadVB->Unlock();
//建立纹理
D3DXCreateTextureFromFile(g_pd3dDevice, "porpcart.jpg", &quadTexture0);
D3DXCreateTextureFromFile(g_pd3dDevice, "luoqi.jpg", &quadTexture1);
//检测系统是否支持像素着色器
D3DCAPS9 caps;
g_pd3dDevice->GetDeviceCaps(&caps);
if(caps.PixelShaderVersion < D3DPS_VERSION(1, 1))
{
MessageBox(0, "NotSupport Pixel Shader - FAILED", 0, 0);
exit(0);
}
//建立像素着色器
ID3DXBuffer* codeBuffer = 0;
ID3DXBuffer* errorBuffer = 0;
HRESULT hr = D3DXCompileShaderFromFile("ps.txt",
0,
0,
"PS_Main", // entry point function name
"ps_1_1",
D3DXSHADER_DEBUG,
&codeBuffer,
&errorBuffer,
&pixelConstTable);
// output any error messages
if(errorBuffer)
{
MessageBox(0, (char*)errorBuffer->GetBufferPointer(), 0, 0);
ReleaseCOM(errorBuffer);
}
if(FAILED(hr))
{
MessageBox(0, "D3DXCompileShaderFromFile() - FAILED", 0, 0);
return false;
}
hr = g_pd3dDevice->CreatePixelShader((DWORD*)codeBuffer->GetBufferPointer(), &pixelShader);
if(FAILED(hr))
{
MessageBox(0, "CreatePixelShader - FAILED", 0, 0);
return false;
}
ReleaseCOM(codeBuffer);
ReleaseCOM(errorBuffer);
//获得各常量句柄
ScalarHandle = pixelConstTable->GetConstantByName(0, "Scalar");
Samp0Handle = pixelConstTable->GetConstantByName(0, "Samp0");
Samp1Handle = pixelConstTable->GetConstantByName(0, "Samp1");
//获得对着色器变量Samp0、Samp0的描述
UINT count;
pixelConstTable->GetConstantDesc(Samp0Handle, & Samp0Desc, &count);
pixelConstTable->GetConstantDesc(Samp1Handle, & Samp1Desc, &count);
//设定各着色器变量为初始值
pixelConstTable->SetDefaults(g_pd3dDevice);
…
/********************渲染*****************/
g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(153,153,153), 1.0f, 0 );
g_pd3dDevice->BeginScene();
//为着色器全局变量Scalar赋值
D3DXVECTOR4 scalar(0.5f, 0.5f, 0.0f, 1.0f);
pixelConstTable->SetVector(g_pd3dDevice, ScalarHandle, &scalar);
//设置像素着色器
g_pd3dDevice->SetPixelShader(pixelShader);
//设置定点格式、绑定数据流
g_pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX);
g_pd3dDevice->SetStreamSource(0, quadVB, 0, sizeof(CUSTOMVERTEX));
//设置第1、二层纹理
g_pd3dDevice->SetTexture(Samp0Desc.RegisterIndex, quadTexture0);
g_pd3dDevice->SetTexture(Samp1Desc.RegisterIndex, quadTexture1);
//绘制图形
g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
g_pd3dDevice->EndScene();
g_pd3dDevice->Present(NULL, NULL, NULL, NULL);
…
程序中像素着色器的使用和以前顶点着色器的使用无二,只是设置着色器中纹理采样器变量Samp0、Samp1和设定着色器其余变量稍有不一样:
1. 首先经过变量名称获得变量句柄:
Tex0Handle = pixelConstTable->GetConstantByName(0, " Samp0");
Tex1Handle = pixelConstTable->GetConstantByName(0, " Samp1");
2. 而后经过句柄获得对变量的描述:
UINT count;
pixelConstTable->GetConstantDesc(Samp0Handle, & Samp0Desc, &count);
pixelConstTable->GetConstantDesc(Samp1Handle, & Samp1Desc, &count);
3.最后经过SetTexture配合所获得的描述信息设置纹理:
g_pd3dDevice->SetTexture(Samp0Desc.RegisterIndex, quadTexture0);
g_pd3dDevice->SetTexture(Samp1Desc.RegisterIndex, quadTexture1);
编译运行程序,运行效果如图3.2,这里咱们将颜色混合比例设置为0.5,若是读者在渲染过程当中不断变换对着色器变量Scalar的赋值,你将会获得一个混合度不断变换的多纹理效果。
D3DXVECTOR4 scalar(0.5f, 0.5f, 0.0f, 1.0f); //读者能够尝试改变混合采用的比例值
pixelConstTable->SetVector(g_pd3dDevice, ScalarHandle, &scalar);
纹理一
纹理二
混合后纹理三
图3.2
进行到这里,读者可能会以为使用着色器多少有些繁琐,Effect(效果框架)被提出以解决这些问题。做为一种方法,Effect简化了使用着色器的操做;做为一个框架,Effect把顶点着色器和像素着色器有机地组织了起来。
4.1Effect代码结构
一个Effect效果代码的结构以下:
//effect
technique T0
{
pass P0
{
...
}
}
technique T1
{
pass P0
{
...
}
pass P1
{
...
}
}
...
technique Tn
{
pass P0
{
...
}
}
首先理解三个术语effect(效果)、technique(技术)、pass(过程),所幸这三个术语从字面意思上就能获得很好的诠释。
要实现一种效果effect,可使用多种技术technique,而每种技术中可能使用多个过程pass进行渲染,这样就构成了上述effect包含多个technique,technique又包含多个pass的代码结构。
理解了代码结构,effect知识就已经掌握了大半,下面咱们直接使用一个程序实例对effect进行介绍。
4.2用Effect实现多纹理化效果
前面咱们介绍了一个使用像素着色器实现的多纹理化,这里用Effect框架从新给于实现,读者能够比较二者之间的异同,体会Effect框架给咱们带来了哪些方面的改善。
4.2.1着色器
下面是着色器代码,该代码存储于Effect.txt中,代码中包含了一个顶点着色器和一个像素着色器和一个Effect效果框架。
//---------------------------------------------
// 顶点着色器
//---------------------------------------------
matrix WVPMatrix;
struct VS_INPUT
{
vector position : POSITION;
float2 uvCoords0 : TEXCOORD0;
float2 uvCoords1 : TEXCOORD1;
};
struct VS_OUTPUT
{
vector position : POSITION;
float2 uvCoords0 : TEXCOORD0;
float2 uvCoords1 : TEXCOORD1;
};
VS_OUTPUT VS_Main(VS_INPUT input)
{
VS_OUTPUT output = (VS_OUTPUT)0;
output.position = mul(input.position, WVPMatrix);
output.uvCoords0 = input.uvCoords0;
output.uvCoords1 = input.uvCoords1;
return output;
}
//---------------------------------------------
// 像素着色器
//---------------------------------------------
vector Scalar;
texture Tex0;
texture Tex1;
sampler Samp0 =
sampler_state
{
Texture = <Tex0>;
MipFilter = LINEAR;
MinFilter = LINEAR;
MagFilter = LINEAR;
};
sampler Samp1 =
sampler_state
{
Texture = <Tex1>;
MipFilter = LINEAR;
MinFilter = LINEAR;
MagFilter = LINEAR;
};
struct PS_INPUT
{
float2 uvCoords0 : TEXCOORD0;
float2 uvCoords1 : TEXCOORD1;
};
struct PS_OUTPUT
{
float4 Color : COLOR0;
};
PS_OUTPUT PS_Main(PS_INPUT input)
{
PS_OUTPUT output = (PS_OUTPUT)0;
output.Color = tex2D(Samp0, input.uvCoords0)*Scalar.x + tex2D(Samp1, input.uvCoords1)*Scalar.y;
return output;
}
//---------------------------------------------
// 效果框架
//---------------------------------------------
technique T0
{
pass P0
{
vertexShader = compile vs_1_1 VS_Main();
pixelShader = compile ps_1_1 PS_Main();
}
}
注意程序中是如何使用效果框架将顶点着色器和像素着色器组织起来的:
pass P0
{
//着色器类型 版本号 入口函数名称
vertexShader = compile vs_1_1 VS_Main();
pixelShader = compile ps_1_1 PS_Main();
}
也能够直接将着色代码写在pass过程当中,相关用法请读者参看DirectX文档:
pass P0
{
//这里书写着色器代码
…
}
有了以前的基础,着色器代码读者应该很容易理解,下面具体介绍如何在应用程序中使用Effect。
4.2.2应用程序
…
/*********************顶点格式定义*****************/
struct CUSTOMVERTEX
{
//定点位置坐标
float x,y,z;
//两套纹理坐标;
float tu0, tv0;
float tu1, tv1;
};
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ | D3DFVF_TEX2)
…
/*********************声明变量*****************/
//Effect效果指针
ID3DXEffect *g_pEffect = 0;
//常量句柄
D3DXHANDLE WVPMatrixHandle = 0;
D3DXHANDLE ScalarHandle = 0;
D3DXHANDLE Tex0Handle = 0;
D3DXHANDLE Tex1Handle = 0;
D3DXHANDLE TechHandle = 0;
//四边形顶点缓存
LPDIRECT3DVERTEXBUFFER9 quadVB = NULL;
//两个纹理
LPDIRECT3DTEXTURE9 quadTexture0 = NULL;
LPDIRECT3DTEXTURE9 quadTexture1 = NULL;
…
/********************初始化应用程序*****************/
//定义四边顶点模型
CUSTOMVERTEX quad[] =
// x y z tu0 tv0 tu1 tv1
{{-3.0f, -3.0f, 10.0f, 0.0f, 1.0f, 0.0f, 1.0f},
{ -3.0f, 3.0f, 10.0f, 0.0f, 0.0f, 0.0f, 0.0f},
{ 3.0f, -3.0f, 10.0f, 1.0f, 1.0f, 1.0f, 1.0f},
{ 3.0f, 3.0f, 10.0f, 1.0f, 0.0f, 1.0f, 0.0f}};
//设置顶点缓存
void *ptr = NULL;
g_pd3dDevice->CreateVertexBuffer(sizeof(quad),
D3DUSAGE_WRITEONLY,
0,
D3DPOOL_MANAGED,
&quadVB,
NULL);
quadVB->Lock(0, 0, (void**)&ptr, 0);
memcpy((void*)ptr, (void*)quad, sizeof(quad));
quadVB->Unlock();
//建立纹理
D3DXCreateTextureFromFile(g_pd3dDevice, "chopper.bmp", &quadTexture0);
D3DXCreateTextureFromFile(g_pd3dDevice, "Bleach.jpg", &quadTexture1);
//检测像素着色器是否被支持
D3DCAPS9 caps;
g_pd3dDevice->GetDeviceCaps(&caps);
if(caps.PixelShaderVersion < D3DPS_VERSION(1, 1))
{
MessageBox(0, "NotSupport Pixel Shader - FAILED", 0, 0);
exit(0);
}
//建立Effect效果
ID3DXBuffer* errorBuffer = 0;
HRESULT hr = D3DXCreateEffectFromFile(g_pd3dDevice,
"Effect.txt",
0,
0,
D3DXSHADER_DEBUG,
0,
&g_pEffect,
&errorBuffer);
// output any error messages
if(errorBuffer)
{
MessageBox(0, (char*)errorBuffer->GetBufferPointer(), 0, 0);
ReleaseCOM(errorBuffer);
exit(0);
}
if(FAILED(hr))
{
MessageBox(0, "D3DXCreateEffectFromFile() - FAILED", 0, 0);
return false;
}
//获得各常量句柄
WVPMatrixHandle = g_pEffect->GetParameterByName(0, "WVPMatrix");
ScalarHandle = g_pEffect->GetParameterByName(0, "Scalar");
Tex0Handle = g_pEffect->GetParameterByName(0, "Tex0");
Tex1Handle = g_pEffect->GetParameterByName(0, "Tex1");
//获得技术technique T0的句柄
TechHandle = g_pEffect->GetTechniqueByName("T0");
//设置纹理,注意这里设置纹理的方式比以前像素着色器简便不少
g_pEffect->SetTexture(Tex0Handle, quadTexture0);
g_pEffect->SetTexture(Tex1Handle, quadTexture1);
…
/********************渲染*****************/
g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(153,153,153), 1.0f, 0 );
g_pd3dDevice->BeginScene();
//为着色器变量WVPMatrix赋值
D3DXMATRIX matWorld, matView, matProj;
g_pd3dDevice->GetTransform(D3DTS_WORLD, &matWorld);
g_pd3dDevice->GetTransform(D3DTS_VIEW, &matView);
g_pd3dDevice->GetTransform(D3DTS_PROJECTION, &matProj);
D3DXMATRIX matWVP = matWorld * matView * matProj;
g_pEffect->SetMatrix(WVPMatrixHandle, &matWVP);
//为着色器全局变量Scalar赋值
D3DXVECTOR4 scalar(0.5f, 0.5f, 0.0f, 1.0f);
g_pEffect->SetVector(ScalarHandle, &scalar);
//设置定点格式、绑定数据流
g_pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX);
g_pd3dDevice->SetStreamSource(0, quadVB, 0, sizeof(CUSTOMVERTEX));
//注意下面使用effect框架进行渲染的方法
//设置要使用的技术
g_pEffect->SetTechnique(TechHandle);
//遍历技术中包含的全部过程进行屡次渲染
UINT numPasses = 0;
g_pEffect->Begin(&numPasses, 0);
for(UINT i = 0; i<numPasses; ++i)
{
//开始过程
g_pEffect->BeginPass(i);
//绘制图形
g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
//结束过程
g_pEffect->EndPass();
}
//结束使用技术
g_pEffect->End();
g_pd3dDevice->EndScene();
g_pd3dDevice->Present(NULL, NULL, NULL, NULL);
…
以上是应用程序中使用Effect框架的代码,能够看到Effect在着色器加载、着色器变量赋值、顶点着色器和像素着色器配合使用等方面作出了简化,这里只是个简单的例子,当读者深刻学习Effect的时候,会了解到更多Effect框架为着色器编程提供的方便。
编译运行程序,运行效果如图4.1所示,这和第三章使用像素着色器实现的多纹理化效果是同样的。
纹理一
纹理二
混合后纹理三
图4.1