レンダリング フレームワーク I: レンダリングの概要

注意

このトピックは、「DirectX を使った単純なユニバーサル Windows プラットフォーム (UWP) ゲームの作成」チュートリアル シリーズの一部です。 リンク先のトピックでは、このシリーズのコンテキストを設定しています。

これまで、ユニバーサル Windows プラットフォーム (UWP) ゲームを構築する方法、ステート マシンを定義してゲームのフローを処理する方法について説明してきました。 ここでは、レンダリング フレームワークを開発する方法について説明します。 サンプル ゲームで Direct3D 11を使用して、ゲームのシーンをレンダリングする方法を見てみましょう。

Direct3D 11 には、ゲームなどのグラフィックスを多用するアプリケーションの 3D グラフィックスの作成に使用できる、高パフォーマンス グラフィックス ハードウェアの高度な機能へのアクセスを提供する API のセットが含まれています。

画面上のゲームのグラフィックのレンダリングは、基本的に画面上のフレームのシーケンスのレンダリングを意味します。 各フレームでは、ビューに基づいて、シーンに表示されているオブジェクトをレンダリングする必要があります。

フレームをレンダリングするには、画面に表示できるように、必要なシーンの情報をハードウェアに渡す必要があります。 何かを画面に表示するには、ゲームの実行を開始すると同時にレンダリングを開始する必要があります。

目標

基本的なレンダリング フレームワークを設定して、UWP DirectX ゲームのグラフィックス出力を表示すること。 おおまかに 3 つの手順に分けることができます。

  1. グラフィックス インターフェイスへの接続を確立する。
  2. グラフィックスを描画するために必要なリソースを作成する。
  3. フレームをレンダリングすることによってグラフィックスを表示する。

このトピックでは、手順 1 と 3 を取り上げて、グラフィックスをレンダリングする方法について説明します。

レンダリング フレームワーク II: ゲームのレンダリング」では手順 2 を取り上げて、レンダリング フレームワークを設定する方法と、レンダリングの前にデータを準備する方法について説明します。

作業開始

グラフィックスとレンダリングの基本的な概念を理解しておく必要があります。 Direct3D とレンダリングを使用して初めて開発を行う場合、このトピックで使用するグラフィックスとレンダリングの用語の簡単な説明については、「用語と概念」を参照してください。

このゲームでは、GameRenderer クラスがこのサンプル ゲームのレンダラーを表します。 レンダラーは、ゲームの視覚効果を生成するために使用する、すべての Direct3D 11 オブジェクトと Direct2D オブジェクトの作成と保持を担当します。 また、レンダリングするオブジェクトのリストとヘッドアップ ディスプレイ (HUD) 用にゲームの状態を取得するために使用される Simple3DGame オブジェクトへの参照も保持します。

このチュートリアルでは、ゲームの 3D オブジェクトのレンダリングに重点を置いています。

グラフィックス インターフェイスへの接続を確立する

レンダリングのためにハードウェアにアクセスする方法については、「ゲームの UWP アプリ フレームワークの定義」を参照してください。

App::Initialize メソッド

std::make_shared 関数は (以下に示すように) DX::DeviceResources への shared_ptr を作成するために使用されます。これもデバイスへのアクセスを提供します。

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 メソッド内で始まります。

単純なフローは次のとおりです。

  1. アップデート
  2. Render
  3. 存在

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 つのレンダリング パスがあります。 各レンダリング パスで、レンダー ターゲットと深度ステンシル ビューをデバイスにバインドします。 後で深度ステンシル ビューをクリアします。

注意

ステレオ レンダリングは、頂点のインスタンス化やジオメトリ シェーダーを使用する単一パス ステレオなど、他の方法で実現することもできます。 2 つのレンダリング パスを使用する方法は時間がかかりますが、ステレオ レンダリングを実現するのに便利な方法です。

ゲームが実行され、リソースが読み込まれたら、レンダリング パスごとに 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::Present メソッドを呼び出して、バッファーに配置した内容を表示します。

ユーザーにフレームを表示するために使用されるバッファーのコレクションという意味で、スワップ チェーンという用語を使用します。 アプリケーションが表示する新しいフレームを提供するたびに、スワップ チェーンの最初のバッファーが、表示されているバッファーの場所を取得します。 このプロセスは、スワップまたはフリップと呼ばれます。 詳しくは、「スワップ チェーン」をご覧ください。

  • IDXGISwapChain1 インターフェイスの Present メソッドは、DXGI に対して垂直同期 (VSync) が行われるまでブロックするよう指示し、アプリケーションを次の VSync までスリープ状態にします。 これによって、画面に表示されないフレームのレンダリングによるサイクルの無駄をなくします。
  • ID3D11DeviceContext3 インターフェイスの DiscardView メソッドは、レンダー ターゲットの内容を破棄します。 これは、既存の内容が完全に上書きされる場合にのみ有効な操作です。 dirty または scroll の rect を使用する場合は、この呼び出しを削除してください。
  • 同じ 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 プログラミング言語を使用することによってプログラミングできます。 これは、"グラフィックス レンダリング パイプライン" または単に "パイプライン" とも呼ばれます。

このパイプラインを作成するには、以下の詳細情報についての知識が必要です。

詳細については、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 という拡張子を持つファイルは、定数バッファーの形式を格納します。 サンプル・ゲームでは、このファイルは Shaders>ConstantBuffers.hlsli です。

注意

シェーダーを埋め込むには、実行時に .cso ファイルを読み込むか、または実行可能コードに .h ファイルを追加します。 ただし、同じシェーダーで両方を使用しないでください。

DirectX に関する高度な知識

Direct3D 11 は、ゲームなどのグラフィックスを多用するアプリケーションのグラフィックスを作成するのに役立つ一連の API であり、負荷の高い計算を処理するために優れたグラフィックス カードを使用できるようにします。 このセクションでは、Direct3D 11 グラフィックス プログラミングの概念 (リソース、サブリソース、デバイス、デバイス コンテキスト) について簡単に説明します。

リソース

リソース (デバイス リソースとも呼ばれる) は、テクスチャ、位置、色など、オブジェクトをレンダリングする方法に関する情報と考えてもかまいません。 リソースは、パイプラインにデータを提供し、シーン内でレンダリングされる対象を定義します。 リソースは、ゲーム メディアから読み込むことも、実行時に動的に作成することもできます。

リソースは、実際には、Direct3D パイプラインからアクセスできるメモリ内の領域です。 パイプラインでメモリに効率的にアクセスするには、パイプラインに渡すデータ (入力ジオメトリ、シェーダー リソース、テクスチャなど) をリソースに格納する必要があります。 すべての Direct3D リソースの派生元となるリソースは 2 種類あります。バッファーとテクスチャです。 各パイプライン ステージでは最大 128 個のリソースをアクティブにできます。 詳しくは、リソースに関する記事をご覧ください。

サブリソース

サブリソースという用語はリソースのサブセットを指します。 Direct3D は、リソース全体を参照することも、リソースのサブセットを参照することもできます。 詳しくは、「サブリソース」をご覧ください。

深度/ステンシル

深度/ステンシル リソースには、深度とステンシルの情報を保持するための形式とバッファーが含まれます。 このリソースは、テクスチャー リソースを使用して作成されます。 深度/ステンシル リソースを作成する方法について詳しくは、「深度/ステンシル機能の構成」を参照してください。 深度/ステンシル リソースにアクセスするには、ID3D11DepthStencilView インターフェイスを使って実装される深度/ステンシル ビュー使用します。

深度情報は、多角形のどの部分が他の領域の背後あるかを示すため、どの領域が隠れているのか判断できます。 ステンシル情報は、マスクするピクセルを示します。 ステンシル情報は、ピクセルを描画するかどうかを決定する (ビットを 1 または 0 に設定する) ため、特殊効果を生成するために使用できます。

詳細については、「深度ステンシル ビュー」、「深度バッファー」、「ステンシル バッファー」を参照してください。

レンダー ターゲット

レンダー ターゲットは、レンダリング パスの最後に書き込むことができるリソースです。 通常、ID3D11Device::CreateRenderTargetView メソッドで、入力パラメーターとしてスワップ チェーン バック バッファー (これもリソースです) を使用して作成されます。

各レンダー ターゲットには、対応する深度/ステンシル ビューも必要です。これは、レンダー ターゲットを使用する前に、OMSetRenderTargets を使用してレンダー ターゲットを設定するときに、深度/ステンシル ビューも必要となるためです。 レンダー ターゲット リソースにアクセスするには、ID3D11RenderTargetView インターフェイスを使用して実装されるレンダー ターゲット ビューを使用します。

Device

デバイスは、オブジェクトの割り当てと破棄、プリミティブのレンダリング、グラフィックス ドライバー経由でのグラフィックス カードとの通信を行うための方法であると考えることができます。

より正確に説明すると、Direct3D デバイスは Direct3D のレンダリング コンポーネントです。 デバイスは、レンダリングの状態をカプセル化して格納します。また、変換や照明の操作、サーフェスへのイメージのラスタライズを実行します。 詳しくは、「デバイス」をご覧ください。

デバイスは、ID3D11Device インターフェイスによって表されます。 つまり、ID3D11Device インターフェイスは仮想ディスプレイ アダプターを表し、デバイスによって所有されるリソースを作成するために使用します。

ID3D11Device にはさまざまなバージョンがあります。 ID3D11Device5 は最新バージョンであり、ID3D11Device4 のメソッドに新しいメソッドを追加したものです。 Direct3D が基になるハードウェアと通信する方法の詳細については、Windows Device Driver Model (WDDM) アーキテクチャに関するページを参照してください。

各アプリケーションには少なくとも 1 つのデバイスが必要であり、ほとんどのアプリケーションは 1 つだけデバイスを作成します。 コンピューターにインストールされているいずれかのハードウェア ドライバーについてデバイスを作成するには、D3D11CreateDevice または D3D11CreateDeviceAndSwapChain を呼び出して、D3D_DRIVER_TYPE フラグでドライバーの種類を指定します。 各デバイスでは、必要な機能に応じて、1 つまたは複数のデバイス コンテキストを使用できます。 詳細については、D3D11CreateDevice 関数に関するページを参照してください。

デバイスのコンテキスト

デバイス コンテキストは、パイプラインの状態を設定し、デバイスによって所有されるリソースを使用してレンダリング コマンドを生成するために使用されます。

Direct3D 11 は 2 種類のデバイス コンテキストを実装します。1 つは即時レンダリング用で、もう 1 つは遅延レンダリング用です。いずれのコンテキストも ID3D11DeviceContext インターフェイスを使用して表されます。

ID3D11DeviceContext インターフェイスには複数のバージョンがあり、ID3D11DeviceContext4ID3D11DeviceContext3 に新しいメソッドを追加します。

ID3D11DeviceContext4 は Windows 10 Creators Update で導入されたものであり、ID3D11DeviceContext インターフェイスの最新バージョンです。 Windows 10 Creators Update 以降を対象とするアプリケーションでは、以前のバージョンではなく、このインターフェイスを使用する必要があります。 詳しくは、ID3D11DeviceContext4 に関するページを参照してください。

DX::DeviceResources

DX::DeviceResources クラスは、DeviceResources.cpp/.h ファイルに含まれており、すべての DirectX デバイス リソースを制御します。

バッファー

バッファー リソースは完全に型指定されたデータのコレクションであり、複数の要素にグループ化されます。 バッファーを使用して、さまざまなデータを格納できます。たとえば、位置ベクトル、法線ベクトル、頂点バッファー内のテクスチャ座標、インデックス バッファー内のインデックス、デバイスの状態などのデータが格納されます。 バッファー要素には、圧縮済みデータ値 (R8G8B8A8 サーフェス値など)、単一の 8 ビット整数、または 4 つの 32 ビット浮動小数点値を含めることができます。

利用可能なバッファーには、頂点バッファー、インデックス バッファー、定数バッファーの 3 種類があります。

頂点バッファー

ジオメトリの定義に使われる頂点データが格納されます。 頂点データには、位置座標、色データ、テクスチャ座標データ、法線データなどが格納されます。

インデックス バッファー

頂点バッファーへの整数オフセットが格納されます。このバッファーは、より効率的なプリミティブのレンダリングに使われます。 インデックス バッファーは 16 ビットまたは 32 ビットの連続するインデックスを格納します。各インデックスは頂点バッファーの頂点を識別するのに使用されます。

定数バッファーまたはシェーダー定数バッファー

シェーダー データをパイプラインに効率的に提供できます。 定数バッファーは、各プリミティブについて実行され、レンダリング パイプラインのストリーム出力ステージの結果を格納するシェーダーの入力として使用できます。 定数バッファーは、概念的には要素が 1 つの頂点バッファーに似ています。

バッファーの設計と実装

バッファーはデータ型に基づいて設計できます。たとえば、サンプル ゲームでは、バッファーを静的データ用に 1 つ、フレーム全体で一定のデータ用に 1 つ、プリミティブに固有のデータ用に 1 つ作成します。

すべてのバッファーの種類は ID3D11Buffer インターフェイスによってカプセル化され、ID3D11Device::CreateBuffer を呼び出すことによってバッファー リソースを作成できます。 ただし、バッファーにアクセスするには、その前にバッファーがパイプラインにバインドされている必要があります。 バッファーは、読み取りのために同時に複数のパイプライン ステージにバインドできます。 バッファーは、書き込みのために単一のパイプライン ステージにバインドすることもできます。ただし、同じバッファーを読み取りと書き込みの両方のために同時にバインドすることはできません。

バッファーは、次の方法でバインドできます。

  • 入力アセンブラー ステージ。そのためには、ID3D11DeviceContext のメソッド ID3D11DeviceContext::IASetVertexBuffersID3D11DeviceContext::IASetIndexBuffer を呼び出します。
  • ストリーム出力ステージ。そのためには、ID3D11DeviceContext::SOSetTargets を呼び出します。
  • シェーダー ステージ。そのためには、ID3D11DeviceContext::VSSetConstantBuffers などのシェーダー メソッドを呼び出します。

詳しくは、「Direct3D 11 のバッファーについて」をご覧ください。

DXGI

Microsoft DirectX Graphics Infrastructure (DXGI) は、Direct3D で必要とされる低レベルのタスクの一部をカプセル化するサブシステムです。 マルチスレッド アプリケーションで DXGI を使用する場合、デッドロックが発生しないように特に注意する必要があります。 詳細については、「マルチスレッドと DXGI」を参照してください。

機能レベル

機能レベルは、最新または既存のコンピューターで多様なビデオ カードを処理するために、Direct3D 11 で導入された概念です。 機能レベルは、明確に定義されたグラフィックス処理装置 (GPU) 機能のセットです。

各ビデオ カードは、インストールされている GPU に応じて、特定のレベルの DirectX の機能を実装します。 以前のバージョンの Microsoft Direct3D では、ビデオ カードが実装しているバージョンを検出し、それに応じてアプリケーションをプログラミングすることができました。

機能レベルを使用すると、デバイスを作成するときに、必要な機能レベルのデバイスを作成してみることができます。 デバイスの作成に成功した場合は、その機能レベルが存在します。失敗した場合は、ハードウェアはその機能レベルをサポートしていません。 低い機能レベルでデバイスを再作成してみることも、アプリケーションの終了を選択することもできます。 たとえば、12_0 機能レベルでは、Direct3D 11.3 や Direct3D 12、およびシェーダー モデル 5.1 が必要です。 詳細については、Direct3D の各機能レベルの概要に関するページを参照してください。

機能レベルを使用して、Direct3D 9 や Microsoft Direct3D 10、Direct3D 11 のアプリケーションを開発し、9、10、または 11 のハードウェアで実行できます (いくつか例外があります)。 詳細については、Direct3D の機能レベルに関するページを参照してください。

ステレオ レンダリング

ステレオ レンダリングは奥行き感を強化するために使用されます。 左目の視点から画像と右目の視点からの画像の 2 つの画像を使用して、ディスプレイの画面にシーンを表示します。

数学的には、ステレオ射影行列を適用することによってこれを実現します。ステレオ射影行列は、通常のモノラル射影行列を水平方向にわずかに右および左にオフセットしたものです。

このサンプル ゲームでは、ステレオ レンダリングを実現するために、次の 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 を使用して計算された、射影座標への変換を定義します (ステレオ射影の場合は、片目に 1 つずつのビューで、2 つの射影行列を使用します)。

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 の累乗だけ小さくなり、最終的には 1 x 1 のテクスチャになります。 縮小を使用すると、ゲームはレンダリング時に必要なサイズに最も近いミップマップ レベルを選択します。

BasicLoader クラス

BasicLoader は、ディスク上のファイルからのシェーダー、テクスチャ、メッシュの読み込みをサポートする、単純なローダー クラスです。 同期メソッドと非同期メソッドの両方を提供します。 このサンプル ゲームでは、BasicLoader.h/.cpp ファイルは [Utilities] (ユーティリティ) フォルダーにあります。

詳細については、BasicLoader を参照してください。