前言
由於在Direct3D 11中取消了固定管線,要想繪制圖形必須要了解可編程渲染管線的流程,一個能繪制出圖形的渲染管線最少需要有這兩個可編程着色器:頂點着色器和像素着色器。
在閱讀本章內容之前,你還需要先瀏覽下面的章節:
章節 |
---|
HLSL編譯着色器的三種方法 |
學習目標:
- 初步了解渲染管線
- 能夠動手編寫第一個頂點着色器和像素着色器
- 掌握HLSL編譯着色器的方法
- 掌握將着色器綁定到渲染管線上的代碼實現
- 了解頂點緩沖區,掌握創建和綁定到輸入裝配階段的方法
- 渲染出第一個三角形
DirectX11 With Windows SDK完整目錄
歡迎加入QQ群: 727623616 可以一起探討DX11,以及有什么問題也可以在這里匯報。
渲染管線概述(渲染流水線)
若給出某個3D場景的幾何描述,並在其中架設一台具有確定位置和朝向的虛擬攝像機,那么渲染管線(rendering pipeline)是以此攝像機為觀察視角而生成2D圖像的一系列完整步驟。下圖展示了DirectX11的渲染管線完整流程:
其中橢圓區域的着色器階段是可編程的,而其余階段只提供了有限的設置。默認情況下當前階段的着色器為null時,可以跳過當前步驟。對於初學者來說最常用的是下面這種情況:
輸入裝配階段
輸入裝配階段會從顯存中讀取頂點和索引(如果有)數據,再將它們裝配成幾何圖元。通常用到的圖元拓撲有點、線段和三角形。
頂點着色器階段
待圖元被裝配完畢后,其頂點就會被送入到頂點着色器階段。我們可以把頂點着色器看作一種輸入與輸出皆為單個頂點的函數,只是輸入的頂點和輸出的頂點不僅內容上可能有變化,頂點包含的屬性也可能有變化。
我們可以利用頂點着色器來實現許多特效,例如變換、光照和置換貼圖。在頂點着色器中,不但可以訪問輸入的頂點數據,也能夠訪問紋理和其他存於顯存中的數據(如變換矩陣與場景的光照信息)。
光柵化階段
光柵化階段需要完成的任務很多,其中包括對輸出的頂點進行視口變換,這樣x、y坐標都將以像素為單位表示。然后根據原頂點和圖元,確定當前圖元會出現在待繪制紋理的哪些像素位置上,根據圖元的頂點信息,並利用透視校正插值法來計算出像素片段(pixel fragment)的位置、顏色(如果有)等信息。
對於三角形來說,不同的頂點順序有不同的含義。默認情況下,三角形的頂點繞順時針排布時(從攝像機朝着該三角形看)則說明當前三角形的面朝向當前攝像機,而三角形的頂點繞逆時針排布則說明當前三角形的面背對當前攝像機。對於背朝攝像機的三角形會被剔除,從而不進行光柵化。這種做法叫背面消隱。
在光柵化的時候,每個像素都是以它的中點位置進行插值計算的。如下圖所示,若只有2x2的像素,黑色頂點為構成三角形的頂點,藍色頂點則為光柵化時選擇的頂點以及插值計算得到的結果。
像素着色器階段
作為可編程着色器階段,它會針對每一個像素片段進行處理(每處理一個像素就要執行一次像素着色器)。它既可以直接返回一種單一的恆定顏色,也可以實現如逐像素光照、反射及陰影等更為復雜的效果
輸出合並階段
通過像素着色器生成的像素片段會被移送至渲染管線的輸出合並階段。在這個階段中,一些像素片段需要經過模板測試和深度測試來確定是否能留下,然后必要的時候還需要與綁定的緩沖區對應像素片段進行混合操作,可以實現透明等效果。
只有當我們接觸到后續的管線階段時,才會進行更加詳細的展開敘述。
第一份HLSL代碼
現在我們在項目中創建HLSL文件夾,將所有的着色器代碼放到這里。
在里面創建一個Triangle.hlsli
的文件,內容如下:
// Triangle.hlsli
struct VertexIn
{
float3 pos : POSITION;
float4 color : COLOR;
};
struct VertexOut
{
float4 posH : SV_POSITION;
float4 color : COLOR;
};
接下來創建Triangle_VS.hlsl
文件,用於存放頂點着色器代碼:
#include "Triangle.hlsli"
// 頂點着色器
VertexOut VS(VertexIn vIn)
{
VertexOut vOut;
vOut.posH = float4(vIn.pos, 1.0f);
vOut.color = vIn.color; // 這里alpha通道的值默認為1.0
return vOut;
}
最后創建Triangle_PS.hlsl
文件,用於存放像素着色器代碼:
#include "Triangle.hlsli"
// 像素着色器
float4 PS(VertexOut pIn) : SV_Target
{
return pIn.color;
}
HLSL代碼的語法和C/C++的語法非常相似,也許后面會開坑描述一下HLSL語言,不過現在先把注意力放在這份代碼中比較特別的地方。
float3
和float4
都是內置的變量類型,可以看作是C++的struct
類型,支持多種構造方式和成員訪問。除此之外,還有float
和float2
兩種類型。對於float4
,它的四個成員分別為x
,y
,z
和w
然后具體講述一下變量名后面的語義:
語義名 | 具體含義 |
---|---|
POSITION | 描述該變量是一個坐標點 |
SV_POSITION | 說明該頂點的位置在從頂點着色器輸出后,后續的着色器都不能改變它的值,作為光柵化時最終確定的像素位置 |
COLOR | 描述該變量是一個顏色 |
SV_Target | 說明輸出的顏色值將會直接保存到渲染目標視圖的后備緩沖區對應位置 |
輸入布局(Input Layout)
ID3D11Device::CreateInputLayout方法--創建輸入布局
在HLSL中,用於輸入的結構體為:
struct VertexIn
{
float3 pos : POSITION;
float4 color : COLOR;
};
該項目與之對應的C++結構體為:
struct VertexPosColor
{
DirectX::XMFLOAT3 pos;
DirectX::XMFLOAT4 color;
static const D3D11_INPUT_ELEMENT_DESC inputLayout[2];
};
注意:DX SDK中的
xnamath.h
在Windows SDK中已經被拋棄,取而代之的則是要包含頭文件directxmath.h
,XNA相關的數學庫基本上都移植到這里了,除此之外,他們都已經被放入到名稱空間DirectX中。
由於頂點緩沖區的本質是二進制流,為了能夠建立C++結構體與HLSL結構體的對應關系,需要使用ID3D11InputLayout
輸入布局來描述每一個成員的用途、語義、大小等信息。
還要留意的是,其中inputLayout
並不是結構體VertexPosColor
的內部成員,而是靜態成員,不占用該結構體的空間。我們使用D3D11_INPUT_ELEMENT_DESC
結構體來描述待傳入結構體中每個成員的具體信息,定義如下:
typedef struct D3D11_INPUT_ELEMENT_DESC
{
LPCSTR SemanticName; // 語義名
UINT SemanticIndex; // 語義索引
DXGI_FORMAT Format; // 數據格式
UINT InputSlot; // 輸入槽索引(0-15)
UINT AlignedByteOffset; // 初始位置(字節偏移量)
D3D11_INPUT_CLASSIFICATION InputSlotClass; // 輸入類型
UINT InstanceDataStepRate; // 忽略
} D3D11_INPUT_ELEMENT_DESC;
inputLayout
的初始化信息如下,描述了C++對應到HLSL的兩個成員的信息:
const D3D11_INPUT_ELEMENT_DESC VertexPosColor::inputLayout[2] = {
{ "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}
};
其中,語義名要與HLSL結構體中的語義名相同,若有多個相同的語義名,則語義索引就是另外一種區分。相同的語義按從上到下所以分別為0,1,2...
然后,DXGI_FORMAT
在這里通常描述數據的存儲方式、大小。用DXGI_FORMAT_R32G32B32_FLOAT
僅僅是解釋為3個float類型的值;而用DXGI_FORMAT_R32G32B32A32_FLOAT
在這里是說明顏色按RGBA存儲,並且為4個float類型的值
輸入槽這里只使用1個,即索引為0的輸入槽。
初始位置則指的是該成員的位置與起始成員所在的字節偏移量。
輸入類型有兩種:D3D11_INPUT_PER_VERTEX_DATA
為按每個頂點數據輸入,D3D11_INPUT_PER_INSTANCE_DATA
則是按每個實例數據輸入。
通過語義、數據類型和起始偏移量,我們就可以建立起C++頂點緩沖區數據和HLSL頂點之間的聯系。
接下來就可以使用ID3D11Device::CreateInputLayout
方法創建一個輸入布局:
HRESULT ID3D11Device::CreateInputLayout(
const D3D11_INPUT_ELEMENT_DESC *pInputElementDescs, // [In]輸入布局描述
UINT NumElements, // [In]上述數組元素個數
const void *pShaderBytecodeWithInputSignature, // [In]頂點着色器字節碼
SIZE_T BytecodeLength, // [In]頂點着色器字節碼長度
ID3D11InputLayout **ppInputLayout); // [Out]獲取的輸入布局
ID3D11DeviceContext::IASetInputLayout方法--輸入裝配階段設置輸入布局
下面的方法可以讓我們使用剛創建好的輸入布局:
void ID3D11DeviceContext::IASetInputLayout(
ID3D11InputLayout *pInputLayout); // [In]輸入布局
頂點/像素着色器的創建
ID3D11Device::CraeteXXXXShader方法--創建着色器
從D3D設備可以創建出6種着色器:
方法 | 着色器類型 | 描述 |
---|---|---|
ID3D11Device::CreateVertexShader | ID3D11VertexShader | 頂點着色器 |
ID3D11Device::CreateHullShader | ID3D11HullShader | 外殼着色器 |
ID3D11Device::CreateDomainShader | ID3D11DomainShader | 域着色器 |
ID3D11Device::CreateComputeShader | ID3D11ComputeShader | 計算着色器 |
ID3D11Device::CreateGeometryShader | ID3D11GeometryShader | 幾何着色器 |
ID3D11Device::CreatePixelShader | ID3D11PixelShader | 像素着色器 |
這些方法的輸入形參都是一致的,只是輸出的是不同的着色器,以創建頂點着色器的方法為例:
HRESULT ID3D11Device::CreateVertexShader(
const void *pShaderBytecode, // [In]着色器字節碼
SIZE_T BytecodeLength, // [In]字節碼長度
ID3D11ClassLinkage *pClassLinkage, // [In_Opt]忽略
ID3D11VertexShader **ppVertexShader); // [Out]獲取頂點着色器
GameApp::InitEffect方法--着色器或特效相關的初始化
下面展示了GameApp::InitEffect
方法的實現,其中輸入布局的創建也需要放到這里。
CreateShaderFromFile
函數請到文章開頭的 HLSL編譯着色器的三種方法 查看。
bool GameApp::InitEffect()
{
ComPtr<ID3DBlob> blob;
// 創建頂點着色器
HR(CreateShaderFromFile(L"HLSL\\Triangle_VS.cso", L"HLSL\\Triangle_VS.hlsl", "VS", "vs_5_0", blob.ReleaseAndGetAddressOf()));
HR(m_pd3dDevice->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, m_pVertexShader.GetAddressOf()));
// 創建並綁定頂點布局
HR(m_pd3dDevice->CreateInputLayout(VertexPosColor::inputLayout, ARRAYSIZE(VertexPosColor::inputLayout),
blob->GetBufferPointer(), blob->GetBufferSize(), m_pVertexLayout.GetAddressOf()));
// 創建像素着色器
HR(CreateShaderFromFile(L"HLSL\\Triangle_PS.cso", L"HLSL\\Triangle_PS.hlsl", "PS", "ps_5_0", blob.ReleaseAndGetAddressOf()));
HR(m_pd3dDevice->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, m_pPixelShader.GetAddressOf()));
return true;
}
GameApp::InitResource方法
頂點緩沖區(Vertex Buffer)
頂點緩沖區的作用是,將頂點數組以緩沖區ID3D11Buffer
的形式提供給輸入裝配階段。
ID3D11Device::CreateBuffer方法--創建一個緩沖區
要創建頂點緩沖區,首先需要填充好緩沖區描述D3D11_BUFFER_DESC
:
typedef struct D3D11_BUFFER_DESC
{
UINT ByteWidth; // 數據字節數
D3D11_USAGE Usage; // CPU和GPU的讀寫權限相關
UINT BindFlags; // 緩沖區類型的標志
UINT CPUAccessFlags; // CPU讀寫權限的指定
UINT MiscFlags; // 忽略
UINT StructureByteStride; // 忽略
} D3D11_BUFFER_DESC;
在這里需要詳細講述一下D3D11_USAGE
枚舉類型對應的讀寫關系:
CPU讀 | CPU寫 | GPU讀 | GPU寫 | |
---|---|---|---|---|
D3D11_USAGE_DEFAULT | √ | √ | ||
D3D11_USAGE_IMMUTABLE | √ | |||
D3D11_USAGE_DYNAMIC | √ | √ | ||
D3D11_USAGE_STAGING | √ | √ | √ | √ |
對於D3D11_USAGE_DEFAULT
類型的緩沖區,應當使用 ID3D11DeviceContext::UpdateSubresource
方法來更新緩沖區資源,它的原理是將內存中的某段數據傳遞到顯存中,然后再將該顯存中的數據復制到在顯存中的緩沖區。這種更新方式我們是無法直接訪問緩沖區的內容的。在繪制完成/開始前調用可以比較快地更新顯存中的數據。
而對於D3D11_USAGE_DYNAMIC
類型的緩沖區,則應當使用ID3D11DeviceContext::Map
和ID3D11DeviceContext::Unmap
方法,將顯存中的數據映射到內存中,然后修改該片內存的數據,最后將修改好的數據映射回顯存中。這種更新方式我們是可以直接獲取來自顯存的數據的,但代價就是更新的效率會比上面的方式更低一些。
由於目前的教程所涉及到的頂點緩沖區在創建后通常是不會修改的,因此將其設為D3D11_USAGE_IMMUTABLE
。
這里將創建包含三個頂點數據的緩沖區:
// 設置三角形頂點
// 注意三個頂點的給出順序應當按順時針排布
VertexPosColor vertices[] =
{
{ XMFLOAT3(0.0f, 0.5f, 0.5f), XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f) },
{ XMFLOAT3(0.5f, -0.5f, 0.5f), XMFLOAT4(0.0f, 0.0f, 1.0f, 1.0f) },
{ XMFLOAT3(-0.5f, -0.5f, 0.5f), XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f) }
};
// 設置頂點緩沖區描述
D3D11_BUFFER_DESC vbd;
ZeroMemory(&vbd, sizeof(vbd));
vbd.Usage = D3D11_USAGE_IMMUTABLE;
vbd.ByteWidth = sizeof vertices;
vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vbd.CPUAccessFlags = 0;
有了緩沖區描述,還需要使用D3D11_SUBRESOURCE_DATA
結構體來指定要用來初始化的數據:
typedef struct D3D11_SUBRESOURCE_DATA
{
const void *pSysMem; // 用於初始化的數據
UINT SysMemPitch; // 忽略
UINT SysMemSlicePitch; // 忽略
} D3D11_SUBRESOURCE_DATA;
子資源數據結構體的填充也很簡單:
// 新建頂點緩沖區
D3D11_SUBRESOURCE_DATA InitData;
ZeroMemory(&InitData, sizeof(InitData));
InitData.pSysMem = vertices;
最后通過ID3D11Device::CreateBuffer
來創建一個頂點緩沖區:
HRESULT ID3D11Device::CreateBuffer(
const D3D11_BUFFER_DESC *pDesc, // [In]頂點緩沖區描述
const D3D11_SUBRESOURCE_DATA *pInitialData, // [In]子資源數據
ID3D11Buffer **ppBuffer); // [Out] 獲取緩沖區
演示如下:
ComPtr<ID3D11Buffer> m_pVertexBuffer = nullptr;
HR(m_pd3dDevice->CreateBuffer(&vbd, &InitData, m_pVertexBuffer.GetAddressOf()));
ID3D11DeviceContext::IASetVertexBuffers方法--渲染管線輸入裝配階段設置頂點緩沖區
創建好頂點緩沖區后,就可以在渲染管線輸入裝配階段設置該頂點緩沖區了:
void ID3D11DeviceContext::IASetVertexBuffers(
UINT StartSlot, // [In]輸入槽索引
UINT NumBuffers, // [In]緩沖區數目
ID3D11Buffer *const *ppVertexBuffers, // [In]指向緩沖區數組的指針
const UINT *pStrides, // [In]一個數組,規定了對所有緩沖區每次讀取的字節數分別是多少
const UINT *pOffsets); // [In]一個數組,規定了對所有緩沖區的初始字節偏移量
// 輸入裝配階段的頂點緩沖區設置
UINT stride = sizeof(VertexPosColor); // 跨越字節數
UINT offset = 0; // 起始偏移量
m_pd3dImmediateContext->IASetVertexBuffers(0, 1, m_pVertexBuffer.GetAddressOf(), &stride, &offset);
只要繪制的內容不變,該部分的設置則只需要進行一次即可,因為渲染管線中各個部分的設置方法一經調用就會立即生效。然而如果需要繪制不同的內容或者效果,則需要在繪制前給渲染管線綁定好各種所需的資源。
圖元類型
D3D_PRIMITIVE_TOPOLOGY
枚舉定義了許多種圖元類型,通常會根據頂點緩沖區的頂點索引(如果有索引緩沖區則是根據這些索引的值)和裝配方式進行解釋,其中:
圖元類型 | 含義 |
---|---|
D3D11_PRIMITIVE_TOPOLOGY_POINTLIST | 按一系列點進行裝配 |
D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP | 按一系列線段進行裝配,每相鄰兩個頂點(或索引數組相鄰的兩個索引對應的頂點)構成一條線段 |
D3D11_PRIMITIVE_TOPOLOGY_LINELIST | 按一系列線段進行裝配,每兩個頂點(或索引數組每兩個索引對應的頂點)構成一條線段 |
D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP | 按一系列三角形進行裝配,每相鄰三個頂點(或索引數組相鄰的三個索引對應的頂點)構成一個三角形 |
D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST | 按一系列三角形進行裝配,每三個頂點(或索引數組每三個索引對應的頂點)構成一個三角形 |
D3D11_PRIMITIVE_TOPOLOGY_LINELIST_ADJ | 每4個頂點為一組,只繪制第2個頂點與第3個頂點的連線(或索引數組每4個索引為一組,只繪制索引模4余數為2和3的連線) |
D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP_ADJ | 繪制除了最開始和結尾的所有線段(或者索引數組不繪制索引0和1的連線,以及n-2和n-1的連線) |
D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ | 每6個頂點為一組,只繪制第1、3、5個頂點構成的三角形(或索引數組每6個索引為一組,只繪制索引模6余數為0, 2, 4的三角形) |
D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP_ADJ | 拋棄所有索引模2為奇數的頂點或索引,剩余的進行Triangle Strip的繪制 |
Point List
Line list(左) or line strip(右)
Triangle list(左) or triangle strip(右)
Line list with adjacency(左) or line strip with adjacency(右)
Triangle list with adjacency(v6, v8, v10也構成一個三角形)
Triangle strip with adjacency缺圖
通常絕大多數情況下,我們都會使用D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST
ID3D11DeviceContext::IASetPrimitiveTopology方法--渲染管線輸入裝配階段設置圖元類型
void ID3D11DeviceContext::IASetPrimitiveTopology(
D3D11_PRIMITIVE_TOPOLOGY Topology); // [In]圖元類型
該操作只需要設置一次即可。
ID3D11DeviceContext::*SSetShader方法--給渲染管線某一着色階段設置對應的着色器
這里的*
可以是V, H, D, C, G, P,對應六個可編程渲染管線階段,除了第一個參數提供的着色器類型不同外,其余參數一致。
以ID3D11DeviceContext::VSSetShader
為例:
void ID3D11DeviceContext::VSSetShader(
ID3D11VertexShader *pVertexShader, // [In]頂點着色器
ID3D11ClassInstance *const *ppClassInstances, // [In_Opt]忽略
UINT NumClassInstances); // [In]忽略
注意: 類似給渲染管線綁定資源的一切方法,在綁定之后就會一直生效,而不是說僅能夠使用一次。所以,以后如果你需要用別的特效去繪制當前物體,就要重新綁定好渲染管線所需要的一切資源。
最后給出GameApp::InitResource
方法的實現
bool GameApp::InitResource()
{
// 設置三角形頂點
VertexPosColor vertices[] =
{
{ XMFLOAT3(0.0f, 0.5f, 0.5f), XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f) },
{ XMFLOAT3(0.5f, -0.5f, 0.5f), XMFLOAT4(0.0f, 0.0f, 1.0f, 1.0f) },
{ XMFLOAT3(-0.5f, -0.5f, 0.5f), XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f) }
};
// 設置頂點緩沖區描述
D3D11_BUFFER_DESC vbd;
ZeroMemory(&vbd, sizeof(vbd));
vbd.Usage = D3D11_USAGE_IMMUTABLE;
vbd.ByteWidth = sizeof vertices;
vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vbd.CPUAccessFlags = 0;
// 新建頂點緩沖區
D3D11_SUBRESOURCE_DATA InitData;
ZeroMemory(&InitData, sizeof(InitData));
InitData.pSysMem = vertices;
HR(m_pd3dDevice->CreateBuffer(&vbd, &InitData, m_pVertexBuffer.GetAddressOf()));
// ******************
// 給渲染管線各個階段綁定好所需資源
// 輸入裝配階段的頂點緩沖區設置
UINT stride = sizeof(VertexPosColor); // 跨越字節數
UINT offset = 0; // 起始偏移量
m_pd3dImmediateContext->IASetVertexBuffers(0, 1, m_pVertexBuffer.GetAddressOf(), &stride, &offset);
// 設置圖元類型,設定輸入布局
m_pd3dImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
m_pd3dImmediateContext->IASetInputLayout(m_pVertexLayout.Get());
// 將着色器綁定到渲染管線
m_pd3dImmediateContext->VSSetShader(m_pVertexShader.Get(), nullptr, 0);
m_pd3dImmediateContext->PSSetShader(m_pPixelShader.Get(), nullptr, 0);
return true;
}
GameApp::DrawScene方法
ID3D11DeviceContext::Draw方法--根據已經綁定的頂點緩沖區進行繪制
該方法不需要提供索引緩沖區:
void ID3D11DeviceContext::Draw(
UINT VertexCount, // [In]需要繪制的頂點數目
UINT StartVertexLocation); // [In]起始頂點索引
調用該方法后,從輸入裝配階段開始,該繪制的進行將會經歷一次完整的渲染管線階段,直到輸出合並階段為止。
通過指定VertexCount
和StartVertexLocation
的值我們可以按順序繪制從索引StartVertexLocation
到StartVertexLocation + VertexCount - 1
的頂點
GameApp::DrawScene
方法的實現如下:
void GameApp::DrawScene()
{
assert(m_pd3dImmediateContext);
assert(m_pSwapChain);
static float black[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; // RGBA = (0,0,0,255)
m_pd3dImmediateContext->ClearRenderTargetView(m_pRenderTargetView.Get(), black);
m_pd3dImmediateContext->ClearDepthStencilView(m_pDepthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);
// 繪制三角形
m_pd3dImmediateContext->Draw(3, 0);
HR(m_pSwapChain->Present(0, 0));
}
最終的效果如下:
練習題
粗體字為自定義題目
- 嘗試交換三角形第一個和第三個頂點的數據,屏幕將顯示什么?為什么?
- 嘗試用6個頂點繪制矩形表面
- 嘗試用4個頂點繪制矩形表面(提示:
D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP
)
DirectX11 With Windows SDK完整目錄
歡迎加入QQ群: 727623616 可以一起探討DX11,以及有什么問題也可以在這里匯報。