顶点着色器其实就是我们自己编写的一段在GPU中运行的程序,有了顶点着色器,我们就可以从固定的功能流水线中代替一些模块,
从而获得更多的顶点操作的灵活性。
对于顶点位置进行操作的的能力具有广泛的应该场合:织物模拟、粒子系统的点尺寸处理等。
可编程流水线中的顶点结构比固定的流水线具有更加丰富的数据。
首先我们应该检测一下,硬件是否支持顶点着色器:
//如果设备支持的版本低于2.0版
if(caps. VertexshaderVersion
之前使用的都是灵活顶点格式来表示顶点,在可编程的流水线中,我们使用比灵活顶点格式表示的内容更丰富,功能更强大的顶点声明
我们将顶点声明描述为一个D3DVERTEXELEMENT9类型的一个数组,该数组中的没有一个元素都描述了一个顶点结构的分量
所以如果你的顶点结构具有3个分量(位置,颜色,法线),则相对应的顶点声明就可以用一个维数为3的D3DVERTEXELEMENT9类型数组来描述
D3DVERTEXELEMENT9结构定义:
typedef struct D3DVERTEXELEMENT9{
WORD Stream;
WORD Offset;
BYTE Type;
BYTE Method;
BYTE Usage;
BYTE UsageIndex;
)D3DVERTEXELEMENT9,*LPD3DVERTEXELEMENT9;
Stream:指定与顶点分量相关的数据流
Offset:自地顶点数据起始点到特定数据类型的偏移量
比如:
顶点结构:
struct Vretex{
D3DVECTOR3 pos;
D3DVECTOR3 normal;
}
当前位置存放到的是法线normal,由于sizeof(pos) = 12,所以normal相对于顶点结构起始点偏移量就是12,该参数的值就是12
Type 指定数据类型。该参数可取枚举类型D3DDECLTYPE的任何一个成员,一些常用的类型如下:
D3DDECLTYPE_FLOATI浮点类型的标量。
D3DDECLTYPE_FLOAT2浮点类型的2D向量。
D3DDECLTYPE_FLOAT3浮点类型的3D向量。
D3DDECLTYPE_FLOAT4浮点类型的4D向量。
D3DDECLTYPE_D3DCOLOR 一个被扩展为RGBA浮点类型颜色向量(r,g,b,a)的D3DCOLOR类型,
其中颜色向量的每个分量都被规范化至区间[0,]内。
Method 指定了顶点分量的用途,比如:某一分量是作为位置向量,法向量还是纹理向量
主要取自下面的枚举类型:
typedef enum D3DDECLUSAGE
{
D3DDECLUSAGE POSITION=0,
D3DDECLUSAGE BLENDWEIGHT=1,
D3DDECLUSAGE BLENDINDICES=2,
D3DDECLUSAGE NORMAL=3,
D3DDECLUSAGE PSIZE=4,
D3DDECLUSAGE TEXCOORD=5,
D3DDECLUSAGE TANGENT=6,
D3DDECLUSAGE BINORMAL=7,
D3DDECLUSAGE TESSFACTOR =8,
D3DDECLUSAGE POSITIONT=9,
D3DDECLUSAGE _COLOR=10,
D3DDECLUSAGE FOG=11,
D3DDECLUSAGE DEPTH=12,
D3DDECLUSAGE SAMPLE=13,
}D3DDECLUSAGE,*LPD3DDECLUSAGE;
UsageIndex:该成员是一个位于[0,15]之间的整数,当我们创造的点具有相同的分量的时候,我们可以使用该属性来按序标记
比如:我们现在点向量里面有4个分量:位置和3个法线
那么我们可以按序将这3个法向量的索引分别指定为0,1,2。按照这种方式,我们那就可以通过用法索引表示每一个特定的法向量
像下面这样:
D3DVERTEXELEMENT9 decl()=
{
{0,0,D3DDECLTYPE_FLOAT3,D3DDECLMETHOD DEFAULT, D3DDECLUSAGE_POSITION,0},
{0,12,D3DDECLTYPE_FLOAT3,D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL,0},
(0,24,D3DDECLTYPE_FLOAT3,D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL,1),
(0,36,D3DDECLTYPE_FLOAT3,D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL,2},
D3DDECL_END()
};
其中,D3DDECL_END宏用于初始化D3DVERTEXELEMENT9数组中的最后一个顶点元素。
一旦我们将顶点声明为一个D3DVERTEXELEMENT9类型的数组,我们就可以使用如下方法获得指向接口IDirect3DVertexDeclaration9的指针:
HRESULT CreateVertexDeclaration(
CONST D3DVERTEXELEMENT9*pVertexElements,
IDirect3DVertexDeclaration9**ppDec1
);
·pVertexElements 指向一个D3DVERTEXELEMENT9类型的结构数组,该数组描述了我们想要创建的顶点声明。
·ppDecl 用于返回·个指向所创建的IDirect3DVertexDeclaration9接口的指针。
下面是一个该函数的调用实例,其中decl是一个D3DVERTEXELEMENT9类型的结构数组。
IDirect3DVertexDeclaration9* _decl = 0;
hr=device->CreateVertexDeclaration(decl,&_decl);
使用灵活顶点格式的时候,我们使用SetFVF来开启灵活顶点的使用,
现在我们使用 Device->SetVertexDeclaration(_decl) 即可
参数是我们的顶点声明的创建的时候返回的指针
参考这个顶点声明
D3DVERTEXELEMENT9 decl()=
{
{0,0,D3DDECLTYPE_FLOAT3,D3DDECLMETHOD DEFAULT, D3DDECLUSAGE_POSITION,0},
{0,12,D3DDECLTYPE_FLOAT3,D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL,0},
(0,24,D3DDECLTYPE_FLOAT3,D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL,1),
(0,36,D3DDECLTYPE_FLOAT3,D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL,2},
D3DDECL_END()
};
我们需要一种方法来定义 从顶点声明中的元素 到 顶点输入结构成员的映射
我们在输入结构中通常为每一个成员都有一种定义: vector position:POSITION
这种定义通过 用法类型和用法索引 来标识顶点声明中的每一个元素。
比如:
struct VS_INPUT
{
vector position :POSITION;
vector normal :NORMAL0;
vector faceNormal1 :NORMAL1;
vector faceNormal2 :NORMAL2;
);
Tip:如果我们略去索引,表示该索引为0,就像第一个POSITIOON和POSITION0表示的含义一模一样
此时我们的顶点声明和输入结构体就可以对应起来了。
顶点着色器支持的输入用法包括:
POSITION[n]位置。
BLENDWEIGHTS[n]融合权值(blend weights)。
BLENDINDICES[n]融合索引(blend indices)。
NORMAL[n]法向量。
PSIZE[n]顶点的点尺寸。
DIFFUSE[n]漫反射颜色。
SPECULAR[n]高光颜色(specular color)。
TEXCOORD[n]纹理坐标。
TANGENTIn]切向量(tangent vector)。
BINORMAL[n]副法向量(binormal vector)。
TESSFACTOR[n]网格化因子(tessellation factor)。
其中n为可自选的整数,但是必须在0到15中间
顶点着色器支持的输出用法包括:
POSITION[n]位置。
PSIZE[n]顶点的点尺寸。
FOG[n]雾融合值(fog blend value)。
COLOR[n]顶点颜色。注意,可输出多个顶点颜色,这些颜色混合在一起生成最终颜色。
TEXCOORD[n]顶点纹理坐标。注意,可能输出多个顶点纹理坐标。
使用顶点着色器的步骤
1)编写一个顶点着色器程序,并进行编译。
2)创建一个IDirect3DVertexShader9接口的对象,以表示我们所编译的顶点着色器。
3)使用SetVertexShader方法启用顶点着色器
顶点着色器使用完毕之后,必须要对其进行销毁
我们现在就需要用HLSL语言编写一个顶点着色器,一旦着色器编写完成,我们就可用方法D3DXCompileShaderFromFile来编译
我们编写的着色器
一旦我们有了已经编译过的着色器代码之后,我们就以使用CreateVertexShder方法来获取顶点着色器的指针
HRESULT IDirect3DDevice9::CreateVertexShader(
const DWORD* pFunction,
IDirect3DVertexshader9** ppShader
);
pFunction:指向一个经过编译的代码的指针
ppShader:返回一个指向IDirect3DVertexShader9的借口的指针
例如,假定变量shader是一个指向ID3DXBuffer 接口的指针。
则为了获取指向IDirect3DVertexShader9接口的指针,我们可这样做:
IDirect3DVertexshader9* Toonshader=0;
hr=Device->CreateVertexshader(
(DWORD*)Shader->GetBufferPointer(),
&ToonShader);
Tip:D3DXCompileShaderFromFile函数能够返回经过编译的着色器代码,也就是上述代码中的:shader
顶点着色器的设置
当我们获取了指向接口IDirect3DVertexShader9的指针之后,就可以使用下述方法来启用顶点着色器:
HRESULT IDirect3DDevice9::SetVertexshader(
IDirect3DVertexShader9*pShader
);
一段完整的顶点着色器代码:
//
// Global variables we use to hold the view matrix, projection matrix,
// ambient material, diffuse material, and the light vector that describes
// the direction to the light source. These variables are initialized from
// the application.
// 我们用来保存视图矩阵,投影矩阵,环境材质,漫反射材质以及描述光源方向的光矢量的全局变量。
// 这些变量是从应用程序初始化的。
//视图矩阵
matrix ViewMatrix;
//投影矩阵
matrix ViewProjMatrix;
//环境材质
vector AmbientMtrl;
//漫反射材质
vector DiffuseMtrl;
//光源方向
vector LightDirection;
//
// 用于保持环境光强度(光源发出的环境光)和漫射光强度(光源发出的漫射光)的全局变量。
// 这些变量在着色器中初始化。
//
//漫反射光强度
vector DiffuseLightIntensity = {0.0f, 0.0f, 1.0f, 1.0f};
// 环境光强度
vector AmbientLightIntensity = {0.0f, 0.0f, 0.2f, 1.0f};
//
// Input and Output structures.
//输出与输入结构
struct VS_INPUT
{
vector position : POSITION;
vector normal : NORMAL;
};
struct VS_OUTPUT
{
vector position : POSITION;
vector diffuse : COLOR;
};
//
// Main
//
VS_OUTPUT Main(VS_INPUT input)
{
// 初始化即将输出的结构
VS_OUTPUT output = (VS_OUTPUT)0;
//
// 将位置转换到同一个坐标系中,然后赋值给输出结构的位置成员
//
output.position = mul(input.position, ViewProjMatrix);
//
// 把灯光和法线转化到观察坐标系. 把w的值设置为0,因为我们现在转化的是向量不是点
//
LightDirection.w = 0.0f;
input.normal.w = 0.0f;
LightDirection = mul(LightDirection, ViewMatrix);
input.normal = mul(input.normal, ViewMatrix);
//
// 计算光和法线之间的余弦值,也就是点积
//
float s = dot(LightDirection, input.normal);
//
//当表面和光线的角度大于90度的时候,余弦值小于0,此时表面不会发光 ,
//设想一下,当表面和光线为180度的时候,表面就没有光
//
if( s < 0.0f )
s = 0.0f;
//
//通过与环境材料矢量和环境光强度矢量进行分量相乘来计算反射的环境光。
//通过与漫反射材料矢量和漫反射光强度矢量进行分量相乘来计算反射的漫反射光。
//此外,我们通过阴影标量s来缩放每个分量,阴影标量s基于顶点从光源接收多少光来着色
//
//环境和漫反射组件的总和为我们提供了最终的顶点颜色
//
output.diffuse = (AmbientMtrl * AmbientLightIntensity) +
(s * (DiffuseLightIntensity * DiffuseMtrl));
return output;
}