dx HLSL編程

目錄
前言

1.HLSL入門

       1.1什麼是着色器

       1.2什麼是HLSL

       1.3怎麼寫HLSL着色器

       1.4怎麼用HLSL着色器

2.頂點着色器

       2.1可編程數據流模型

       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.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 着色器效果

 

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

·       一個頂點由n個數據流組成。

·       一個數據流由m個元素組成。

·       一個元素是[位置、顏色、法向、紋理座標]。

程序中使用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應用程序

我們在應用程序中執行以下操作:

·         加載兩個兩個Mesh模型:源網格模型,目標網格模型;

·         創建、設置頂點聲明;

·         創建、設置頂點着色器;

·         爲着色器全局賦值;

·         把兩個Mesh模型數據分別綁定到兩個數據流中;

·         渲染Mesh模型;

下面是應用程序代碼:

/*********************聲明變量*****************/

//兩個指向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

 

3.像素着色器
       像素着色器是在對每個像素進行光柵化處理期間在圖形卡的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));

 

//設置第一、二層紋理

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

4.HLSL Effect(效果框架)
       進行到這裏,讀者可能會覺得使用着色器多少有些繁瑣,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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章