移植著色器物件
重要 API
從 OpenGL ES 2.0 移植簡單轉譯器時,第一步是在 Direct3D 11 中設定等效的頂點和片段著色器物件,並確保主程式在編譯後可以與著色器物件進行通訊。
注意
您是否已建立新的 Direct3D 專案? 如果沒有,請遵循為通用 Windows 平台 (UWP)建立新的 DirectX 11 專案中的指示。 本逐步解說假設您已建立 DXGI 和 Direct3D 資源以繪製到畫面,並在範本中提供。
與 OpenGL ES 2.0 類似,Direct3D 中的已編譯著色器必須與繪圖內容相關聯。 不過,Direct3D 本身沒有著色器程序物件的概念;相反地,您必須將著色器直接指派給ID3D11DeviceContext。 此步驟遵循 OpenGL ES 2.0 建立和繫結著色器物件的程序,並為您提供 Direct3D 中對應的 API 行為。
指示
步驟 1:編譯著色器
在這個簡單的 OpenGL ES 2.0 範例中,著色器會儲存為文字檔,並載入為字串資料以進行執行階段編譯。
OpenGL ES 2.0:編譯著色器
GLuint __cdecl CompileShader (GLenum shaderType, const char *shaderSrcStr)
// shaderType can be GL_VERTEX_SHADER or GL_FRAGMENT_SHADER. Returns 0 if compilation fails.
{
GLuint shaderHandle;
GLint compiledShaderHandle;
// Create an empty shader object.
shaderHandle = glCreateShader(shaderType);
if (shaderHandle == 0)
return 0;
// Load the GLSL shader source as a string value. You could obtain it from
// from reading a text file or hardcoded.
glShaderSource(shaderHandle, 1, &shaderSrcStr, NULL);
// Compile the shader.
glCompileShader(shaderHandle);
// Check the compile status
glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &compiledShaderHandle);
if (!compiledShaderHandle) // error in compilation occurred
{
// Handle any errors here.
glDeleteShader(shaderHandle);
return 0;
}
return shaderHandle;
}
在 Direct3D 中,著色器不會在執行階段編譯;編譯程式的其餘部分時,它們一律會編譯為 CSO 檔案。 當您使用 Microsoft Visual Studio 編譯應用程式時,HLSL 檔案會編譯成應用程式必須載入的 CSO (.cso) 檔案。 當您封裝這些 CSO 檔案時,請確保將這些 CSO 檔案包含在您的應用程式中!
注意:下列範例會使用 auto 關鍵字和 lambda 語法,以非同步方式執行著色器載入和編譯。 ReadDataAsync() 是針對範本實作的方法,該範本會將 CSO 檔案中讀取為位元組資料陣列 (fileData)。
Direct3D 11:編譯著色器
auto loadVSTask = DX::ReadDataAsync(m_projectDir + "SimpleVertexShader.cso");
auto loadPSTask = DX::ReadDataAsync(m_projectDir + "SimplePixelShader.cso");
auto createVSTask = loadVSTask.then([this](Platform::Array<byte>^ fileData) {
m_d3dDevice->CreateVertexShader(
fileData->Data,
fileData->Length,
nullptr,
&m_vertexShader);
auto createPSTask = loadPSTask.then([this](Platform::Array<byte>^ fileData) {
m_d3dDevice->CreatePixelShader(
fileData->Data,
fileData->Length,
nullptr,
&m_pixelShader;
};
步驟 2:建立和載入頂點和片段 (像素) 著色器
OpenGL ES 2.0 具有著色器「程式」的概念,其可做為在 CPU 上執行的主要程式與 GPU 上執行的著色器之間的介面。 著色器會編譯 (或從已編譯的來源載入),並與可在 GPU 上執行的程式相關聯。
OpenGL ES 2.0:將頂點和片段著色器載入著色程式中
GLuint __cdecl LoadShaderProgram (const char *vertShaderSrcStr, const char *fragShaderSrcStr)
{
GLuint programObject, vertexShaderHandle, fragmentShaderHandle;
GLint linkStatusCode;
// Load the vertex shader and compile it to an internal executable format.
vertexShaderHandle = CompileShader(GL_VERTEX_SHADER, vertShaderSrcStr);
if (vertexShaderHandle == 0)
{
glDeleteShader(vertexShaderHandle);
return 0;
}
// Load the fragment/pixel shader and compile it to an internal executable format.
fragmentShaderHandle = CompileShader(GL_FRAGMENT_SHADER, fragShaderSrcStr);
if (fragmentShaderHandle == 0)
{
glDeleteShader(fragmentShaderHandle);
return 0;
}
// Create the program object proper.
programObject = glCreateProgram();
if (programObject == 0) return 0;
// Attach the compiled shaders
glAttachShader(programObject, vertexShaderHandle);
glAttachShader(programObject, fragmentShaderHandle);
// Compile the shaders into binary executables in memory and link them to the program object..
glLinkProgram(programObject);
// Check the project object link status and determine if the program is available.
glGetProgramiv(programObject, GL_LINK_STATUS, &linkStatusCode);
if (!linkStatusCode) // if link status <> 0
{
// Linking failed; delete the program object and return a failure code (0).
glDeleteProgram (programObject);
return 0;
}
// Deallocate the unused shader resources. The actual executables are part of the program object.
glDeleteShader(vertexShaderHandle);
glDeleteShader(fragmentShaderHandle);
return programObject;
}
// ...
glUseProgram(renderer->programObject);
Direct3D 沒有著色器程式物件的概念。 相反地,著色器是在呼叫 ID3D11Device 介面上的著色器建立方法之一 (例如 ID3D11Device::CreateVertexShader 或 ID3D11Device::CreatePixelShader) 時建立的。 若要設定目前繪圖內容的著色器,我們透過設定著色器方法將它們提供給對應的 ID3D11DeviceContext,例如用於頂點著色器的 ID3D11DeviceContext::VSSetShader,或用於片段著色器的 ID3D11DeviceContext::PSSetShader。
Direct3D 11:設定圖形裝置繪圖內容的著色器。
m_d3dContext->VSSetShader(
m_vertexShader.Get(),
nullptr,
0);
m_d3dContext->PSSetShader(
m_pixelShader.Get(),
nullptr,
0);
步驟 3:定義要提供給著色器的資料
在我們的OpenGL ES 2.0 範例中,我們有一個統一來宣告著色器管線:
- u_mvpMatrix:一個 4x4 浮點數陣列,表示最終的模型-檢視-投影變換矩陣,該矩陣採用立方體的模型座標並將其轉換為 2D 投影座標以進行掃描轉換。
頂點資料的兩個屬性值:
- a_position:頂點模型座標的 4 浮點向量。
- a_color:與頂點關聯的 RGBA 色彩值的 4 個浮點向量。
開啟 GL ES 2.0:統一和屬性的 GLSL 定義
uniform mat4 u_mvpMatrix;
attribute vec4 a_position;
attribute vec4 a_color;
在此案例中,對應的主要程式變數會定義為轉譯器物件上的欄位。 (請參閱如何:將簡單的 OpenGL ES 2.0 轉譯器移植到 Direct3D 11 中的標題。) 完成此操作後,我們需要指定記憶體中的位置,主程式將在其中為著色器管線提供這些值,該位置我們通常在繪製呼叫之前執行以下操作:
OpenGL ES 2.0:標記統一和屬性資料的位置
// Inform the shader of the attribute locations
loc = glGetAttribLocation(renderer->programObject, "a_position");
glVertexAttribPointer(loc, 3, GL_FLOAT, GL_FALSE,
sizeof(Vertex), 0);
glEnableVertexAttribArray(loc);
loc = glGetAttribLocation(renderer->programObject, "a_color");
glVertexAttribPointer(loc, 4, GL_FLOAT, GL_FALSE,
sizeof(Vertex), (GLvoid*) (sizeof(float) * 3));
glEnableVertexAttribArray(loc);
// Inform the shader program of the uniform location
renderer->mvpLoc = glGetUniformLocation(renderer->programObject, "u_mvpMatrix");
Direct3D 在相同意義上沒有「屬性」或「統一」的概念 (或者,至少不共用此語法)。 相反地,它有常數緩衝區,以 Direct3D 子資源表示 -- 主要程式與著色器程式之間共用的資源。 其中一些子資源,例如頂點位置和色彩,會描述為 HLSL 語意。 有關與 OpenGL ES 2.0 概念相關的常量緩衝區和 HLSL 語意的更多資訊,請閱讀移植畫面緩衝區物件、統一和屬性。
將此程序移至 Direct3D 時,我們將統一轉換為 Direct3D 常數緩衝區 (cbuffer),並為其指派一個暫存器以使用暫存器 HLSL 語意進行查找。 這兩個頂點屬性做為著色器管線階段的輸入元素進行處理,並且也指派有通知著色器的 HLSL 語意 (POSITION 和 COLOR0)。 像素著色器會採用 SV_POSITION,並具有SV_開頭,指出它是 GPU 所產生的系統值。 (在此情況下,它是掃描轉換期間產生的像素位置。) VertexShaderInput 和 PixelShaderInput 未宣告為常數緩衝區,因為前者將用於定義頂點緩衝區 (請參閱移植頂點緩衝區和資料),以及後者是管線中前一階段的結果產生的,在本案例中是頂點著色器。
Direct3D:常數緩衝區和頂點資料的 HLSL 定義
cbuffer ModelViewProjectionConstantBuffer : register(b0)
{
matrix mvp;
};
// Per-vertex data used as input to the vertex shader.
struct VertexShaderInput
{
float4 pos : POSITION;
float4 color : COLOR0;
};
// Per-vertex color data passed through the pixel shader.
struct PixelShaderInput
{
float4 pos : SV_POSITION;
float3 color : COLOR0;
};
有關移植到常量緩衝區和 HLSL 語意應用程式的詳細資訊,請閱讀移植畫面緩衝區物件、統一和屬性。
以下是使用常數或頂點緩衝區傳遞至著色器管線之資料配置的結構。
Direct3D 11:宣告常數和頂點緩衝區配置
// Constant buffer used to send MVP matrices to the vertex shader.
struct ModelViewProjectionConstantBuffer
{
DirectX::XMFLOAT4X4 modelViewProjection;
};
// Used to send per-vertex data to the vertex shader.
struct VertexPositionColor
{
DirectX::XMFLOAT4 pos;
DirectX::XMFLOAT4 color;
};
針對常數緩衝區元素使用 DirectXMath XM* 類型,因為它們會在傳送至著色器管線時,為內容提供適當的封裝和對齊方式。 如果您使用標準 Windows 平台浮點數類型和陣列,您必須自行執行封裝和對齊。
若要繫結常數緩衝區,請建立一個配配置描述做為 CD3D11_BUFFER_DESC 結構,並將其傳遞給 ID3DDevice::CreateBuffer。 然後,在轉譯方法中,於繪製之前將常數緩衝區傳遞給 ID3D11DeviceContext::UpdateSubresource。
Direct3D 11:繫結常數緩衝區
CD3D11_BUFFER_DESC constantBufferDesc(sizeof(ModelViewProjectionConstantBuffer), D3D11_BIND_CONSTANT_BUFFER);
m_d3dDevice->CreateBuffer(
&constantBufferDesc,
nullptr,
&m_constantBuffer);
// ...
// Only update shader resources that have changed since the last frame.
m_d3dContext->UpdateSubresource(
m_constantBuffer.Get(),
0,
NULL,
&m_constantBufferData,
0,
0);
頂點緩衝區的建立和更新類似,並將在下一步移植頂點緩衝區和資料中討論。