Marble Maze サンプルへの視覚的なコンテンツの追加

このドキュメントでは、ユニバーサル Windows プラットフォーム (UWP) アプリ環境で、Marble Maze ゲームが Direct3D と Direct2D を使用する方法について説明します。これにより、パターンを学習し、独自のゲーム コンテンツを操作するときにそれらを適応できるようになります。 ビジュアル ゲーム コンポーネントが Marble Maze のアプリケーション構造全体にどのように適合するかについては、「Marble Maze のアプリケーション構造」を参照してください。

Marble Maze の視覚的な側面を開発する際に従った基本的な手順は、次のとおりです。

  1. Direct3D 環境と Direct2D 環境を初期化する基本的なフレームワークを作成します。
  2. 画像およびモデル編集プログラムを使って、ゲームに表示される 2D と 3D のアセットを設計します。
  3. 2D と 3D のアセットがゲームに適切に読み込まれて表示されることを確かめます。
  4. ゲーム アセットのビジュアル品質を高める頂点シェーダーとピクセル シェーダーを統合します。
  5. アニメーションやユーザー入力などのゲーム ロジックを統合します。

また、最初は 3D アセット、次に 2D アセットの追加を集中して行いました。 たとえば、メニュー システムとタイマーを追加する前に、コア ゲーム ロジックに重点的に取り組みました。

また、開発プロセスでは、これらの手順の一部を何度も繰り返す必要がありました。 たとえば、メッシュや大理石のモデルを変更するときには、それらのモデルをサポートするシェーダー コードの一部も変更する必要がありました。

Note

このドキュメントに対応するサンプル コードは、DirectX Marble Maze ゲームのサンプルに関するページにあります。

  DirectX やビジュアル ゲーム コンテンツを操作するとき、つまり、DirectX グラフィック ライブラリの初期化、シーン リソースの読み込み、シーンの更新とレンダリングを行う場合の重要なポイントの一部を次に示し、これらについてこのドキュメントで説明します。

  • 通常、ゲーム コンテンツを追加するには、多くの手順が必要です。 さらに、多くの場合、これらの手順は、繰り返す必要があります。 多くの場合、ゲーム開発者は最初に 3D ゲーム コンテンツの追加を集中的に行い、次に 2D コンテンツを追加します。
  • 可能な限り幅広いグラフィックス ハードウェアをサポートして、より多くの顧客にリーチし、すべての顧客に優れたエクスペリエンスを提供します。
  • 設計時と実行時の形式を明確に分離します。 設計時のアセットは、柔軟性を最大限に高め、コンテンツを迅速に反復できるように構造化します。 アセットは、実行時に可能な限り効率的に読み込みとレンダリングを行うことができるように、形式を設定して圧縮します。
  • 従来の Windows デスクトップ アプリとほぼ同様に、UWP アプリで Direct3D デバイスと Direct2D デバイスを作成します。 1 つの重要な違いは、スワップ チェーンと出力ウィンドウを関連付ける方法です。
  • ゲームを設計する際、選択したメッシュ形式が主要なシナリオをサポートすることを確認します。 たとえば、ゲームで衝突が必要な場合、メッシュから衝突データを取得できることを確認します。
  • 最初にすべてのシーン オブジェクトを更新してからレンダリングすることで、ゲーム ロジックとレンダリング ロジックを分離します。
  • 通常は、3D シーン オブジェクトを描画してから、そのシーンの前面に表示される 2D オブジェクトを描画します。
  • 描画と垂直空白を同期して、実際にディスプレイに表示されないフレームの描画にゲームが時間を費やさないようにします。 "垂直ブランク" は、モニターで 1 フレームの描画が終了してから次のフレームが開始するまでの時間です。

DirectX グラフィックスの概要

Marble Maze ユニバーサル Windows プラットフォーム (UWP) ゲームを計画したとき、C++ と Direct3D 11.1 を選択しました。レンダリングの最大の制御と高パフォーマンスを必要とする 3D ゲームを作成するための最適な選択であるためです。 DirectX 11.1 は DirectX 9 から DirectX 11 までのハードウェアをサポートします。したがって、以前の各 DirectX バージョン用にコードを書き換える必要がないため、より多くの顧客に効率よくリーチできます。

Marble Maze は Direct3D 11.1 を使って、3D ゲーム アセット (大理石と迷路) をレンダリングします。 Marble Maze では、2D ゲーム アセット (メニューやタイマー) を描画するために Direct2D、DirectWrite、Windows Imaging Component (WIC) も使います。

ゲーム開発には、計画が必要です。 DirectX グラフィックスを初めて扱う場合は、「DirectX: 概要」を読んで、UWP DirectX ゲームの作成の基本概念を理解しておくことをお勧めします。 このドキュメントを読み、Marble Maze のソース コードを調べるにあたり、次のリソースを参照して、DirectX グラフィックスに関するより詳しい情報を入手できます。

  • Direct3D 11 グラフィックス: Direct3D 11 は、Windows プラットフォームで 3D ジオメトリをレンダリングするための、ハードウェア アクセラレータによる強力な 3-D グラフィックス API です。
  • Direct2D: Direct2D は、2D ジオメトリ、ビットマップ、テキストの高パフォーマンスかつ高品質のレンダリングを実現する、ハードウェア アクセラレータによる 2D グラフィックス API です。
  • DirectWrite: DirectWrite は、高品質のテキスト レンダリングをサポートします。
  • Windows Imaging Component: WIC は、デジタル画像向けの低レベル API を提供する拡張可能なプラットフォームです。

機能レベル

Direct3D 11 では、"機能レベル" というパラダイムが導入されています。 機能レベルは、明確に定義された GPU 機能のセットです。 機能レベルを使用して、Direct3D ハードウェアの以前のバージョンで実行できるようにゲームのターゲットを設定します。 Marble Maze では、上位レベルの高度な機能が必要ないため、機能レベル 9.1 がサポートされています。 所有するコンピューターがハイエンドかローエンドかにかかわらず、すべての顧客に優れたエクスペリエンスを提供できるように、可能な限り幅広いハードウェアをサポートし、ゲーム コンテンツをスケーリングすることをお勧めします。 機能レベルの詳細については、「ダウンレベル ハードウェア上の Direct3D 11」を参照してください。

Direct3D と Direct2D の初期化

デバイスは、ビデオ カードを表します。 従来の Windows デスクトップ アプリとほぼ同様に、UWP アプリで Direct3D デバイスと Direct2D デバイスを作成します。 主な違いは、Direct3D スワップ チェーンをウィンドウ システムに接続する方法です。

DeviceResources クラスは、Direct3D と Direct2D を管理するための基盤です。 このクラスは、ゲーム固有のアセットではなく一般的なインフラストラクチャを処理します。 Marble Maze で定義する MarbleMazeMain クラスは、Direct3D と Direct2D へのアクセスを提供する DeviceResources オブジェクトを参照する、ゲーム固有のアセットを処理します。

初期化中に DeviceResources コンストラクターが、デバイスに依存しないリソース、および Direct3D と Direct2D のデバイスを作成します。

// Initialize the Direct3D resources required to run. 
DX::DeviceResources::DeviceResources() :
    m_screenViewport(),
    m_d3dFeatureLevel(D3D_FEATURE_LEVEL_9_1),
    m_d3dRenderTargetSize(),
    m_outputSize(),
    m_logicalSize(),
    m_nativeOrientation(DisplayOrientations::None),
    m_currentOrientation(DisplayOrientations::None),
    m_dpi(-1.0f),
    m_deviceNotify(nullptr)
{
    CreateDeviceIndependentResources();
    CreateDeviceResources();
}

DeviceResources クラスは、環境が変更されたときにより簡単に対応できるように、この機能を分離します。 たとえば、ウィンドウ サイズが変更されたときに、CreateWindowSizeDependentResources メソッドを呼び出します。

Direct2D、DirectWrite、WIC ファクトリの初期化

DeviceResources::CreateDeviceIndependentResources メソッドは、Direct2D、DirectWrite、WIC のファクトリを作成します。 DirectX グラフィックスでは、ファクトリは、グラフィックス リソースを作成するための開始点です。 Marble Maze では、メイン スレッドですべての描画を実行するため、D2D1_FACTORY_TYPE_SINGLE_THREADED を指定しています。

// These are the resources required independent of hardware. 
void DX::DeviceResources::CreateDeviceIndependentResources()
{
    // Initialize Direct2D resources.
    D2D1_FACTORY_OPTIONS options;
    ZeroMemory(&options, sizeof(D2D1_FACTORY_OPTIONS));

#if defined(_DEBUG)
    // If the project is in a debug build, enable Direct2D debugging via SDK Layers.
    options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
#endif

    // Initialize the Direct2D Factory.
    DX::ThrowIfFailed(
        D2D1CreateFactory(
            D2D1_FACTORY_TYPE_SINGLE_THREADED,
            __uuidof(ID2D1Factory2),
            &options,
            &m_d2dFactory
            )
        );

    // Initialize the DirectWrite Factory.
    DX::ThrowIfFailed(
        DWriteCreateFactory(
            DWRITE_FACTORY_TYPE_SHARED,
            __uuidof(IDWriteFactory2),
            &m_dwriteFactory
            )
        );

    // Initialize the Windows Imaging Component (WIC) Factory.
    DX::ThrowIfFailed(
        CoCreateInstance(
            CLSID_WICImagingFactory2,
            nullptr,
            CLSCTX_INPROC_SERVER,
            IID_PPV_ARGS(&m_wicFactory)
            )
        );
}

Direct3D デバイスと Direct2D デバイスの作成

DeviceResources::CreateDeviceResources メソッドは、D3D11CreateDevice を呼び出して、Direct3D ビデオ カードを表すデバイス オブジェクトを作成します。 Marble Maze は機能レベル 9.1 以上をサポートするため、DeviceResources::CreateDeviceResources メソッドはレベル 9.1 から 11.1 を featureLevels 配列に指定します。 Direct3D は一覧を順番に調べて、使用可能な最初の機能レベルをアプリに提供します。 したがって、D3D_FEATURE_LEVEL 配列のエントリは高い方から順に並べて、使用可能な最上位の機能レベルをアプリが取得するようにします。 DeviceResources::CreateDeviceResources メソッドは、D3D11CreateDevice から返される Direct3D 11 デバイスに対してクエリを実行して、Direct3D 11.1 デバイスを取得します。

// This flag adds support for surfaces with a different color channel ordering
// than the API default. It is required for compatibility with Direct2D.
UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;

#if defined(_DEBUG)
    if (DX::SdkLayersAvailable())
    {
        // If the project is in a debug build, enable debugging via SDK Layers 
        // with this flag.
        creationFlags |= D3D11_CREATE_DEVICE_DEBUG;
    }
#endif

// This array defines the set of DirectX hardware feature levels this app will support.
// Note the ordering should be preserved.
// Don't forget to declare your application's minimum required feature level in its
// description.  All applications are assumed to support 9.1 unless otherwise stated.
D3D_FEATURE_LEVEL featureLevels[] =
{
    D3D_FEATURE_LEVEL_11_1,
    D3D_FEATURE_LEVEL_11_0,
    D3D_FEATURE_LEVEL_10_1,
    D3D_FEATURE_LEVEL_10_0,
    D3D_FEATURE_LEVEL_9_3,
    D3D_FEATURE_LEVEL_9_2,
    D3D_FEATURE_LEVEL_9_1
};

// Create the Direct3D 11 API device object and a corresponding context.
ComPtr<ID3D11Device> device;
ComPtr<ID3D11DeviceContext> context;

HRESULT hr = D3D11CreateDevice(
    nullptr,                    // Specify nullptr to use the default adapter.
    D3D_DRIVER_TYPE_HARDWARE,   // Create a device using the hardware graphics driver.
    0,                          // Should be 0 unless the driver is D3D_DRIVER_TYPE_SOFTWARE.
    creationFlags,              // Set debug and Direct2D compatibility flags.
    featureLevels,              // List of feature levels this app can support.
    ARRAYSIZE(featureLevels),   // Size of the list above.
    D3D11_SDK_VERSION,          // Always set this to D3D11_SDK_VERSION for UWP apps.
    &device,                    // Returns the Direct3D device created.
    &m_d3dFeatureLevel,         // Returns feature level of device created.
    &context                    // Returns the device immediate context.
    );

if (FAILED(hr))
{
    // If the initialization fails, fall back to the WARP device.
    // For more information on WARP, see:
    // https://go.microsoft.com/fwlink/?LinkId=286690
    DX::ThrowIfFailed(
        D3D11CreateDevice(
            nullptr,
            D3D_DRIVER_TYPE_WARP, // Create a WARP device instead of a hardware device.
            0,
            creationFlags,
            featureLevels,
            ARRAYSIZE(featureLevels),
            D3D11_SDK_VERSION,
            &device,
            &m_d3dFeatureLevel,
            &context
            )
        );
}

// Store pointers to the Direct3D 11.1 API device and immediate context.
DX::ThrowIfFailed(
    device.As(&m_d3dDevice)
    );

DX::ThrowIfFailed(
    context.As(&m_d3dContext)
    );

その後、DeviceResources::CreateDeviceResources メソッドが、Direct2D デバイスを作成します。 Direct2D では、Microsoft DirectX Graphic Infrastructure (DXGI) を使用して、Direct3D と相互運用します。 DXGI を使用すると、グラフィックス メモリ サーフェスをグラフィックス ランタイム間で共有できます。 Marble Maze では、Direct3D デバイスの基になる DXGI デバイスを使用して、Direct2D ファクトリから Direct2D デバイスを作成します。

// Create the Direct2D device object and a corresponding context.
ComPtr<IDXGIDevice3> dxgiDevice;
DX::ThrowIfFailed(
    m_d3dDevice.As(&dxgiDevice)
    );

DX::ThrowIfFailed(
    m_d2dFactory->CreateDevice(dxgiDevice.Get(), &m_d2dDevice)
    );

DX::ThrowIfFailed(
    m_d2dDevice->CreateDeviceContext(
        D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
        &m_d2dContext
        )
    );

DXGI の詳細、および Direct2D と Direct3D の相互運用性の詳細については、「DXGI の概要」と「Direct2D と Direct3D の相互運用性に関する概要」を参照してください。

Direct3D とビューの関連付け

DeviceResources::CreateWindowSizeDependentResources メソッドは、スワップ チェーン、Direct3D および Direct2D のレンダー ターゲットなど、所定のウィンドウ サイズによって異なるグラフィックス リソースを作成します。 DirectX UWP アプリがデスクトップ アプリと異なる重要な点の 1 つは、スワップ チェーンと出力ウィンドウを関連付ける方法です。 スワップ チェーンは、デバイスがモニターにレンダリングするバッファーを表示する役割を担います。 「Marble Maze のアプリケーション構造」では、UWP アプリのウィンドウ システムがデスクトップ アプリとどのように違うかを説明しています。 UWP アプリは HWND オブジェクトでは動作しないため、Marble Maze では、IDXGIFactory2::CreateSwapChainForCoreWindow メソッドを使って、デバイス出力をビューに関連付ける必要があります。 次の例は、スワップ チェーンを作成する DeviceResources::CreateWindowSizeDependentResources メソッドの一部を示しています。

// Obtain the final swap chain for this window from the DXGI factory.
DX::ThrowIfFailed(
    dxgiFactory->CreateSwapChainForCoreWindow(
        m_d3dDevice.Get(),
        reinterpret_cast<IUnknown*>(m_window.Get()),
        &swapChainDesc,
        nullptr,
        &m_swapChain
        )
    );

電力消費を最小限に抑えるために (ノート PC やタブレットなどのバッテリ駆動デバイスでは重要)、DeviceResources::CreateWindowSizeDependentResources メソッドは IDXGIDevice1::SetMaximumFrameLatency メソッドを呼び出して、垂直空白の後にのみゲームがレンダリングされるようにします。 垂直ブランクとの同期については、このドキュメントの「シーンの表示」で詳しく説明します。

// Ensure that DXGI does not queue more than one frame at a time. This both 
// reduces latency and ensures that the application will only render after each
// VSync, minimizing power consumption.
DX::ThrowIfFailed(
    dxgiDevice->SetMaximumFrameLatency(1)
    );

DeviceResources::CreateWindowSizeDependentResources メソッドは、ほとんどのゲームで動作する方法でグラフィックス リソースを初期化します。

Note

"ビュー" という用語は、Windows ランタイムと Direct3D で意味が異なります。 Windows ランタイムでは、ビューは、アプリのユーザー インターフェイス設定のコレクション (表示領域、入力動作など) と、処理に使用するスレッドを指します。 ビューを作成するときに、必要な構成と設定を指定します。 アプリのビューを設定するプロセスについては、「Marble Maze のアプリケーション構造」で説明しています。 Direct3D では、ビューという用語に複数の意味があります。 リソース ビューは、リソースがアクセスできるサブリソースを定義します。 たとえば、テクスチャ オブジェクトがシェーダー リソース ビューに関連付けられている場合、そのシェーダーは後でテクスチャにアクセスできます。 リソース ビューの 1 つの利点は、レンダリング パイプラインのさまざまな段階で、さまざまな方法でデータを解釈できることです。 リソース ビューについて詳しくは、リソース ビューに関する記事を参照してください。 ビュー変換またはビュー変換行列のコンテキストで使用される場合、ビューは、カメラの位置と向きを表します。 ビュー変換は、カメラの位置と向きを中心にワールド内でオブジェクトを再配置します。 ビュー変換の詳細については、「ビュー変換 (Direct3D 9)」を参照してください。 Marble Maze でリソース ビューおよび行列ビューがどのように使用されているかについては、このトピックで詳しく説明します。

 

シーン リソースの読み込み

Marble Maze では、BasicLoader.h で宣言される BasicLoader クラスを使って、テクスチャとシェーダーを読み込みます。 Marble Maze は、SDKMesh クラスを使って、迷路と大理石のための 3D メッシュを読み込みます。

Marble Maze では、アプリの高い応答性を確保するために、非同期的に、つまりバックグラウンドでシーン リソースを読み込みます。 バックグラウンドでのアセットの読み込み中、ゲームはウィンドウ イベントに応答できます。 このプロセスについては、このガイドの「バックグラウンドでのゲーム アセットの読み込み」で詳しく説明します。

2D オーバーレイとユーザー インターフェイスの読み込み

Marble Maze では、オーバーレイは画面の一番上に表示される画像です。 オーバーレイは常にシーンの前面に表示されます。 Marble Maze では、オーバーレイに Windows ロゴとテキスト文字列 "DirectX Marble Maze game sample" が含まれます。 オーバーレイの管理は、SampleOverlay.h で定義されている SampleOverlay クラスによって実行されます。 この場合は、オーバーレイを Direct3D サンプルの一部として使用していますが、このコードを利用すると、シーンの前面に任意の画像を表示できます。

オーバーレイの重要な側面の 1 つは、コンテンツが変化しないため、SampleOverlay クラスが初期化中にコンテンツを ID2D1Bitmap1 オブジェクトに描画またはキャッシュすることです。 描画時に SampleOverlay クラスは、ビットマップを画面に描画するだけで済みます。 このように、テキスト描画などのコストの高いルーチンをすべてのフレームで実行する必要がありません。

ユーザー インターフェイス (UI) は、シーンの前面に表示されるメニューやヘッドアップ ディスプレイ (HUD) などの 2D コンポーネントで構成されます。 Marble Maze では、次の UI 要素を定義します。

  • ユーザーがゲームを開始したり、ハイ スコアを表示したりできるようにするメニュー項目。
  • プレイ開始までの 3 秒をカウント ダウンするタイマー。
  • プレイの経過時間を追跡するタイマー。
  • 最速記録を一覧表示する表。
  • ゲームを一時停止したときに表示される "Paused" というテキスト。

Marble Maze では、ゲームに固有の UI 要素を UserInterface.h に定義しています。 Marble Maze では、ElementBase クラスをすべての UI 要素の基本型として定義します。 ElementBase クラスは、UI 要素のサイズ、位置、配置、可視性などの属性を定義します。 また、要素の更新とレンダリングの方法も制御します。

class ElementBase
{
public:
    virtual void Initialize() { }
    virtual void Update(float timeTotal, float timeDelta) { }
    virtual void Render() { }

    void SetAlignment(AlignType horizontal, AlignType vertical);
    virtual void SetContainer(const D2D1_RECT_F& container);
    void SetVisible(bool visible);

    D2D1_RECT_F GetBounds();

    bool IsVisible() const { return m_visible; }

protected:
    ElementBase();

    virtual void CalculateSize() { }

    Alignment       m_alignment;
    D2D1_RECT_F     m_container;
    D2D1_SIZE_F     m_size;
    bool            m_visible;
};

UI 要素の共通基底クラスを提供することで、ユーザー インターフェイスを管理する UserInterface クラスは、ElementBase オブジェクトのコレクションを保持するだけで済むため、UI の管理が簡略化され、再利用可能なユーザー インターフェイス マネージャーも提供されます。 Marble Maze では、ゲーム固有の動作を実装する型を ElementBase から派生させて定義します。 たとえば、HighScoreTable は、ハイ スコア表の動作を定義します。 これらの型の詳細については、ソース コードを参照してください。

Note

XAML を使うと、シミュレーション ゲームや戦略ゲームに見られるような複雑なユーザー インターフェイスの作成が簡単になるため、UI の定義に XAML を使うかどうかを検討してください。 XAML を使って DirectX UWP ゲームのユーザー インターフェイスを開発する方法について詳しくは、DirectX 3D のシューティング ゲームのサンプルについて説明した「サンプル ゲームを拡張する」を参照してください。

 

シェーダーの読み込み

Marble Maze では、BasicLoader::LoadShader メソッドを使用して、ファイルからシェーダーを読み込みます。

シェーダーは、現在のゲームでの GPU プログラミングの基本単位です。 ほとんどすべての 3D グラフィックス処理は、それがモデル変換やシーンの照明であっても、またはキャラクターへのスキンの適用からテセレーションに至るより複雑なジオメトリ処理であっても、シェーダーによって駆動されます。 シェーダーのプログラミング モデルの詳細については、HLSLに関するページを参照してください。

Marble Maze では、頂点シェーダーとピクセル シェーダーを使用します。 頂点シェーダーは常に、1 つの入力頂点を操作し、出力として 1 つの頂点を生成します。 ピクセル シェーダーは、数値、テクスチャ データ、補間された頂点単位の値、その他のデータを受け取り、出力としてピクセルの色を生成します。 シェーダーは一度に 1 つの要素を変換するため、複数のシェーダー パイプラインを提供するグラフィックス ハードウェアは要素のセットを並列で処理できます。 GPU で使用できる並列パイプラインの数は、CPU で使用可能な数を大幅に上回る可能性があります。 このため、基本的なシェーダーでもスループットを大幅に向上することができます。

MarbleMazeMain::LoadDeferredResources メソッドは、オーバーレイを読み込んだ後で、1 つの頂点シェーダーと 1 つのピクセル シェーダーを読み込みます。 これらのシェーダーの設計時のバージョンは、BasicVertexShader.hlslBasicPixelShader.hlsl でそれぞれ定義されます。 Marble Maze では、レンダリング フェーズで、これらのシェーダーをボールと迷路の両方に適用します。

Marble Maze プロジェクトには、シェーダー ファイルの .hlsl バージョン (設計時の形式) と .cso バージョン (実行時形式) の両方が含まれています。 ビルド時に、Visual Studio では fxc.exe エフェクト コンパイラを使用して .hlsl ソース ファイルを .cso バイナリ シェーダーにコンパイルします。 エフェクト コンパイラ ツールの詳細については、「Effect-Compiler Tool」を参照してください。

頂点シェーダーは、指定されたモデル、ビュー、プロジェクションの各秒列を使用して、入力ジオメトリを変換します。 入力ジオメトリの位置データは変換されて、2 回出力されます。まずレンダリングのために必要な画面空間内に出力され、ピクセル シェーダーで照明計算を実行できるようにするためにワールド空間内にもう一度出力されます。 面法線ベクトルはワールド空間に変換されます。これもピクセル シェーダーで照明のために使用されます。 テクスチャ座標は、変更されずにピクセル シェーダーに渡されます。

sPSInput main(sVSInput input)
{
    sPSInput output;
    float4 temp = float4(input.pos, 1.0f);
    temp = mul(temp, model);
    output.worldPos = temp.xyz / temp.w;
    temp = mul(temp, view);
    temp = mul(temp, projection);
    output.pos = temp;
    output.tex = input.tex;
    output.norm = mul(float4(input.norm, 0.0f), model).xyz;
    return output;
}

ピクセル シェーダーは、頂点シェーダーの出力を入力として受け取ります。 このシェーダーは照明計算を実行して、ビー玉の位置に合わせて迷路上を浮遊するソフトエッジのスポットライトを模倣します。 照明は、光が直接当たる面に対して最も強くなります。 面法線が光に対して直角になるにつれて、拡散コンポーネントは減少し、ゼロになります。また、法線の向きが光から離れるにつれて、アンビエント項が減少します。 ビー玉に近い (したがって、スポットライトの中心に近い) ほど照明が強くなります。 ただし、ビー玉の下のポイントでは照明が調整され、柔らかい影がシミュレートされます。 実際の環境では、白いビー玉のようなオブジェクトは、スポットライトをシーン内の他のオブジェクトに拡散反射します。 これは、ビー玉の明るい方の半球が見える面に対して近似されます。 追加の照明係数は、ビー玉に対する相対角度と距離です。 生成されるピクセルの色は、サンプリングされたテクスチャと照明計算の結果を合成したものになります。

float4 main(sPSInput input) : SV_TARGET
{
    float3 lightDirection = float3(0, 0, -1);
    float3 ambientColor = float3(0.43, 0.31, 0.24);
    float3 lightColor = 1 - ambientColor;
    float spotRadius = 50;

    // Basic ambient (Ka) and diffuse (Kd) lighting from above.
    float3 N = normalize(input.norm);
    float NdotL = dot(N, lightDirection);
    float Ka = saturate(NdotL + 1);
    float Kd = saturate(NdotL);

    // Spotlight.
    float3 vec = input.worldPos - marblePosition;
    float dist2D = sqrt(dot(vec.xy, vec.xy));
    Kd = Kd * saturate(spotRadius / dist2D);

    // Shadowing from ball.
    if (input.worldPos.z > marblePosition.z)
        Kd = Kd * saturate(dist2D / (marbleRadius * 1.5));

    // Diffuse reflection of light off ball.
    float dist3D = sqrt(dot(vec, vec));
    float3 V = normalize(vec);
    Kd += saturate(dot(-V, N)) * saturate(dot(V, lightDirection))
        * saturate(marbleRadius / dist3D);

    // Final composite.
    float4 diffuseTexture = Texture.Sample(Sampler, input.tex);
    float3 color = diffuseTexture.rgb * ((ambientColor * Ka) + (lightColor * Kd));
    return float4(color * lightStrength, diffuseTexture.a);
}

警告

コンパイルされたピクセル シェーダーには、32 の演算命令と 1 つのテクスチャ命令が含まれます。 このシェーダーは、デスクトップ コンピューターまたは高パフォーマンスのタブレットで正常に動作する必要があります。 ただし、一部のコンピューターでは、このシェーダーを処理できず、対話形式のフレーム レートが提供される場合があります。 対象ユーザーの標準的なハードウェアについて検討し、そのハードウェアの性能に合わせてシェーダーを設計します。

 

MarbleMazeMain::LoadDeferredResources メソッドは、BasicLoader::LoadShader メソッドを使ってシェーダーを読み込みます。 次の例では、頂点シェーダーを読み込みます。 このシェーダーの実行時の形式は BasicVertexShader.cso です。 m_vertexShader メンバー変数は ID3D11VertexShader オブジェクトです。

BasicLoader^ loader = ref new BasicLoader(m_deviceResources->GetD3DDevice());

D3D11_INPUT_ELEMENT_DESC layoutDesc [] =
{
    { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    { "TANGENT", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 32, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};
m_vertexStride = 44; // must set this to match the size of layoutDesc above

Platform::String^ vertexShaderName = L"BasicVertexShader.cso";
loader->LoadShader(
    vertexShaderName,
    layoutDesc,
    ARRAYSIZE(layoutDesc),
    &m_vertexShader,
    &m_inputLayout
    );

m_inputLayout メンバー変数は ID3D11InputLayout オブジェクトです。 入力レイアウト オブジェクトは、入力アセンブラー (IA) ステージの入力状態をカプセル化します。 IA ステージの 1 つのジョブは、システム生成値 (セマンティクスとも呼ばれます) を使用して、まだ処理されていないプリミティブまたは頂点のみを処理して、シェーダーを効率化することです。

ID3D11Device::CreateInputLayout メソッドを使用して、入力要素の説明の配列から入力レイアウトを作成します。 この配列には、1 つまたは複数の入力要素が含まれており、各入力要素は、1 つの頂点バッファーの 1 つの頂点データ要素について説明します。 入力要素の説明セット全体で、IA ステージにバインドされているすべての頂点バッファーのすべての頂点データ要素について説明します。

上記のコードスニペットの layoutDesc は、Marble Maze で使われるレイアウトの説明を示しています。 このレイアウトの説明では、4 つの頂点データ要素を含む頂点バッファーについて説明しています。 配列内の各エントリの重要な部分は、セマンティック名、データ形式、バイト オフセットです。 たとえば、POSITION 要素は、オブジェクト空間内の頂点の位置を指定します。 これは、バイト オフセット 0 で開始し、3 つの浮動小数点コンポーネントが含まれています (合計 12 バイト)。 NORMAL 要素は、法線ベクトルを指定します。 これは、レイアウトで、12 バイトを必要とする POSITION のすぐ後にあるため、バイト オフセット 12 で開始しています。 NORMAL 要素には、4 要素の 32 ビット符号なし整数が含まれています。

次の例に示すように、頂点シェーダーによって定義される sVSInput 構造体と入力レイアウトを比較します。 sVSInput 構造体は、POSITIONNORMALTEXCOORD0 の各要素を定義します。 DirectX ランタイムは、シェーダーによって定義された入力構造体にレイアウト内の各要素をマップします。

struct sVSInput
{
    float3 pos : POSITION;
    float3 norm : NORMAL;
    float2 tex : TEXCOORD0;
};

struct sPSInput
{
    float4 pos : SV_POSITION;
    float3 norm : NORMAL;
    float2 tex : TEXCOORD0;
    float3 worldPos : TEXCOORD1;
};

sPSInput main(sVSInput input)
{
    sPSInput output;
    float4 temp = float4(input.pos, 1.0f);
    temp = mul(temp, model);
    output.worldPos = temp.xyz / temp.w;
    temp = mul(temp, view);
    temp = mul(temp, projection);
    output.pos = temp;
    output.tex = input.tex;
    output.norm = mul(float4(input.norm, 0.0f), model).xyz;
    return output;
}

ドキュメント「セマンティクス」では、使用できる各セマンティクスについて詳しく説明しています。

Note

レイアウトでは、複数のシェーダーが同じレイアウトを共有できるように、使われないその他のコンポーネントを指定できます。 たとえば、TANGENT 要素はシェーダーでは使用されません。 法線マッピングなどの手法を試す場合、TANGENT 要素を使用できます。 法線マッピング (バンプ マッピングとも呼ばれます) を使用すると、オブジェクトの面に対してバンプ エフェクトを作成できます。 バンプ マッピングの詳細については、「バンプ マッピング (Direct3D 9)」を参照してください。

 

入力アセンブリのステージの状態について詳しくは、「入力アセンブラー ステージ」と「入力アセンブラー ステージの基礎知識」をご覧ください。

頂点シェーダーとピクセル シェーダーを使用してシーンをレンダリングするプロセスについては、このドキュメントの後半にある「シーンのレンダリング」セクションで説明します。

定数バッファーの作成

Direct3D バッファーは、データのコレクションをグループ化します。 定数バッファーは、シェーダーにデータを渡すために使用できるバッファーの一種です。 Marble Maze では、定数バッファーを使用して、モデル (またはワールド) ビューと、アクティブなシーン オブジェクトのためのプロジェクション行列を保持します。

MarbleMazeMain::LoadDeferredResources メソッドを使って後でマトリックス データを保持する定数バッファーを作成する方法を次の例に示します。 この例では、D3D11_BIND_CONSTANT_BUFFER フラグを使って定数バッファーとしての使用を指定する D3D11_BUFFER_DESC 構造体を作成しています。 この例では、次にこの構造体を ID3D11Device::CreateBuffer メソッドに渡します。 m_constantBuffer 変数は ID3D11Buffer オブジェクトです。

// Create the constant buffer for updating model and camera data.
D3D11_BUFFER_DESC constantBufferDesc = {0};

// Multiple of 16 bytes
constantBufferDesc.ByteWidth = ((sizeof(ConstantBuffer) + 15) / 16) * 16;

constantBufferDesc.Usage               = D3D11_USAGE_DEFAULT;
constantBufferDesc.BindFlags           = D3D11_BIND_CONSTANT_BUFFER;
constantBufferDesc.CPUAccessFlags      = 0;
constantBufferDesc.MiscFlags           = 0;

// This will not be used as a structured buffer, so this parameter is ignored.
constantBufferDesc.StructureByteStride = 0;

DX::ThrowIfFailed(
    m_deviceResources->GetD3DDevice()->CreateBuffer(
        &constantBufferDesc,
        nullptr,    // leave the buffer uninitialized
        &m_constantBuffer
        )
    );

MarbleMazeMain::Update メソッドは、後で ConstantBuffer オブジェクト (迷路用に 1 つと大理石用に 1 つ) を更新します。 次に、MarbleMazeMain::Render メソッドが各 ConstantBuffer オブジェクトを定数バッファーにバインドしてから、各オブジェクトがレンダリングされます。 次の例は、MarbleMazeMain.h にある ConstantBuffer 構造体を示しています。

// Describes the constant buffer that draws the meshes.
struct ConstantBuffer
{
    XMFLOAT4X4 model;
    XMFLOAT4X4 view;
    XMFLOAT4X4 projection;

    XMFLOAT3 marblePosition;
    float marbleRadius;
    float lightStrength;
};

定数バッファーとシェーダー コードとのマッピングをよく理解するには、MarbleMazeMain.hConstantBuffer 構造体と、BasicVertexShader.hlsl の頂点シェーダーで定義された ConstantBuffer 定数バッファーを比較します。

cbuffer ConstantBuffer : register(b0)
{
    matrix model;
    matrix view;
    matrix projection;
    float3 marblePosition;
    float marbleRadius;
    float lightStrength;
};

ConstantBuffer 構造体のレイアウトは、cbuffer オブジェクトと一致します。 cbuffer 変数は、レジスタ b0 を指定します。つまり、定数バッファー データはレジスタ 0 に格納されます。 MarbleMazeMain::Render メソッドは、定数バッファーをアクティブにするときにレジスタ 0 を指定します。 このプロセスについては、このドキュメントで後ほど詳しく説明します。

定数バッファーの詳細については、「Direct3D 11 のバッファーの概要」を参照してください。 register キーワードの詳細については、registerに関するページを参照してください。

メッシュの読み込み

SDK メッシュ形式では、サンプル アプリケーションのメッシュ データを読み込むための基本的な方法が提供されるため、Marble Maze では、これを実行時の形式として使用します。 運用環境で使用する場合は、ゲーム固有の要件を満たすメッシュ形式を使用する必要があります。

MarbleMazeMain::LoadDeferredResources メソッドは、頂点シェーダーとピクセル シェーダーを読み込んだ後、メッシュ データを読み込みます。 メッシュは頂点データのコレクションであり、多くの場合、位置、法線データ、色、素材、テクスチャ座標などの情報が含まれます。 メッシュは、通常 3D 作成ソフトウェアで作成され、アプリケーション コードとは別のファイルに保存されます。 ビー玉と迷路は、このゲームで使用されるメッシュの 2 つの例です。

Marble Maze では、SDKMesh クラスを使用してメッシュを管理します。 このクラスは SDKMesh.h で宣言されています。 SDKMesh は、メッシュ データの読み込み、レンダリング、破棄を行うメソッドを提供します。

重要

Marble Maze では、SDK メッシュ形式を使っており、SDKMesh クラスは例を示すことだけを目的として提供されています。 SDK メッシュ形式は学習やプロトタイプの作成に役立ちますが、非常に基本的な形式であるため、ほとんどのゲーム開発の要件を満たさない可能性があります。 ゲーム固有の要件を満たすメッシュ形式を使用することをお勧めします。

 

MarbleMazeMain::LoadDeferredResources メソッドが SDKMesh::Create メソッドを使って迷路とボールのメッシュ データを読み込む方法を次の例に示します。

// Load the meshes.
DX::ThrowIfFailed(
    m_mazeMesh.Create(
        m_deviceResources->GetD3DDevice(),
        L"Media\\Models\\maze1.sdkmesh",
        false
        )
    );

DX::ThrowIfFailed(
    m_marbleMesh.Create(
        m_deviceResources->GetD3DDevice(),
        L"Media\\Models\\marble2.sdkmesh",
        false
        )
    );

衝突データの読み込み

このセクションでは、Marble Maze でビー玉と迷路の間の物理シミュレーションを実装する方法について特に説明しませんが、メッシュが読み込まれるときに物理システムのメッシュ ジオメトリが読み取られることに注意してください。

// Extract mesh geometry for the physics system.
DX::ThrowIfFailed(
    ExtractTrianglesFromMesh(
        m_mazeMesh,
        "Mesh_walls",
        m_collision.m_wallTriList
        )
    );

DX::ThrowIfFailed(
    ExtractTrianglesFromMesh(
        m_mazeMesh,
        "Mesh_Floor",
        m_collision.m_groundTriList
        )
    );

DX::ThrowIfFailed(
    ExtractTrianglesFromMesh(
        m_mazeMesh,
        "Mesh_floorSides",
        m_collision.m_floorTriList
        )
    );

m_physics.SetCollision(&m_collision);
float radius = m_marbleMesh.GetMeshBoundingBoxExtents(0).x / 2;
m_physics.SetRadius(radius);

衝突データを読み込む方法は、使う実行時の形式によって大きく異なります。 Marble Maze が SDK メッシュ ファイルから衝突ジオメトリを読み込む方法については、ソース コードの MarbleMazeMain::ExtractTrianglesFromMesh メソッドを参照してください。

ゲームの状態の更新

Marble Maze では、まずすべてのシーン オブジェクトを更新してからレンダリングすることによって、ゲーム ロジックとレンダリング ロジックを分離します。

ドキュメント「Marble Maze のアプリケーション構造」では、メイン ゲーム ループについて説明しています。 ゲーム ループの一部であるシーンの更新は、Windows イベントと入力が処理された後、シーンがレンダリングされる前に行われます。 MarbleMazeMain::Update メソッドが、UI とゲームの更新を処理します。

ユーザー インターフェイスの更新

MarbleMazeMain::Update メソッドは、UserInterface::Update メソッドを呼び出して、UI の状態を更新します。

UserInterface::GetInstance().Update(
    static_cast<float>(m_timer.GetTotalSeconds()), 
    static_cast<float>(m_timer.GetElapsedSeconds()));

UserInterface::Update メソッドは、UI コレクション内の各要素を更新します。

void UserInterface::Update(float timeTotal, float timeDelta)
{
    for (auto iter = m_elements.begin(); iter != m_elements.end(); ++iter)
    {
        (*iter)->Update(timeTotal, timeDelta);
    }
}

ElementBase から派生するクラス (UserInterface.h で定義されています) が、特定の動作を実行するための Update メソッドを実装します。 たとえば、StopwatchTimer::Update メソッドは、指定された時間で経過時間を更新し、後で表示されるテキストを更新します。

void StopwatchTimer::Update(float timeTotal, float timeDelta)
{
    if (m_active)
    {
        m_elapsedTime += timeDelta;

        WCHAR buffer[16];
        GetFormattedTime(buffer);
        SetText(buffer);
    }

    TextElement::Update(timeTotal, timeDelta);
}

シーンの更新

MarbleMazeMain::Update メソッドは、ステート マシンの現在の状態 (m_gameState に格納されている GameState) に基づいてゲームを更新します。 ゲームがアクティブな状態 (GameState::InGameActive) のとき、Marble Maze は大理石を追跡するようにカメラを更新し、定数バッファーに含まれるビュー マトリックスを更新したうえで、物理シミュレーションを更新します。

次の例は、MarbleMazeMain::Update メソッドがカメラの位置を更新する方法を示します。 Marble Maze では、m_resetCamera 変数を使って、カメラを大理石の真上に配置するためにリセットする必要があることを示します。 カメラは、ゲームが開始したとき、またはビー玉が迷路を通り抜けたときにリセットされます。 メイン メニューまたはハイスコア表示画面がアクティブな場合、カメラは定位置に設定されます。 それ以外の場合、Marble Maze では、timeDelta パラメーターを使用して、カメラの位置を現在の位置とターゲットの位置の間で補間します。 ターゲットの位置はビー玉の斜め上前方です。 経過フレーム時間を使用すると、カメラはビー玉の後を徐々にたどる (追跡する) ことができます。

static float eyeDistance = 200.0f;
static XMFLOAT3A eyePosition = XMFLOAT3A(0, 0, 0);

// Gradually move the camera above the marble.
XMFLOAT3A targetEyePosition;
XMStoreFloat3A(
    &targetEyePosition, 
    XMLoadFloat3A(&marblePosition) - (XMLoadFloat3A(&g) * eyeDistance));

if (m_resetCamera)
{
    eyePosition = targetEyePosition;
    m_resetCamera = false;
}
else
{
    XMStoreFloat3A(
        &eyePosition, 
        XMLoadFloat3A(&eyePosition) 
            + ((XMLoadFloat3A(&targetEyePosition) - XMLoadFloat3A(&eyePosition)) 
                * min(1, static_cast<float>(m_timer.GetElapsedSeconds()) * 8)
            )
    );
}

// Look at the marble. 
if ((m_gameState == GameState::MainMenu) || (m_gameState == GameState::HighScoreDisplay))
{
    // Override camera position for menus.
    XMStoreFloat3A(
        &eyePosition, 
        XMLoadFloat3A(&marblePosition) + XMVectorSet(75.0f, -150.0f, -75.0f, 0.0f));

    m_camera->SetViewParameters(
        eyePosition, 
        marblePosition, 
        XMFLOAT3(0.0f, 0.0f, -1.0f));
}
else
{
    m_camera->SetViewParameters(eyePosition, marblePosition, XMFLOAT3(0.0f, 1.0f, 0.0f));
}

次の例は、MarbleMazeMain::Update メソッドが大理石と迷路の定数バッファーを更新する方法を示します。 迷路のモデル (ワールド) 行列は、常に単位行列のままです。 要素がすべて 1 のメイン対角線を除き、単位行列は、0 で構成される正方行列です。 ビー玉のモデル行列は、位置行列に回転号列を掛けた値に基づきます。

// Update the model matrices based on the simulation.
XMStoreFloat4x4(&m_mazeConstantBufferData.model, XMMatrixIdentity());

XMStoreFloat4x4(
    &m_marbleConstantBufferData.model, 
    XMMatrixTranspose(
        XMMatrixMultiply(
            marbleRotationMatrix, 
            XMMatrixTranslationFromVector(XMLoadFloat3A(&marblePosition))
        )
    )
);

// Update the view matrix based on the camera.
XMFLOAT4X4 view;
m_camera->GetViewMatrix(&view);
m_mazeConstantBufferData.view = view;
m_marbleConstantBufferData.view = view;

MarbleMazeMain::Update メソッドがユーザー入力を読み取り、大理石の動きをシミュレートする方法について詳しくは、「Marble Maze サンプルへの入力と対話機能の追加」を参照してください。

シーンのレンダリング

シーンをレンダリングする場合、通常、次の手順が含まれます。

  1. 現在のレンダー ターゲットの深度ステンシル バッファーを設定します。
  2. レンダーおよびステンシル ビューをクリアします。
  3. 描画のために頂点シェーダーとピクセル シェーダーを準備します。
  4. シーン内の 3D オブジェクトをレンダリングします。
  5. シーンの前面に表示する 2D オブジェクトをレンダリングします。
  6. レンダリングした画像をモニターに表示します。

MarbleMazeMain::Render メソッドは、レンダー ターゲットと深度ステンシル ビューをバインドし、これらのビューをクリアします。さらに、シーンを描画し、オーバーレイを描画します。

レンダー ターゲットの準備

シーンをレンダリングする前に、現在のレンダー ターゲットの深度ステンシル バッファーを設定する必要があります。 シーンが画面のすべてのピクセルに描画される保証がない場合は、レンダー ビューとステンシル ビューもクリアします。 Marble Maze では、すべてのフレームのレンダー ビューとステンシル ビューをクリアして、前のフレームのアーティファクトが表示されないことを確かめます。

次の例は、MarbleMazeMain::Render メソッドが ID3D11DeviceContext::OMSetRenderTargets メソッドを呼び出して、レンダー ターゲットと深度ステンシル バッファーを現在のものとして設定する方法を示します。

auto context = m_deviceResources->GetD3DDeviceContext();

// Reset the viewport to target the whole screen.
auto viewport = m_deviceResources->GetScreenViewport();
context->RSSetViewports(1, &viewport);

// Reset render targets to the screen.
ID3D11RenderTargetView *const targets[1] = 
    { m_deviceResources->GetBackBufferRenderTargetView() };

context->OMSetRenderTargets(1, targets, m_deviceResources->GetDepthStencilView());

// Clear the back buffer and depth stencil view.
context->ClearRenderTargetView(
    m_deviceResources->GetBackBufferRenderTargetView(), 
    DirectX::Colors::Black);

context->ClearDepthStencilView(
    m_deviceResources->GetDepthStencilView(), 
    D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 
    1.0f, 
    0);

D3D11RenderTargetView インターフェイスと ID3D11DepthStencilView インターフェイスは、Direct3D 10 以降で提供されるテクスチャ ビューメカニズムをサポートします。 テクスチャ ビューの詳細については、「テクスチャ ビュー (Direct3D 10)」を参照してください。 OMSetRenderTargets メソッドは、Direct3D パイプラインの出力結合ステージを準備します。 出力結合ステージの詳細については、「出力結合ステージ」を参照してください。

頂点シェーダーとピクセル シェーダーの準備

シーン オブジェクトをレンダリングする前に、次の手順を実行して、描画のために頂点シェーダーとピクセル シェーダーを準備します。

  1. シェーダー入力レイアウトを現在のレイアウトとして設定します。
  2. 頂点シェーダーとピクセル シェーダーを現在のシェーダーとして設定します。
  3. シェーダーに渡す必要があるデータで定数バッファーを更新します。

重要

Marble Maze では、すべての 3D オブジェクトで頂点シェーダーとピクセル シェーダーのペアを 1 つ使います。 ゲームでシェーダーのペアを複数使用する場合は、別のシェーダーを使用するオブジェクトを描画するたびに、これらの手順を実行する必要があります。 シェーダーの状態の変更に伴うオーバーヘッドを削減するために、同じシェーダーを使用するすべてのオブジェクトのレンダー呼び出しをグループ化することをお勧めします。

 

このドキュメントの「シェーダーの読み込み」セクションでは、頂点シェーダーが作成されるときに入力レイアウトがどのように作成されるかについて説明しています。 次の例は、MarbleMazeMain::Render メソッドで ID3D11DeviceContext::IASetInputLayout メソッドを使ってこのレイアウトを現在のレイアウトとして設定する方法を示します。

m_deviceResources->GetD3DDeviceContext()->IASetInputLayout(m_inputLayout.Get());

次の例は、MarbleMazeMain::Render メソッドで ID3D11DeviceContext::VSSetShaderID3D11DeviceContext::PSSetShader のメソッドを使って、頂点シェーダーとピクセル シェーダーをそれぞれ現在のシェーダーとして設定する方法を示します。

// Set the vertex shader stage state.
m_deviceResources->GetD3DDeviceContext()->VSSetShader(
    m_vertexShader.Get(),   // use this vertex shader
    nullptr,                // don't use shader linkage
    0);                     // don't use shader linkage

m_deviceResources->GetD3DDeviceContext()->PSSetShader(
    m_pixelShader.Get(),    // use this pixel shader
    nullptr,                // don't use shader linkage
    0);                     // don't use shader linkage

m_deviceResources->GetD3DDeviceContext()->PSSetSamplers(
    0,                          // starting at the first sampler slot
    1,                          // set one sampler binding
    m_sampler.GetAddressOf());  // to use this sampler

MarbleMazeMain::Render でシェーダーと入力レイアウトを設定した後、ID3D11DeviceContext::UpdateSubresource メソッドを使って、迷路のモデル、ビュー、プロジェクションのマトリックスで定数バッファーを更新します。 UpdateSubresource メソッドは、CPU メモリから GPU メモリに行列データをコピーします。 ConstantBuffer 構造体のモデルとビューのコンポーネントは MarbleMazeMain::Update メソッドで更新されることを思い出してください。 次に MarbleMazeMain::Render メソッドが、ID3D11DeviceContext::VSSetConstantBuffersID3D11DeviceContext::PSSetConstantBuffers のメソッドを呼び出して、この定数バッファーを現在のものとして設定します。

// Update the constant buffer with the new data.
m_deviceResources->GetD3DDeviceContext()->UpdateSubresource(
    m_constantBuffer.Get(),
    0,
    nullptr,
    &m_mazeConstantBufferData,
    0,
    0);

m_deviceResources->GetD3DDeviceContext()->VSSetConstantBuffers(
    0,                                  // starting at the first constant buffer slot
    1,                                  // set one constant buffer binding
    m_constantBuffer.GetAddressOf());   // to use this buffer

m_deviceResources->GetD3DDeviceContext()->PSSetConstantBuffers(
    0,                                  // starting at the first constant buffer slot
    1,                                  // set one constant buffer binding
    m_constantBuffer.GetAddressOf());   // to use this buffer

MarbleMazeMain::Render メソッドは、レンダリングする大理石を準備するために同様の手順を実行します。

迷路とビー玉のレンダリング

現在のシェーダーをアクティブにしたら、シーン オブジェクトを描画できます。 MarbleMazeMain::Render メソッドが SDKMesh::Render メソッドを呼び出して、迷路のメッシュをレンダリングします。

m_mazeMesh.Render(
    m_deviceResources->GetD3DDeviceContext(), 
    0, 
    INVALID_SAMPLER_SLOT, 
    INVALID_SAMPLER_SLOT);

MarbleMazeMain::Render メソッドは、大理石をレンダリングするために同様の手順を実行します。

このドキュメントで前述したように、SDKMesh クラスはデモンストレーション用に提供されていますが、運用品質のゲームでの使用はお勧めしません。 ただし、SDKMesh::Render によって呼び出される SDKMesh::RenderMesh メソッドは、ID3D11DeviceContext::IASetVertexBuffers メソッドと ID3D11DeviceContext::IASetIndexBuffer メソッドを使用して、メッシュを定義する現在の頂点バッファーとインデックス バッファーを設定し、ID3D11DeviceContext::DrawIndexed メソッドを使用してバッファーを描画することに注意してください。 頂点バッファーとインデックス バッファーの操作方法の詳細については、「Direct3D 11 のバッファーの概要」を参照してください。

ユーザー インターフェイスとオーバーレイの描画

Marble Maze では、3D シーン オブジェクトを描画した後に、シーンの前面に表示される 2D の UI 要素を描画します。

MarbleMazeMain::Render メソッドは、ユーザー インターフェイスとオーバーレイを描画すると終了します。

// Draw the user interface and the overlay.
UserInterface::GetInstance().Render(m_deviceResources->GetOrientationTransform2D());

m_deviceResources->GetD3DDeviceContext()->BeginEventInt(L"Render Overlay", 0);
m_sampleOverlay->Render();
m_deviceResources->GetD3DDeviceContext()->EndEvent();

UserInterface::Render メソッドは、ID2D1DeviceContext オブジェクトを使用して、UI 要素を描画します。 このメソッドは、描画の状態を設定し、すべてのアクティブな UI 要素を描画した後、前の描画の状態を復元します。

void UserInterface::Render(D2D1::Matrix3x2F orientation2D)
{
    m_d2dContext->SaveDrawingState(m_stateBlock.Get());
    m_d2dContext->BeginDraw();
    m_d2dContext->SetTransform(orientation2D);

    m_d2dContext->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);

    for (auto iter = m_elements.begin(); iter != m_elements.end(); ++iter)
    {
        if ((*iter)->IsVisible())
            (*iter)->Render();
    }

    // We ignore D2DERR_RECREATE_TARGET here. This error indicates that the device
    // is lost. It will be handled during the next call to Present.
    HRESULT hr = m_d2dContext->EndDraw();
    if (hr != D2DERR_RECREATE_TARGET)
    {
        DX::ThrowIfFailed(hr);
    }

    m_d2dContext->RestoreDrawingState(m_stateBlock.Get());
}

SampleOverlay::Render メソッドは、同様の手法を使用してオーバーレイ ビットマップを描画します。

シーンの表示

すべての 2D および 3D シーン オブジェクトを描画した後で、Marble Maze はレンダリングされた画像をモニターに表示します。 描画を垂直空白に同期して、実際にディスプレイに表示されないフレームの描画に時間を費やさないようにします。 Marble Maze では、シーンを表示するときにデバイスの変更も処理します。

MarbleMazeMain::Render メソッドから制御が戻ると、ゲーム ループが DX::DeviceResources::Present メソッドを呼び出して、レンダリングされた画像をモニターやディスプレイに送ります。 DX::DeviceResources::Present メソッドが IDXGISwapChain::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);

この例では、m_swapChainIDXGISwapChain1 オブジェクトです。 このオブジェクトの初期化については、このドキュメントの「Direct3D と Direct2D の初期化」セクションで説明しています。

IDXGISwapChain::Present の最初のパラメーター SyncInterval は、フレームを表示する前に待機する必要がある垂直ブランクの数を指定します。 Marble Maze では 1 を指定して、次の垂直空白まで待機するように指示します。

IDXGISwapChain1::Present1 メソッドは、デバイスが削除されたか失敗したことを示すエラー コードを返します。 この場合、Marble Maze によってデバイスが再初期化されます。

// 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)
{
    HandleDeviceLost();
}
else
{
    DX::ThrowIfFailed(hr);
}

次のステップ

入力デバイスを操作する際に留意すべきいくつかの重要なプラクティスについては、「Marble Maze サンプルへの入力と対話機能の追加」を参照してください。 このドキュメントでは、タッチ、加速度計、ゲーム コントローラー、マウス入力を Marble Maze でどのようにサポートしているかについて説明しています。