DirectX學習筆記(二)

一.頂點緩存與索引緩存

 3D中,各類圖形通常都是由多邊形來逼近的,通常採用三角形來逼近。例如像下圖展現的那樣:數組

這個藍色的球體是由大量的三角形來組成,固然三角形的數量越多球體就會顯得更加的逼真。須要指出的是,任何物體均可以用三角形網格來逼近表示,三角形網格是構建物體模型的基本單元。而一個三角形是由三個頂點組成,因此頂點就能夠說是組成物體模型的基本單位。這裏的頂點並不像我日常所說的點同樣,它不只僅只保存了位置信息,還有能夠保存顏色,法線,紋理座標等信息。緩存

1.頂點緩存

在D3D中,頂點的具體表現形式是頂點緩存(Vertex Buffer),頂點緩存保存了頂點數據的內存空間。咱們能夠建立不少的頂點,將其保存在頂點緩存對應的內存空間,而後就能夠調用相應的函數渲染出頂點對應的圖形。使用頂點緩存的具體步驟以下:ide

(1)定義頂點結構

前面說過,頂點中能夠保存的內容不限於位置信息,還能夠有不少其餘的內容。那咱們須要頂點保存什麼信息呢,因此咱們必須先定義頂點結構,肯定頂點中須要保存那些信息。頂點的定義很簡單,就是一個簡單的結構體定義。例以下面的頂點結構保存了頂點的位置信息和顏色信息:函數

struct Vertex
{
    float x, y, z;
    D3DCOLOR color;
};

也能夠包含其餘信息,例以下面的包含了位置信息和紋理座標:學習

struct Vertex
{
    float x, y, z;
    lloat u, v;
};

頂點結構定義好了,這個還不夠,畢竟只是你知道這個頂點結構包含了什麼信息。D3D並不知道你這個結構所包含的信息,因此你還必須指定頂點結構的格式。靈活頂點格式(Flexible Vertex Format,FVF)用來描述三角形網格的每一個頂點。靈活頂點格式可讓咱們爲所欲爲地自定義其中所包含的頂點屬性信息。例如,上面的第一個頂點結構的靈活頂點格式能夠按以下方式定義:spa

#define VERTEX_FVF D3DFVF_XYZ | D3DFVF_DIFFUSE

上面的頂點格式的宏定義中用到了D3DFVF_XYZ和D3DFVF_DIFFUSE,這兩個都是D3D中已經定義好的一些宏。用來表明各類屬性,比較幾個經常使用的以下,其餘的能夠查看幫助文檔。指針

D3DFVF_XYZ 包含未通過座標變換的頂點座標值,不能夠和D3DFVF_XYZRHW一塊兒使用
D3DFVF_XYZRHW 包含通過座標變換的頂點座標值,不能夠和D3DFVF_XYZ以D3DFVF_NORMAL一塊兒使用
D3DFVF_DIFFUSE 包含漫反射的顏色值
D3DFVF_SPECULAR 包含鏡面反射的數值
D3DFVF_NORMAL 包含法線向量的數值
D3DFVF_TEX1-TEX8 表示包含1~8個紋理座標信息,是幾重紋理後綴就用幾,最多8層紋理

下面舉一個關於頂點結構定義的完整例子:code

struct Vertex
{
    float x, y, z;
    D3DCOLOR color;
};

#define VERTEX_FVF D3DFVF_XYZ | D3DFVF_DIFFUSE


//或者也能夠用靜態變量將頂點格式定義在結構內部
struct Vertex
{
    float x, y, z;
    D3DCOLOR color;
    const static DWORD VERTEX_FVF;
};

const DWORD Vertex::VERTEX_FVF = D3DFVF_XYZ | D3DFVF_DIFFUSE;

(2)建立頂點緩存

定義好了頂點結構,就能夠建立頂點緩存了。D3D中使用IDirect3DVertexBuffer9接口對象來表明頂點緩存。建立頂點緩存的基本步驟以下:orm

a.獲取IDirect3DVertexBuffer9接口指針

D3D中經過CreateVertexBuffer函數來獲取IDirect3DVertexBuffer9接口指針,對應函數的原型聲明以下:對象

HRESULT CreateVertexBuffer(
  [in]           UINT Length,
  [in]           DWORD Usage,
  [in]           DWORD FVF,
  [in]           D3DPOOL Pool,
  [out, retval]  IDirect3DVertexBuffer9 **ppVertexBuffer,
  [in]           HANDLE *pSharedHandle
);

參數說明:

Length 表示頂點緩衝區的長度,單位爲字節
Usage 設置緩存的一些附加屬性。能夠設置爲0,表示沒有附加屬性。具體取值參看幫助文檔
FVF 頂點的靈活頂點格式
Pool 用於指定頂點緩存的存儲的位置,具體參看幫助文檔
ppVertexBuffer 用於返回IDirectDVertexBuffer9對象指針
pSharedHandle 保留參數,通常設爲NULL或者0

例如一個IDirectDVertexBuffer9對象指針的例子:

IDirectDVertexBuffer9 *pVertexBuffer = nullptr;

pDevice->CreateVertexBuffer(sizeof(Vertex) * 3, 0, VERTEX_FVF, D3DPOOL_DEFAULT, &pVertexBuffer, nullptr);

b.數據複製

IDirectDVertexBuffer9對象指針咱們已經獲得了,可是咱們還並無向緩存中寫入數據,因此說咱們的下一步就是向緩存中寫入數據。

首先第一步咱們須要鎖定須要寫入數據的緩存,經過調用IDirect3DVertexBuffer9::Lock()來執行該操做。函數的原型聲明以下:

HRESULT Lock(
  [in]   UINT OffsetToLock,
  [in]   UINT SizeToLock,
  [out]  VOID **ppbData,
  [in]   DWORD Flags
);
OffsetToLock 表示加鎖區域自存儲空間的起始位置到開始鎖定位置的偏移量,單位爲字節
SizeToLock 表示要鎖定的字節數,也就是加鎖區域的大小
ppbData 指向被鎖定的存儲區的起始地址的指針
Flags 表示鎖定的方式,咱們能夠把它設爲0,具體參看幫助文檔D3DLOCK

鎖定時的具體示意圖以下:

獲得了被鎖定緩存的首地址以後就能夠直接向該地址下的存儲區寫入數據了。寫入數據以後而後解鎖緩存,經過調用IDirect3DVertexBuffer9::Unlock()來實現。函數原型聲明以下:

HRESULT Unlock();

函數聲明很簡單,調用也很簡單,直接簡單調用一下就能夠了。

pVertexBuffer->Unlock();

下面附上一段關於向緩存寫入數據的完整代碼:

    Vertex triangle[] = {
        {400, 100, 0, 1.0, D3DCOLOR_XRGB(255, 0, 0)},
        {700, 500, 0, 1.0, D3DCOLOR_XRGB(0, 255, 0)},
        {100, 500, 0, 1.0, D3DCOLOR_XRGB(0, 0, 255)}
    };

    void *temp;

    pDevice->CreateVertexBuffer(sizeof(triangle), 0, Vertex::FVF, D3DPOOL_MANAGED, &g_pVertexBuffer, NULL);
    g_pVertexBuffer->Lock(0, sizeof(triangle), (void **)&temp, 0);

    memcpy(temp, triangle, sizeof(triangle));

    g_pVertexBuffer->Unlock();

(3)使用頂點緩存進行圖形渲染

 如今已經建立好了頂點緩存,而且已經將其中填充好了頂點信息,下一步就能夠進行渲染工做了,在正式進行渲染以前還有幾個工做要完成。

a.設置數據流

首先須要設置數據流,經過調用IDirect3DDevice9::SetStreamSource來完成。IDirect3DDevice9::SetStreamSource用於把包含的幾何體信息的頂點緩存和渲染流水線相關聯,函數原型以下:

HRESULT SetStreamSource(
  [in]  UINT StreamNumber,
  [in]  IDirect3DVertexBuffer9 *pStreamData,
  [in]  UINT OffsetInBytes,
  [in]  UINT Stride
);
StreamNumber 用於指定與該頂點緩存創建鏈接的數據流,因爲通常只用一個數據流,因此一般設爲0
pStreamData 頂點緩存的指針
OffsetInBytes 表示在數據流中以字節爲單位的偏移量,一般設爲0
Stride 表示在頂點緩存中存儲的每一個頂點結構的大小,單位爲字節

一個調用的具體實例爲:

pDevice->SetStreamSource(0, g_pVertexBuffer, 0, sizeof(Vertex));

b.設置靈活頂點格式

在渲染以前還須要設置靈活頂點格式,經過函數IDirect3DDevice9::SetFVF來完成,函數聲明以下:

HRESULT SetFVF(
  [in]  DWORD FVF
);

調用很簡單,參數就是頂點結構的靈活頂點格式,以下所示:

pDevice->SetFVF(Vertex::FVF);

c.進行渲染

全部東西都已經設置好了(若是使用了紋理,材質,還須要進行相關的設置),如今就能夠進行渲染了。經過調用函數IDirect3DDevice9::DrawPrimitive來完成,函數的具體聲明以下:

HRESULT DrawPrimitive(
  [in]  D3DPRIMITIVETYPE PrimitiveType,
  [in]  UINT StartVertex,
  [in]  UINT PrimitiveCount
);

參數說明:

PrimitiveType 繪製的圖元類型,爲D3DPRIMITIVETYPE枚舉類型
StartVertex 從頂點緩存中讀取頂點數據的起始索引位置
PrimitiveCount 須要繪製的圖元的數量

說一下D3DPRIMITIVETYPE,D3DPRIMITIVETYPE是一個枚舉類型,用來表示圖元的類型,聲明以下:

typedef enum D3DPRIMITIVETYPE {
  D3DPT_POINTLIST       = 1,
  D3DPT_LINELIST        = 2,
  D3DPT_LINESTRIP       = 3,
  D3DPT_TRIANGLELIST    = 4,
  D3DPT_TRIANGLESTRIP   = 5,
  D3DPT_TRIANGLEFAN     = 6,
  D3DPT_FORCE_DWORD     = 0x7fffffff 
} D3DPRIMITIVETYPE, *LPD3DPRIMITIVETYPE;
D3DPT_POINTLIST 用來繪製一系列的點
D3DPT_LINELIST 繪製線列,例如:1 2頂點爲第一條線 3 4頂點爲第二條線 以此類推
D3DPT_LINESTRIP 繪製線帶,例如:1 2頂點爲第一條線 2 3頂點爲第二條線 以此類推
D3DPT_TRIANGLELIST 繪製三角形序列,例如: 1 2 3爲第一個三角形 4 5 6爲第二個三角形
D3DPT_TRIANGLESTRIP 繪製三角形帶, 例如:1 2 3爲第一個三角形 2 3 4則爲第二個三角形
D3DPT_TRIANGLEFAN 繪製三角形扇,例如:1 2 3爲第一個三角形 1 3 4爲第二個三角形

調用實例以下:

pDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);

(4)實例:渲染矩形

 如今要畫一個矩形,有了前面的準備,應該來很是簡單了。一個矩形是由兩個三角形組成,因此,填充的頂點數據以下:

    Vertex rect[] = {
        {100, 100, 0, 1.0, D3DCOLOR_XRGB(255, 0, 0)},
        {700, 100, 0, 1.0, D3DCOLOR_XRGB(0, 255, 0)},
        {100, 500, 0, 1.0, D3DCOLOR_XRGB(0, 0, 255)},
        {100, 500, 0, 1.0, D3DCOLOR_XRGB(0, 0, 255)},
        {700, 100, 0, 1.0, D3DCOLOR_XRGB(0, 255, 0)},
        {700, 500, 0, 1.0, D3DCOLOR_XRGB(255, 255, 255)}
    };

    void *temp;

    IDirect3DDevice9 *pDevice = g_pDevice->GetDevice();
    pDevice->CreateVertexBuffer(sizeof(rect), 0, Vertex::FVF, D3DPOOL_MANAGED, &g_pVertexBuffer, NULL);
    g_pVertexBuffer->Lock(0, sizeof(rect), (void **)&temp, 0);

    memcpy(temp, rect, sizeof(rect));

    g_pVertexBuffer->Unlock();

最後再按照前面所說的步驟,設置數據流,設置靈活頂點格式,而後最後渲染矩形。與筆記一中所不一樣的是,這裏是畫的一個矩形,因此須要渲染兩個三角形,IDirect3DDevice9::DrawPrimitive中的第三個參數要改成2,代碼以下:

    pDevice->SetStreamSource(0, g_pVertexBuffer, 0, sizeof(Vertex));
    pDevice->SetFVF(Vertex::FVF);
    pDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 2);

與筆記一中畫三角形的代碼,須要改變的就上面所說的兩個地方。第一個改變寫入緩存的數據,改變圖元繪製時的數量,附上運行截圖:

2.索引緩存

 在前面,咱們學習了頂點緩存的知識,而且渲染了一個矩形。可是咱們會發現一個問題,在向頂點緩存中寫入數據的時候,咱們填充了6個頂點結構,可是一個矩形只有4個頂點。有兩個頂點被寫入緩存了兩次,這樣可能會形成一些內存的浪費,特別是須要渲染的模型很複雜的時候,那時重複的頂點會變得更多,因此在D3D中還引入了索引緩存。索引緩存((Index Buffers)),人如其名,它就是一個索引,用於記錄頂點緩存中每個頂點的索引位置。例如一個矩形:

若是在沒有使用索引緩存的狀況下,咱們須要建立一個這樣的頂點緩存:

vertex = {v1, v2, v3, v3, v2, v4};

須要六個頂點來渲染這個矩形,若是如今咱們使用索引緩存的話,就能夠這樣實現:

vertex = {v1, v2, v3, v4};
index = {1, 2, 3, 3, 2, 4};

索引中代表了,繪製時使用的頂點在頂點緩存中的位置。D3D中用IDirect3DIndexBuffer9接口對象來表示索引緩存,它的建立和使用和頂點緩存相似。具體步驟以下:

(1)獲取IDirect3DIndexBuffer9對象指針

D3D中經過CreateIndexBuffer函數來獲取IDirect3DIndexBuffer9接口指針,對應函數的原型聲明以下:

HRESULT CreateIndexBuffer(
  [in]           UINT Length,
  [in]           DWORD Usage,
  [in]           D3DFORMAT Format,
  [in]           D3DPOOL Pool,
  [out, retval]  IDirect3DIndexBuffer9 **ppIndexBuffer,
  [in]           HANDLE *pSharedHandle
);

這個函數中的幾個參數和CreateIndexBuffer的參數相似,除了第三個參數和第四個參數。第四個參數分別用於返回頂點緩存和索引緩存接口指針。而對於第三個參數,CreateIndexBuffer中傳入靈活頂點格式,而CreateIndexBuffer則用於指定索引的格式,爲一個D3DFORMAT類型的變量。它的取值通常是如下兩個:

D3DFMT_INDEX16

表示爲16位的索引

D3DFMT_INDEX32

表示爲32位的索引

一個具體的實例以下:

IDirect3DIndexBuffer9  *g_pIndexBuffer = nullptr;

pDevice->CreateIndexBuffer(sizeof(WORD) * 6, 0, D3DFMT_INDEX16, D3DPOOL_MANAGED, &g_pIndexBuffer, nullptr);

(2)寫入索引數據

和前面頂點緩存同樣,咱們在獲取IDirect3DIndexBuffer9接口指針以後,還須要向緩存中寫入索引數據。至於寫入索引數據的方法和頂點緩存的方法如出一轍,都是經過調用Lock和Unlock來完成,這裏很少說。附上代碼實例:

    WORD index[] = {0, 1, 2, 2, 1, 3};
    void *temp;
    pDevice->CreateIndexBuffer(sizeof(WORD) * 6, 0, D3DFMT_INDEX16, D3DPOOL_MANAGED, &g_pIndexBuffer, nullptr);
    g_pIndexBuffer->Lock(0, sizeof(index), (void **)&temp, 0);

    memcpy(temp, index, sizeof(index));

    g_pIndexBuffer->Unlock();

(3)結合頂點緩存進行渲染

在使用索引緩存和頂點緩存進行渲染以前,也須要完成一些預先的工做。

a.設置索引

頂點緩存在進行渲染以前,須要設置數據流將它和渲染流水線相關聯。索引緩存也須要相似的工做,因此說咱們首先必需要設置索引。經過IDirect3DDevice9::SetIndices函數來完成這個工做,函數的聲明以下:

HRESULT SetIndices(
  [in]  IDirect3DIndexBuffer9 *pIndexData
);

參數很簡單,就是咱們前面已經填充好索引數據的IDirect3DIndexBuffer9對象的指針。調用實例以下:

pDevice->SetIndices(g_pIndexBuffer);

b.使用IDirect3DDevice9::DrawIndexedPrimitive進行渲染

設置好了索引而後就能夠開始渲染了,經過調用IDirect3DDevice9::DrawIndexedPrimitive來完成,函數的聲明以下:

HRESULT DrawIndexedPrimitive(
  [in]  D3DPRIMITIVETYPE Type,
  [in]  INT BaseVertexIndex,
  [in]  UINT MinIndex,
  [in]  UINT NumVertices,
  [in]  UINT StartIndex,
  [in]  UINT PrimitiveCount
);

參數說明:

Type 表示將要繪製的圖元類型
BaseVertexIndex 表示將要進行繪製的索引緩存的起始頂點的索引位置
MinIndex 表示索引數組中最小的索引值,一般都設爲0
NumVertices 表示頂點的數量
StartIndex 表示從索引中的第幾個索引處開始繪製咱們的圖元
PrimitiveCount 表示要繪製的圖元數量

調用實例:

pDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, 4, 0, 2);

(4)實例:矩形渲染

如今進行一個矩形的渲染,只須要將頂點緩存中的頂點去掉兩個,而後建立好索引緩存,設置好相關的東西就能夠渲染了。修改部分的代碼以下:

    Vertex rect[] = {
        {100, 100, 0, 1.0, D3DCOLOR_XRGB(255, 0, 0)},
        {700, 100, 0, 1.0, D3DCOLOR_XRGB(0, 255, 0)},
        {100, 500, 0, 1.0, D3DCOLOR_XRGB(0, 0, 255)},
        {700, 500, 0, 1.0, D3DCOLOR_XRGB(255, 255, 255)}
    };

    void *temp;

    IDirect3DDevice9 *pDevice = g_pDevice->GetDevice();
    pDevice->CreateVertexBuffer(sizeof(rect), 0, Vertex::FVF, D3DPOOL_MANAGED, &g_pVertexBuffer, NULL);
    g_pVertexBuffer->Lock(0, sizeof(rect), (void **)&temp, 0);

    memcpy(temp, rect, sizeof(rect));

    g_pVertexBuffer->Unlock();

    WORD index[] = {0, 1, 2, 2, 1, 3};
    pDevice->CreateIndexBuffer(sizeof(WORD) * 6, 0, D3DFMT_INDEX16, D3DPOOL_MANAGED, &g_pIndexBuffer, nullptr);
    g_pIndexBuffer->Lock(0, sizeof(index), (void **)&temp, 0);

    memcpy(temp, index, sizeof(index));

    g_pIndexBuffer->Unlock();
    pDevice->SetStreamSource(0, g_pVertexBuffer, 0, sizeof(Vertex));
    pDevice->SetFVF(Vertex::FVF);
    pDevice->SetIndices(g_pIndexBuffer);
    pDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, 4, 0, 2);

 二.背面消隱與着色模式

1.背面消隱

每一個多邊形都有兩個面,一個正面,另外一個是背面。一般狀況下,多邊形的背面是不可見的。在D3D中會將多邊形的背面加以剔除,這個就是背面消隱。爲了實現背面消隱,D3D必須區分哪些多邊形是正面朝向的,哪些多邊形是背面朝向的。在D3D中,默認頂點排列順序爲順時針的三角形是正面朝向的,排列順序爲逆時針的是背面朝向的。能夠看見,在前面不管是三角形仍是矩形的渲染中,咱們三角形的排列順序都是順時針的。那是由於咱們不想D3D將其剔除,而是正確的顯示出來。在D3D你也能夠改變背面消隱的模式,經過SetRenderState函數來實現。函數的原型聲明以下:

HRESULT SetRenderState(
  [in]  D3DRENDERSTATETYPE State,
  [in]  DWORD Value
);

State是一個D3DRENDERSTATETYPE枚舉類型的變量,表示要設置的狀態類型。而第一個表示要將第一個的狀態類型設置爲何,第二個參數的取值範圍取決於第一個參數。這裏咱們要設置背面消隱的模式,第一個就必須設置爲D3DRS_CULLMODE。第二個參數這個時候能夠取如下幾個值:

D3DCULL_NONE 禁用背面消隱
D3DCULL_CW 對順時針方向的三角形進行消隱
D3DCULL_CCW 默認值,對逆時針方向的三角形進行消隱

例如,咱們要對順時針方向的三角形進行消隱操做,能夠進行以下調用:

pDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CW);

2.着色模式

 在上面渲染的三角形中,咱們將第一個頂點的顏色設爲紅色,第二頂點設爲了綠色,第三個頂點設爲了藍色,第四個頂點設爲了灰色。可是最後運行出來的結果,有點不同。只有在每一個頂點附近的那一塊區域和頂點的顏色類似度最高,離頂點越遠和該頂點設置的顏色差異就越大,這是爲何呢?這就是由於D3D中着色模式的緣由,D3D中默認的着色模式是Gouraud着色。在Gouraud着色模式之下,圖元中各個像素的顏色值由各個頂點通過線性插值獲得。例如:

上面的圖中一端的顏色爲紅色,另外一端的顏色爲藍色。他們之間中點的顏色和3/4處的顏色值就是經過如上圖線性插值的方式獲得的。因此說咱們渲染出來的矩形,在離他們頂點處越近的地方,顏色值就和該頂點設置的顏色值越接近,由於在那個位置該頂點顏色在插值時的權重越大。Gouraud着色模式是D3D中默認的着色模式,D3D中還有另外一種着色模式叫平面着色模式。在這種模式下,對於一個圖元,它取圖元頂點中的第一個頂點的顏色爲圖元中每一個像素的顏色值。例如,下面的代碼在平面着色模式下,三角形的顏色爲紅色,由於第一個頂點的顏色爲紅色。

    Vertex rect[] = {
        {100, 100, 0, 1.0, D3DCOLOR_XRGB(255, 0, 0)},
        {700, 100, 0, 1.0, D3DCOLOR_XRGB(0, 255, 0)},
        {100, 500, 0, 1.0, D3DCOLOR_XRGB(0, 0, 255)}
    };

着色模式也能夠經過SetRenderState函數來改變,第一個參數爲D3DRS_SHADEMODE,第二個參數爲能夠取:

D3DSHADE_FLAT 平面着色模式
D3DSHADE_GOURAUD Gouraud着色模式

例以下面的代碼,能夠將其設置爲平面着色模式:

pDevice->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_FLAT);

咱們能夠再上面的矩形渲染截圖中,將着色模式設置爲平面着色模式,而後看下效果。附上運行截圖:

能夠看到矩形變成了一半紅色,一半藍色。那是由於第一個三角形的第一個頂點顏色設置爲紅色,第二個三角形的第一個頂點顏色設置爲藍色。

 

(完)

相關文章
相關標籤/搜索