備註
本主題是 使用 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 應用程式架構 主題。
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方法中呼叫
如果已啟用 立體渲染,則有兩個渲染階段—一個用於左眼,一個用於右眼。 在每個轉譯階段中,我們會將轉譯目標與深度樣板檢視系結至裝置。 我們也在之後清除深度模板視圖。
備註
立體轉譯可以使用其他方法達成,例如使用頂點實例或幾何著色器的單一傳遞立體聲。 雙渲染傳遞方法速度較慢,但更方便達成立體渲染。
一旦遊戲執行,並載入資源,我們會更新 投影矩陣,每個轉譯階段一次。 物件在每個檢視中稍有不同。 接下來,我們會 設定圖形轉譯管線。
備註
如需如何載入資源的詳細資訊,請參閱 建立和載入 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::Present 方法來顯示我們在緩衝區中儲存的內容。
我們會使用「交換鏈」一詞來指稱用於向用戶顯示影格的緩衝區集合。 每次應用程式呈現新畫面帧時,交換鏈中的第一個緩衝區會取代已顯示的緩衝區。 此過程稱為交換或翻轉。 如需詳細資訊,請參閱 交換鏈結。
- IDXGISwapChain1 介面的 Present 方法會指示 DXGI 封鎖直到進行垂直同步處理 (VSync) 為止,讓應用程式進入睡眠狀態,直到下一個 VSync 為止。 這可確保您不會浪費任何計算資源在呈現那些永遠不會顯示在螢幕上的畫面。
- ID3D11DeviceContext3 介面的 DiscardView 方法會捨棄 轉譯目標的內容。 只有在將完全覆寫現有內容時,這個作業才有效。 如果使用髒矩形或捲動矩形,則應該移除此函數。
- 使用相同的 DiscardView 方法,捨棄 深度模板的內容。
- HandleDeviceLost 方法用於管理 裝置 被移除的情況。 如果裝置已透過中斷連線或驅動程序升級移除,則必須重新建立所有裝置資源。 如需詳細資訊,請參閱 在 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 可以參考整個資源,也可以參考資源的子集。 如需詳細資訊,請參閱 子資源。
深度模板
深度模板資源包含格式和緩衝區,用來保存深度和模板資訊。 它是使用紋理資源建立的。 如需如何建立深度樣板資源的詳細資訊,請參閱 設定 Depth-Stencil 功能。 我們會透過使用 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中的方法。
ID3D11DeviceContext4 是在 Windows 10 Creators Update 中引進,而且是 ID3D11DeviceContext 介面的最新版本。 以 Windows 10 Creators Update 和更新版本為目標的應用程式應該使用此介面,而不是舊版。 如需詳細資訊,請參閱 ID3D11DeviceContext4。
DX::D eviceResources
DX::D eviceResources 類別位於 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 圖形基礎結構 (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)是將世界座標轉換為檢視座標的轉換矩陣,也稱為 檢視轉換矩陣。- 這是由相機的視圖矩陣所提供。 它是由相機的位置和視向向量所定義(朝向 的視向向量,從相機直接指向場景,而 上視 的向量則與其垂直向上)。
- 在範例遊戲中, m_viewMatrix 是檢視轉換矩陣,並使用 Camera::SetViewParams 來計算。
-
M(view-to-device)是檢視座標到裝置座標的轉換矩陣,也稱為 投影轉換矩陣。- 這是由相機的投影所提供。 它提供有關最終場景中實際可見多少空間的資訊。 視野(FoV)、長寬比和裁剪平面會定義投影轉換矩陣。
- 在範例遊戲中,m_projectionMatrix 定義了到投影座標的轉換,並使用 Camera::SetProjParams 進行計算。針對立體投影,您可以使用兩個投影矩陣,每個眼睛的視角各一個。
中的 VertexShader.hlsl 著色器程式代碼會從常數緩衝區載入這些向量和矩陣,並針對每個頂點執行此轉換。
座標轉換
Direct3D 使用三個轉換,將 3D 模型座標變更為圖元座標(螢幕空間)。 這些轉換是世界轉換、檢視轉換和投影轉換。 如需詳細資訊,請參閱 轉換概觀。
世界轉換矩陣
世界變換會將座標從頂點相對於模型的本地原點定義的模型空間變更為世界空間,在這裡,頂點是相對於場景中所有物件所共有的原點定義的。 本質上,世界變換是將模型放入現實世界,這就是其名稱的由來。 如需詳細資訊,請參閱 世界轉換。
檢視轉換矩陣
視圖轉換會確定查看器在世界空間中的位置,並將頂點轉換成攝影機空間。 在相機空間中,相機或觀察者位於原點,朝向 z 軸的正方向看。 如需詳細資訊,請前往 檢視轉換。
投影轉換矩陣
檢視錐台的投影轉換會將其轉換為長方體形狀。 視錐體是相對於檢視區攝影機之場景的 3D 立體空間。 檢視區是投影 3D 場景的 2D 矩形。 如需詳細資訊,請參閱 檢視區與裁剪
由於視錐體的近端小於遠端,因此其效果是擴大接近相機的物件;這就是如何對場景運用透視的方法。 因此,較接近玩家的物件看起來更大;較遠的物件看起來較小。
在數學中,投影轉換是一個矩陣,通常是比例縮放和透視投影。 它的運作方式就像相機的鏡頭一樣。 如需詳細資訊,請參閱 投影轉換。
取樣器狀態
取樣器狀態會決定如何使用紋理尋址模式、篩選和詳細數據層級來取樣紋理數據。 每次從紋理讀取紋理圖元(或材質)時,都會進行取樣。
紋理中包含一個紋素陣列。 每個紋素的位置由 (u,v)表示,其中 u 為寬度,v 為高度,並根據材質的寬度和高度映射到 0 到 1 之間。 在紋理取樣時,生成的紋理座標用來定位紋素。
當紋理座標低於 0 或高於 1 時,紋理位址模式會定義紋理座標如何對應到紋素位置。 例如,使用 TextureAddressMode.Clamp 時,0-1 範圍以外的任何座標會固定為最大值 1,並在取樣前的最小值為 0。
如果多邊形的紋理太大或太小,則會篩選紋理以符合空間。 放大篩選會放大紋理,縮小篩選會減少紋理以符合較小的區域。 紋理放大會針對一或多個會產生模糊影像的位址重複取樣紋素。 紋理縮製比較複雜,因為它需要將多個紋素值結合成單一值。 由於材質數據的緣故,這可能會導致影像鋸齒化或出現鋸齒狀邊緣。 進行縮製的最常用方法是使用Mipmap。 Mipmap 是多層次紋理。 每個層級的大小是比上一個層級小 2 的乘冪,低於 1x1 紋理。 使用縮製時,遊戲會選擇最接近轉譯時間所需大小的Mipmap層級。
BasicLoader 類別
BasicLoader 是簡單的載入器類別,可支援從磁碟上的檔案載入著色器、紋理和網格。 它同時提供同步和異步方法。 在此範例遊戲中,檔案 BasicLoader.h/.cpp 位於 [公用程式 ] 資料夾中。
如需詳細資訊,請參閱 基本載入器。