將 OpenGL ES 2.0 緩衝區、統一和頂點屬性與 Direct3 進行比較
重要 API
在從 OpenGL ES 2.0 移植到 Direct3D 11 的過程中,您必須變更在應用程式和著色器程式之間傳遞資料的語法和 API 行為。
在 OpenGL ES 2.0 中,資料會以四種方式來回傳遞至著色器程式:做為常數資料的統一,做為頂點資料的屬性,做為其他資源資料的緩衝區物件 (例如紋理)。 在 Direct3D 11 中,這些大致對應至常數緩衝區、頂點緩衝區和子資源。 儘管表面上的共通性,但它們的使用方式卻大不相同。
以下是基本對應:
OpenGL ES 2.0 | Direct3D 11 |
---|---|
uniform | 常數緩衝區 (cbuffer) 欄位。 |
屬性 | 頂點緩衝區元素欄位,由輸入配置指定,並以特定的 HLSL 語意標示。 |
緩衝區物件 | 緩衝區;有關通用緩衝區定義,請參閱 D3D11_SUBRESOURCE_DATA 和 D3D11_BUFFER_DESC。 |
畫面緩衝區物件 (FBO) | 轉譯目標;請參閱 ID3D11RenderTargetView 與 ID3D11Texture2D。 |
後端緩衝區 | 具有「後端緩衝區」表面的交換鏈結;請參閱附加 IDXGISurface1 的 IDXGISwapChain1。 |
移植緩衝區
在OpenGL ES 2.0中,建立和繫結任何類型的緩衝區的程式通常會遵循此模式
- 呼叫 glGenBuffers 以產生一或多個緩衝區,並將控制代碼傳回給它們。
- 呼叫 glBindBuffer 來定義緩衝區的配置,例如GL_ELEMENT_ARRAY_BUFFER。
- 呼叫 glBufferData,以特定配置中的特定資料填入緩衝區 (例如頂點結構、索引資料或色彩資料)。
最常見的緩衝區類型是頂點緩衝區,其幾乎包含某些座標系統中頂點的位置。 在一般使用中,頂點是由包含位置座標的結構、頂點位置的一般向量、頂點位置的正切向量,以及紋理查閱 (uv) 座標表示。 緩衝區會依某種順序包含這些頂點的連續清單 (例如三角形清單、帶狀或扇形),並共同代表場景中可見的多邊形。 (在 Direct3D 11 和 OpenGL ES 2.0 中,每個繪製呼叫有多個頂點緩衝區效率不佳。)
以下是使用 OpenGL ES 2.0 建立的頂點緩衝區和索引緩衝區範例:
OpenGL ES 2.0:建立和填入頂點緩衝區和索引緩衝區。
glGenBuffers(1, &renderer->vertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, renderer->vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * CUBE_VERTICES, renderer->vertices, GL_STATIC_DRAW);
glGenBuffers(1, &renderer->indexBuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, renderer->indexBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(int) * CUBE_INDICES, renderer->vertexIndices, GL_STATIC_DRAW);
其他緩衝區包括像素緩衝區和對應,例如紋理。 著色器管線可以轉譯為紋理緩衝區 (pixmap) 或轉譯緩衝區物件,並在未來的著色器階段使用這些緩衝區。 在最簡單的情況下,呼叫流程是:
- 呼叫 glGenFramebuffers 以產生畫面緩衝區物件。
- 呼叫 glBindFramebuffer 以繫結畫面緩衝區物件以進行寫入。
- 呼叫 glFramebufferTexture2D 以繪製到指定的紋理對應中。
在 Direct3D 11 中,緩衝區資料元素會被視為「子資源」,而且範圍可以從個別頂點資料元素到 MIP 對應紋理。
- 使用緩衝區資料元素的配置填入 D3D11_SUBRESOURCE_DATA 結構。
- 使用緩衝區中各個元素的大小以及緩衝區類型填入 D3D11_BUFFER_DESC 結構。
- 使用這兩個結構呼叫 ID3D11Device1::CreateBuffer。
Direct3D 11:建立和填入頂點緩衝區和索引緩衝區。
D3D11_SUBRESOURCE_DATA vertexBufferData = {0};
vertexBufferData.pSysMem = cubeVertices;
vertexBufferData.SysMemPitch = 0;
vertexBufferData.SysMemSlicePitch = 0;
CD3D11_BUFFER_DESC vertexBufferDesc(sizeof(cubeVertices), D3D11_BIND_VERTEX_BUFFER);
m_d3dDevice->CreateBuffer(
&vertexBufferDesc,
&vertexBufferData,
&m_vertexBuffer);
m_indexCount = ARRAYSIZE(cubeIndices);
D3D11_SUBRESOURCE_DATA indexBufferData = {0};
indexBufferData.pSysMem = cubeIndices;
indexBufferData.SysMemPitch = 0;
indexBufferData.SysMemSlicePitch = 0;
CD3D11_BUFFER_DESC indexBufferDesc(sizeof(cubeIndices), D3D11_BIND_INDEX_BUFFER);
m_d3dDevice->CreateBuffer(
&indexBufferDesc,
&indexBufferData,
&m_indexBuffer);
可寫入像素緩衝區或對應 (例如畫面緩衝區) 可以建立為 ID3D11Texture2D 物件。 這些可以做為資源繫結到 ID3D11RenderTargetView 或 ID3D11ShaderResourceView,一旦繪製到其中,就可以分別與關聯的交換鏈結一起顯示或傳遞給著色器。
Direct3D 11:建立畫面緩衝區物件。
ComPtr<ID3D11RenderTargetView> m_d3dRenderTargetViewWin;
// ...
ComPtr<ID3D11Texture2D> frameBuffer;
m_swapChainCoreWindow->GetBuffer(0, IID_PPV_ARGS(&frameBuffer));
m_d3dDevice->CreateRenderTargetView(
frameBuffer.Get(),
nullptr,
&m_d3dRenderTargetViewWin);
將統一和統一緩衝區物件變更為 Direct3D 常數緩衝區
在 Open GL ES 2.0 中,統一是將常數資料提供給個別著色器程序的機制。 著色器無法改變此資料。
設定統一通常牽涉到提供 GPU 中上傳位置的其中一個 glUniform* 方法,以及應用程式記憶體中資料的指標。 執行 glUniform* 方法之後,統一資料會位於 GPU 記憶體中,並由宣告該統一的著色器存取。 您預期會確保資料會以著色器中的統一宣告 (使用相容類型) 為基礎來解釋資料。
OpenGL ES 2.0 建立統一並上傳資料
renderer->mvpLoc = glGetUniformLocation(renderer->programObject, "u_mvpMatrix");
// ...
glUniformMatrix4fv(renderer->mvpLoc, 1, GL_FALSE, (GLfloat*) &renderer->mvpMatrix.m[0][0]);
在著色器的 GLSL 中,對應的統一宣告看起來像這樣:
開啟 GL ES 2.0:GLSL 統一宣告
uniform mat4 u_mvpMatrix;
Direct3D 會將統一資料指定為「常數緩衝區」,例如統一,包含提供給個別著色器的常數資料。 如同統一緩衝區,請務必將常數緩衝區資料封裝在記憶體中,與著色器預期解釋它的方式相同。 使用 DirectXMath 類型 (例如 XMFLOAT4) 而不是平台類型 (例如float* 或 float[4]) 可確保正確的資料元素對齊。
常數緩衝區必須有相關聯的 GPU 暫存器,用來參考 GPU 上的該資料。 資料會封裝到暫存器位置,如緩衝區的配置所指示。
Direct3D 11:建立常數緩衝區並將資料上傳至該緩衝區
struct ModelViewProjectionConstantBuffer
{
DirectX::XMFLOAT4X4 mvp;
};
// ...
ModelViewProjectionConstantBuffer m_constantBufferData;
// ...
XMStoreFloat4x4(&m_constantBufferData.mvp, mvpMatrix);
CD3D11_BUFFER_DESC constantBufferDesc(sizeof(ModelViewProjectionConstantBuffer), D3D11_BIND_CONSTANT_BUFFER);
m_d3dDevice->CreateBuffer(
&constantBufferDesc,
nullptr,
&m_constantBuffer);
在著色器的 HLSL 中,對應的常數緩衝區宣告看起來像這樣:
Direct3D 11:常量緩衝區 HLSL 宣告
cbuffer ModelViewProjectionConstantBuffer : register(b0)
{
matrix mvp;
};
請注意,每個常數緩衝區都必須宣告暫存器。 不同的 Direct3D 功能層級具有不同的可用暫存器上限,因此請勿超過您所設定的最低功能層級數目上限。
將頂點屬性移植到 Direct3D 輸入配置和 HLSL 語意
由於著色器管線可以修改頂點資料,因此 OpenGL ES 2.0 會要求您將其指定為「屬性」,而不是「統一」。 (這在更新版本的 OpenGL 和 GLSL 中有所變更。) 頂點特定資料,例如頂點位置、法線、正切值和色彩值,會以屬性值的形式提供給著色器。 這些屬性值會對應至頂點資料中每個元素的特定位移;例如,第一個屬性可以指向個別頂點的位置元件,而第二個屬性則指向一般等。
將頂點緩衝區資料從主要記憶體移至 GPU 的基本程序如下所示:
- 使用 glBindBuffer 上傳頂點資料。
- 使用 glGetAttribLocation 取得 GPU 上屬性的位置。 針對頂點資料元素中的每個屬性呼叫它。
- 呼叫 glVertexAttribPointer 以提供在個別頂點資料元素內設定正確的屬性大小和位移。 針對每個屬性執行此動作。
- 使用 glEnableVertexAttribArray 啟用頂點資料配置資訊。
OpenGL ES 2.0:將頂點緩衝區資料上傳至著色器屬性
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, renderer->vertexBuffer);
loc = glGetAttribLocation(renderer->programObject, "a_position");
glVertexAttribPointer(loc, 3, GL_FLOAT, GL_FALSE,
sizeof(Vertex), 0);
loc = glGetAttribLocation(renderer->programObject, "a_color");
glEnableVertexAttribArray(loc);
glVertexAttribPointer(loc, 4, GL_FLOAT, GL_FALSE,
sizeof(Vertex), (GLvoid*) (sizeof(float) * 3));
glEnableVertexAttribArray(loc);
現在,在頂點著色器中,您會使用您在 glGetAttribLocation 呼叫中定義的相同名稱來宣告屬性。
OpenGL ES 2.0:在 GLSL 中宣告屬性
attribute vec4 a_position;
attribute vec4 a_color;
在某些方面,Direct3D 的相同程式會保留。 頂點數據不是屬性,而是在輸入緩衝區中提供,其中包含頂點緩衝區和對應的索引緩衝區。 但是,由於 Direct3D 沒有「屬性」宣告,因此您必須指定輸入配置,該配置宣告頂點緩衝區中資料元素的各個元件,以及指示頂點著色器在何處以及如何解釋這些元件的 HLSL 語意。 HLSL 語意需要您使用特定字串來定義每個元件的使用方式,以通知著色器引擎的用途。 例如,頂點位置資料會標示為 POSITION、一般資料標示為 NORMAL,而頂點色彩資料則會標示為 COLOR。 (其他著色器階段也需要特定的語意,並且這些語意根據著色器階段有不同的解釋。) 有關 HLSL 語意的詳細資訊,請閱讀移植著色器管線和 HLSL 語意。
總的來說,設定頂點和索引緩衝區以及設定輸入配置的程序稱為 Direct3D 圖形管道的「輸入組件」(IA) 階段。
Direct3D 11:設定輸入組件階段
// Set up the IA stage corresponding to the current draw operation.
UINT stride = sizeof(VertexPositionColor);
UINT offset = 0;
m_d3dContext->IASetVertexBuffers(
0,
1,
m_vertexBuffer.GetAddressOf(),
&stride,
&offset);
m_d3dContext->IASetIndexBuffer(
m_indexBuffer.Get(),
DXGI_FORMAT_R16_UINT,
0);
m_d3dContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
m_d3dContext->IASetInputLayout(m_inputLayout.Get());
藉由宣告頂點資料元素的格式和用於每個元件的語意,來宣告輸入配置並與頂點著色器相關聯。 您所建立之 D3D11_INPUT_ELEMENT_DESC 中所述的頂點元素資料配置,必須對應至對應結構的版面配置。 您在此會為具有兩個元件的頂點資料建立配置:
- 頂點位置座標,以 XMFLOAT3 表示在主要記憶體中,這是 (x,y,z) 座標 3 32 位元浮點值的對齊陣列。
- 頂點色彩值,以 XMFLOAT4 表示,這是色彩 4 32 位浮點值的對齊陣列 (RGBA)。
您可以為每個語意指派語意,以及格式類型。 然後將描述傳遞至 ID3D11Device1::CreateInputLayout。 當我們在轉譯方法期間設定輸入組件時呼叫 ID3D11DeviceContext1::IASetInputLayout 時,將使用輸入配置。
Direct3D 11:使用特定語意描述輸入配置
ComPtr<ID3D11InputLayout> m_inputLayout;
// ...
const D3D11_INPUT_ELEMENT_DESC vertexDesc[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};
m_d3dDevice->CreateInputLayout(
vertexDesc,
ARRAYSIZE(vertexDesc),
fileData->Data,
fileData->Length,
&m_inputLayout);
// ...
// When we start the drawing process...
m_d3dContext->IASetInputLayout(m_inputLayout.Get());
最後,您可以藉由宣告輸入,確定著色器可以瞭解輸入資料。 您在配置中指派的語意可用來選取 GPU 記憶體中的正確位置。
Direct3D 11:使用 HLSL 語意宣告著色器輸入資料
struct VertexShaderInput
{
float3 pos : POSITION;
float3 color : COLOR;
};