注
このトピックは、DirectX チュートリアル シリーズを使用して簡単なユニバーサル Windows プラットフォーム (UWP) ゲームを作成する
ここまでは、ユニバーサル Windows プラットフォーム (UWP) ゲームを構築する方法と、ゲームのフローを処理するステート マシンを定義する方法について説明しました。 次に、レンダリング フレームワークを開発する方法を学習します。 サンプル ゲームが Direct3D 11 を使用してゲーム シーンをレンダリングする方法を見てみましょう。
Direct3D 11 には、ゲームなどのグラフィックスを集中的に使用するアプリケーション用の 3D グラフィックスを作成するために使用できる高性能グラフィック ハードウェアの高度な機能へのアクセスを提供する API のセットが含まれています。
ゲーム グラフィックスを画面にレンダリングするということは、基本的に一連のフレームを画面上にレンダリングすることです。 各フレームでは、ビューに基づいて、シーンに表示されるオブジェクトをレンダリングする必要があります。
フレームをレンダリングするには、必要なシーン情報をハードウェアに渡して、画面に表示できるようにする必要があります。 画面に何かを表示する場合は、ゲームの実行が開始されたらすぐにレンダリングを開始する必要があります。
目標
UWP DirectX ゲームのグラフィックス出力を表示する基本的なレンダリング フレームワークを設定するには。 これは、これら 3 つの手順に大まかに分けることができます。
- グラフィックス インターフェイスへの接続を確立します。
- グラフィックスを描画するために必要なリソースを作成します。
- フレームをレンダリングしてグラフィックスを表示します。
このトピックでは、手順 1 と 3 をカバーするグラフィックスのレンダリング方法について説明します。
レンダリング フレームワーク II: ゲームレンダリング では、レンダリング フレームワークを設定する方法と、レンダリングを実行する前にデータを準備する方法について説明します。
始めましょう
基本的なグラフィックスとレンダリングの概念を理解することをお勧めします。 Direct3D とレンダリングを初めて使用する場合は、このトピックで使用するグラフィックスとレンダリングの用語の簡単な説明については、「 用語と概念 」を参照してください。
このゲームでは、 GameRenderer クラスは、このサンプル ゲームのレンダラーを表します。 ゲーム ビジュアルの生成に使用されるすべての Direct3D 11 オブジェクトと Direct2D オブジェクトの作成と保守を担当します。 また、レンダリングするオブジェクトの一覧を取得するために使用される Simple3DGame オブジェクトへの参照と、ヘッドアップ ディスプレイ (HUD) のゲームの状態も保持されます。
チュートリアルのこの部分では、ゲームで 3D オブジェクトをレンダリングすることに重点を置きます。
グラフィックス インターフェイスへの接続を確立する
レンダリング用のハードウェアへのアクセスの詳細については、「ゲームの UWP アプリ フレームワークの定義 」トピックを参照してください。
App::Initialize メソッド
次に示すように、
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 メソッドを呼び出すことによって実装されます。
ステレオ レンダリングが有効になっている場合は、2 つのレンダリング パスがあります。1 つは左目用、1 つは右側用です。 各レンダリング パスでは、レンダー ターゲットと深度ステンシル ビューをデバイスにバインドします。 後で深度ステンシル ビューもクリアします。
注
ステレオ レンダリングは、頂点インスタンス化シェーダーやジオメトリ シェーダーを使用したシングル パス ステレオなどの他の方法を使用して実現できます。 2-rendering-passes メソッドは、ステレオ レンダリングを実現するために低速ですが、より便利な方法です。
ゲームが実行され、リソースが読み込まれたら、レンダリング パスごとに 1 回、 プロジェクション マトリックスを更新します。 オブジェクトは各ビューとは少し異なります。 次に、 グラフィックス レンダリング パイプラインを設定します。
注
リソースの読み込み方法の詳細については、「DirectX グラフィック リソースの作成と読み込み」を参照してください。
このサンプル ゲームでは、レンダラーはすべてのオブジェクトで標準の頂点レイアウトを使用するように設計されています。 これによりシェーダーの設計が簡素化され、オブジェクトのジオメトリに関係なくシェーダー間で簡単に変更できます。
GameRenderer::Render メソッド
入力頂点レイアウトを使用するように Direct3D コンテキストを設定します。 入力レイアウト オブジェクトは、頂点バッファー データを レンダリング パイプラインにストリーミングする方法を記述します。
次に、頂点 シェーダー パイプライン ステージと ピクセル シェーダー パイプライン ステージで使用される、先ほど定義した定数バッファーを使用するように Direct3D コンテキストを設定します。
注
定数バッファーの定義の詳細については、「 レンダリング フレームワーク II: ゲーム レンダリング」を参照してください。
同じ入力レイアウトと定数バッファーのセットがパイプライン内のすべてのシェーダーに使用されるため、フレームごとに 1 回設定されます。
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には、各オブジェクトのパラメーターが含まれています。 これには、オブジェクト対ワールド変換マトリックス、および照明計算用の色や反射指数などのマテリアル プロパティが含まれます。
- レンダリング パイプラインの入力アセンブラー (IA) ステージにストリーミングされるメッシュ オブジェクト データの入力頂点レイアウトを使用するように Direct3D コンテキストを設定します。
- IA ステージで インデックス バッファー を使用するように Direct3D コンテキストを設定します。 プリミティブ情報 (型、データ順序) を指定します。
- 描画呼び出しを送信して、インデックス付きのインスタンス化されていないプリミティブを描画します。 GameObject::Render メソッドは、プリミティブ定数バッファーを特定のプリミティブに固有のデータで更新します。 これにより、コンテキストでDrawIndexedが呼び出され、各プリミティブのジオメトリが描画されます。 具体的には、この描画呼び出しでは、定数バッファー データによってパラメーター化されたコマンドとデータがグラフィックス処理装置 (GPU) にキューに格納されます。 各描画呼び出しでは、頂点シェーダーが頂点ごとに 1 回実行され、プリミティブ内の各三角形のピクセルごとに ピクセル シェーダーが 1 回 されます。 テクスチャは、レンダリングを行うためにピクセル シェーダーが使用する状態の一部です。
複数の定数バッファーを使用する理由を次に示します。
- ゲームでは複数の定数バッファーを使用しますが、これらのバッファーを更新する必要があるのはプリミティブごとに 1 回だけです。 前述のように、定数バッファーは、各プリミティブに対して実行されるシェーダーへの入力に似ています。 一部のデータは静的 (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 メソッドは、垂直同期 (VSync) が行われるまでブロックするように DXGI に指示し、アプリケーションを次の VSync までスリープ状態にします。 これにより、画面に表示されないフレームをレンダリングするサイクルを無駄にしないようにします。
- ID3D11DeviceContext3 インターフェイスの DiscardView メソッドは、レンダー ターゲットの内容を破棄します。 これは、既存のコンテンツが完全に上書きされる場合にのみ有効な操作です。 ダーティまたはスクロールレクトが使用されている場合は、この呼び出しは削除する必要があります。
- 同じ DiscardView メソッドを使用して、 深度ステンシルの内容を破棄します。
- HandleDeviceLost メソッドは、削除されるデバイスのシナリオを管理するために使用されます。 切断またはドライバーのアップグレードによってデバイスが削除された場合は、すべてのデバイス リソースを再作成する必要があります。 詳細については、「 Direct3D 11 でデバイスが削除されたシナリオを処理する」を参照してください。
ヒント
スムーズなフレーム レートを実現するには、フレームをレンダリングする作業量が VSync 間の時間に収まるようにする必要があります。
// 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 リソースの派生元となるリソースには、バッファーとテクスチャの 2 種類があります。 パイプライン ステージごとに最大 128 個のリソースをアクティブにすることができます。 詳細については、リソースを参照してください。
サブリソース
サブリソースという用語は、リソースのサブセットを指します。 Direct3D は、リソース全体を参照することも、リソースのサブセットを参照することもできます。 詳細については、「サブリソース 」を参照してください。
深度ステンシル
深度ステンシル リソースには、深度とステンシルの情報を保持する形式とバッファーが含まれています。 テクスチャ リソースを使用して作成されます。 深度ステンシル リソースを作成する方法の詳細については、「 Depth-Stencil 機能の構成」を参照してください。 ID3D11DepthStencilView インターフェイスを使用して実装された深度ステンシル ビューを使用して、深度ステンシル リソースにアクセスします。
深度情報は、非表示になっているポリゴンを特定できるように、他のポリゴンの背後にある領域を示します。 ステンシル情報は、マスクされているピクセルを示します。 ピクセルが描画されるかどうかを決定するため、特殊効果を生成するために使用できます。はビットを 1 または 0 に設定します。
詳細については、「深度ステンシル ビュー 」、深度バッファー 、ステンシル バッファー を参照してください。
レンダー ターゲット
レンダー ターゲットは、レンダー パスの最後に書き込むことができるリソースです。 これは通常、入力パラメーターとしてスワップ チェーン バック バッファー (リソース) を使用して ID3D11Device::CreateRenderTargetView メソッドを使用して作成されます。
また、各レンダー ターゲットには対応する深度ステンシル ビューが必要です。 OMSetRenderTargets を 使用してレンダー ターゲットを使用する前に設定する場合は、深度ステンシル ビューも必要になるためです。 ID3D11RenderTargetView インターフェイスを使用して実装されたレンダー ターゲット ビューを使用して、レンダー ターゲット リソースにアクセスします。
デバイス
デバイスは、オブジェクトの割り当てと破棄、プリミティブのレンダリング、グラフィックス ドライバーを介したグラフィックス カードとの通信を行う方法として考えることができます。
より正確に説明するために、Direct3D デバイスは Direct3D のレンダリング コンポーネントです。 デバイスは、レンダリング状態をカプセル化して格納し、変換と照明操作を実行し、イメージをサーフェスにラスタライズします。 詳細については、「デバイス」を参照してください。
デバイスは ID3D11Device インターフェイスによって表されます。 つまり、 ID3D11Device インターフェイスは仮想ディスプレイ アダプターを表し、デバイスが所有するリソースを作成するために使用されます。
ID3D11Device にはさまざまなバージョンがあります。 ID3D11Device5 は最新バージョンであり、 ID3D11Device4 のメソッドに新しいメソッドを追加します。 Direct3D が基になるハードウェアと通信する方法の詳細については、「 Windows デバイス ドライバー モデル (WDDM) アーキテクチャ」を参照してください。
各アプリケーションには少なくとも 1 つのデバイスが必要です。ほとんどのアプリケーションでは 1 つだけ作成されます。 D3D11CreateDevice または D3D11CreateDeviceAndSwapChain を呼び出し、ドライバーの種類を D3D_DRIVER_TYPE フラグで指定することで、コンピューターにインストールされているハードウェア ドライバーのデバイスを作成します。 各デバイスは、必要な機能に応じて、1 つ以上のデバイス コンテキストを使用できます。 詳細については、「 D3D11CreateDevice 関数」を参照してください。
デバイス コンテキスト
デバイス コンテキストは、パイプライン 状態
Direct3D 11 では、2 種類のデバイス コンテキストが実装されています。1 つは即時レンダリング用、もう 1 つは遅延レンダリング用です。両方のコンテキストは 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 ビット整数、または 4 つの 32 ビット浮動小数点値を含めることができます。
使用できるバッファーには、頂点バッファー、インデックス バッファー、定数バッファーの 3 種類があります。
頂点バッファー
ジオメトリの定義に使用する頂点データが含まれます。 頂点データには、位置座標、カラー データ、テクスチャ座標データ、標準データなどが含まれます。
インデックス バッファー
頂点バッファーへの整数オフセットが含まれており、プリミティブをより効率的にレンダリングするために使用されます。 インデックス バッファーには、16 ビットまたは 32 ビットインデックスのシーケンシャル セットが含まれています。各インデックスは、頂点バッファー内の頂点を識別するために使用されます。
定数バッファーまたはシェーダー定数バッファー
シェーダー データをパイプラインに効率的に提供できます。 プリミティブごとに実行されるシェーダーへの入力として定数バッファーを使用し、レンダリング パイプラインのストリーム出力ステージの結果を格納できます。 概念的には、定数バッファーは単一要素の頂点バッファーと同じように見えます。
バッファーの設計と実装
サンプル ゲームのように、データ型に基づいてバッファーを設計できます。たとえば、静的データ用に 1 つのバッファーが作成され、もう 1 つはフレーム上で一定のデータ用に作成され、もう 1 つはプリミティブに固有のデータ用です。
すべてのバッファー型は ID3D11Buffer インターフェイスによってカプセル化され、 ID3D11Device::CreateBuffer を呼び出すことによってバッファー リソースを作成できます。 ただし、バッファーにアクセスするには、その前にパイプラインにバインドする必要があります。 バッファーは、読み取りのために複数のパイプライン ステージに同時にバインドできます。 バッファーは、書き込みのために 1 つのパイプライン ステージにバインドすることもできます。ただし、同じバッファーを読み取りと書き込みの両方に同時にバインドすることはできません。
これらの方法でバッファーをバインドできます。
ID3D11DeviceContext::IASetVertexBuffers 、ID3D11DeviceContext::IASetIndexBuffer などの メソッドを呼び出して、入力アセンブラー ステージに渡します。 - ID3D11DeviceContext::SOSetTargets を呼び出してストリーム出力ステージに。
- ID3D11DeviceContext::VSSetConstantBuffers などのシェーダー メソッドを呼び出してシェーダー ステージに。
詳細については、「 Direct3D 11 のバッファーの概要」を参照してください。
DXGI(DirectX グラフィックス インフラストラクチャ)
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 機能レベル」を参照してください。
ステレオ レンダリング
ステレオ レンダリングは、深度の錯覚を高めるために使用されます。 2つの画像を使用します。1つは左目からのもので、もう1つは右目からのものです。それらを使ってディスプレイ画面にシーンを表示します。
数学的には、これを実現するために、通常のモノラル投影行列の右と左へのわずかな水平オフセットであるステレオ投影行列を適用します。
このサンプル ゲームでステレオ レンダリングを実現するために、2 つのレンダリング パスを実行しました。
- 右レンダー ターゲットにバインドし、右プロジェクションを適用してから、プリミティブ オブジェクトを描画します。
- 左レンダー ターゲットにバインドし、左プロジェクションを適用してから、プリミティブ オブジェクトを描画します。
カメラと座標空間
ゲームには、独自の座標系 (ワールド空間またはシーン空間とも呼ばれます) でワールドを更新するためのコードが用意されています。 カメラを含むすべてのオブジェクトは、この空間に配置され、方向付けされます。 詳細については、「 座標系」を参照してください。
頂点シェーダーは、次のアルゴリズム (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 を使用して計算された投影座標への変換を定義します (ステレオ 投影の場合は、2 つの投影マトリックスを使用します。1 つは目のビューごとに 1 つ)。
VertexShader.hlsl
のシェーダー コードには、定数バッファーからこれらのベクトルとマトリックスが読み込まれ、すべての頂点に対してこの変換が実行されます。
座標変換
Direct3D では 3 つの変換を使用して、3D モデル座標をピクセル座標 (画面空間) に変更します。 これらの変換は、ワールド変換、ビュー変換、およびプロジェクション変換です。 詳細については、変換の概要を参照してください。
ワールド変換行列
ワールド変換では、座標がモデル空間から変更され、頂点はモデルのローカル原点を基準にして定義されます。ワールド空間では、頂点はシーン内のすべてのオブジェクトに共通する原点を基準にして定義されます。 本質的には、ワールドトランスフォームはモデルを世界に配置します。そのため、この名前が付けられています。 詳細については、「 ワールド変換」を参照してください。
変換マトリックスを表示する
ビュー変換は、ワールド空間においてビューアーを配置し、頂点をカメラ空間に変換します。 カメラ空間では、カメラ (ビューアー) は原点にあり、正の z 方向を見ています。 詳細情報については、ビュー変換を参照してください。
プロジェクション変換マトリックス
投影変換は、視錐台を直方体形状に変換します。 視錐台(あるいは視野錐)は、ビューポートのカメラに対して相対的に配置されたシーン内の 3D ボリュームです。 ビューポートは、3D シーンが投影される 2D 四角形です。 詳細については、「ビューポートとクリッピング」を参照してください。
視錐台の近端は遠端よりも小さいため、カメラに近いオブジェクトを拡張する効果があります。これは、パースペクティブをシーンに適用する方法です。 したがって、プレイヤーに近いオブジェクトは大きく表示されます。さらに離れているオブジェクトは小さく表示されます。
数学的には、投影変換は、通常はスケールとパースペクティブプロジェクションの両方である行列です。 カメラのレンズのように機能します。 詳細については、プロジェクション変換を参照してください。
サンプラーの状態
サンプラー状態は、テクスチャ アドレッシング モード、フィルター処理、詳細レベルを使用してテクスチャ データをサンプリングする方法を決定します。 サンプリングは、テクスチャ ピクセル (またはテクセル) がテクスチャから読み取されるたびに実行されます。
テクスチャにはテクセルの配列が含まれています。 各テクセルの位置は (u,v)
で示されます。ここで、 u
は幅、 v
は高さであり、テクスチャの幅と高さに基づいて 0 から 1 の間でマップされます。 結果として得られるテクスチャ座標は、テクスチャをサンプリングするときにテクセルをアドレス指定するために使用されます。
テクスチャ座標が 0 以上 1 未満の場合、テクスチャ アドレス モードでは、テクスチャ座標がテクセル位置にどのように対応するかを定義します。 たとえば、 TextureAddressMode.Clamp を使用する場合、0 から 1 の範囲外の座標は、サンプリング前に最大値 1、最小値 0 にクランプされます。
テクスチャが多角形に対して大きすぎるか小さすぎる場合は、空間に合わせてテクスチャがフィルター処理されます。 拡大フィルターはテクスチャを拡大し、縮小フィルターはテクスチャをより小さな領域に収まるように縮小します。 テクスチャ倍率では、1 つ以上のアドレスに対してサンプル テクセルが繰り返され、ぼやけた画像が生成されます。 テクスチャの縮小は、複数のテクセル値を 1 つの値に結合する必要があるため、より複雑です。 これにより、テクスチャ データに応じて、エイリアシングやジャグエッジが発生する可能性があります。 縮小の最も一般的なアプローチは、ミップマップを使用することです。 ミップマップは複数レベルのテクスチャです。 各レベルのサイズは、前のレベルの 2 の累乗小さいものになり、1x1 のテクスチャになるまで続きます。 縮小を使用すると、ゲームはレンダリング時に必要なサイズに最も近いミップマップ レベルを選択します。
BasicLoader クラス
BasicLoader は、ディスク上のファイルからシェーダー、テクスチャ、メッシュを読み込むためのサポートを提供する単純なローダー クラスです。 同期メソッドと非同期メソッドの両方が提供されます。 このサンプル ゲームでは、 BasicLoader.h/.cpp
ファイルは Utilities フォルダーにあります。
詳細については、「基本的なローダー」を参照してください。