頂點著色器(vertex shader)是一個在圖形卡的GPU上執行的程序,它替換了固定功能管線(fixed
function pipeline)中的變換(transformation)和光照(lighting)階段。(這不是百分之百的正確,因為頂點著色器可以被Direct3D運行時(Direct3D
runtime)以軟件模擬,如果硬件不支持頂點著色器的話)。圖17.1說明了管線中頂點著色器替換的部件。

從圖17.1,我們知道,頂點以局部坐標輸入到頂點著色器,并且必須輸出齊次剪裁空間的有顏色的頂點。(經投影矩陣變換頂點后的空間稱作齊次剪裁空間(homogeneous
clip space)。因此,要把一個頂點從局部空間變換到齊次坐標空間,我們必須應用下列變換序列:世界變換(world
transformation),視圖變換(view transformation)和投影變換(projection
transformation),它們分別由世界矩陣,視圖矩陣和投影矩陣來完成。)對于點元(point
primitive),頂點著色器也被用于操作每個頂點的頂點大小。
由于頂點著色器是我們(在HLSL中)寫的一個自定義程序,因此我們在圖形效果方面獲得了極大的自由性。我們不再受限于Direct3D的固定光照算法。此外,應用程序操縱頂點位置的能力也有了多樣性,例如:cloth
simulation,粒子系統的點大小操縱,還有頂點混合/morphing。此外,我們的頂點數據結構更自由了,并且可以在可編程管線中包含比在固定功能管線中多得多的數據。
頂點著色器仍然是相對新的特性,并且許多圖形卡不支持它們,特別是隨DirectX
9發布的較新版本的頂點著色器。通過檢查D3DCAPS9結構的VertexShaderVersion成員,可以測試頂點著色器的版本。下列代碼段展示了這一點:
// If
the device's supported version is less than version 2.0
if(
caps.VertexShaderVersion < D3DVS VERSION(2, 0) )
//
Then vertex shader version 2.0 is not supported on this device.
|
我們看到D3D_VERSION的兩個參數分別接收主和次版本號?,F在,D3DXCompileShaderFromFile函數支持頂點著色器版本1.1和2.0。
我們已經使用自由頂點格式(flexible vertex
format,FVF)來描述頂點結構中的各分量。但是,在可編程管線中,頂點數據包含的數據比用FVF所能表達的多很多。因此,我們通常使用更具表達性并且更強大的頂點聲明(vertex
declaration)。注意:如果FVF能夠描述我們的頂點格式
我們仍然可以在可編程管線中使用它。不管用何種方法,只是為了方便,同樣FVF會在內部被轉換為一個頂點聲明。
17.1.1
描述頂點聲明
我們將一個頂點聲明描述為一個D3DVERTEXELEMENT9結構的數組。D3DVERTEXELEMENT9數組中的每個成員描述了一個頂點的分量。所以,如果你的頂點結構有三個分量(例如:位置、法線、顏色),那么其相應的頂點聲明將描述3個D3DVERTEXELEMENT9結構的數組。這個D3DVERTEXELEMENT9結構定義如下:
typedef struct _D3DVERTEXELEMENT9 {
BYTE Stream;
BYTE Offset;
BYTE Type;
BYTE Method;
BYTE Usage;
BYTE UsageIndex;
}
D3DVERTEXELEMENT9;
|
Stream——指定與頂點分量相關聯的流
ffset——偏移,按字節,相對于頂點結構成員的頂點分量的開始。例如,如果頂點結構是:
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。按這種方式,我們可以通過其用途索引標識每個特定的法線。
頂點描述聲明的例子:假設我們想要描述的頂點格式由位置向量和三個法線向量組成。頂點聲明可以指定如下:
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數組的最后一個頂點元素。同樣的,注意法向量的用途索引標簽。
17.1.2
創建頂點聲明
一旦你描述了一個頂點聲明為D3DVERTEXELEMENT9數組,我們就可以使用下面的方法獲得一個IDirect3DVertexDeclaration9接口指針:
HRESULT IDirect3DDevice9::CreateVertexDeclaration(
CONST D3DVERTEXELEMENT9* pVertexElements,
IDirect3DVertexDeclaration9** ppDecl
);
|
pVertexElements——D3DVERTEXELEMENT9結構數組,它描述我們想要創建的頂點聲明。
ppDecl——用于返回創建的IDirect3DVertexDeclaration9接口指針
例子調用,其中decl是一個D3DVERTEXELEMENT9數組:
IDirect3DVertexDeclaration9* _decl = 0;
hr =
_device->CreateVertexDeclaration(decl, &_decl);
|
17.1.3
使用一個頂點聲明
回憶一下:自由頂點格式是一個方便的特性并且在內部轉換成了頂點聲明。因此,當直接使用頂點聲明,我們不再需要調用:Device->SetFVF(
fvf );
相反,我們調用:Device->SetVertexDeclaration(
_decl );
其中,_decl是一個IDirect3DVertexDeclaration9接口指針。
考慮這個頂點聲明:
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()
};
|
我們需要一種方式,來定義一個頂點聲明的元素到頂點著色器的Input結構的數據成員的映射。我們在Input結構中通過指定每個數據成員的語義(:
usage-type [usage-index])定義這個映射。語義通過元素的用途類型和用途索引標識頂點聲明中的一個元素。由數據成員的語義標識的頂點元素是得以映射到數據成員的元素。例如,對應于前面的頂點聲明的輸入結構是:
struct
VS_INPUT
{
vector position : POSITION;
vector normal : NORMAL0;
vector faceNormal1 : NORMAL1;
vector faceNormal2 : NORMAL2;
};
|
注意:如果我們遺漏了用途索引,就意味著用途索引為零。例如,POSITION和POSITION0是同一樣東西。
這里decl中的元素0,由用途POSITION和用途索引0標識,它映射到position。decl中的元素1,由用途NORMAL和用途索引0標識,它映射到normal。decl中的元素2,由NORMAL和用途索引1標識,它映射到faceNormal1。decl中的元素3,由用途NORMAL和用途索引2標識,它映射到faceNormal2。
受支持的頂點著色器輸入用途(input
usage)是:
POSITION [n]——位置
BLENDWEIGHTS [n]——混合權重
BLENDINDICES [n]——混合索引
NORMAL [n]——法線向量
PSIZE[n]——頂點大小
DIFFUSE [n]——散射顏色
SPECULAR [n]——鏡面顏色
TEXCOORD [n]——紋理坐標
其中,n是一個位于區間[0,
15]的可選整數。
此外,對于輸出結構,我們必須指定每個成員是用來做什么的。例如,數據成員應該被作為位置向量、顏色、紋理坐標等對待嗎?圖形卡沒主意,除非你強制的告訴它。這也需要通過語法的語義來完成:
struct
VS_OUTPUT
{
vector position : POSITION;
vector diffuse : COLOR0;
vector specular : COLOR1;
};
|
受支持的頂點著色器輸出用途是:
POSITION—位置
PSIZE—頂點大小
FOG—霧混合值
COLOR [n]—頂點顏色。注意:可以有多個頂點顏色被輸出,并且這些顏色可以被混合在一起以產生最終的顏色。
TEXCOORD [n]—頂點紋理坐標。注意:多個頂點紋理坐標可以被輸出。
其中,n是一個位于區間[0,
15]的可選整數。