共用方式為


移植著色器物件

重要 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::CreateVertexShaderID3D11Device::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);

頂點緩衝區的建立和更新類似,並將在下一步移植頂點緩衝區和資料中討論。

後續步驟

移植頂點緩衝區和資料

如何:將簡單的 OpenGL ES 2.0 轉譯器連線至 Direct3D 11

移植頂點緩衝區和資料

移植 GLSL

繪製到螢幕