DirectX/10.0/Direct3D/緩衝區著色器 HLSL
本教程將介紹如何在 DirectX 11 中編寫頂點和畫素著色器。它也將介紹在 DirectX 11 中使用頂點和索引緩衝區。這些是渲染 3D 圖形所需要理解和利用的最基本概念。
首先要理解的概念是頂點緩衝區。為了說明這個概念,讓我們以 3D 球體模型為例
3D 球體模型實際上是由數百個三角形組成的
球體模型中的每個三角形都有三個點,我們稱每個點為頂點。因此,為了渲染球體模型,我們需要將構成球體的所有頂點放到一個稱為頂點緩衝區的特殊資料陣列中。一旦球體模型的所有點都在頂點緩衝區中,我們就可以將頂點緩衝區傳送到 GPU,以便它可以渲染模型。
索引緩衝區與頂點緩衝區相關。它們的目的是記錄頂點緩衝區中每個頂點的位置。然後 GPU 使用索引緩衝區快速找到頂點緩衝區中的特定頂點。索引緩衝區類似於使用書籍索引的概念,它有助於以更快的速度找到你正在尋找的主題。DirectX SDK 文件指出,使用索引緩衝區還可以增加將頂點資料快取到影片記憶體中更快的區域的可能性。因此,出於效能方面的考慮,強烈建議使用它們。
頂點著色器是主要用於將頂點緩衝區中的頂點轉換為 3D 空間的小程式。還可以進行其他計算,例如計算每個頂點的法線。頂點著色器程式將在 GPU 需要處理每個頂點時被呼叫。例如,一個 5,000 多邊形模型將在每一幀執行你的頂點著色器程式 15,000 次,僅僅是為了繪製那個模型。因此,如果你將圖形程式鎖定在 60 幀每秒,它每秒將呼叫你的頂點著色器 900,000 次,只繪製 5,000 個三角形。正如你所見,編寫高效的頂點著色器非常重要。
畫素著色器是專門用於對我們繪製的多邊形進行著色的小程式。它們由 GPU 為將要繪製到螢幕上的每個可見畫素執行。對你的多邊形面進行著色、紋理、照明和大多數其他效果都是由畫素著色器程式處理的。由於畫素著色器會被 GPU 呼叫很多次,因此必須編寫高效的畫素著色器。
HLSL 是我們在 DirectX 11 中用來編寫這些小型頂點和畫素著色器程式的語言。語法與 C 語言幾乎完全相同,只是有一些預定義的型別。HLSL 程式檔案由全域性變數、型別定義、頂點著色器、畫素著色器和幾何著色器組成。由於這是第一個 HLSL 教程,我們將使用 DirectX 11 做一個非常簡單的 HLSL 程式,以開始學習。
該框架已針對本教程進行了更新。在 GraphicsClass 下,我們添加了三個新類,名為 CameraClass、ModelClass 和 ColorShaderClass。CameraClass 將負責我們之前談到的檢視矩陣。它將處理相機在世界中的位置,並在著色器需要繪製並弄清楚我們從哪裡觀察場景時將它傳遞給著色器。ModelClass 將處理我們的 3D 模型的幾何形狀,在本教程中,3D 模型為了簡單起見,將只是一個簡單的三角形。最後,ColorShaderClass 將負責渲染模型到螢幕上,並呼叫我們的 HLSL 著色器。
我們將從檢視 HLSL 著色器程式開始本教程程式碼。
這些將是我們的第一個著色器程式。著色器是進行模型實際渲染的小程式。這些著色器是用 HLSL 編寫的,並存儲在名為 color.vs 和 color.ps 的原始檔中。我暫時將這些檔案與 .cpp 和 .h 檔案放在了引擎中。該著色器的目的是隻繪製彩色三角形,因為在這個第一個 HLSL 教程中,我儘可能地保持簡單。以下是頂點著色器的程式碼
//////////////////////////////////////////////////////////////////////////////// // Filename: color.vs ////////////////////////////////////////////////////////////////////////////////
在著色器程式中,你從全域性變數開始。這些全域性變數可以從你的 C++ 程式碼中外部修改。你可以使用多種型別的變數,如 int 或 float,然後從外部設定它們供著色器程式使用。通常,即使只有一個全域性變數,你也會將大多數全域性變數放到名為“cbuffer”的緩衝區物件型別中。邏輯上組織這些緩衝區對於高效執行著色器以及圖形卡儲存緩衝區的方式也很重要。在本例中,我將三個矩陣放在同一個緩衝區中,因為我將在每一幀同時更新它們。
/////////////
// GLOBALS //
/////////////
cbuffer MatrixBuffer
{
matrix worldMatrix;
matrix viewMatrix;
matrix projectionMatrix;
};
與 C 類似,我們可以建立自己的型別定義。我們將使用 HLSL 提供的不同型別,例如 float4,這使得程式設計著色器更加容易和可讀。在本例中,我們正在建立具有 x、y、z、w 位置向量和紅色、綠色、藍色、alpha 顏色的型別。POSITION、COLOR 和 SV_POSITION 是語義,它們向 GPU 傳達了變數的用途。我在這裡必須建立兩個不同的結構,因為即使結構相同,頂點著色器和畫素著色器的語義也不同。POSITION 對頂點著色器有效,SV_POSITION 對畫素著色器有效,而 COLOR 對兩者都有效。如果你想要多個相同型別的變數,則必須在末尾新增一個數字,例如 COLOR0、COLOR1 等。
//////////////
// TYPEDEFS //
//////////////
struct VertexInputType
{
float4 position : POSITION;
float4 color : COLOR;
};
struct PixelInputType
{
float4 position : SV_POSITION;
float4 color : COLOR;
};
當 GPU 處理從傳送到它的頂點緩衝區中獲取的資料時,將呼叫頂點著色器。這個名為 ColorVertexShader 的頂點著色器將針對頂點緩衝區中的每個頂點呼叫。頂點著色器的輸入必須與頂點緩衝區中的資料格式以及著色器原始檔中定義的型別定義相匹配,在本例中為 VertexInputType。頂點著色器的輸出將傳送到畫素著色器。在本例中,輸出型別稱為 PixelInputType,它也在上面定義。
考慮到這一點,你會發現頂點著色器建立了一個輸出變數,該變數的型別為 PixelInputType。然後它獲取輸入頂點的位置,並將其乘以世界矩陣、檢視矩陣和投影矩陣。這將根據我們的檢視將頂點放置在 3D 空間中正確的位置,然後放置到 2D 螢幕上。之後,輸出變數將獲取輸入顏色的副本,然後返回輸出,該輸出將用作畫素著色器的輸入。還要注意,我確實設定了輸入位置的 W 值為 1.0,否則它將是未定義的,因為我們只讀入了位置的 XYZ 向量。
////////////////////////////////////////////////////////////////////////////////
// Vertex Shader
////////////////////////////////////////////////////////////////////////////////
PixelInputType ColorVertexShader(VertexInputType input)
{
PixelInputType output;
// Change the position vector to be 4 units for proper matrix calculations.
input.position.w = 1.0f;
// Calculate the position of the vertex against the world, view, and projection matrices.
output.position = mul(input.position, worldMatrix);
output.position = mul(output.position, viewMatrix);
output.position = mul(output.position, projectionMatrix);
// Store the input color for the pixel shader to use.
output.color = input.color;
return output;
}
畫素著色器繪製將在螢幕上渲染的多邊形上的每個畫素。在此畫素著色器中,它使用 PixelInputType 作為輸入,並返回一個 float4 作為輸出,表示最終的畫素顏色。此畫素著色器程式非常簡單,我們只需告訴它將畫素顏色設定為與輸入值的顏色相同。請注意,畫素著色器從頂點著色器的輸出獲取其輸入。
////////////////////////////////////////////////////////////////////////////////
// Filename: color.ps
////////////////////////////////////////////////////////////////////////////////
//////////////
// TYPEDEFS //
//////////////
struct PixelInputType
{
float4 position : SV_POSITION;
float4 color : COLOR;
};
////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
float4 ColorPixelShader(PixelInputType input) : SV_TARGET
{
return input.color;
}
Modelclass.h
[edit | edit source]如前所述,ModelClass 負責封裝 3D 模型的幾何圖形。在本教程中,我們將手動設定單個綠色三角形的 data。我們還將為三角形建立頂點和索引緩衝區,以便可以渲染它。
////////////////////////////////////////////////////////////////////////////////
// Filename: modelclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _MODELCLASS_H_
#define _MODELCLASS_H_
//////////////
// INCLUDES //
//////////////
#include <d3d11.h>
#include <d3dx10math.h>
////////////////////////////////////////////////////////////////////////////////
// Class name: ModelClass
////////////////////////////////////////////////////////////////////////////////
class ModelClass
{
private:
以下是我們將與 ModelClass 中的頂點緩衝區一起使用的頂點型別的定義。還要注意,此 typedef 必須與稍後在本教程中介紹的 ColorShaderClass 中的佈局匹配。
struct VertexType
{
D3DXVECTOR3 position;
D3DXVECTOR4 color;
};
public:
ModelClass();
ModelClass(const ModelClass&);
~ModelClass();
此處的函式處理模型頂點和索引緩衝區的初始化和關閉。Render 函式將模型幾何圖形放在顯示卡上,以準備由顏色著色器繪製。
bool Initialize(ID3D11Device*); void Shutdown(); void Render(ID3D11DeviceContext*); int GetIndexCount(); private: bool InitializeBuffers(ID3D11Device*); void ShutdownBuffers(); void RenderBuffers(ID3D11DeviceContext*);
ModelClass 中的私有變數是頂點和索引緩衝區,以及兩個整數用於跟蹤每個緩衝區的大小。請注意,所有 DirectX 11 緩衝區通常使用通用 ID3D11Buffer 型別,並且在首次建立時透過緩衝區描述更清楚地識別。
private: ID3D11Buffer *m_vertexBuffer, *m_indexBuffer; int m_vertexCount, m_indexCount; }; #endif
Modelclass.cpp
[edit | edit source]//////////////////////////////////////////////////////////////////////////////// // Filename: modelclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "modelclass.h"
類建構函式將頂點和索引緩衝區指標初始化為 null。
ModelClass::ModelClass()
{
m_vertexBuffer = 0;
m_indexBuffer = 0;
}
ModelClass::ModelClass(const ModelClass& other)
{
}
ModelClass::~ModelClass()
{
}
Initialize 函式將呼叫頂點和索引緩衝區的初始化函式。
bool ModelClass::Initialize(ID3D11Device* device)
{
bool result;
// Initialize the vertex and index buffer that hold the geometry for the triangle.
result = InitializeBuffers(device);
if(!result)
{
return false;
}
return true;
}
Shutdown 函式將呼叫頂點和索引緩衝區的關閉函式。
void ModelClass::Shutdown()
{
// Release the vertex and index buffers.
ShutdownBuffers();
return;
}
Render 由 GraphicsClass::Render 函式呼叫。此函式呼叫 RenderBuffers 將頂點和索引緩衝區放在圖形管道上,以便顏色著色器能夠渲染它們。
void ModelClass::Render(ID3D11DeviceContext* deviceContext)
{
// Put the vertex and index buffers on the graphics pipeline to prepare them for drawing.
RenderBuffers(deviceContext);
return;
}
GetIndexCount 返回模型中的索引數量。顏色著色器需要此資訊才能繪製此模型。
int ModelClass::GetIndexCount()
{
return m_indexCount;
}
InitializeBuffers 函式是我們在其中處理建立頂點和索引緩衝區的地方。通常,您會讀取模型並從該 data 檔案建立緩衝區。在本教程中,我們只會手動設定頂點和索引緩衝區中的點,因為它只是一個三角形。
bool ModelClass::InitializeBuffers(ID3D11Device* device)
{
VertexType* vertices;
unsigned long* indices;
D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc;
D3D11_SUBRESOURCE_DATA vertexData, indexData;
HRESULT result;
首先建立兩個臨時陣列來儲存頂點和索引 data,我們將在稍後使用這些資料填充最終緩衝區。
// Set the number of vertices in the vertex array.
m_vertexCount = 3;
// Set the number of indices in the index array.
m_indexCount = 3;
// Create the vertex array.
vertices = new VertexType[m_vertexCount];
if(!vertices)
{
return false;
}
// Create the index array.
indices = new unsigned long[m_indexCount];
if(!indices)
{
return false;
}
現在用三角形的三個點以及每個點的索引填充頂點和索引陣列。請注意,我按繪製它們的順時針順序建立點。如果您以逆時針方向執行此操作,它將認為三角形面向相反方向,並且由於背面剔除而不會繪製它。請始終記住,將頂點發送到 GPU 的順序非常重要。顏色也在這裡設定,因為它是頂點描述的一部分。我將顏色設定為綠色。
// Load the vertex array with data. vertices[0].position = D3DXVECTOR3(-1.0f, -1.0f, 0.0f); // Bottom left. vertices[0].color = D3DXVECTOR4(0.0f, 1.0f, 0.0f, 1.0f); vertices[1].position = D3DXVECTOR3(0.0f, 1.0f, 0.0f); // Top middle. vertices[1].color = D3DXVECTOR4(0.0f, 1.0f, 0.0f, 1.0f); vertices[2].position = D3DXVECTOR3(1.0f, -1.0f, 0.0f); // Bottom right. vertices[2].color = D3DXVECTOR4(0.0f, 1.0f, 0.0f, 1.0f); // Load the index array with data. indices[0] = 0; // Bottom left. indices[1] = 1; // Top middle. indices[2] = 2; // Bottom right.
填充頂點陣列和索引陣列後,我們現在可以使用它們來建立頂點緩衝區和索引緩衝區。建立這兩個緩衝區的方式相同。首先填寫緩衝區的描述。在描述中,ByteWidth(緩衝區的大小)和 BindFlags(緩衝區的型別)是您需要確保正確填寫的內容。填充描述後,您還需要填充一個子資源指標,該指標將指向您之前建立的頂點或索引陣列。有了描述和子資源指標,您就可以使用 D3D 裝置呼叫 CreateBuffer,它將返回指向新緩衝區的指標。
// Set up the description of the static vertex buffer.
vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount;
vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vertexBufferDesc.CPUAccessFlags = 0;
vertexBufferDesc.MiscFlags = 0;
vertexBufferDesc.StructureByteStride = 0;
// Give the subresource structure a pointer to the vertex data.
vertexData.pSysMem = vertices;
vertexData.SysMemPitch = 0;
vertexData.SysMemSlicePitch = 0;
// Now create the vertex buffer.
result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer);
if(FAILED(result))
{
return false;
}
// Set up the description of the static index buffer.
indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
indexBufferDesc.ByteWidth = sizeof(unsigned long) * m_indexCount;
indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
indexBufferDesc.CPUAccessFlags = 0;
indexBufferDesc.MiscFlags = 0;
indexBufferDesc.StructureByteStride = 0;
// Give the subresource structure a pointer to the index data.
indexData.pSysMem = indices;
indexData.SysMemPitch = 0;
indexData.SysMemSlicePitch = 0;
// Create the index buffer.
result = device->CreateBuffer(&indexBufferDesc, &indexData, &m_indexBuffer);
if(FAILED(result))
{
return false;
}
建立頂點緩衝區和索引緩衝區後,您可以刪除頂點和索引陣列,因為它們不再需要,因為 data 已複製到緩衝區中。
// Release the arrays now that the vertex and index buffers have been created and loaded. delete [] vertices; vertices = 0; delete [] indices; indices = 0; return true; }
ShutdownBuffers 函式只是釋放了在 InitializeBuffers 函式中建立的頂點緩衝區和索引緩衝區。
void ModelClass::ShutdownBuffers()
{
// Release the index buffer.
if(m_indexBuffer)
{
m_indexBuffer->Release();
m_indexBuffer = 0;
}
// Release the vertex buffer.
if(m_vertexBuffer)
{
m_vertexBuffer->Release();
m_vertexBuffer = 0;
}
return;
}
RenderBuffers 由 Render 函式呼叫。此函式的目的是將頂點緩衝區和索引緩衝區設定為 GPU 中輸入彙編器上的活動緩衝區。一旦 GPU 擁有活動頂點緩衝區,它就可以使用著色器來渲染該緩衝區。此函式還定義了這些緩衝區應如何繪製,例如三角形、線、扇形等。在本教程中,我們將頂點緩衝區和索引緩衝區設定為輸入彙編器上的活動緩衝區,並告訴 GPU 使用 IASetPrimitiveTopology DirectX 函式以三角形的形式繪製緩衝區。
void ModelClass::RenderBuffers(ID3D11DeviceContext* deviceContext)
{
unsigned int stride;
unsigned int offset;
// Set vertex buffer stride and offset.
stride = sizeof(VertexType);
offset = 0;
// Set the vertex buffer to active in the input assembler so it can be rendered.
deviceContext->IASetVertexBuffers(0, 1, &m_vertexBuffer, &stride, &offset);
// Set the index buffer to active in the input assembler so it can be rendered.
deviceContext->IASetIndexBuffer(m_indexBuffer, DXGI_FORMAT_R32_UINT, 0);
// Set the type of primitive that should be rendered from this vertex buffer, in this case triangles.
deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
return;
}
Colorshaderclass.h
[edit | edit source]ColorShaderClass 是我們將用來呼叫 HLSL 著色器來繪製 GPU 上的 3D 模型的。
////////////////////////////////////////////////////////////////////////////////
// Filename: colorshaderclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _COLORSHADERCLASS_H_
#define _COLORSHADERCLASS_H_
//////////////
// INCLUDES //
//////////////
#include <d3d11.h>
#include <d3dx10math.h>
#include <d3dx11async.h>
#include <fstream>
using namespace std;
////////////////////////////////////////////////////////////////////////////////
// Class name: ColorShaderClass
////////////////////////////////////////////////////////////////////////////////
class ColorShaderClass
{
private:
以下是將與頂點著色器一起使用的 cBuffer 型別的定義。此 typedef 必須與頂點著色器中的 typedef 完全相同,因為模型 data 需要與著色器中的 typedef 匹配,才能進行正確渲染。
struct MatrixBufferType
{
D3DXMATRIX world;
D3DXMATRIX view;
D3DXMATRIX projection;
};
public:
ColorShaderClass();
ColorShaderClass(const ColorShaderClass&);
~ColorShaderClass();
此處的函式處理著色器的初始化和關閉。render 函式設定著色器引數,然後使用著色器繪製準備好的模型頂點。
bool Initialize(ID3D11Device*, HWND); void Shutdown(); bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX); private: bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*); void ShutdownShader(); void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*); bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX); void RenderShader(ID3D11DeviceContext*, int); private: ID3D11VertexShader* m_vertexShader; ID3D11PixelShader* m_pixelShader; ID3D11InputLayout* m_layout; ID3D11Buffer* m_matrixBuffer; }; #endif
Colorshaderclass.cpp
[edit | edit source]//////////////////////////////////////////////////////////////////////////////// // Filename: colorshaderclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "colorshaderclass.h"
與往常一樣,類建構函式將類中所有私有指標初始化為 null。
ColorShaderClass::ColorShaderClass()
{
m_vertexShader = 0;
m_pixelShader = 0;
m_layout = 0;
m_matrixBuffer = 0;
}
ColorShaderClass::ColorShaderClass(const ColorShaderClass& other)
{
}
ColorShaderClass::~ColorShaderClass()
{
}
Initialize 函式將呼叫著色器的初始化函式。我們傳入 HLSL 著色器檔案的檔名,在本教程中,它們分別命名為 color.vs 和 color.ps。
bool ColorShaderClass::Initialize(ID3D11Device* device, HWND hwnd)
{
bool result;
// Initialize the vertex and pixel shaders.
result = InitializeShader(device, hwnd, L"../Engine/color.vs", L"../Engine/color.ps");
if(!result)
{
return false;
}
return true;
}
Shutdown 函式將呼叫著色器的關閉。
void ColorShaderClass::Shutdown()
{
// Shutdown the vertex and pixel shaders as well as the related objects.
ShutdownShader();
return;
}
Render 將首先使用 SetShaderParameters 函式設定著色器內部的引數。設定完引數後,它將呼叫 RenderShader 使用 HLSL 著色器繪製綠色三角形。
bool ColorShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix,
D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix)
{
bool result;
// Set the shader parameters that it will use for rendering.
result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix);
if(!result)
{
return false;
}
// Now render the prepared buffers with the shader.
RenderShader(deviceContext, indexCount);
return true;
}
現在我們將從本教程中最重要的函式之一開始,它被稱為 InitializeShader。此函式實際上是載入著色器檔案並使其可供 DirectX 和 GPU 使用的函式。您還將看到佈局的設定以及頂點緩衝區 data 在 GPU 的圖形管道上的外觀。佈局將需要與 modelclass.h 檔案中的 VertexType 以及 color.vs 檔案中定義的 VertexType 相匹配。
bool ColorShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename)
{
HRESULT result;
ID3D10Blob* errorMessage;
ID3D10Blob* vertexShaderBuffer;
ID3D10Blob* pixelShaderBuffer;
D3D11_INPUT_ELEMENT_DESC polygonLayout[2];
unsigned int numElements;
D3D11_BUFFER_DESC matrixBufferDesc;
// Initialize the pointers this function will use to null.
errorMessage = 0;
vertexShaderBuffer = 0;
pixelShaderBuffer = 0;
這裡是我們將著色器程式編譯成緩衝區的地方。我們提供著色器檔案的名稱、著色器的名稱、著色器版本(DirectX 11 中為 5.0)以及要將著色器編譯到的緩衝區。如果它無法編譯著色器,它將把錯誤訊息放在 errorMessage 字串中,我們將其傳送到另一個函式以輸出錯誤。如果它仍然失敗並且沒有 errorMessage 字串,則意味著它無法找到著色器檔案,在這種情況下,我們將彈出一個對話方塊來說明情況。
// Compile the vertex shader code.
result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "ColorVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL,
&vertexShaderBuffer, &errorMessage, NULL);
if(FAILED(result))
{
// If the shader failed to compile it should have writen something to the error message.
if(errorMessage)
{
OutputShaderErrorMessage(errorMessage, hwnd, vsFilename);
}
// If there was nothing in the error message then it simply could not find the shader file itself.
else
{
MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK);
}
return false;
}
// Compile the pixel shader code.
result = D3DX11CompileFromFile(psFilename, NULL, NULL, "ColorPixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL,
&pixelShaderBuffer, &errorMessage, NULL);
if(FAILED(result))
{
// If the shader failed to compile it should have writen something to the error message.
if(errorMessage)
{
OutputShaderErrorMessage(errorMessage, hwnd, psFilename);
}
// If there was nothing in the error message then it simply could not find the file itself.
else
{
MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK);
}
return false;
}
頂點著色器和畫素著色器程式碼成功編譯成緩衝區後,我們將使用這些緩衝區建立著色器物件本身。從現在開始,我們將使用這些指標來與頂點著色器和畫素著色器進行互動。
// Create the vertex shader from the buffer.
result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader);
if(FAILED(result))
{
return false;
}
// Create the pixel shader from the buffer.
result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader);
if(FAILED(result))
{
return false;
}
下一步是建立將由著色器處理的頂點 data 的佈局。由於此著色器使用位置和顏色向量,因此我們需要在佈局中建立兩者,指定兩者的 size。語義名稱是佈局中要填寫的第一個內容,這允許著色器確定佈局的此元素的用途。由於我們有兩個不同的元素,因此我們對第一個元素使用 POSITION,對第二個元素使用 COLOR。佈局的下一個重要部分是 Format。對於位置向量,我們使用 DXGI_FORMAT_R32G32B32_FLOAT,對於顏色,我們使用 DXGI_FORMAT_R32G32B32A32_FLOAT。您需要注意的最後一件事是 AlignedByteOffset,它指示 data 在緩衝區中的間距。對於此佈局,我們告訴它前 12 個位元組是位置,接下來的 16 個位元組是顏色,AlignedByteOffset 顯示每個元素的起點。您可以使用 D3D11_APPEND_ALIGNED_ELEMENT 而不是在 AlignedByteOffset 中放置自己的值,它將為您計算間距。我暫時將其他設定設為預設值,因為它們在本教程中不需要。
// Now setup the layout of the data that goes into the shader. // This setup needs to match the VertexType structure in the ModelClass and in the shader. polygonLayout[0].SemanticName = "POSITION"; polygonLayout[0].SemanticIndex = 0; polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT; polygonLayout[0].InputSlot = 0; polygonLayout[0].AlignedByteOffset = 0; polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[0].InstanceDataStepRate = 0; polygonLayout[1].SemanticName = "COLOR"; polygonLayout[1].SemanticIndex = 0; polygonLayout[1].Format = DXGI_FORMAT_R32G32B32A32_FLOAT; polygonLayout[1].InputSlot = 0; polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[1].InstanceDataStepRate = 0;
設定好佈局描述後,我們可以獲取它的 size,然後使用 D3D 裝置建立輸入佈局。另外,釋放頂點和畫素著色器緩衝區,因為它們在建立佈局後就不再需要了。
// Get a count of the elements in the layout.
numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]);
// Create the vertex input layout.
result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(),
vertexShaderBuffer->GetBufferSize(), &m_layout);
if(FAILED(result))
{
return false;
}
// Release the vertex shader buffer and pixel shader buffer since they are no longer needed.
vertexShaderBuffer->Release();
vertexShaderBuffer = 0;
pixelShaderBuffer->Release();
pixelShaderBuffer = 0;
要利用著色器,需要設定的最後一件事是常量緩衝區。正如您在頂點著色器中看到的那樣,我們目前只有一個常量緩衝區,因此我們只需要在這裡設定一個,以便我們可以與著色器進行互動。緩衝區使用需要設定為動態,因為我們將在每幀更新它。繫結標誌指示此緩衝區將是一個常量緩衝區。CPU 訪問標誌需要與使用匹配,因此它設定為 D3D11_CPU_ACCESS_WRITE。填寫描述後,我們可以建立常量緩衝區介面,然後使用該介面訪問著色器中的內部變數,使用 SetShaderParameters 函式。
// Setup the description of the dynamic matrix constant buffer that is in the vertex shader.
matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType);
matrixBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
matrixBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
matrixBufferDesc.MiscFlags = 0;
matrixBufferDesc.StructureByteStride = 0;
// Create the constant buffer pointer so we can access the vertex shader constant buffer from within this class.
result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer);
if(FAILED(result))
{
return false;
}
return true;
}
ShutdownShader 釋放了在 InitializeShader 函式中設定的四個介面。
void ColorShaderClass::ShutdownShader()
{
// Release the matrix constant buffer.
if(m_matrixBuffer)
{
m_matrixBuffer->Release();
m_matrixBuffer = 0;
}
// Release the layout.
if(m_layout)
{
m_layout->Release();
m_layout = 0;
}
// Release the pixel shader.
if(m_pixelShader)
{
m_pixelShader->Release();
m_pixelShader = 0;
}
// Release the vertex shader.
if(m_vertexShader)
{
m_vertexShader->Release();
m_vertexShader = 0;
}
return;
}
OutputShaderErrorMessage 輸出編譯頂點著色器或畫素著色器時產生的錯誤訊息。
void ColorShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename)
{
char* compileErrors;
unsigned long bufferSize, i;
ofstream fout;
// Get a pointer to the error message text buffer.
compileErrors = (char*)(errorMessage->GetBufferPointer());
// Get the length of the message.
bufferSize = errorMessage->GetBufferSize();
// Open a file to write the error message to.
fout.open("shader-error.txt");
// Write out the error message.
for(i=0; i<bufferSize; i++)
{
fout Release();
errorMessage = 0;
// Pop a message up on the screen to notify the user to check the text file for compile errors.
MessageBox(hwnd, L"Error compiling shader. Check shader-error.txt for message.", shaderFilename, MB_OK);
return;
}
SetShaderVariables 函式用於簡化設定著色器中的全域性變數。該函式中使用的矩陣是在 GraphicsClass 中建立的,然後在 Render 函式呼叫期間呼叫該函式,將這些矩陣從 GraphicsClass 傳送到頂點著色器。
bool ColorShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix,
D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix)
{
HRESULT result;
D3D11_MAPPED_SUBRESOURCE mappedResource;
MatrixBufferType* dataPtr;
unsigned int bufferNumber;
在將矩陣傳送到著色器之前,請確保對其進行轉置。這是 DirectX 11 的要求。
// Transpose the matrices to prepare them for the shader. D3DXMatrixTranspose(&worldMatrix, &worldMatrix); D3DXMatrixTranspose(&viewMatrix, &viewMatrix); D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix);
鎖定 m_matrixBuffer,在其中設定新的矩陣,然後解鎖它。
// Lock the constant buffer so it can be written to.
result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
if(FAILED(result))
{
return false;
}
// Get a pointer to the data in the constant buffer.
dataPtr = (MatrixBufferType*)mappedResource.pData;
// Copy the matrices into the constant buffer.
dataPtr->world = worldMatrix;
dataPtr->view = viewMatrix;
dataPtr->projection = projectionMatrix;
// Unlock the constant buffer.
deviceContext->Unmap(m_matrixBuffer, 0);
現在在 HLSL 頂點著色器中設定更新後的矩陣緩衝區。
// Set the position of the constant buffer in the vertex shader. bufferNumber = 0; // Finanly set the constant buffer in the vertex shader with the updated values. deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer); return true; }
RenderShader 是 Render 函式中呼叫的第二個函式。在呼叫它之前,會先呼叫 SetShaderParameters,以確保著色器引數設定正確。
此函式的第一步是在輸入裝配器中將我們的輸入佈局設定為活動狀態。這將讓 GPU 瞭解頂點緩衝區中資料的格式。第二步是設定我們將用於渲染此頂點緩衝區的頂點著色器和畫素著色器。設定完著色器後,我們將透過使用 D3D 裝置上下文呼叫 DrawIndexed DirectX 11 函式來渲染三角形。呼叫此函式後,它將渲染綠色三角形。
void ColorShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount)
{
// Set the vertex input layout.
deviceContext->IASetInputLayout(m_layout);
// Set the vertex and pixel shaders that will be used to render this triangle.
deviceContext->VSSetShader(m_vertexShader, NULL, 0);
deviceContext->PSSetShader(m_pixelShader, NULL, 0);
// Render the triangle.
deviceContext->DrawIndexed(indexCount, 0, 0);
return;
}
我們已經研究瞭如何編寫 HLSL 著色器、如何設定頂點和索引緩衝區,以及如何使用 ColorShaderClass 呼叫 HLSL 著色器來繪製這些緩衝區。但是,我們缺少的是用於繪製這些緩衝區的視點。為此,我們將需要一個攝像機類,以便讓 DirectX 11 知道我們從哪裡以及如何檢視場景。攝像機類將跟蹤攝像機的位置及其當前旋轉。它將使用位置和旋轉資訊來生成一個檢視矩陣,該矩陣將傳遞到 HLSL 著色器中以進行渲染。
////////////////////////////////////////////////////////////////////////////////
// Filename: cameraclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _CAMERACLASS_H_
#define _CAMERACLASS_H_
//////////////
// INCLUDES //
//////////////
#include <d3dx10math.h>
////////////////////////////////////////////////////////////////////////////////
// Class name: CameraClass
////////////////////////////////////////////////////////////////////////////////
class CameraClass
{
public:
CameraClass();
CameraClass(const CameraClass&);
~CameraClass();
void SetPosition(float, float, float);
void SetRotation(float, float, float);
D3DXVECTOR3 GetPosition();
D3DXVECTOR3 GetRotation();
void Render();
void GetViewMatrix(D3DXMATRIX&);
private:
float m_positionX, m_positionY, m_positionZ;
float m_rotationX, m_rotationY, m_rotationZ;
D3DXMATRIX m_viewMatrix;
};
#endif
CameraClass 標頭非常簡單,只有四個函式將被使用。SetPosition 和 SetRotation 函式將用於設定攝像機物件的位置和旋轉。Render 將用於基於攝像機的位置和旋轉建立檢視矩陣。最後,GetViewMatrix 將用於從攝像機物件中檢索檢視矩陣,以便著色器可以使用它來進行渲染。
//////////////////////////////////////////////////////////////////////////////// // Filename: cameraclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "cameraclass.h"
類建構函式將初始化攝像機的位置和旋轉,使其位於場景的原點。
CameraClass::CameraClass()
{
m_positionX = 0.0f;
m_positionY = 0.0f;
m_positionZ = 0.0f;
m_rotationX = 0.0f;
m_rotationY = 0.0f;
m_rotationZ = 0.0f;
}
CameraClass::CameraClass(const CameraClass& other)
{
}
CameraClass::~CameraClass()
{
}
SetPosition 和 SetRotation 函式用於設定攝像機的位置和旋轉。
void CameraClass::SetPosition(float x, float y, float z)
{
m_positionX = x;
m_positionY = y;
m_positionZ = z;
return;
}
void CameraClass::SetRotation(float x, float y, float z)
{
m_rotationX = x;
m_rotationY = y;
m_rotationZ = z;
return;
}
GetPosition 和 GetRotation 函式返回攝像機的位置和旋轉,以便呼叫函式使用。
D3DXVECTOR3 CameraClass::GetPosition()
{
return D3DXVECTOR3(m_positionX, m_positionY, m_positionZ);
}
D3DXVECTOR3 CameraClass::GetRotation()
{
return D3DXVECTOR3(m_rotationX, m_rotationY, m_rotationZ);
}
Render 函式使用攝像機的位置和旋轉來構建和更新檢視矩陣。我們首先設定用於向上、位置、旋轉等的變數。然後,在場景的原點,我們首先根據攝像機的 x、y 和 z 旋轉來旋轉攝像機。旋轉到位後,我們將其平移到 3D 空間中的位置。使用位置、lookAt 和向上中的正確值,我們可以使用 D3DXMatrixLookAtLH 函式來建立檢視矩陣,以表示當前的攝像機旋轉和平移。
void CameraClass::Render()
{
D3DXVECTOR3 up, position, lookAt;
float yaw, pitch, roll;
D3DXMATRIX rotationMatrix;
// Setup the vector that points upwards.
up.x = 0.0f;
up.y = 1.0f;
up.z = 0.0f;
// Setup the position of the camera in the world.
position.x = m_positionX;
position.y = m_positionY;
position.z = m_positionZ;
// Setup where the camera is looking by default.
lookAt.x = 0.0f;
lookAt.y = 0.0f;
lookAt.z = 1.0f;
// Set the yaw (Y axis), pitch (X axis), and roll (Z axis) rotations in radians.
pitch = m_rotationX * 0.0174532925f;
yaw = m_rotationY * 0.0174532925f;
roll = m_rotationZ * 0.0174532925f;
// Create the rotation matrix from the yaw, pitch, and roll values.
D3DXMatrixRotationYawPitchRoll(&rotationMatrix, yaw, pitch, roll);
// Transform the lookAt and up vector by the rotation matrix so the view is correctly rotated at the origin.
D3DXVec3TransformCoord(&lookAt, &lookAt, &rotationMatrix);
D3DXVec3TransformCoord(&up, &up, &rotationMatrix);
// Translate the rotated camera position to the location of the viewer.
lookAt = position + lookAt;
// Finally create the view matrix from the three updated vectors.
D3DXMatrixLookAtLH(&m_viewMatrix, &position, &lookAt, &up);
return;
}
呼叫 Render 函式以建立檢視矩陣後,我們可以使用此 GetViewMatrix 函式將更新後的檢視矩陣提供給呼叫函式。檢視矩陣將是 HLSL 頂點著色器中使用的三個主要矩陣之一。
void CameraClass::GetViewMatrix(D3DXMATRIX& viewMatrix)
{
viewMatrix = m_viewMatrix;
return;
}
GraphicsClass 現在添加了三個新類。CameraClass、ModelClass 和 ColorShaderClass 的標頭在這裡被新增,以及私有成員變數。請記住,GraphicsClass 是用於渲染場景的主要類,它呼叫專案所需的全部類物件。
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _GRAPHICSCLASS_H_ #define _GRAPHICSCLASS_H_ /////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "d3dclass.h"
#include "cameraclass.h" #include "modelclass.h" #include "colorshaderclass.h"
/////////////
// GLOBALS //
/////////////
const bool FULL_SCREEN = true;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;
////////////////////////////////////////////////////////////////////////////////
// Class name: GraphicsClass
////////////////////////////////////////////////////////////////////////////////
class GraphicsClass
{
public:
GraphicsClass();
GraphicsClass(const GraphicsClass&);
~GraphicsClass();
bool Initialize(int, int, HWND);
void Shutdown();
bool Frame();
private:
bool Render();
private:
D3DClass* m_D3D;
CameraClass* m_Camera; ModelClass* m_Model; ColorShaderClass* m_ColorShader;
}; #endif
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "graphicsclass.h"
對 GraphicsClass 的第一個更改是在類建構函式中將攝像機、模型和顏色著色器物件初始化為 null。
GraphicsClass::GraphicsClass()
{
m_D3D = 0;
m_Camera = 0; m_Model = 0; m_ColorShader = 0;
}
Initialize 函式也已更新,以建立和初始化三個新物件。
bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{
bool result;
// Create the Direct3D object.
m_D3D = new D3DClass;
if(!m_D3D)
{
return false;
}
// Initialize the Direct3D object.
result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR);
if(!result)
{
MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK);
return false;
}
// Create the camera object.
m_Camera = new CameraClass;
if(!m_Camera)
{
return false;
}
// Set the initial position of the camera.
m_Camera->SetPosition(0.0f, 0.0f, -10.0f);
// Create the model object.
m_Model = new ModelClass;
if(!m_Model)
{
return false;
}
// Initialize the model object.
result = m_Model->Initialize(m_D3D->GetDevice());
if(!result)
{
MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK);
return false;
}
// Create the color shader object.
m_ColorShader = new ColorShaderClass;
if(!m_ColorShader)
{
return false;
}
// Initialize the color shader object.
result = m_ColorShader->Initialize(m_D3D->GetDevice(), hwnd);
if(!result)
{
MessageBox(hwnd, L"Could not initialize the color shader object.", L"Error", MB_OK);
return false;
}
return true; }
Shutdown 也已更新,以關閉和釋放三個新物件。
void GraphicsClass::Shutdown()
{
// Release the color shader object.
if(m_ColorShader)
{
m_ColorShader->Shutdown();
delete m_ColorShader;
m_ColorShader = 0;
}
// Release the model object.
if(m_Model)
{
m_Model->Shutdown();
delete m_Model;
m_Model = 0;
}
// Release the camera object.
if(m_Camera)
{
delete m_Camera;
m_Camera = 0;
}
// Release the Direct3D object.
if(m_D3D)
{
m_D3D->Shutdown();
delete m_D3D;
m_D3D = 0;
}
return;
}
Frame 函式與之前的教程相同。
bool GraphicsClass::Frame()
{
bool result;
// Render the graphics scene.
result = Render();
if(!result)
{
return false;
}
return true;
}
正如您所料,Render 函式發生了最大的變化。它仍然從清除場景開始,只不過它被清除為黑色。之後,它呼叫攝像機物件的 Render 函式,以根據在 Initialize 函式中設定的攝像機位置建立一個檢視矩陣。建立完檢視矩陣後,我們從攝像機類中獲取它的副本。我們還從 D3DClass 物件中獲取世界和投影矩陣的副本。然後,我們呼叫 ModelClass::Render 函式將綠色三角形模型幾何體放在圖形管道上。現在準備好了頂點後,我們呼叫顏色著色器,使用模型資訊和三個矩陣來定位每個頂點,以繪製頂點。現在綠色三角形被繪製到後緩衝區。這樣,場景就完成了,我們呼叫 EndScene 將其顯示到螢幕上。
bool GraphicsClass::Render()
{
D3DXMATRIX viewMatrix, projectionMatrix, worldMatrix;
bool result;
// Clear the buffers to begin the scene.
m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);
// Generate the view matrix based on the camera's position.
m_Camera->Render();
// Get the world, view, and projection matrices from the camera and d3d objects.
m_Camera->GetViewMatrix(viewMatrix);
m_D3D->GetWorldMatrix(worldMatrix);
m_D3D->GetProjectionMatrix(projectionMatrix);
// Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing.
m_Model->Render(m_D3D->GetDeviceContext());
// Render the model using the color shader.
result = m_ColorShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix);
if(!result)
{
return false;
}
// Present the rendered scene to the screen. m_D3D->EndScene(); return true; }
總之,您應該已經瞭解了頂點和索引緩衝區的基本工作原理。您還應該瞭解頂點和畫素著色器的基礎知識,以及如何使用 HLSL 編寫它們。最後,您應該瞭解我們如何將這些新概念合併到我們的框架中,以生成一個渲染到螢幕上的綠色三角形。我還想說,我知道程式碼很長,只是為了繪製一個三角形,並且它本可以全部放在一個 main() 函式中。但是,我使用了一個合適的框架來實現它,這樣一來,在接下來的教程中,只需要對程式碼進行很少的更改,就可以實現更復雜的圖形。
1. 編譯並執行教程。確保它在螢幕上繪製一個綠色三角形。繪製完成後,按 Escape 鍵退出。
2. 將三角形的顏色更改為紅色。
3. 將三角形更改為正方形。
4. 將攝像機向後移動 10 個單位。
5. 將畫素著色器更改為輸出亮度為一半的顏色。(巨大提示:將 ColorPixelShader 中的某個東西乘以 0.5f)