轉譯架構 I:轉譯簡介
注意
本主題屬於<使用 DirectX 建立簡單的通用 Windows 平台 (UWP) 遊戲>教學課程系列的一部分。 該連結主題是提供這系列教學的基本背景介紹。
到目前為止,我們已討論如何建構通用 Windows 平台 (UWP) 遊戲,以及如何定義處理遊戲流程的狀態機器。 現在可以進一步瞭解如何開發轉譯架構了。 我們看看範例遊戲如何使用 Direct3D 11 轉譯遊戲場景。
Direct3D 11 包含一組 API,可存取高效能圖形硬體的進階功能,適用於為圖形密集型應用程式 (例如:遊戲) 建立 3D 圖形。
在畫面上轉譯遊戲圖形,基本上就意味著轉譯一連串的螢幕畫面格。 每格畫面都必須根據檢視轉譯場景中的可見物件。
若要轉譯畫面格,您必須將必要的場景資訊傳遞至硬體,才能呈現在畫面上。 若想在螢幕上顯示任何內容,您需要在遊戲一開始執行,就立即開始轉譯。
目標
您可以設定基本的轉譯架構,以顯示UWP DirectX 遊戲的圖形輸出。 整個過程可簡略拆解成以下三個步驟。
- 建立與圖形介面的連接。
- 建立繪製圖形所需的資源。
- 轉譯畫面格以顯示圖形。
本主題說明如何轉譯圖形 (涵蓋步驟 1 和 3)。
<轉譯架構 II:遊戲轉譯>一文說明步驟 2:如何設定轉譯架構,以及如何在轉譯之前準備好資料。
開始使用
建議您先熟悉基本圖形和轉譯概念。 如果您不熟悉 Direct3D 和轉譯,請參閱本主題的「詞彙和概念」一節,內有圖形和轉譯專用詞彙的簡介。
在此遊戲中,GameRenderer 類別代表此範例遊戲的轉譯器。 其負責建立和維護用來產生遊戲視覺效果的所有 Direct3D 11 和 Direct2D 物件。 此外也會維護 Simple3DGame 物件的參照,該物件用來擷取要轉譯的物件清單,以及平視顯示 (HUD) 的遊戲狀態。
在本教學課程的這個部分,我們會著重於在遊戲中轉譯 3D 物件。
建立與圖形介面的連接
如需有關存取硬體以進行轉譯的資訊,請參閱<定義遊戲的 UWP 應用程式架構>主題。
The App::Initialize 方法
std::make_shared 函式 (如下所示) 用於建立 shared_ptr 至 DX::DeviceResources,其也提供裝置的存取權。
在 Direct3D 11 中,裝置用來分配和終結物件、轉譯基本類型,以及透過圖形驅動程式與圖形卡通訊。
void Initialize(CoreApplicationView const& applicationView)
{
...
// At this point we have access to the device.
// We can create the device-dependent resources.
m_deviceResources = std::make_shared<DX::DeviceResources>();
}
轉譯畫面格以顯示圖形
遊戲場景必須在遊戲啟動時轉譯。 轉譯指示始於 GameMain::Run 方法,如下所示。
流程簡化如下:
- 更新
- 轉譯
- 展示
GameMain::Run 方法
void GameMain::Run()
{
while (!m_windowClosed)
{
if (m_visible) // if the window is visible
{
switch (m_updateState)
{
...
default:
CoreWindow::GetForCurrentThread().Dispatcher().ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent);
Update();
m_renderer->Render();
m_deviceResources->Present();
m_renderNeeded = false;
}
}
else
{
CoreWindow::GetForCurrentThread().Dispatcher().ProcessEvents(CoreProcessEventsOption::ProcessOneAndAllPending);
}
}
m_game->OnSuspending(); // Exiting due to window close, so save state.
}
更新
請參閱<管理遊戲流程>主題,取得有關如何在 GameMain::Update 方法中更新遊戲狀態的詳細資訊。
轉譯
從 GameMain::Run 呼叫 GameRenderer::Render 方法,即可實作轉譯。
如已啟用立體轉譯,則有兩個轉譯階段,分別是左眼和右眼轉譯。 在每個轉譯階段,我們都會將轉譯目標與深度樣板檢視繫結至裝置。 之後則會清除深度樣板檢視。
注意
使用其他方法也可做到立體轉譯,例如:使用頂點執行個體或幾何著色器的單階段立體轉譯。 雙階段轉譯方法較慢,但更方便達到立體轉譯。
一旦遊戲開始執行並載入資源,我們就會更新投影矩陣,每轉譯階段更新一次。 每個檢視的物件都稍有不同。 接下來,我們要設定圖形轉譯管線。
注意
請參閱<建立及載入 DirectX 圖形資源>,瞭解有關如何載入資源的詳細資訊。
在此範例遊戲中,轉譯器的用途是對所有物件使用標準頂點配置。 這可簡化著色器設計,且無論物件形狀為何,著色器之間都能輕鬆變更。
GameRenderer::Render 方法
我們將 Direct3D 內容設定為使用輸入頂點配置。 「輸入配置」物件會描述頂點緩衝區資料如何串流至轉譯管線。
接下來,我們將 Direct3D 內容設為採用稍早定義的常數緩衝區,頂點著色器管線階段和像素著色器管線階段會用到這些緩衝區。
注意
請參閱<轉譯架構 II:遊戲轉譯>,瞭解有關常數緩衝區定義的詳細資訊。
管線中所有的著色器都會使用相同的輸入配置和常數緩衝區集,因此每格畫面都要設定一次。
void GameRenderer::Render()
{
bool stereoEnabled{ m_deviceResources->GetStereoState() };
auto d3dContext{ m_deviceResources->GetD3DDeviceContext() };
auto d2dContext{ m_deviceResources->GetD2DDeviceContext() };
int renderingPasses = 1;
if (stereoEnabled)
{
renderingPasses = 2;
}
for (int i = 0; i < renderingPasses; i++)
{
// Iterate through the number of rendering passes to be completed.
// 2 rendering passes if stereo is enabled.
if (i > 0)
{
// Doing the Right Eye View.
ID3D11RenderTargetView* const targets[1] = { m_deviceResources->GetBackBufferRenderTargetViewRight() };
// Resets render targets to the screen.
// OMSetRenderTargets binds 2 things to the device.
// 1. Binds one render target atomically to the device.
// 2. Binds the depth-stencil view, as returned by the GetDepthStencilView method, to the device.
// For more info, see
// https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-omsetrendertargets
d3dContext->OMSetRenderTargets(1, targets, m_deviceResources->GetDepthStencilView());
// Clears the depth stencil view.
// A depth stencil view contains the format and buffer to hold depth and stencil info.
// For more info about depth stencil view, go to:
// https://learn.microsoft.com/windows/uwp/graphics-concepts/depth-stencil-view--dsv-
// A depth buffer is used to store depth information to control which areas of
// polygons are rendered rather than hidden from view. To learn more about a depth buffer,
// go to: https://learn.microsoft.com/windows/uwp/graphics-concepts/depth-buffers
// A stencil buffer is used to mask pixels in an image, to produce special effects.
// The mask determines whether a pixel is drawn or not,
// by setting the bit to a 1 or 0. To learn more about a stencil buffer,
// go to: https://learn.microsoft.com/windows/uwp/graphics-concepts/stencil-buffers
d3dContext->ClearDepthStencilView(m_deviceResources->GetDepthStencilView(), D3D11_CLEAR_DEPTH, 1.0f, 0);
// Direct2D -- discussed later
d2dContext->SetTarget(m_deviceResources->GetD2DTargetBitmapRight());
}
else
{
// Doing the Mono or Left Eye View.
// As compared to the right eye:
// m_deviceResources->GetBackBufferRenderTargetView instead of GetBackBufferRenderTargetViewRight
ID3D11RenderTargetView* const targets[1] = { m_deviceResources->GetBackBufferRenderTargetView() };
// Same as the Right Eye View.
d3dContext->OMSetRenderTargets(1, targets, m_deviceResources->GetDepthStencilView());
d3dContext->ClearDepthStencilView(m_deviceResources->GetDepthStencilView(), D3D11_CLEAR_DEPTH, 1.0f, 0);
// d2d -- Discussed later under Adding UI
d2dContext->SetTarget(m_deviceResources->GetD2DTargetBitmap());
}
const float clearColor[4] = { 0.5f, 0.5f, 0.8f, 1.0f };
// Only need to clear the background when not rendering the full 3D scene since
// the 3D world is a fully enclosed box and the dynamics prevents the camera from
// moving outside this space.
if (i > 0)
{
// Doing the Right Eye View.
d3dContext->ClearRenderTargetView(m_deviceResources->GetBackBufferRenderTargetViewRight(), clearColor);
}
else
{
// Doing the Mono or Left Eye View.
d3dContext->ClearRenderTargetView(m_deviceResources->GetBackBufferRenderTargetView(), clearColor);
}
// Render the scene objects
if (m_game != nullptr && m_gameResourcesLoaded && m_levelResourcesLoaded)
{
// This section is only used after the game state has been initialized and all device
// resources needed for the game have been created and associated with the game objects.
if (stereoEnabled)
{
// When doing stereo, it is necessary to update the projection matrix once per rendering pass.
auto orientation = m_deviceResources->GetOrientationTransform3D();
ConstantBufferChangeOnResize changesOnResize;
// Apply either a left or right eye projection, which is an offset from the middle
XMStoreFloat4x4(
&changesOnResize.projection,
XMMatrixMultiply(
XMMatrixTranspose(
i == 0 ?
m_game->GameCamera().LeftEyeProjection() :
m_game->GameCamera().RightEyeProjection()
),
XMMatrixTranspose(XMLoadFloat4x4(&orientation))
)
);
d3dContext->UpdateSubresource(
m_constantBufferChangeOnResize.get(),
0,
nullptr,
&changesOnResize,
0,
0
);
}
// Update variables that change once per frame.
ConstantBufferChangesEveryFrame constantBufferChangesEveryFrameValue;
XMStoreFloat4x4(
&constantBufferChangesEveryFrameValue.view,
XMMatrixTranspose(m_game->GameCamera().View())
);
d3dContext->UpdateSubresource(
m_constantBufferChangesEveryFrame.get(),
0,
nullptr,
&constantBufferChangesEveryFrameValue,
0,
0
);
// Set up the graphics pipeline. This sample uses the same InputLayout and set of
// constant buffers for all shaders, so they only need to be set once per frame.
// For more info about the graphics or rendering pipeline, see
// https://learn.microsoft.com/windows/win32/direct3d11/overviews-direct3d-11-graphics-pipeline
// IASetInputLayout binds an input-layout object to the input-assembler (IA) stage.
// Input-layout objects describe how vertex buffer data is streamed into the IA pipeline stage.
// Set up the Direct3D context to use this vertex layout. For more info, see
// https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-iasetinputlayout
d3dContext->IASetInputLayout(m_vertexLayout.get());
// VSSetConstantBuffers sets the constant buffers used by the vertex shader pipeline stage.
// Set up the Direct3D context to use these constant buffers. For more info, see
// https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-vssetconstantbuffers
ID3D11Buffer* constantBufferNeverChanges{ m_constantBufferNeverChanges.get() };
d3dContext->VSSetConstantBuffers(0, 1, &constantBufferNeverChanges);
ID3D11Buffer* constantBufferChangeOnResize{ m_constantBufferChangeOnResize.get() };
d3dContext->VSSetConstantBuffers(1, 1, &constantBufferChangeOnResize);
ID3D11Buffer* constantBufferChangesEveryFrame{ m_constantBufferChangesEveryFrame.get() };
d3dContext->VSSetConstantBuffers(2, 1, &constantBufferChangesEveryFrame);
ID3D11Buffer* constantBufferChangesEveryPrim{ m_constantBufferChangesEveryPrim.get() };
d3dContext->VSSetConstantBuffers(3, 1, &constantBufferChangesEveryPrim);
// Sets the constant buffers used by the pixel shader pipeline stage.
// For more info, see
// https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-pssetconstantbuffers
d3dContext->PSSetConstantBuffers(2, 1, &constantBufferChangesEveryFrame);
d3dContext->PSSetConstantBuffers(3, 1, &constantBufferChangesEveryPrim);
ID3D11SamplerState* samplerLinear{ m_samplerLinear.get() };
d3dContext->PSSetSamplers(0, 1, &samplerLinear);
for (auto&& object : m_game->RenderObjects())
{
// The 3D object render method handles the rendering.
// For more info, see Primitive rendering below.
object->Render(d3dContext, m_constantBufferChangesEveryPrim.get());
}
}
// Start of 2D rendering
...
}
}
基本轉譯
轉譯場景時,應循環查看所有需要轉譯的物件。 以下步驟將針對各物件重複執行 (基本類型)。
- 使用模型的世界轉換矩陣和材質資訊,更新常數緩衝區 (m_constantBufferChangesEveryPrim)
- m_constantBufferChangesEveryPrim 涵蓋每個物件的參數。 其中包括物件至世界的轉換矩陣,以及用於計算光源的色彩和反射指數等材質屬性。
- 將 Direct3D 內容設為使用網格物件資料的輸入頂點配置,以將資料串流至轉譯管線的輸入組合器 (IA) 階段。
- 將 Direct3D 內容設為在 IA 階段中使用索引緩衝區。 提供基本資訊:類型、資料順序。
- 提交繪製呼叫,以繪製已建立索引的基本非執行個體。 GameObject::Render 方法會以指定基本類型專用的資料更新基本常數緩衝區。 這會導致對內容呼叫 DrawIndexed,以繪製該基本類型的幾何形狀。 具體而言,此繪製呼叫會將命令和資料排入圖形處理器 (GPU) 的佇列,並由常數緩衝區資料參數化。 每個繪製呼叫會對各頂點執行一次頂點著色器,然後對基本類型中各個三角形的每個像素,執行一次像素著色器。 紋理是像素著色器進行轉譯時所用狀態的一部分。
以下是使用多個常數緩衝區的原因。
- 遊戲會使用多個常數緩衝區,但各基本類型只需要更新這些緩衝區一次。 如先前所述,常數緩衝區就像是針對各基本類型執行的著色器輸入。 有些資料是靜態的 (m_constantBufferNeverChanges);有些資料在整個畫面格中保持固定 (m_constantBufferChangesEveryFrame),例如相機的位置;有些資料是基本類型特定,例如色彩和紋理 (m_constantBufferChangesEveryPrim)。
- 遊戲轉譯器會將這些輸入劃分為不同的常數緩衝區,以最佳化 CPU 和 GPU 使用的記憶體頻寬。 此方式也有助於將 GPU 需要追蹤的資料量降到最低。 GPU 有大量的命令佇列,且每次遊戲呼叫 Draw 時,該命令都會與相關聯的資料一起排入佇列。 遊戲更新基本常數緩衝區並發出下一個 Draw 命令時,圖形驅動程式會將該命令和相關聯的資料排入佇列。 如果遊戲繪製了 100 個基本類型,則佇列中可能會有 100 個常數緩衝區資料副本。 為了將遊戲傳送至 GPU 的資料量降到最低,遊戲會使用個別的基本常數緩衝區,只包含每個基本類型的更新項目。
GameObject::Render 方法
void GameObject::Render(
_In_ ID3D11DeviceContext* context,
_In_ ID3D11Buffer* primitiveConstantBuffer
)
{
if (!m_active || (m_mesh == nullptr) || (m_normalMaterial == nullptr))
{
return;
}
ConstantBufferChangesEveryPrim constantBuffer;
// Put the model matrix info into a constant buffer, in world matrix.
XMStoreFloat4x4(
&constantBuffer.worldMatrix,
XMMatrixTranspose(ModelMatrix())
);
// Check to see which material to use on the object.
// If a collision (a hit) is detected, GameObject::Render checks the current context, which
// indicates whether the target has been hit by an ammo sphere. If the target has been hit,
// this method applies a hit material, which reverses the colors of the rings of the target to
// indicate a successful hit to the player. Otherwise, it applies the default material
// with the same method. In both cases, it sets the material by calling Material::RenderSetup,
// which sets the appropriate constants into the constant buffer. Then, it calls
// ID3D11DeviceContext::PSSetShaderResources to set the corresponding texture resource for the
// pixel shader, and ID3D11DeviceContext::VSSetShader and ID3D11DeviceContext::PSSetShader
// to set the vertex shader and pixel shader objects themselves, respectively.
if (m_hit && m_hitMaterial != nullptr)
{
m_hitMaterial->RenderSetup(context, &constantBuffer);
}
else
{
m_normalMaterial->RenderSetup(context, &constantBuffer);
}
// Update the primitive constant buffer with the object model's info.
context->UpdateSubresource(primitiveConstantBuffer, 0, nullptr, &constantBuffer, 0, 0);
// Render the mesh.
// See MeshObject::Render method below.
m_mesh->Render(context);
}
MeshObject::Render 方法
void MeshObject::Render(_In_ ID3D11DeviceContext* context)
{
// PNTVertex is a struct. stride provides us the size required for all the mesh data
// struct PNTVertex
//{
// DirectX::XMFLOAT3 position;
// DirectX::XMFLOAT3 normal;
// DirectX::XMFLOAT2 textureCoordinate;
//};
uint32_t stride{ sizeof(PNTVertex) };
uint32_t offset{ 0 };
// Similar to the main render loop.
// Input-layout objects describe how vertex buffer data is streamed into the IA pipeline stage.
ID3D11Buffer* vertexBuffer{ m_vertexBuffer.get() };
context->IASetVertexBuffers(0, 1, &vertexBuffer, &stride, &offset);
// IASetIndexBuffer binds an index buffer to the input-assembler stage.
// For more info, see
// https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-iasetindexbuffer.
context->IASetIndexBuffer(m_indexBuffer.get(), DXGI_FORMAT_R16_UINT, 0);
// Binds information about the primitive type, and data order that describes input data for the input assembler stage.
// For more info, see
// https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-iasetprimitivetopology.
context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
// Draw indexed, non-instanced primitives. A draw API submits work to the rendering pipeline.
// For more info, see
// https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-drawindexed.
context->DrawIndexed(m_indexCount, 0, 0);
}
DeviceResources::Present 方法
我們會呼叫 DeviceResources::P resent 方法以顯示放置於緩衝區的內容。
我們以「交換鏈結」一詞表示向用戶顯示畫面格的緩衝區集合。 每次應用程式在螢幕上呈現新的畫面格,交換鏈結中的第一個緩衝區就會取代已顯示緩衝區的位置。 此程序稱為交換或翻轉。 如需詳細資訊,請參閱<交換鏈結>。
- IDXGISwapChain1 介面的 Present 方法會指示 DXGI 封鎖,直到系統開始垂直同步處理 (VSync),此時應用程式將進入睡眠狀態直到下次 VSync。 這可確保系統不會浪費任何週期,轉譯永遠不會在螢幕上顯示的畫面格。
- ID3D11DeviceContext3 介面的 DiscardView 方法會捨棄轉譯目標的內容。 只有系統已完全覆寫現有內容,此作業才有效。 若使用已修改 (dirty) 或捲動 (scroll) 矩形,則應移除此呼叫。
- 使用相同的 DiscardView 方法,捨棄深度樣板的內容。
- HandleDeviceLost 方法用來管理要移除的 device 情境。 如果裝置因連線中斷或驅動程序升級而遭到移除,則必須重新建立所有裝置資源。 如需詳細資訊,請參閱<處理 Direct3D 11 中的裝置已移除案例>。
提示
若要達到流暢的畫面播放速率,您必須確保 VSyncs 之間的時間能處理轉譯畫面格的工作量。
// Present the contents of the swap chain to the screen.
void DX::DeviceResources::Present()
{
// The first argument instructs DXGI to block until VSync, putting the application
// to sleep until the next VSync. This ensures we don't waste any cycles rendering
// frames that will never be displayed to the screen.
HRESULT hr = m_swapChain->Present(1, 0);
// Discard the contents of the render target.
// This is a valid operation only when the existing contents will be entirely
// overwritten. If dirty or scroll rects are used, this call should be removed.
m_d3dContext->DiscardView(m_d3dRenderTargetView.get());
// Discard the contents of the depth stencil.
m_d3dContext->DiscardView(m_d3dDepthStencilView.get());
// If the device was removed either by a disconnection or a driver upgrade, we
// must recreate all device resources.
if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET)
{
HandleDeviceLost();
}
else
{
winrt::check_hresult(hr);
}
}
下一步
本主題說明如何在顯示器上轉譯圖形,且下文將簡介一些使用的轉譯詞彙。 請參閱<轉譯架構 II:遊戲轉譯>主題,瞭解有關轉譯的詳細資訊,以及轉譯前如何準備必要資料。
詞彙和概念
簡單的遊戲場景
具有數個光源的物件即可構成一個簡單的遊戲場景。
物件形狀是由空間中的一組 X、Y、Z 座標所定義。 將轉換矩陣套用至 X、Y、Z 位置座標,即可確定遊戲世界中的實際轉譯位置。 也可能會有一組紋理座標 (U 和 V),用於指定材質如何套用至物件。 此可定義物件的表面屬性,並讓您查看物件表面是粗糙 (如網球) 或光滑亮澤 (如保齡球)。
轉譯架構會使用場景和物件資訊逐格重建場景,使其躍然於顯示器螢幕。
轉譯管線
轉譯管線是指將 3D 場景資訊轉譯為螢幕顯示影像的處理程序。 在 Direct3D 11 中,此管線可程式化。 您可以根據轉譯需求調整階段。 對於提供通用著色器核心的階段,可使用 HLSL 程式設計語言進行程式化。 這又稱為「圖形轉譯管線」,或簡稱「管線」。
若要建立此管線,請務必熟悉這些詳細資料。
- HLSL. 針對 UWP DirectX 遊戲,我們建議使用 HLSL 著色器模型 5.1 及更新版本。
- 著色器。
- 頂點著色器和像素著色器。
- 著色器階段。
- 各種著色器檔案格式。
如需詳細資訊,請參閱<瞭解 Direct3D 11 轉譯管線>和<圖形管線>。
HLSL
HLSL 是 DirectX 的高階著色器語言。 透過 HLSL,您可以為 Direct3D 管線建立類 C 程式語言的可程式化著色器。 如需詳細資訊,請參閱 HLSL。
著色器
您可將著色器視為一組指示,用來決定物件轉譯時的表面呈現方式。 使用 HLSL 進行程式設計的即稱為 HLSL 著色器。 [HLSL])(#hlsl) 著色器的原始程式碼檔案具有 .hlsl
副檔名。 這些著色器可在組建期間或執行階段進行編譯,並在執行階段設為適當的管線階段。 已編譯的著色器物件具有 .cso
副檔名。
Direct3D 9 著色器可用著色器模型 1、著色器模型 2 和著色器模型 3 進行設計;Direct3D 10 著色器只能使用著色器模型 4 設計。 Direct3D 11 著色器可在著色器模型 5 上設計。 Direct3D 11.3 和 Direct3D 12 可在著色器模型 5.1 上設計;Direct3D 12 也可在著色器模型 6 上設計。
頂點著色器和像素著色器
資料會以基本類型資料流的形式進入圖形管線,並由各種著色器 (例如:頂點和像素著色器) 進行處理。
頂點著色器會處理頂點,通常會執行轉換、面板設定和光源設定之類的作業。 像素著色器提供豐富的著色技術,例如:每像素光線和後續處理。 該著色器會結合常數變數、紋理資料、每頂點插補值及其他資料,以按像素產生輸出。
著色器階段
在轉譯管線中,定義一系列不同的著色器,以處理此基本類型資料流,就稱為著色器階段。 實際階段取決於 Direct3D 版本,但通常包含頂點、幾何和像素階段。 另外還有其他階段,例如:用於鑲嵌的輪廓和網域著色器,以及計算著色器。 這所有階段全都可以使用 HLSL 程式化。 如需詳細資訊,請參閱<圖形管線>。
各種著色器檔案格式
以下是著色器程式碼檔案副檔名。
- 副檔名為
.hlsl
的檔案會保留 [HLSL])(#hlsl) 原始程式碼。 - 副檔名為
.cso
的檔案會保留已編譯的著色器物件。 - 副檔名為
.h
的檔案是標頭檔,但在著色器程式碼內容中,該標頭檔會定義保留著色器資料的位元組陣列。 - 副檔名為
.hlsli
的檔案包含常數緩衝區的格式。 在範例遊戲中,檔案為 [著色器]>[ConstantBuffers.hlsli]。
注意
在執行階段載入 .cso
檔案,或在可執行檔程式碼中新增 .h
檔案,即可內嵌著色器。 不過,這兩種做法不能同時用於同一個著色器。
DirectX 深入解析
Direct3D 11 包含一組 API,可用來為圖形密集型應用程式建立圖形,例如遊戲應用程式,在此情況下,我們通常會想用好一點的圖形卡處理大量運算作業。 本節將簡要說明 Direct3D 11 圖形程式設計概念:資源、子資源、裝置和裝置內容。
資源
您可以將資源 (也稱為裝置資源) 想成是物件轉譯方式的相關資訊,例如:紋理、位置或色彩。 資源會提供資料給管線,並定義在場景中轉譯的內容。 您可以從遊戲媒體載入資源,或在執行階段動態建立。
事實上,資源即 Direct3D 管線在記憶體中可存取的區域。 為了讓管線有效地存取記憶體,提供給管線的資料 (例如,輸入幾何、著色器資源及紋理) 必須儲存在資源中。 所有 Direct3D 資源衍生兩種類型的資源︰緩衝區或紋理。 每個管線階段可使用多達 128 種資源。 如需詳細資訊,請參閱資源。
子資源
子資源一詞是指資源的子集。 Direct3D 可參照整個資源,也可參照資源的子集。 如需詳細資訊,請參閱<子資源>。
深度樣板
深度樣板資源包含用以保存深度和樣板資訊的格式和緩衝區。 其使用紋理資源建立而成。 如需如何建立深度樣板資源的詳細資訊,請參閱<設定深度樣板功能>。 我們會透過以 ID3D11DepthStencilView 介面實作的深度樣板檢視,存取深度樣板資源。
深度資訊會指出多邊形的哪些區域位在其他物件後面,有助我們判斷隱藏的區域。 樣板資訊指出哪些像素有遮罩。 該資訊可用來產生特殊效果,因為其可判斷是否要繪製像素;將位元設為 1 或 0。
如需詳細資訊,請參閱<深度樣板檢視>、<深度緩衝區>和<樣板緩衝區>。
轉譯目標
轉譯目標是可在轉譯階段結尾寫入的資源。 通常使用 ID3D11Device::CreateRenderTargetView 方法建立,並以交換鏈結背景緩衝區 (也就是資源) 作為輸入參數。
每個轉譯目標也應有對應的深度樣板檢視,因為在使用轉譯目標之前以 OMSetRenderTargets 進行設定時,亦需要深度樣板檢視。 我們透過以 ID3D11RenderTargetView 介面實作的轉譯目標檢視,存取轉譯目標資源。
裝置
您可以想像裝置是用來分配和終結物件、轉譯基本類型,以及透過圖形驅動程式與圖形卡通訊。
更精確來說,Direct3D 裝置其實是 Direct3D 的轉譯元件。 裝置封裝並儲存呈現狀態、執行轉換照明作業,並將影像點陣化到表面。 如需詳細資訊,請參閱<裝置>。
裝置是由 ID3D11Device 介面轉譯。 換句話說,ID3D11Device 介面代表虛擬顯示卡,用來建立裝置擁有的資源。
ID3D11Device 有不同版本。 ID3D11Device5 是最新版本,會將新方法新增至 ID3D11Device4 中的方法。 如需 Direct3D 如何與基礎硬體通訊的詳細資訊,請參閱<Windows 裝置驅動程式模型 (WDDM) 架構>。
每個應用程式都須有至少一個裝置;大部分應用程式只建立一個。 呼叫 D3D11CreateDevice 或 D3D11CreateDeviceAndSwapChain,並使用 D3D_DRIVER_TYPE 旗標指定驅動程式類型。 根據所需的功能,各裝置都可使用一或多項裝置內容。 如需詳細資訊,請參閱<D3D11CreateDevice 函式>。
裝置內容
裝置內容可用來設定管線狀態,並使用裝置擁有的資源產生轉譯命令。
Direct3D 11 會實作兩類裝置內容:一種用於立即轉譯,另一種用於延遲轉譯。這兩種內容都以 ID3D11DeviceContext 介面轉譯。
ID3D11DeviceContext 介面有不同版本;ID3D11DeviceContext4 會將新方法新增至 ID3D11DeviceContext3 中的方法。
Windows 10 Creators Update 已引進 ID3D11DeviceContext4,其為最新版的 ID3D11DeviceContext 介面。 若應用程式的目標設定為 Windows 10 Creators Update 和更新版本,則應使用此介面,而非舊版介面。 如需詳細資訊,請參閱<ID3D11DeviceContext4>。
DX::DeviceResources
DX::DeviceResources 類別位於 DeviceResources.cpp/.h 檔案,並控制所有 DirectX 裝置資源。
緩衝區
緩衝區資源是完整類型化資料的集合,可依元素區分。 您可使用緩衝區儲存各種資料,包括位置向量、法線向量、頂點緩衝區中的紋理座標、索引緩衝區中的索引或裝置狀態。 緩衝區項目可包含封裝的資料值 (例如 R8G8B8A8 表面值)、單一 8 位元整數,或四個 32 位元浮點值。
有三種類型的緩衝區可用:頂點緩衝區、索引緩衝區和常數緩衝區。
頂點緩衝區
包含用來定義幾何的頂點資料。 頂點資料包含位置座標、色彩資料、紋理座標資料,以及法線資料等。
索引緩衝區
包含頂點緩衝區的整數位移,可用來更有效率地轉譯基本類型。 索引緩衝區包含一組循序的 16 位元或 32 位元索引;每個索引都會用來識別頂點緩衝區中的頂點。
常數緩衝區 (又稱「著色器常數緩衝區」)
可讓您有效率地將著色器資料提供給管線。 對於執行每個基本類型的著色器,您可將常數緩衝區作為著色器的輸入,並儲存轉譯管線的資料流輸出階段結果。 在概念上,常數緩衝區看起來就像是單一元素頂點緩衝區。
緩衝區的設計和實作
您可以根據數據類型設計緩衝區,例如:在範例遊戲中,可設計一個緩衝區用於靜態資料、一個用於在整個畫面保持不變的資料,還有一個用於基本類型專用的資料。
所有緩衝區類型都是由 ID3D11Buffer 介面封裝,您可呼叫 ID3D11Device::CreateBuffer 以建立緩衝區資源。 但是必須先將緩衝區繫結至管線,才能存取該緩衝區。 緩衝區可同時繫結至多個管線階段,以進行讀取。 緩衝區也可繫結至單一管線階段以進行寫入,但無法繫結同一個緩衝區同時進行讀取和寫入。
您可以透過下列方式繫結緩衝區。
- 呼叫 ID3D11DeviceContext 方法 (例如:ID3D11DeviceContext::IASetVertexBuffers 和 ID3D11DeviceContext::IASetIndexBuffer) 以繫結至輸入組合器階段。
- 呼叫 ID3D11DeviceContext::SOSetTargets 以繫結至串流輸出階段。
- 呼叫著色器方法 (例如:ID3D11DeviceContext::VSSetConstantBuffers) 以繫結至著色器階段。
如需詳細資訊,請參閱<Direct3D 11 中的緩衝區簡介>。
DXGI
Microsoft DirectX Graphics Infrastructure (DXGI) 是一個子系統,可封裝 Direct3D 所需的一些低階工作。 在多執行緒應用程式中使用 DXGI 時,請務必特別小心,確保不會發生鎖死。 如需詳細資訊,請參閱<多執行緒和 DXGI>
功能層級
Direct3D 11 引進了「功能層級」概念,用於處理新機器和現有機器中的多樣視訊卡。 功能層級是一組定義明確的圖形處理器 (GPU) 功能。
每張視訊卡會根據安裝的 GPU 實作特定層級的 DirectX 功能。 在舊版 Microsoft Direct3D 中,您可以找出實作視訊卡的 Direct3D 版本,然後據以進行應用程式的程式設計。
若使用功能層級,您建立裝置時,可嘗試為要求的功能層級建立裝置。 若裝置可正常建立,代表已有該功能層級;若不能建立,代表硬體不支援該功能層級。 您可以嘗試在較低的功能層級重新建立裝置,或選擇結束應用程式。 例如,12_0 功能層級須使用 Direct3D 11.3 或 Direct3D 12,以及著色器模型 5.1。 如需詳細資訊,請參閱<Direct3D 功能層級:各功能層級概觀>。
若使用功能層級,您可以開發適用於 Direct3D 9、Microsoft Direct3D 10 或 Direct3D 11 的應用程式,然後在 9、10 或 11 硬體上執行該應用程式 (但有些例外狀況)。 如需詳細資訊,請參閱<Direct3D 功能層級>。
立體轉譯
立體轉譯可用來增強深度錯覺。 其使用兩個影像,分別從左、右眼在顯示螢幕上呈現場景。
在數學上,我們會採用立體投影矩陣 (一般單眼投影矩陣的輕微左右水平位移),以達到此目的。
我們進行了兩個轉譯階段,在此範例遊戲中完成立體轉譯。
- 繫結至正確的轉譯目標、套用右投影,然後繪製基本物件。
- 繫結至左轉譯目標、套用左投影,然後繪製基本物件。
相機和座標空間
遊戲已有程式碼,可在自己的座標系統 (有時稱為世界空間或場景空間) 中更新世界。 所有物件 (包括相機) 都是在這個空間定位和定向。 如需詳細資訊,請參閱<座標系統>。
頂點著色器會使用下列演算法進行繁重的工作,將模型座標轉換成裝置座標 (其中,V 為向量、M 為矩陣)。
V(device) = V(model) x M(model-to-world) x M(world-to-view) x M(view-to-device)
M(model-to-world)
是將模型座標轉換成世界座標的矩陣,也稱為「世界轉換矩陣」。 此由基本類型提供。M(world-to-view)
是將世界座標轉換成檢視座標的矩陣,也稱為「檢視轉換矩陣」。- 此由相機的檢視矩陣提供。 由相機的位置及視角向量定義 (look at 向量直接從相機指向場景;look up 向量則是垂直向上)。
- 在範例遊戲中,m_viewMatrix 是檢視轉換矩陣,使用 Camera::SetViewParams 計算而得。
M(view-to-device)
是將檢視座標轉換成裝置座標的矩陣,也稱為「投影轉換矩陣」。- 此由相機的投影提供。 可提供最終場景中實際可見多少空間的相關資訊。 視野 (FoV)、外觀比例和裁剪平面可定義投影轉換矩陣。
- 在範例遊戲中,m_projectionMatrix 可定義投影座標的轉換,使用 Camera::SetProjParams 計算而得 (若為立體投影,可使用兩個投影矩陣:兩個視角各一)。
VertexShader.hlsl
中的著色器程式碼會從常數緩衝區載入這些向量和矩陣,並針對每個頂點執行此轉換。
座標轉換
Direct3D 使用三個轉換,將 3D 模型座標變更為像素座標 (螢幕空間)。 這些轉換是世界轉換、檢視轉換和投影轉換。 如需詳細資訊,請參閱<轉換概觀>。
世界轉換矩陣
世界轉換會將座標從模型空間 (頂點是相對於模型本地原點來定義) 變更為世界空間 (頂點是相對於場景中所有物件通用的原點來定義)。 基本上,世界轉換是將模型置於世界 (顧名思義)。 如需詳細資訊,請參閱<世界轉換>。
檢視轉換矩陣
檢視轉換在世界空間中定位檢視器,並將頂點轉換為相機空間。 在相機空間中,相機 (檢視器) 位於原點,並朝正向 z 軸。 如需詳細資訊,請移至<檢視轉換>頁面。
投影轉換矩陣
投影轉換會將檢視範圍轉換為長方體形狀。 檢視範圍是場景中的 3D 體積,定位相對於檢視區相機。 檢視區是 2D 矩形,3D 場景會投影至該區。 如需詳細資訊,請參閱<檢視區和裁剪>
由於檢視範圍的近端小於遠端,因此會使相機附近的物件產生擴大效果;這也是將透視圖套用至場景的方式。 因此,接近玩家的物件看起來較大;較遠的物件看起來較小。
在數學上,投影轉換通常是一個縮放比例和透視投影的矩陣。 運作方式如同相機鏡頭。 如需詳細資訊,請參閱<投影轉換>。
取樣器狀態
取樣器狀態會以紋理定址模式、篩選和詳細程度,決定紋理資料的取樣方式。 每次從紋理讀取紋理像素 (texel) 時都會進行取樣。
紋理包含紋理像素陣列。 每個紋理像素的位置都以 (u,v)
表示,其中 u
是寬度、v
是高度,且會根據紋理寬度和高度對應至 0 到 1 之間的值。 取樣紋理時,產生的紋理座標可用來定址紋理像素。
紋理座標低於 0 或高於 1 時,紋理定址模式會定義紋理座標如何處理紋理像素的位置。 例如:使用 TextureAddressMode.Clamp時,所有在 0-1 範圍以外的座標在取樣前的最大值都限為 1、最小值則限為 0。
如果多邊形的紋理太大或太小,則篩選符合空間的紋理。 放大篩選會放大紋理,縮小篩選則減少紋理,以符合較小的區域。 紋理放大會針對一或多個位址重複執行取樣紋理像素,這會產生較模糊的影像。 紋理縮小的作業比較複雜,因為需要將多個紋理像素值合併成單一值。 這有可能造成失真或鋸齒狀邊緣,視紋理資料而定。 最常見的縮小方式是使用 mipmap。 Mipmap 是多層級紋理。 每一層大小都會比前一層小 2 次方,直到 1 x 1 紋理。 使用縮小紋理時,遊戲會依轉譯當下所需的大小選擇最接近的 mipmap 層級。
BasicLoader 類別
BasicLoader 是簡單的載入器類別,支援從磁碟上的檔案載入著色器、紋理和網格。 其提供同步及非同步方法。 在此範例遊戲,BasicLoader.h/.cpp
檔案位於 [公用程式] 資料夾中。
如需詳細資訊,請參閱<基本載入器>。