1. 程式人生 > >【DirectX11】【學習筆記(2)】開始畫圖

【DirectX11】【學習筆記(2)】開始畫圖

可程式設計渲染管線

概述:渲染管線就是用來建立虛擬攝像機看到的2d影象的設定步驟。它包含以下幾個階段

1. Input Assembler (IA) Stage

2. Vertex Shader (VS) Stage

3. Hull Shader (HS) Stage

4. Tesselator Shader (TS) Stage

5. Domain Shader (DS) Stage

6. Geometry Shader (GS) Stage

7. Stream Output (SO) Stage

8. Rasterizer (RS) Stage

9. Pixel Shader (PS) Stage

10. Output Merger (OM) Stage

其中7個是從Direc3D 10繼承過來的。3個是新增的,

分別是Hull, Tesselator, and Domain shaders。這三個階段都是用來增加物體物件細節的。是比較高階的功能。打個比方:可以把一個三角形分成多個三角形,然後在新生成的頂點上增加更多細節。

注意:我們必須編譯每個著色器,確保它沒有錯誤。我們可以隨時啟用設定各個著色器。而不是設定效果檔案中的程式碼。

Direct3D Pipeline

圓角階段是可程式設計的,我們可以自己建立。方角階段是不可程式設計的,不過我們可以通過device context來更改它們的配置

Input Assembler (IA) Stage

這個階段讀取圖形資料,用來建立圖形,比如三角形,方形和線段。這個圖形會被後面的階段採用。

首先建立頂點結構和頂點佈局(佈局是用來告訴direct3d我們的頂點結構中的每一個元素分別代表什麼)

程式碼如下

//The vertex Structure
struct Vertex
{
    D3DXVECTOR3 pos;
    D3DXCOLOR   color;
};


//The input-layout description
D3D11_INPUT_ELEMENT_DESC layout[] =
{
    {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
    {"COLOR",    0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0}
};

建立好結構desc(描述資訊)之後,我們就通過device呼叫建立佈局方法

ID3D11Device::CreateInputLayout()

IA階段需要兩個快取,一個是頂點快取,一個是索引快取。我們首先建立頂點快取描述結構,然後把真正的資料填到頂點結構中。我們就可以開始建立快取了。

ID3D11Device::CreateBuffer()

最後是設定頂點快取,和佈局

ID3D11DeviceContext::IASetVertexBuffers()
ID3D11DeviceContext::IASetInputLayout()

接著我們設定頂點拓撲,好知道頂點之間是如何連線的(每個頂點之間是哪幾個頂點組成一個圖形)

最終我們就可以把圖形傳送給IA了,呼叫方法:

ID3D11DeviceContext::Draw()

Vertex Shader (VS) Stage(可程式設計)

每個從IA階段傳送過來的圖形頂點都要經過VS Stage,這個階段主要是用來對頂點進行座標變換,燈光計算。用HLSL(一種類C++語言)編寫。程式碼如下

float4 VS(float4 inPos : POSITION) : SV_POSITION
{
    return inPos;
}

本節涉及到的功能比較簡單,只是接受頂點位置做輸入,然後輸出一個頂點位置資訊。

注意:VS Stage必須被實施,即使不需要對頂點進行操作。

Hull Shader (HS) Stage(可程式設計)

這個階段是用來計算如何在圖形上增加新頂點。

Tessellator (TS) Stage

接收HS傳來的資料,然後分割圖形。

Domain Shader (DS) Stage

拿到HS階段的頂點座標和TS階段頂點變換的座標。來建立更多細節。

Geometry Shader (GS) Stage(可選擇 && 可程式設計)

它接收一個圖元作為輸入,比如接收三個頂點作為三角形輸入。

它的優勢在於它可以建立或者銷燬圖元,而VS不可以(它接受一個頂點輸入,輸出一個頂點)

Stream Output (SO) Stage

這個階段是把從VS/GS階段傳來的圖元資料放進不同的頂點快取中,然後按照列表輸出,比如三角形列表,線段列表。

不完整的圖元會被剔除,比如只有兩個頂點的三角形,只有一個頂點的線段。這個是為了防止在GS階段產生一些錯的資料。

Rasterization Stage (RS) Stage

這個階段對每個圖元的每一個頂點進行插值,然後把它們放進下一階段PS,這一階段同樣處理clipping,這個是用於裁剪在畫面之外的部分。

RS階段設定視口的函式如下:

ID3D11DeviceContext::RSSetViewports()

Pixel Shader (PS) Stage

RS會對插值的每一個畫素呼叫一次PS,PS決定了每個畫素在螢幕上的最終顏色。當一個畫素上有兩個前後不同的色彩時,將有OM來決定哪一個被最終展示在螢幕上。

float4 PS() : SV_TARGET
{
    return float4(0.0f, 0.0f, 1.0f, 1.0f);
}

返回一個4D向量為藍色。

Output Merger (OM) Stage

這個階段主要是用畫素片段和深度/模板快取來決定哪一個畫素被最終寫入rendertarget。我們設定rendertarget通過呼叫

ID3D11DeviceContext::OMSetRenderTargets()

最終所有的階段執行完成,我們通過交換鏈函式來交換前後緩衝

IDXGISwapChain::Present();

以下介紹每一個步驟的函式的具體引數。

Vertex Structure & Input Layout

每一個物體都由帶屬性的頂點組成,屬性諸如:位置,顏色。

注意:D3D已經從d3dx數學庫轉移到了xna數學庫。

輸入佈局結構如下:

typedef struct D3D11_INPUT_ELEMENT_DESC
{
   LPCSTR                         SemanticName;
   UINT                         SemanticIndex;
   DXGI_FORMAT                     Format;
   UINT                         InputSlot;
   UINT                         AlignedByteOffset;
   D3D11_INPUT_CLASSIFICATION     InputSlotClass;
   UINT                         InstanceDataStepRate;
}     D3D11_INPUT_ELEMENT_DESC;

SemanticName - 連線元素的字串。用來匹配頂點結構中的元素。

SemanticIndex - 名字後的索引號。eg:如果我們頂點結構中有兩個貼圖,我們不會建立兩個不同的名字,我們只需要兩個不同的索引。如果一個名字後沒有索引,預設為0。

Format - 頂點結構中元素格式,必須為DXGI_FORMAT 列舉值之一,本節我們對於positoin使用DXGI_FORMAT_R32G32B32_FLOAT

InputSlot - d3d最多允許15個元素槽。eg:我們可以把頂點結構中的位置和顏色放入同一個槽裡,也可以放進不同的槽。

AlignedByteOffset  - 當前元素的位元組偏移。例如第一個元素位置偏移為0,而第二個元素color偏移為12bytes,因為位置佔DXGI_FORMAT_R32G32B32_FLOAT,12個位元組。

InputSlotClass 0 現在我們只用D3D10_INPUT_PER_VERTEX_DATA。

InstanceDataStepRate  - 用於例項化,現在設定為0.

接下來我們建立一個全域性陣列來儲存多個佈局資訊。

struct Vertex    //Overloaded Vertex Structure
{
    Vertex(){}
    Vertex(float x, float y, float z)
        : pos(x,y,z){}

    XMFLOAT3 pos;
};

D3D11_INPUT_ELEMENT_DESC layout[] =
{
    { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },  
};
UINT numElements = ARRAYSIZE(layout);

Cleaning Up

注意一定要釋放介面

void CleanUp()
{
    //Release the COM Objects we created
    SwapChain->Release();
    d3d11Device->Release();
    d3d11DevCon->Release();
    renderTargetView->Release();
    triangleVertBuffer->Release();
    VS->Release();
    PS->Release();
    VS_Buffer->Release();
    PS_Buffer->Release();
    vertLayout->Release();
}

Initializing the Scene

bool InitScene()
{
    //Compile Shaders from shader file
    hr = D3DX11CompileFromFile(L"Effects.fx", 0, 0, "VS", "vs_5_0", 0, 0, 0, &VS_Buffer, 0, 0);
    hr = D3DX11CompileFromFile(L"Effects.fx", 0, 0, "PS", "ps_5_0", 0, 0, 0, &PS_Buffer, 0, 0);

    //Create the Shader Objects
    hr = d3d11Device->CreateVertexShader(VS_Buffer->GetBufferPointer(), VS_Buffer->GetBufferSize(), NULL, &VS);
    hr = d3d11Device->CreatePixelShader(PS_Buffer->GetBufferPointer(), PS_Buffer->GetBufferSize(), NULL, &PS);

    //Set Vertex and Pixel Shaders
    d3d11DevCon->VSSetShader(VS, 0, 0);
    d3d11DevCon->PSSetShader(PS, 0, 0);

    //Create the vertex buffer
    Vertex v[] =
    {
        Vertex( 0.0f, 0.5f, 0.5f ),
        Vertex( 0.5f, -0.5f, 0.5f ),
        Vertex( -0.5f, -0.5f, 0.5f ),
    };

    D3D11_BUFFER_DESC vertexBufferDesc;
    ZeroMemory( &vertexBufferDesc, sizeof(vertexBufferDesc) );

    vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
    vertexBufferDesc.ByteWidth = sizeof( Vertex ) * 3;
    vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    vertexBufferDesc.CPUAccessFlags = 0;
    vertexBufferDesc.MiscFlags = 0;

    D3D11_SUBRESOURCE_DATA vertexBufferData; 

    ZeroMemory( &vertexBufferData, sizeof(vertexBufferData) );
    vertexBufferData.pSysMem = v;
    hr = d3d11Device->CreateBuffer( &vertexBufferDesc, &vertexBufferData, &triangleVertBuffer);

    //Set the vertex buffer
    UINT stride = sizeof( Vertex );
    UINT offset = 0;
    d3d11DevCon->IASetVertexBuffers( 0, 1, &triangleVertBuffer, &stride, &offset );

    //Create the Input Layout
    hr = d3d11Device->CreateInputLayout( layout, numElements, VS_Buffer->GetBufferPointer(), 
        VS_Buffer->GetBufferSize(), &vertLayout );

    //Set the Input Layout
    d3d11DevCon->IASetInputLayout( vertLayout );

    //Set Primitive Topology
    d3d11DevCon->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST );

    //Create the Viewport
    D3D11_VIEWPORT viewport;
    ZeroMemory(&viewport, sizeof(D3D11_VIEWPORT));

    viewport.TopLeftX = 0;
    viewport.TopLeftY = 0;
    viewport.Width = Width;
    viewport.Height = Height;

    //Set the Viewport
    d3d11DevCon->RSSetViewports(1, &viewport);

    return true;
}   

這部分主要對場景的各種變數進行初始化。包括對IA階段的設定,啟用著色器,RS階段設定視口。

Compiling the Shaders

我們從效果檔案中編譯著色器。通過呼叫:

HRESULT WINAPI D3DX11CompileFromFile(
            LPCSTR pSrcFile,
            CONST D3D10_SHADER_MACRO* pDefines, 
            LPD3D10INCLUDE pInclude,
            LPCSTR pFunctionName, 
            LPCSTR pProfile, 
            UINT Flags1, 
            UINT Flags2, 
            ID3DX11ThreadPump* pPump, 
            ID3D10Blob** ppShader, 
            ID3D10Blob** ppErrorMsgs, 
            HRESULT* pHResult);

pSrcFile  - 檔名字

pDefines  - 指向陣列巨集的指標。設為NULL

pInclude - 指向include介面的指標,如果shader中有include語句,這裡不能設為空。

pFunctionName - 檔案中的函式名字

pProfile  - shader版本。我們這裡用的比較老 "vs_4_0" and "ps_4_0".

Flags1 - 編譯標記

Flags2 - 效果標記

pPump  - 與多執行緒有關

ppShader  - 指向返回的shader物件,這個更像是一個包含shader資訊的緩衝區,可以用來建立真正的shader

ppErrorMsgs  - 返回一系列編譯警告,錯誤資訊。

pHResult  - 編譯結果。(詳情見上一章番外篇)

hr = D3DX11CompileFromFile(L"Effects.fx", 0, 0, "VS", "vs_5_0", 0, 0, 0, &VS_Buffer, 0, 0);
hr = D3DX11CompileFromFile(L"Effects.fx", 0, 0, "PS", "ps_5_0", 0, 0, 0, &PS_Buffer, 0, 0);

Creating the Shaders

HRESULT CreateVertexShader( 
  [in]        const void *pShaderBytecode,
  [in]        SIZE_T BytecodeLength,
  [in]        ID3D11ClassLinkage *pClassLinkage,
  [in, out]   ID3D11VertexShader **ppVertexShader) = 0;
);

HRESULT CreatePixelShader( 
  [in]        const void *pShaderBytecode,
  [in]        SIZE_T BytecodeLength,
  [in]        ID3D11ClassLinkage *pClassLinkage,
  [in, out]   ID3D11PixelShader **ppPixelShader) = 0;
);

pShaderBytecode  - 指向shader緩衝區的指標

BytecodeLength  - 緩衝區大小

pClassLinkage - 指向類介面指標。這裡設為空

ppVertexShader  - 返回的頂點著色器

ppPixelShader  - 返回的畫素著色器

Setting the Shaders

一個應用可能會需要多個著色器,我們必須在執行時動態設定他們。D3D會保持這次的設定直到下次更改,所以我們必須根據不同的需要實時設定。

void  VSSetShader( 
  [in]   ID3D11VertexShader *pVertexShader,
  [in]   (NumClassInstances)  ID3D11ClassInstance *const *ppClassInstances,
  [in]   UINT NumClassInstances);
);

void PSSetShader( 
  [in]   ID3D11PixelShader *pPixelShader,
  [in]   (NumClassInstances)  ID3D11ClassInstance *const *ppClassInstances,
  [in]   UINT NumClassInstances);
);

pVertexShader - 指向頂點著色器

pPixelShader  - 指向畫素著色器

ppClassInstances - 設為NULL

NumClassInstances  - 上一個引數的個數,設為NULL

d3d11DevCon->VSSetShader(VS, 0, 0);
d3d11DevCon->PSSetShader(PS, 0, 0);

建立頂點快取

typedef struct D3D11_BUFFER_DESC
{
   UINT             ByteWidth;
   D3D11_USAGE         Usage;
   UINT             BindFlags;
   UINT             CPUAccessFlags;
   UINT             MiscFlags;
   UINT             StructureByteStride;
}    D3D11_BUFFER_DESC;

ByteWidth  - 緩衝區位元組大小

Usage - 描述我們的緩衝區如何被讀寫

BindFlags - 設為D3D11_BIND_VERTEX_BUFFER,因為這是一個頂點緩衝

 CPUAccessFlags  - 緩衝區如何被CPU使用,設為NULL

MiscFlags  - 額外標記

StructureByteStride  - 設為NULL

有了描述資訊之後,我們需要填滿D3D11_SUBRESOURCE_DATA結構,裡面的資料是將被放入頂點快取的資料

typedef struct D3D11_SUBRESOURCE_DATA
{
   const    void *pSysMem;
   UINT     SysMemPitch;
   UINT     SysMemSlicePitch;
}     D3D11_SUBRESOURCE_DATA;

pSysMem  - 要放入的資料

SysMemPitch - 貼圖一行的位元組數

SysMemSlicePitch - 3d貼圖的深度值間距

有了描述資訊和資料,我們就可以建立緩衝了

HRESULT CreateBuffer( 
   [in]    const D3D11_BUFFER_DESC *pDesc,
   [in]    const D3D11_SUBRESOURCE_DATA *pInitialData,
   [in]    ID3D11Buffer **ppBuffer
);

pDesc - 指向描述資訊

pInitialData - 指向資料

ppBuffer - 返回的緩衝指標

整段程式碼如下:

Vertex v[] =
{
    Vertex( 0.0f, 0.5f, 0.5f ),
    Vertex( 0.5f, -0.5f, 0.5f ),
    Vertex( -0.5f, -0.5f, 0.5f ),
};

D3D11_BUFFER_DESC vertexBufferDesc;
ZeroMemory( &vertexBufferDesc, sizeof(vertexBufferDesc) );

vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
vertexBufferDesc.ByteWidth = sizeof( Vertex ) * 3;
vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vertexBufferDesc.CPUAccessFlags = 0;
vertexBufferDesc.MiscFlags = 0;

D3D11_SUBRESOURCE_DATA vertexBufferData; 

ZeroMemory( &vertexBufferData, sizeof(vertexBufferData) );
vertexBufferData.pSysMem = v;
hr = d3d11Device->CreateBuffer( &vertexBufferDesc, &vertexBufferData, &triangleVertBuffer);

Setting the Vertex Buffer

我們需要把頂點快取繫結到IA階段

void IASetVertexBuffers(
   [in]   UINT StartSlot,
   [in]   UINT NumBuffers,
   [in]   ID3D11Buffer *const *ppVertexBuffers,
   [in]   const UINT *pStrides,
   [in]   const UINT *pOffsets
);

StartSlot - 輸入槽

NumBuffers - 我們要繫結的緩衝數,這裡我們只繫結一個

ppVertexBuffers - 指向頂點快取

pStrides  - 每個頂點大小

pOffsets - 從緩衝的第N個位元組開始。偏移量,我們設為0

UINT stride = sizeof( Vertex );
UINT offset = 0;
d3d11DevCon->IASetVertexBuffers( 0, 1, &triangleVertBuffer, &stride, &offset );

Creating the Input (Vertex) Layout

建立輸入佈局,通過函式ID3D11Device::CreateInputLayout():

HRESULT CreateInputLayout( 
   [in]   const D3D11_INPUT_ELEMENT_DESC *pInputElementDescs,
   [in]   UINT NumElements,
   [in]   const void *pShaderBytecodeWithInputSignature,
   [in]   SIZE_T BytecodeLength,
   [out]  ID3D11InputLayout **ppInputLayout
);

pInputElementDescs  - 指向描述資訊陣列

NumElements - 陣列元素個數

pShaderBytecodeWithInputSignature -指向頂點著色器

BytecodeLength - 頂點著色器大小

ppInputLayout - 返回的輸入佈局指標

hr = d3d11Device->CreateInputLayout( layout, numElements, VS_Buffer->GetBufferPointer(), 
    VS_Buffer->GetBufferSize(), &vertLayout );

Setting the Input (Vertex) Layout

把layout繫結到IA,ID3D11DeviceContext::IASetInputLayout():

void STDMETHODCALLTYPE IASetInputLayout( 
   [in]   ID3D11InputLayout *pInputLayout
);

Setting the Primitive Topology

( ID3D11DeviceContext::IASetPrimitiveTopology() )

我們想告訴IA,我們傳送了怎樣的圖元。

引數是D3D11_PRIMITIVE_TOPOLOGY列舉值

D3D10_PRIMITIVE_TOPOLOGY_POINTLIST - 每個頂點都是一個單獨的點

D3D10_PRIMITIVE_TOPOLOGY_LINESTRIP - 連線起來的點

D3D10_PRIMITIVE_TOPOLOGY_LINELIST - 每兩個點構成一條線,和上一個不同的是,上一個引數的每一個頂點都會連線到之前的線的頂點上

D3D10_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP - 每個三角形都和鄰接的三角形分享頂點

D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST - 每三個頂點構成一個三角形。和上一個引數不同的是,STRIP需要四個點構成兩個三角形,而LIST需要6個點

D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ - 只用於GS Stage

d3d11DevCon->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST );

Creating the Viewport

RS Stage決定繪圖在視窗的哪個部分。

D3D11_VIEWPORT viewport;
ZeroMemory(&viewport, sizeof(D3D11_VIEWPORT));
viewport.TopLeftX = 0;
viewport.TopLeftY = 0;
viewport.Width = Width;
viewport.Height = Height;

因為我們打算繪製在整個螢幕上,所以需要設定為視窗的大小

Setting the Viewport

( ID3D11DeviceContext::RSSetViewports() )

第一個引數是繫結的個數,第二個是視口  這樣我們可以擁有多個視口 給多個玩家

Rendering the Primitive

void DrawScene()
{
    float bgColor[4] = {(0.0f, 0.0f, 0.0f, 0.0f)};
    d3d11DevCon->ClearRenderTargetView(renderTargetView, bgColor);

    d3d11DevCon->Draw( 3, 0 );

    SwapChain->Present(0, 0);
}

Draw函式第一個引數是要繪製的頂點數,第二個是繪製頂點的偏移量

Effect File (Shaders)

( Vertex Shader )

我們本節只有一個非常簡單的頂點著色器,

float4 VS(float4 inPos : POSITION) : SV_POSITION
{
    return inPos;
}

函式接收一個命inPos的引數,POSITION指的是IA階段傳送的頂點結構中的元素位置

我們這裡只是把位置資訊讀入,然後返回一個位置。

( Pixel Shader )

本節的 PS只返回一個固定的顏色給RS。 之後我們會學習更多有趣的功能!

本節內容很多,我寫完部落格後還是決定做一個思維導圖來加深一下自己對整體的把握。

這節內容其實之前就已經看過幾次了,但是今天又重新回頭系統的閱讀並記錄下來,感覺自己對這部分內容掌握又深了一點。

好了本節內容就到這裡拉,如果你也喜歡Ditect3D,歡迎評論給出建議,我們可以一起學習,一起進步!

下午還有個報告,要去看論文了~ 溜了溜了

                                                                            ———————— 小明 2018.11.16 13.29