DirectX9

【C++】DirectX9サンプルコードで始めるゲーム描画入門—初期化とレンダリングループの実装

DirectX9のC++サンプルは、Win32ウィンドウを用意し、Direct3DCreate9でデバイスを取得、ClearBeginScene→描画→EndScenePresentのループで三角形やスプライトを描く手順を完結に示します。

初期化、描画、リソース解放の3点を押さえるだけで動作確認まで到達できます。

目次から探す
  1. DirectX9の特徴とメリット
  2. Win32ウィンドウの作成
  3. Direct3Dオブジェクトの初期化
  4. レンダリングループの実装
  5. 頂点バッファとインデックスバッファ
  6. テクスチャの読み込みと設定
  7. シェーダープログラミング入門
  8. スプライト描画の簡易化
  9. 入力デバイスとの連携
  10. サウンド再生の基本
  11. リソース管理と解放
  12. デバッグとパフォーマンス計測
  13. マルチスレッドレンダリングの注意点
  14. フレームワークへの拡張
  15. 実装例:回転する三角形
  16. 実装例:テクスチャ付き四角形
  17. 実装例:カメラ移動を伴う3Dシーン
  18. ユーザーインターフェイスの組み込み
  19. よくあるエラーと対処法
  20. 今後の発展例
  21. まとめ

DirectX9の特徴とメリット

DirectX9は、Microsoftが提供するマルチメディアAPIの一つで、特にゲームや3Dグラフィックスの開発に広く利用されてきました。

2002年にリリースされて以来、多くのゲームタイトルやアプリケーションで採用されており、安定した動作と豊富な機能が特徴です。

ここでは、DirectX9の主な特徴とメリットについて解説します。

ハードウェアアクセラレーションのサポート

DirectX9は、GPU(グラフィックス処理装置)を活用したハードウェアアクセラレーションを強力にサポートしています。

これにより、CPUだけで処理する場合に比べて、グラフィックス描画のパフォーマンスが大幅に向上します。

具体的には、Direct3D 9という3DグラフィックスAPIを通じて、GPUのシェーダー機能やテクスチャマッピング、頂点処理などの機能を利用可能です。

これにより、リアルタイムで複雑な3Dシーンを描画したり、エフェクトを適用したりすることができます。

たとえば、DirectX9では以下のようなハードウェアアクセラレーション機能が利用できます。

  • 頂点シェーダーとピクセルシェーダー

GPU上で頂点変換やピクセル単位の色計算を行うプログラムを実行でき、リアルなライティングや影の表現が可能です。

  • テクスチャフィルタリング

テクスチャの拡大縮小時に滑らかな表示を実現するための各種フィルタリング(バイリニア、トリリニア、異方性フィルタリング)をサポートしています。

  • マルチテクスチャリング

複数のテクスチャを同時に合成して描画できるため、複雑な表面表現が可能です。

  • ハードウェアトランスフォーム&ライト(T&L)

頂点の変換やライティング計算をGPUに任せることで、CPU負荷を軽減し、より多くのオブジェクトを描画できます。

これらの機能を活用することで、ゲームや3Dアプリケーションの描画処理を効率化し、滑らかで美しい映像表現を実現できます。

APIの長期サポート状況

DirectX9は2002年のリリース以来、長期間にわたり多くのWindows環境でサポートされてきました。

特にWindows XP、Vista、7、8、10といった幅広いOSバージョンで動作し、ゲーム開発者やユーザーにとって安定した環境を提供しています。

MicrosoftはDirectXの新しいバージョン(DirectX10、11、12)をリリースしていますが、DirectX9は依然として多くの既存タイトルやレガシーアプリケーションで利用されています。

これは、以下の理由によります。

  • 互換性の高さ

古いハードウェアやOSでも動作するため、幅広いユーザー層に対応可能です。

  • 成熟したAPI設計

多くのバグ修正や最適化が行われており、安定性が高いです。

  • 豊富なドキュメントとサンプルコード

開発者コミュニティや公式ドキュメントが充実しているため、学習やトラブルシューティングがしやすいです。

  • ゲームエンジンやミドルウェアの対応

多くのゲームエンジンやミドルウェアがDirectX9をサポートしており、開発の敷居が低いです。

ただし、最新のグラフィックス機能やパフォーマンスを求める場合は、DirectX11や12の利用が推奨されますが、DirectX9は依然として教育用やレガシーサポート、軽量な3Dアプリケーションに適しています。

このように、DirectX9はハードウェアアクセラレーションを活用した高性能な描画が可能であり、長期間にわたる安定したサポートがあるため、ゲーム描画の入門として最適なAPIです。

Win32ウィンドウの作成

WindowsアプリケーションでDirectX9を利用するには、まずWin32のウィンドウを作成する必要があります。

ここでは、ウィンドウクラスの登録とメッセージループの設計について詳しく説明します。

ウィンドウクラス登録

Win32アプリケーションでは、ウィンドウを作成する前に「ウィンドウクラス」を登録します。

ウィンドウクラスは、ウィンドウの基本的な属性や動作を定義する構造体で、WNDCLASSEX構造体を使って設定します。

以下に、ウィンドウクラスを登録するサンプルコードを示します。

#include <windows.h>
// ウィンドウプロシージャの宣言
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow)
{
    // ウィンドウクラスの設定
    WNDCLASSEX wc = {};
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW; // ウィンドウの再描画スタイル
    wc.lpfnWndProc = WndProc;           // ウィンドウプロシージャの指定
    wc.cbClsExtra = 0;                  // 追加メモリなし
    wc.cbWndExtra = 0;                  // 追加メモリなし
    wc.hInstance = hInstance;           // インスタンスハンドル
    wc.hIcon = LoadIcon(nullptr, IDI_APPLICATION); // アイコン
    wc.hCursor = LoadCursor(nullptr, IDC_ARROW);   // カーソル
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); // 背景色
    wc.lpszMenuName = nullptr;          // メニューなし
    wc.lpszClassName = L"MyWindowClass"; // クラス名
    wc.hIconSm = LoadIcon(nullptr, IDI_APPLICATION); // 小アイコン
    // ウィンドウクラスの登録
    if (!RegisterClassEx(&wc))
    {
        MessageBox(nullptr, L"ウィンドウクラスの登録に失敗しました。", L"エラー", MB_OK | MB_ICONERROR);
        return 0;
    }
    // ウィンドウの作成
    HWND hwnd = CreateWindowEx(
        0,
        wc.lpszClassName,
        L"DirectX9 Sample Window",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT,
        800, 600,
        nullptr,
        nullptr,
        hInstance,
        nullptr);
    if (!hwnd)
    {
        MessageBox(nullptr, L"ウィンドウの作成に失敗しました。", L"エラー", MB_OK | MB_ICONERROR);
        return 0;
    }
    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);
    // メッセージループはここで行う(後述)
    return 0;
}
// ウィンドウプロシージャの定義
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    default:
        return DefWindowProc(hwnd, msg, wParam, lParam);
    }
}

このコードのポイントは以下の通りです。

項目説明
WNDCLASSEXウィンドウクラスの属性を設定する構造体。サイズ、スタイル、プロシージャ、アイコン、カーソル、背景色などを指定します。
RegisterClassExウィンドウクラスをOSに登録します。登録に失敗するとウィンドウ作成ができません。
CreateWindowEx登録したクラス名を使ってウィンドウを作成します。ウィンドウのタイトルやサイズ、スタイルを指定します。
WndProcウィンドウに送られるメッセージを処理する関数です。ここではウィンドウが閉じられたときにアプリケーションを終了させています。

このようにウィンドウクラスを登録し、ウィンドウを作成することで、DirectX9の描画対象となるウィンドウが用意できます。

メッセージループ設計

Win32アプリケーションは、OSから送られてくるメッセージを受け取り処理することで動作します。

メッセージループは、これらのメッセージを取得し、適切に処理するための仕組みです。

以下に、基本的なメッセージループの例を示します。

MSG msg = {};
while (true)
{
    // メッセージがあるかどうかをチェックし、あれば取得する
    if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
    {
        if (msg.message == WM_QUIT)
            break; // WM_QUITが来たらループを抜けて終了
        TranslateMessage(&msg); // キーボードメッセージの変換
        DispatchMessage(&msg);  // ウィンドウプロシージャにメッセージを送る
    }
    else
    {
        // メッセージがない場合はここで描画処理などを行う
        // 例: RenderFrame();
    }
}

このメッセージループのポイントは以下の通りです。

関数説明
PeekMessageメッセージキューからメッセージを取得します。PM_REMOVEを指定すると取得したメッセージはキューから削除されます。
TranslateMessageキーボードメッセージを文字メッセージに変換します。
DispatchMessageメッセージをウィンドウプロシージャに送ります。
WM_QUITアプリケーション終了を示すメッセージ。これを受け取ったらループを抜けます。

PeekMessageを使うことで、メッセージがない場合でもループを継続できるため、ゲームのように常に画面を更新し続けるアプリケーションに適しています。

メッセージがないときに描画処理を行うことで、スムーズなフレーム更新が可能です。

一方、GetMessageを使うとメッセージが来るまで処理が停止するため、リアルタイム描画には向きません。

以上のように、ウィンドウクラスの登録とメッセージループの設計を正しく行うことで、DirectX9の描画を行うための基盤が整います。

次のステップでは、Direct3Dオブジェクトの初期化に進みます。

Direct3Dオブジェクトの初期化

DirectX9で描画を行うためには、Direct3Dのオブジェクトを初期化し、描画デバイスを作成する必要があります。

ここでは、IDirect3D9の生成からデバイスの作成、そしてデバイスロストへの対応まで詳しく説明します。

IDirect3D9の生成

Direct3D 9の機能を利用するための最初のステップは、IDirect3D9インターフェースの生成です。

これはDirect3Dのエントリーポイントとなるオブジェクトで、デバイスの作成やハードウェア情報の取得に使います。

生成にはDirect3DCreate9関数を使用します。

引数にはDirect3Dのバージョンを指定し、通常はD3D_SDK_VERSIONを渡します。

以下にサンプルコードを示します。

#include <windows.h>
#include <d3d9.h>
#pragma comment(lib, "d3d9.lib")
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow)
{
    // Direct3D9オブジェクトの生成
    IDirect3D9* pD3D = Direct3DCreate9(D3D_SDK_VERSION);
    if (!pD3D)
    {
        MessageBox(nullptr, L"Direct3D9の生成に失敗しました。", L"エラー", MB_OK | MB_ICONERROR);
        return 0;
    }
    // ここでデバイス作成などを行う
    // 使用後は解放
    pD3D->Release();
    return 0;
}

このコードでは、Direct3DCreate9が成功するとIDirect3D9のポインタが返されます。

失敗するとnullptrが返るため、必ずチェックしてください。

デバイスパラメータの設定

Direct3Dデバイスを作成する際には、描画に関する様々なパラメータを設定します。

これらはD3DPRESENT_PARAMETERS構造体にまとめて指定します。

バックバッファフォーマット選択

バックバッファは描画結果を一時的に保持する領域で、最終的に画面に表示されます。

フォーマットは色深度やアルファチャンネルの有無を決める重要な設定です。

代表的なフォーマットは以下の通りです。

フォーマット名説明
D3DFMT_X8R8G8B832ビットカラー(アルファなし)
D3DFMT_A8R8G8B832ビットカラー(アルファあり)
D3DFMT_R5G6B516ビットカラー(アルファなし)

一般的にはD3DFMT_X8R8G8B8が使われますが、ハードウェアの対応状況に応じて選択します。

スワップエフェクト指定

スワップエフェクトは、バックバッファとフロントバッファ(画面表示用バッファ)の切り替え方法を指定します。

主な種類は以下の通りです。

スワップエフェクト説明
D3DSWAPEFFECT_DISCARD最も高速で一般的。バックバッファの内容は不定になります。
D3DSWAPEFFECT_FLIPバックバッファとフロントバッファを入れ替えます。フルスクリーンで使用。
D3DSWAPEFFECT_COPYバックバッファの内容をフロントバッファにコピー。ウィンドウモードで使われることが多い。

以下にD3DPRESENT_PARAMETERSの設定例を示します。

D3DPRESENT_PARAMETERS d3dpp = {};
d3dpp.Windowed = TRUE; // ウィンドウモード
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
d3dpp.BackBufferWidth = 800;
d3dpp.BackBufferHeight = 600;
d3dpp.EnableAutoDepthStencil = TRUE; // 深度バッファを自動生成
d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8; // 24ビット深度+8ビットステンシル
d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_ONE; // 垂直同期あり

デバイスの作成

IDirect3D9オブジェクトのCreateDeviceメソッドを使って、Direct3Dデバイスを作成します。

これが描画の中心となるオブジェクトです。

IDirect3DDevice9* pDevice = nullptr;
HRESULT hr = pD3D->CreateDevice(
    D3DADAPTER_DEFAULT,          // デフォルトのグラフィックスアダプター
    D3DDEVTYPE_HAL,              // ハードウェア抽象レイヤーを使用
    hwnd,                       // 描画対象のウィンドウハンドル
    D3DCREATE_HARDWARE_VERTEXPROCESSING, // 頂点処理をGPUで行う
    &d3dpp,                     // プレゼンテーションパラメータ
    &pDevice                    // 作成したデバイスのポインタ
);
if (FAILED(hr))
{
    MessageBox(nullptr, L"Direct3Dデバイスの作成に失敗しました。", L"エラー", MB_OK | MB_ICONERROR);
    pD3D->Release();
    return 0;
}

フルスクリーンとウィンドウモードの切替

D3DPRESENT_PARAMETERSWindowedメンバをTRUEにするとウィンドウモード、FALSEにするとフルスクリーンモードになります。

フルスクリーンモードでは、解像度やリフレッシュレートを指定することも可能です。

d3dpp.Windowed = FALSE; // フルスクリーン
d3dpp.BackBufferWidth = 1920;
d3dpp.BackBufferHeight = 1080;
d3dpp.FullScreen_RefreshRateInHz = 60; // 60Hz

フルスクリーンはパフォーマンスが高い反面、ユーザーの操作で画面が切り替わるとデバイスがロストしやすくなります。

デバイスロストへの対応

Direct3Dデバイスは、画面モードの切り替えやAlt+Tab操作などで「ロスト」状態になることがあります。

ロスト状態とは、デバイスが描画できない状態で、再度描画可能にするためには復旧処理が必要です。

ロスト状態の検出は、IDirect3DDevice9::TestCooperativeLevelメソッドで行います。

戻り値によって状態を判定します。

戻り値意味
D3DERR_DEVICELOSTデバイスはロスト状態。復旧できない。描画は不可。
D3DERR_DEVICENOTRESETデバイスはリセット可能です。復旧処理を行う必要あり。
D3D_OKデバイスは正常。描画可能です。

ロスト状態の復旧手順は以下の通りです。

  1. 描画を停止します。
  2. すべてのリソース(特にD3DPOOL_DEFAULTで作成したもの)を解放します。
  3. Resetメソッドでデバイスをリセットします。
  4. リソースを再作成します。

以下に簡単な復旧処理の例を示します。

HRESULT hr = pDevice->TestCooperativeLevel();
if (hr == D3DERR_DEVICELOST)
{
    // デバイスロスト中。描画は行わない。
    return;
}
else if (hr == D3DERR_DEVICENOTRESET)
{
    // リセット可能です。リソースを解放してリセットを試みる。
    // 例: ReleaseResources();
    hr = pDevice->Reset(&d3dpp);
    if (SUCCEEDED(hr))
    {
        // リセット成功。リソースを再作成。
        // 例: CreateResources();
    }
}
else if (hr == D3D_OK)
{
    // 正常。描画処理を続行。
}

デバイスロストはゲームやリアルタイムアプリケーションで頻繁に発生するため、適切に対応しないとクラッシュや描画異常の原因になります。

リソース管理をしっかり行い、復旧処理を実装することが重要です。

レンダリングループの実装

DirectX9での描画は、メッセージループの中で繰り返しレンダリング処理を行うことで実現します。

ここでは、レンダリングループの基本的な構成要素であるクリア処理、BeginSceneEndSceneの使い方、バックバッファの転送、そして固定フレームレート制御について詳しく説明します。

クリア処理

レンダリングを開始する前に、画面をクリア(初期化)することが一般的です。

これにより、前のフレームの描画内容が残らず、きれいな描画が可能になります。

Direct3DデバイスのClearメソッドを使って、バックバッファや深度バッファをクリアします。

主なパラメータは以下の通りです。

  • クリア対象のバッファ(バックバッファ、深度バッファ、ステンシルバッファ)
  • クリア色(RGBA)
  • 深度値(通常は1.0fで最大深度)
  • ステンシル値(通常は0)

以下にクリア処理の例を示します。

pDevice->Clear(
    0,              // クリアする矩形の数(0は全画面)
    nullptr,        // クリアする矩形の配列(nullptrで全画面)
    D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, // バックバッファと深度バッファをクリア
    D3DCOLOR_XRGB(0, 0, 64), // クリア色(暗い青)
    1.0f,           // 深度バッファの初期値
    0               // ステンシルバッファの初期値
);

この例では、画面全体を暗い青色で塗りつぶし、深度バッファも初期化しています。

これにより、次の描画が正しく行われる準備が整います。

BeginSceneとEndScene

Direct3Dの描画は、BeginSceneEndSceneの間で行います。

これらは描画の開始と終了を示すメソッドで、描画コマンドをGPUに送るための準備と完了処理を行います。

if (SUCCEEDED(pDevice->BeginScene()))
{
    // ここに描画処理を記述
    // 例: pDevice->DrawPrimitive(...);
    pDevice->EndScene();
}

BeginSceneが成功した場合のみ描画処理を行い、最後にEndSceneで描画を終了します。

これにより、描画コマンドが正しくバッチ処理され、効率的にGPUに送られます。

Presentによるバックバッファ転送

Direct3Dはダブルバッファリングを採用しており、描画はバックバッファに行われます。

描画が完了したら、Presentメソッドを呼び出してバックバッファの内容をフロントバッファ(画面表示用バッファ)に転送します。

pDevice->Present(nullptr, nullptr, nullptr, nullptr);

引数は通常nullptrで問題ありません。

これにより、描画結果が画面に表示されます。

固定フレームレート制御

ゲームやリアルタイムアプリケーションでは、一定のフレームレートで描画を行うことが重要です。

DirectX9自体にはフレームレート制御機能はないため、自前で制御します。

一般的な方法は、1フレームの理想的な時間(例えば60fpsなら約16.67ms)を計測し、描画処理後に残り時間だけスリープする方法です。

以下に簡単な固定フレームレート制御の例を示します。

#include <windows.h>
#include <chrono>
const int TARGET_FPS = 60;
const int FRAME_TIME_MS = 1000 / TARGET_FPS;
auto lastTime = std::chrono::high_resolution_clock::now();
while (true)
{
    // メッセージ処理(省略)
    // 描画処理開始
    pDevice->Clear(0, nullptr, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 64), 1.0f, 0);
    if (SUCCEEDED(pDevice->BeginScene()))
    {
        // 描画コマンド
        pDevice->EndScene();
    }
    pDevice->Present(nullptr, nullptr, nullptr, nullptr);
    // フレーム時間計測
    auto currentTime = std::chrono::high_resolution_clock::now();
    auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(currentTime - lastTime).count();
    if (elapsed < FRAME_TIME_MS)
    {
        Sleep(FRAME_TIME_MS - elapsed);
    }
    lastTime = std::chrono::high_resolution_clock::now();
}

このコードでは、1フレームの処理にかかった時間を計測し、目標フレーム時間に満たない場合はSleepで待機します。

これにより、CPU負荷を抑えつつ安定したフレームレートを維持できます。

ただし、Sleepの精度は環境によって異なるため、より高精度なタイマーやフレームスキップの実装も検討すると良いでしょう。

以上の要素を組み合わせることで、DirectX9のレンダリングループが完成します。

クリア処理で画面を初期化し、BeginSceneEndSceneで描画を囲み、Presentで画面に表示。

さらに固定フレームレート制御で滑らかな描画を実現します。

頂点バッファとインデックスバッファ

DirectX9で3Dオブジェクトを描画する際には、頂点データをGPUに渡すための頂点バッファと、頂点の描画順序を指定するインデックスバッファを利用します。

ここでは、カスタム頂点フォーマットの定義からバッファの生成・ロック、そして描画呼び出しまで詳しく説明します。

カスタム頂点フォーマットの定義

Direct3Dでは、頂点の構造を自由に定義できます。

頂点には位置情報だけでなく、法線ベクトルやテクスチャ座標、色など様々な属性を持たせることが可能です。

これらの属性をまとめた構造体を作成し、Direct3Dに認識させるために頂点フォーマット(FVF: Flexible Vertex Format)を指定します。

以下は、位置(x, y, z)とテクスチャ座標(u, v)を持つカスタム頂点構造体の例です。

#include <d3d9.h>
// カスタム頂点構造体
struct CUSTOMVERTEX
{
    FLOAT x, y, z;   // 頂点の位置
    FLOAT u, v;      // テクスチャ座標
};
// FVFコードの定義
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ | D3DFVF_TEX1)
  • D3DFVF_XYZは3D空間の位置を示します
  • D3DFVF_TEX1は1セットのテクスチャ座標を持つことを示します

このようにFVFを定義することで、Direct3Dは頂点データの内容を正しく解釈できます。

バッファの生成とロック

頂点バッファはIDirect3DDevice9::CreateVertexBufferで作成します。

作成後、Lockメソッドでバッファのメモリにアクセスし、頂点データを書き込みます。

書き込みが終わったらUnlockで解放します。

以下に、四角形を描画するための頂点バッファ生成とデータ書き込みの例を示します。

IDirect3DVertexBuffer9* pVertexBuffer = nullptr;
// 頂点数
const int vertexCount = 4;
// 頂点バッファの作成
HRESULT hr = pDevice->CreateVertexBuffer(
    sizeof(CUSTOMVERTEX) * vertexCount, // バッファサイズ
    0,                                  // 使用フラグ(通常は0)
    D3DFVF_CUSTOMVERTEX,                // FVFコード
    D3DPOOL_MANAGED,                    // メモリプール
    &pVertexBuffer,                     // 作成したバッファのポインタ
    nullptr);
if (FAILED(hr))
{
    MessageBox(nullptr, L"頂点バッファの作成に失敗しました。", L"エラー", MB_OK | MB_ICONERROR);
    return;
}
// バッファのロック
CUSTOMVERTEX* pVertices = nullptr;
hr = pVertexBuffer->Lock(0, 0, (void**)&pVertices, 0);
if (FAILED(hr))
{
    MessageBox(nullptr, L"頂点バッファのロックに失敗しました。", L"エラー", MB_OK | MB_ICONERROR);
    return;
}
// 頂点データの設定(四角形の4頂点)
pVertices[0] = { -1.0f,  1.0f, 0.0f, 0.0f, 0.0f }; // 左上
pVertices[1] = {  1.0f,  1.0f, 0.0f, 1.0f, 0.0f }; // 右上
pVertices[2] = { -1.0f, -1.0f, 0.0f, 0.0f, 1.0f }; // 左下
pVertices[3] = {  1.0f, -1.0f, 0.0f, 1.0f, 1.0f }; // 右下
// バッファのアンロック
pVertexBuffer->Unlock();

インデックスバッファも同様にCreateIndexBufferで作成し、Lock/Unlockでデータを書き込みます。

インデックスは頂点の描画順序を指定し、三角形の組み合わせを効率的に表現できます。

以下は四角形を2つの三角形で描画するためのインデックスバッファの例です。

IDirect3DIndexBuffer9* pIndexBuffer = nullptr;
const int indexCount = 6; // 2三角形 × 3頂点
hr = pDevice->CreateIndexBuffer(
    sizeof(WORD) * indexCount,
    0,
    D3DFMT_INDEX16, // 16ビットインデックス
    D3DPOOL_MANAGED,
    &pIndexBuffer,
    nullptr);
if (FAILED(hr))
{
    MessageBox(nullptr, L"インデックスバッファの作成に失敗しました。", L"エラー", MB_OK | MB_ICONERROR);
    return;
}
WORD* pIndices = nullptr;
hr = pIndexBuffer->Lock(0, 0, (void**)&pIndices, 0);
if (FAILED(hr))
{
    MessageBox(nullptr, L"インデックスバッファのロックに失敗しました。", L"エラー", MB_OK | MB_ICONERROR);
    return;
}
// インデックスデータの設定(2つの三角形)
pIndices[0] = 0; pIndices[1] = 1; pIndices[2] = 2; // 三角形1
pIndices[3] = 2; pIndices[4] = 1; pIndices[5] = 3; // 三角形2
pIndexBuffer->Unlock();

描画呼び出し

頂点バッファとインデックスバッファが準備できたら、描画時にこれらをデバイスにセットし、描画コマンドを発行します。

// 頂点フォーマットの設定
pDevice->SetFVF(D3DFVF_CUSTOMVERTEX);
// 頂点バッファのセット
pDevice->SetStreamSource(0, pVertexBuffer, 0, sizeof(CUSTOMVERTEX));
// インデックスバッファのセット
pDevice->SetIndices(pIndexBuffer);
// プリミティブの描画(インデックス付き三角形リスト)
pDevice->DrawIndexedPrimitive(
    D3DPT_TRIANGLELIST, // プリミティブタイプ
    0,                  // ベース頂点インデックス
    0,                  // 最小頂点インデックス
    4,                  // 頂点数
    0,                  // インデックス開始位置
    2                   // プリミティブ数(三角形2つ)
);

このコードでは、まず頂点フォーマットを設定し、頂点バッファとインデックスバッファをデバイスにバインドします。

その後、DrawIndexedPrimitiveで三角形リストとして描画します。

これらの手順を組み合わせることで、DirectX9で効率的に3Dモデルの頂点データを管理し、描画できます。

頂点バッファとインデックスバッファを活用することで、メモリ使用量を抑えつつ複雑な形状を表現可能です。

テクスチャの読み込みと設定

DirectX9でリアルな描画を行うためには、テクスチャを利用してポリゴンに画像を貼り付けることが重要です。

ここでは、テクスチャの読み込みに便利なD3DXCreateTextureFromFile関数の使い方、サンプラーステートの調整方法、そしてUV座標の扱いについて詳しく説明します。

D3DXCreateTextureFromFileの利用

DirectX9の補助ライブラリであるD3DXには、画像ファイルから簡単にテクスチャを作成できる関数D3DXCreateTextureFromFileが用意されています。

この関数を使うと、BMPやPNG、JPEGなどの一般的な画像ファイルを読み込み、IDirect3DTexture9オブジェクトを生成できます。

以下にテクスチャの読み込み例を示します。

#include <d3dx9.h>
#pragma comment(lib, "d3dx9.lib")
IDirect3DTexture9* pTexture = nullptr;
// テクスチャの読み込み
HRESULT hr = D3DXCreateTextureFromFile(pDevice, L"texture.png", &pTexture);
if (FAILED(hr))
{
    MessageBox(nullptr, L"テクスチャの読み込みに失敗しました。", L"エラー", MB_OK | MB_ICONERROR);
    return;
}
  • pDeviceIDirect3DDevice9のポインタです
  • 第2引数に読み込みたい画像ファイルのパスを指定します
  • 第3引数に作成されたテクスチャのポインタが返されます

この関数は内部で画像のフォーマット変換やミップマップ生成も自動的に行うため、非常に便利です。

テクスチャを使う際は、描画前にSetTextureメソッドでテクスチャをセットします。

pDevice->SetTexture(0, pTexture);

ここでの0はテクスチャステージ番号で、複数のテクスチャを使う場合はステージ番号を変えて設定します。

サンプラーステートの調整

テクスチャの表示品質や描画方法は、サンプラーステート(Sampler State)で制御します。

主に以下のような設定があります。

サンプラーステート説明代表的な値
D3DSAMP_MINFILTERテクスチャの縮小時のフィルタリングD3DTEXF_LINEAR(線形補間)、D3DTEXF_POINT(最近傍)
D3DSAMP_MAGFILTERテクスチャの拡大時のフィルタリング同上
D3DSAMP_MIPFILTERミップマップの選択方法D3DTEXF_LINEARD3DTEXF_POINTD3DTEXF_NONE
D3DSAMP_ADDRESSUU座標のラップモードD3DTADDRESS_WRAP(繰り返し)、D3DTADDRESS_CLAMP(端で固定)
D3DSAMP_ADDRESSVV座標のラップモード同上

以下はサンプラーステートを設定する例です。

pDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
pDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
pDevice->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);
pDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP);
pDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP);

この設定では、テクスチャの拡大縮小時に線形補間を使い、UV座標が0~1の範囲を超えた場合は繰り返し表示されます。

これにより、滑らかで自然なテクスチャ表示が可能です。

UV座標の扱い

UV座標は、頂点ごとにテクスチャのどの部分を貼り付けるかを指定する2D座標です。

通常、UとVは0.0から1.0の範囲で表され、(0,0)がテクスチャの左上、(1,1)が右下を示します。

頂点構造体にUV座標を含める場合は、以下のように定義します。

struct CUSTOMVERTEX
{
    FLOAT x, y, z;   // 頂点位置
    FLOAT u, v;      // テクスチャ座標
};

UV座標を正しく設定することで、テクスチャの特定の部分をポリゴンにマッピングできます。

例えば、四角形の4頂点に以下のUV座標を割り当てると、テクスチャ全体が正しく貼り付けられます。

頂点UV
左上0.0f0.0f
右上1.0f0.0f
左下0.0f1.0f
右下1.0f1.0f

UV座標はテクスチャの繰り返しや反転、部分的な切り出しにも利用できます。

例えば、UV座標を(0.0, 0.0)~(0.5, 0.5)に設定すると、テクスチャの左上半分だけを表示できます。

また、UV座標の範囲を1.0以上に設定すると、サンプラーステートのラップモードに応じてテクスチャが繰り返し表示されます。

これらの手順を組み合わせることで、DirectX9でテクスチャを効率的に読み込み、表示品質を調整し、UV座標を活用した多彩な表現が可能になります。

シェーダープログラミング入門

DirectX9では、HLSL(High Level Shader Language)を使って頂点シェーダーやピクセルシェーダーを記述し、GPU上で高度なグラフィックス処理を実現できます。

ここでは、HLSLの概要から始めて、頂点シェーダーとピクセルシェーダーの作成方法、そしてシェーダー定数の更新方法について詳しく説明します。

シェーダー言語HLSL概要

HLSLはMicrosoftが開発したシェーダー言語で、C言語に似た構文を持ちます。

GPU上で実行されるプログラムを高水準言語で記述でき、頂点変換やピクセルの色計算などを柔軟に制御可能です。

主な特徴は以下の通りです。

  • 頂点シェーダー(Vertex Shader)

頂点ごとの処理を行い、位置変換や法線計算、テクスチャ座標の補間などを担当します。

  • ピクセルシェーダー(Pixel Shader)

ピクセルごとの色計算を行い、ライティングやテクスチャ合成、エフェクトを実装します。

  • シェーダーモデル

DirectX9ではシェーダーモデル2.0や3.0が主に使われ、機能や命令数に制限があります。

HLSLのコードは.hlsl.fxファイルに記述し、コンパイルしてバイナリを生成します。

DirectX9ではD3DXCompileShaderD3DXCreateEffectなどのAPIを使ってコンパイル・ロードします。

頂点シェーダーの作成

頂点シェーダーは、頂点の位置を変換し、描画に必要なデータをピクセルシェーダーに渡す役割を持ちます。

以下に簡単な頂点シェーダーの例を示します。

// 頂点入力構造体
struct VS_INPUT
{
    float4 position : POSITION; // 頂点位置(x,y,z,w)
    float2 texcoord : TEXCOORD0; // テクスチャ座標
};
// 頂点出力構造体
struct VS_OUTPUT
{
    float4 position : POSITION; // 変換後の位置
    float2 texcoord : TEXCOORD0; // テクスチャ座標をそのまま渡す
};
// 定数バッファ(行列)
uniform float4x4 WorldViewProj;
// 頂点シェーダー関数
VS_OUTPUT main(VS_INPUT input)
{
    VS_OUTPUT output;
    // ワールド・ビュー・射影行列で頂点位置を変換
    output.position = mul(input.position, WorldViewProj);
    output.texcoord = input.texcoord;
    return output;
}

このシェーダーは、頂点の位置をWorldViewProj行列で変換し、テクスチャ座標をそのままピクセルシェーダーに渡しています。

DirectX9のC++コードでこの頂点シェーダーをコンパイルし、デバイスにセットする例は以下の通りです。

#include <d3d9.h>
#include <d3dx9.h>
#pragma comment(lib, "d3d9.lib")
#pragma comment(lib, "d3dx9.lib")
IDirect3DVertexShader9* pVertexShader = nullptr;
ID3DXConstantTable* pConstantTable = nullptr;
HRESULT CompileVertexShader(IDirect3DDevice9* pDevice)
{
    ID3DXBuffer* pCode = nullptr;
    ID3DXBuffer* pErrors = nullptr;
    // HLSLコード(文字列で記述)
    const char* vsCode =
        "struct VS_INPUT { float4 position : POSITION; float2 texcoord : TEXCOORD0; };"
        "struct VS_OUTPUT { float4 position : POSITION; float2 texcoord : TEXCOORD0; };"
        "uniform float4x4 WorldViewProj;"
        "VS_OUTPUT main(VS_INPUT input) {"
        " VS_OUTPUT output;"
        " output.position = mul(input.position, WorldViewProj);"
        " output.texcoord = input.texcoord;"
        " return output;"
        "}";
    HRESULT hr = D3DXCompileShader(
        vsCode,
        strlen(vsCode),
        nullptr,
        nullptr,
        "main",
        "vs_3_0", // シェーダーモデル3.0
        0,
        &pCode,
        &pErrors,
        &pConstantTable);
    if (FAILED(hr))
    {
        if (pErrors)
        {
            MessageBoxA(nullptr, (char*)pErrors->GetBufferPointer(), "Vertex Shader Compile Error", MB_OK);
            pErrors->Release();
        }
        return hr;
    }
    hr = pDevice->CreateVertexShader((DWORD*)pCode->GetBufferPointer(), &pVertexShader);
    pCode->Release();
    if (FAILED(hr))
        return hr;
    return S_OK;
}

ピクセルシェーダーの作成

ピクセルシェーダーは、画面上の各ピクセルの色を計算します。

以下はテクスチャをそのまま表示するシンプルなピクセルシェーダーの例です。

struct PS_INPUT
{
    float2 texcoord : TEXCOORD0;
};
uniform sampler2D tex;
float4 main(PS_INPUT input) : COLOR
{
    // テクスチャの色を取得して返す
    return tex2D(tex, input.texcoord);
}

C++でのコンパイルとセットアップ例は以下の通りです。

IDirect3DPixelShader9* pPixelShader = nullptr;
ID3DXConstantTable* pPSConstantTable = nullptr;
HRESULT CompilePixelShader(IDirect3DDevice9* pDevice)
{
    ID3DXBuffer* pCode = nullptr;
    ID3DXBuffer* pErrors = nullptr;
    const char* psCode =
        "struct PS_INPUT { float2 texcoord : TEXCOORD0; };"
        "uniform sampler2D tex;"
        "float4 main(PS_INPUT input) : COLOR {"
        " return tex2D(tex, input.texcoord);"
        "}";
    HRESULT hr = D3DXCompileShader(
        psCode,
        strlen(psCode),
        nullptr,
        nullptr,
        "main",
        "ps_3_0",
        0,
        &pCode,
        &pErrors,
        &pPSConstantTable);
    if (FAILED(hr))
    {
        if (pErrors)
        {
            MessageBoxA(nullptr, (char*)pErrors->GetBufferPointer(), "Pixel Shader Compile Error", MB_OK);
            pErrors->Release();
        }
        return hr;
    }
    hr = pDevice->CreatePixelShader((DWORD*)pCode->GetBufferPointer(), &pPixelShader);
    pCode->Release();
    if (FAILED(hr))
        return hr;
    return S_OK;
}

シェーダー定数の更新

シェーダー内で使う定数(行列や色など)は、定数テーブルを通じてC++側から更新します。

ID3DXConstantTableSetMatrixSetFloatなどのメソッドを使い、シェーダーに値を渡します。

例えば、先ほどの頂点シェーダーのWorldViewProj行列を更新する例です。

D3DXMATRIX worldViewProj;
// 行列の計算(例として単位行列)
D3DXMatrixIdentity(&worldViewProj);
// 定数テーブルから変数を取得してセット
pConstantTable->SetMatrix(pDevice, "WorldViewProj", &worldViewProj);

ピクセルシェーダーのテクスチャサンプラーも同様にセットします。

pDevice->SetTexture(0, pTexture);

シェーダー定数は描画前に毎フレーム更新することが多いため、効率的に管理しましょう。

これらの手順を踏むことで、DirectX9でHLSLを使ったシェーダープログラミングが可能になります。

頂点シェーダーで頂点変換を行い、ピクセルシェーダーで色を計算し、定数を適切に更新することで多彩なグラフィックス表現が実現できます。

スプライト描画の簡易化

DirectX9で2D画像やテクスチャを画面に描画する際、低レベルの頂点バッファやインデックスバッファを使って描画するのは手間がかかります。

ID3DXSpriteインターフェースを使うと、スプライト(2D画像)描画を簡単に行えます。

ここでは、ID3DXSpriteの基本的な使い方と、フォント描画との併用方法について詳しく説明します。

ID3DXSpriteの使い方

ID3DXSpriteはDirect3Dの補助ライブラリD3DXが提供するインターフェースで、2Dスプライトの描画を簡単に行うための機能をまとめています。

スプライトの位置や回転、拡大縮小、アルファブレンドなどを簡単に設定可能です。

スプライトオブジェクトの作成

まず、Direct3DデバイスからID3DXSpriteオブジェクトを作成します。

#include <d3dx9.h>
#pragma comment(lib, "d3dx9.lib")
ID3DXSprite* pSprite = nullptr;
HRESULT hr = D3DXCreateSprite(pDevice, &pSprite);
if (FAILED(hr))
{
    MessageBox(nullptr, L"スプライトオブジェクトの作成に失敗しました。", L"エラー", MB_OK | MB_ICONERROR);
    return;
}

スプライト描画の基本的な流れ

スプライト描画は以下の手順で行います。

  1. Beginメソッドで描画開始を宣言
  2. Drawメソッドでテクスチャを描画
  3. Endメソッドで描画終了を宣言
pSprite->Begin(D3DXSPRITE_ALPHABLEND); // アルファブレンド有効で開始
// 描画したいテクスチャをセットし、位置を指定して描画
pSprite->Draw(
    pTexture,           // 描画するテクスチャ
    nullptr,            // 描画するテクスチャの矩形(nullptrで全体)
    nullptr,            // スケーリング・回転の中心(nullptrで左上)
    &D3DXVECTOR3(x, y, 0.0f), // 描画位置
    D3DCOLOR_ARGB(255, 255, 255, 255) // 色(白で不透明)
);
pSprite->End();
  • D3DXSPRITE_ALPHABLENDを指定すると、透明度を考慮した描画が可能です
  • Drawの第2引数でテクスチャの一部を切り出して描画できます
  • 第3引数で回転や拡大縮小の中心点を指定可能です

スプライトの変形例

スプライトの回転や拡大縮小を行うには、Drawの第3引数に中心点を指定し、SetTransformで変換行列を設定します。

D3DXMATRIX mat;
D3DXVECTOR2 center(32.0f, 32.0f); // テクスチャ中心(例: 64x64テクスチャの半分)
D3DXVECTOR2 position(x, y);
float rotation = D3DXToRadian(45.0f); // 45度回転
float scale = 1.5f;
D3DXMatrixTransformation2D(
    &mat,
    nullptr,
    0.0f,
    &D3DXVECTOR2(scale, scale),
    ¢er,
    rotation,
    &position);
pSprite->SetTransform(&mat);
pSprite->Begin(D3DXSPRITE_ALPHABLEND);
pSprite->Draw(pTexture, nullptr, nullptr, nullptr, D3DCOLOR_ARGB(255, 255, 255, 255));
pSprite->End();

このように、ID3DXSpriteを使うと2D描画が非常に簡単に実装できます。

フォント描画との併用

DirectX9のD3DXライブラリには、テキスト描画用のID3DXFontインターフェースも用意されています。

ID3DXSpriteと組み合わせて使うことで、スプライト描画とテキスト描画を効率的に行えます。

フォントオブジェクトの作成

ID3DXFont* pFont = nullptr;
HRESULT hr = D3DXCreateFont(
    pDevice,
    24,             // フォントサイズ
    0,              // フォント幅(0で自動)
    FW_NORMAL,      // フォントの太さ
    1,              // ミップマップレベル
    FALSE,          // イタリック体かどうか
    DEFAULT_CHARSET,
    OUT_DEFAULT_PRECIS,
    DEFAULT_QUALITY,
    DEFAULT_PITCH | FF_DONTCARE,
    L"Arial",       // フォント名
    &pFont);
if (FAILED(hr))
{
    MessageBox(nullptr, L"フォントの作成に失敗しました。", L"エラー", MB_OK | MB_ICONERROR);
    return;
}

スプライトとフォントの描画例

ID3DXFontDrawTextメソッドは、内部でID3DXSpriteを使って描画するため、両者の描画は同じスプライトオブジェクトを共有するか、描画順序に注意が必要です。

pSprite->Begin(D3DXSPRITE_ALPHABLEND);
// スプライト描画
pSprite->Draw(pTexture, nullptr, nullptr, &D3DXVECTOR3(100, 100, 0), D3DCOLOR_ARGB(255, 255, 255, 255));
// テキスト描画
RECT rect = { 200, 200, 0, 0 };
pFont->DrawText(nullptr, L"Hello, DirectX9!", -1, &rect, DT_NOCLIP, D3DCOLOR_ARGB(255, 255, 255, 0));
pSprite->End();
  • DrawTextの第1引数にnullptrを渡すと、内部でスプライトを作成して描画します
  • 複数のスプライト描画やテキスト描画を行う場合は、Begin/Endの呼び出し回数を減らすために、同じID3DXSpriteオブジェクトを共有する方法もあります

ID3DXSpriteを使うことで、2D画像の描画が非常に簡単になり、フォント描画ともスムーズに連携できます。

これにより、ゲームのUIやHUD、メニュー画面などの実装が効率的に行えます。

入力デバイスとの連携

ゲームやインタラクティブなアプリケーションでは、ユーザーからの入力を正確に取得し処理することが重要です。

DirectX9環境での入力デバイス連携として、キーボード入力の検知、マウス座標の取得、そしてXInputを使ったゲームパッド対応について詳しく説明します。

キーボード入力検知

Win32 APIを使ったキーボード入力の基本的な検知方法は、ウィンドウプロシージャでWM_KEYDOWNWM_KEYUPメッセージを受け取る方法です。

これにより、キーが押された瞬間や離された瞬間を検知できます。

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
    case WM_KEYDOWN:
        if (wParam == VK_LEFT)
        {
            // 左矢印キーが押された
            OutputDebugString(L"Left arrow key pressed\n");
        }
        else if (wParam == VK_ESCAPE)
        {
            // ESCキーが押されたらアプリ終了
            PostQuitMessage(0);
        }
        return 0;
    case WM_KEYUP:
        // キーが離されたときの処理
        return 0;
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    default:
        return DefWindowProc(hwnd, msg, wParam, lParam);
    }
}
  • wParamには押されたキーの仮想キーコードが入ります
  • VK_LEFTVK_ESCAPEなどの定数で特定のキーを判別します

よりリアルタイムなキー状態の取得には、GetAsyncKeyState関数を使う方法もあります。

これは任意のタイミングでキーの押下状態を調べられます。

if (GetAsyncKeyState(VK_SPACE) & 0x8000)
{
    // スペースキーが押されている間の処理
}

GetAsyncKeyStateはポーリング方式で、ゲームループ内で連続的にキー状態をチェックするのに適しています。

マウス座標の取得

マウスの位置は、ウィンドウ内のクライアント座標で取得することが多いです。

WM_MOUSEMOVEメッセージでマウスの移動を検知し、lParamから座標を取得します。

case WM_MOUSEMOVE:
{
    int x = LOWORD(lParam); // マウスのX座標
    int y = HIWORD(lParam); // マウスのY座標
    wchar_t buf[100];
    swprintf_s(buf, L"Mouse moved: X=%d, Y=%d\n", x, y);
    OutputDebugString(buf);
}
return 0;
  • LOWORD(lParam)でX座標、HIWORD(lParam)でY座標を取得します
  • 座標はウィンドウのクライアント領域基準です

また、GetCursorPos関数を使うとスクリーン座標でマウス位置を取得できます。

ウィンドウ内の座標に変換するにはScreenToClientを使います。

POINT pt;
GetCursorPos(&pt);
ScreenToClient(hwnd, &pt);
// pt.x, pt.y がウィンドウ内のマウス座標

マウスボタンの押下はWM_LBUTTONDOWNWM_RBUTTONDOWNなどのメッセージで検知可能です。

XInputによるゲームパッド対応

DirectX9では、ゲームパッド入力にXInput APIを使うのが一般的です。

XInputはXboxコントローラーをはじめとした多くのゲームパッドをサポートし、ボタンやスティックの状態を簡単に取得できます。

XInputの初期化と状態取得

まず、XInputGetState関数を使ってコントローラーの状態を取得します。

コントローラーは最大4台まで対応しています。

#include <Xinput.h>
#pragma comment(lib, "Xinput.lib")
XINPUT_STATE state;
ZeroMemory(&state, sizeof(XINPUT_STATE));
DWORD dwResult = XInputGetState(0, &state); // 0はコントローラー番号
if (dwResult == ERROR_SUCCESS)
{
    // コントローラーが接続されている
    WORD buttons = state.Gamepad.wButtons;
    if (buttons & XINPUT_GAMEPAD_A)
    {
        OutputDebugString(L"Aボタンが押されています\n");
    }
    SHORT leftTrigger = state.Gamepad.bLeftTrigger;
    SHORT rightTrigger = state.Gamepad.bRightTrigger;
    SHORT leftThumbX = state.Gamepad.sThumbLX;
    SHORT leftThumbY = state.Gamepad.sThumbLY;
    // スティックの値は-32768~32767の範囲
}
else
{
    // コントローラーが接続されていない
}
  • wButtonsはビットフラグで各ボタンの押下状態を示します
  • トリガーやスティックの値はアナログ値として取得可能です

ゲームループ内でのポーリング

ゲームループの中で定期的にXInputGetStateを呼び出し、最新の入力状態を取得して処理します。

while (true)
{
    // メッセージ処理(省略)
    DWORD result = XInputGetState(0, &state);
    if (result == ERROR_SUCCESS)
    {
        // 入力処理
    }
    // 描画処理など
}

バイブレーションの制御

XInputでは振動機能もサポートしています。

XInputSetStateでバイブレーションの強さを設定可能です。

XINPUT_VIBRATION vibration = {};
vibration.wLeftMotorSpeed = 65535;  // 左モーター最大
vibration.wRightMotorSpeed = 0;     // 右モーター停止
XInputSetState(0, &vibration);

これらの方法を組み合わせることで、DirectX9アプリケーションで多様な入力デバイスを扱い、ユーザーの操作に応じたインタラクティブな動作を実現できます。

サウンド再生の基本

ゲームやマルチメディアアプリケーションでは、効果音やBGMの再生が重要な要素です。

DirectX9環境でサウンドを扱う代表的なAPIにDirectSoundがあります。

ここでは、DirectSoundの初期化方法と、サウンドバッファを使った音声の再生およびループ再生の基本について詳しく説明します。

DirectSound初期化

DirectSoundはWindowsの低レベルサウンドAPIで、ハードウェアアクセラレーションを利用した高性能なサウンド再生が可能です。

初期化の流れは以下の通りです。

  1. IDirectSound8オブジェクトの作成
  2. 協調レベル(Cooperative Level)の設定
  3. サウンドバッファの作成

以下にサンプルコードを示します。

#include <windows.h>
#include <dsound.h>
#pragma comment(lib, "dsound.lib")
#pragma comment(lib, "dxguid.lib")
IDirectSound8* pDirectSound = nullptr;
bool InitializeDirectSound(HWND hwnd)
{
    // DirectSoundオブジェクトの作成
    HRESULT hr = DirectSoundCreate8(nullptr, &pDirectSound, nullptr);
    if (FAILED(hr))
    {
        MessageBox(hwnd, L"DirectSoundの作成に失敗しました。", L"エラー", MB_OK | MB_ICONERROR);
        return false;
    }
    // 協調レベルの設定(優先度レベル)
    hr = pDirectSound->SetCooperativeLevel(hwnd, DSSCL_PRIORITY);
    if (FAILED(hr))
    {
        MessageBox(hwnd, L"DirectSoundの協調レベル設定に失敗しました。", L"エラー", MB_OK | MB_ICONERROR);
        pDirectSound->Release();
        pDirectSound = nullptr;
        return false;
    }
    return true;
}
  • DirectSoundCreate8でDirectSoundオブジェクトを生成します
  • SetCooperativeLevelでアプリケーションのサウンド優先度を設定し、他のアプリとの共存を調整します。DSSCL_PRIORITYは比較的高い優先度です
  • hwndはウィンドウハンドルで、サウンドのフォーカス管理に使われます

バッファ再生とループ

DirectSoundで音声を再生するには、サウンドデータを格納するサウンドバッファIDirectSoundBufferを作成し、そこに音声データを書き込みます。

バッファを再生することで音が出ます。

サウンドバッファの作成

WAVEファイルなどのPCMデータを再生する場合、まずWAVEフォーマットを指定してバッファを作成します。

IDirectSoundBuffer* pPrimaryBuffer = nullptr;
IDirectSoundBuffer8* pSecondaryBuffer = nullptr;
bool CreateSoundBuffer(WAVEFORMATEX* pwfx, BYTE* pData, DWORD dataSize)
{
    // プライマリバッファの作成(音質設定用)
    DSBUFFERDESC dsbd = {};
    dsbd.dwSize = sizeof(DSBUFFERDESC);
    dsbd.dwFlags = DSBCAPS_PRIMARYBUFFER;
    dsbd.dwBufferBytes = 0;
    dsbd.lpwfxFormat = nullptr;
    HRESULT hr = pDirectSound->CreateSoundBuffer(&dsbd, &pPrimaryBuffer, nullptr);
    if (FAILED(hr))
    {
        MessageBox(nullptr, L"プライマリバッファの作成に失敗しました。", L"エラー", MB_OK | MB_ICONERROR);
        return false;
    }
    // プライマリバッファのフォーマット設定
    hr = pPrimaryBuffer->SetFormat(pwfx);
    if (FAILED(hr))
    {
        MessageBox(nullptr, L"プライマリバッファのフォーマット設定に失敗しました。", L"エラー", MB_OK | MB_ICONERROR);
        return false;
    }
    // セカンダリバッファの作成(実際の音声データ用)
    DSBUFFERDESC dsbd2 = {};
    dsbd2.dwSize = sizeof(DSBUFFERDESC);
    dsbd2.dwFlags = DSBCAPS_CTRLVOLUME | DSBCAPS_GLOBALFOCUS;
    dsbd2.dwBufferBytes = dataSize;
    dsbd2.lpwfxFormat = pwfx;
    IDirectSoundBuffer* pTempBuffer = nullptr;
    hr = pDirectSound->CreateSoundBuffer(&dsbd2, &pTempBuffer, nullptr);
    if (FAILED(hr))
    {
        MessageBox(nullptr, L"セカンダリバッファの作成に失敗しました。", L"エラー", MB_OK | MB_ICONERROR);
        return false;
    }
    // IDirectSoundBuffer8に変換
    hr = pTempBuffer->QueryInterface(IID_IDirectSoundBuffer8, (void**)&pSecondaryBuffer);
    pTempBuffer->Release();
    if (FAILED(hr))
    {
        MessageBox(nullptr, L"セカンダリバッファのインターフェース取得に失敗しました。", L"エラー", MB_OK | MB_ICONERROR);
        return false;
    }
    // バッファに音声データを書き込む
    void* pBuffer = nullptr;
    DWORD bufferSize = 0;
    hr = pSecondaryBuffer->Lock(0, dataSize, &pBuffer, &bufferSize, nullptr, nullptr, 0);
    if (FAILED(hr))
    {
        MessageBox(nullptr, L"バッファのロックに失敗しました。", L"エラー", MB_OK | MB_ICONERROR);
        return false;
    }
    memcpy(pBuffer, pData, dataSize);
    pSecondaryBuffer->Unlock(pBuffer, bufferSize, nullptr, 0);
    return true;
}
  • プライマリバッファは音質設定用で、通常は1つだけ作成します
  • セカンダリバッファに実際の音声データをロードし、再生します
  • WAVEFORMATEX構造体でサンプリングレートやチャンネル数などを指定します

バッファの再生とループ設定

音声の再生はPlayメソッドで行います。

ループ再生も簡単に設定可能です。

// 通常再生
pSecondaryBuffer->Play(0, 0, 0);
// ループ再生
pSecondaryBuffer->Play(0, 0, DSBPLAY_LOOPING);
  • 第3引数にDSBPLAY_LOOPINGを指定すると、バッファの終端に達しても自動的に先頭に戻り繰り返し再生します
  • 再生停止はStopメソッドで行います

DirectSoundを使うことで、低レイテンシかつ高品質なサウンド再生が可能です。

初期化からバッファ作成、再生までの流れを理解し、効果音やBGMの実装に役立ててください。

リソース管理と解放

DirectX9をはじめとするCOMベースのAPIを使う際は、リソース管理が非常に重要です。

特にCOMオブジェクトの参照カウントの理解と適切な解放処理を行わないと、メモリリークやクラッシュの原因になります。

ここでは、COMオブジェクトの参照カウントの仕組みと、遅延解放の実装方法について詳しく説明します。

COMオブジェクトの参照カウント

DirectX9の多くのオブジェクトはCOM(Component Object Model)インターフェースとして実装されており、IUnknownインターフェースを継承しています。

COMオブジェクトは参照カウント方式でメモリ管理を行い、オブジェクトの利用者が増減するたびにカウントを増減させます。

主なメソッドは以下の3つです。

メソッド名役割
AddRef()参照カウントを1増やす。オブジェクトを利用開始したときに呼ぶ。
Release()参照カウントを1減らす。利用終了時に呼び、カウントが0になるとオブジェクトが破棄されます。
QueryInterface()他のインターフェースポインタを取得するためのメソッド。

例えば、IDirect3DDevice9IDirect3DTexture9などのオブジェクトは、生成時に参照カウントが1になっています。

利用が終わったら必ずRelease()を呼んで解放しなければなりません。

IDirect3DTexture9* pTexture = nullptr;
HRESULT hr = pDevice->CreateTexture(256, 256, 1, 0, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, &pTexture, nullptr);
if (SUCCEEDED(hr))
{
    // テクスチャを使用する処理
    // 使用後は必ずReleaseを呼ぶ
    pTexture->Release();
    pTexture = nullptr;
}

Release()を呼ばないと、オブジェクトはメモリ上に残り続け、メモリリークの原因となります。

逆に、Release()を多く呼びすぎると、まだ使っているオブジェクトが破棄されてしまい、アクセス違反が発生します。

遅延解放の実装

DirectX9のアプリケーションでは、デバイスのロストやリセット時にリソースを一時的に解放し、復旧後に再作成する必要があります。

このとき、リソースの解放を即座に行うのではなく、遅延解放(Deferred Release)を実装することで安定性を高められます。

遅延解放とは、リソースの解放を一時的に保留し、適切なタイミングでまとめて解放する仕組みです。

これにより、複数のリソースを安全に管理し、デバイスの状態変化に柔軟に対応できます。

遅延解放の基本的な考え方

  1. 解放が必要なCOMオブジェクトのポインタをリストやキューに登録します。
  2. メインループやデバイスリセット処理の直前など、適切なタイミングで登録されたオブジェクトのRelease()を呼び、リストをクリアします。

実装例

#include <vector>
#include <d3d9.h>
std::vector<IUnknown*> g_DeferredReleaseList;
// 解放予約関数
void AddToDeferredRelease(IUnknown* pObj)
{
    if (pObj)
    {
        g_DeferredReleaseList.push_back(pObj);
    }
}
// 遅延解放実行関数
void ExecuteDeferredRelease()
{
    for (IUnknown* pObj : g_DeferredReleaseList)
    {
        pObj->Release();
    }
    g_DeferredReleaseList.clear();
}

使用例として、デバイスがロストした際にリソースを解放する場合は以下のようにします。

// デバイスロスト時の処理例
void OnDeviceLost()
{
    // 解放が必要なリソースを遅延解放リストに追加
    AddToDeferredRelease(pTexture);
    pTexture = nullptr;
    AddToDeferredRelease(pVertexBuffer);
    pVertexBuffer = nullptr;
    // 他のリソースも同様に追加
}
// デバイスリセット前に遅延解放を実行
void OnDeviceReset()
{
    ExecuteDeferredRelease();
    // リソースの再作成処理
}

遅延解放のメリット

  • 安全性の向上

複数のリソースを一括で解放できるため、解放漏れや二重解放のリスクを減らせます。

  • コードの整理

解放処理を一箇所にまとめられ、メンテナンスが容易になります。

  • デバイス状態管理の簡素化

デバイスのロスト・リセット時の処理が明確になり、安定した動作を実現できます。

COMオブジェクトの参照カウントを正しく理解し、遅延解放を適切に実装することで、DirectX9アプリケーションのリソース管理が効率的かつ安全になります。

これにより、メモリリークやクラッシュを防ぎ、安定した動作を維持できます。

デバッグとパフォーマンス計測

DirectX9でのゲーム開発やグラフィックスプログラミングでは、描画の正確性やパフォーマンスの最適化が重要です。

ここでは、DirectX9のデバッグランタイムの活用方法、GPUパフォーマンススナップショットの取得、そしてスタッタリング(カクつき)の原因とその対処法について詳しく説明します。

デバッグランタイムの活用

DirectX9には、開発時に役立つ「デバッグランタイム」が用意されています。

通常のランタイムよりも詳細なエラーメッセージや警告を出力し、問題の早期発見に役立ちます。

デバッグランタイムのインストール

  • MicrosoftのDirectX SDKに含まれているデバッグランタイムをインストールします
  • Windowsのバージョンによっては標準で含まれている場合もありますが、SDKの最新版を利用するのが確実です

デバッグランタイムの有効化

デバッグランタイムを有効にするには、Direct3Dデバイスの作成時にD3DCREATE_DEBUGフラグを指定します。

HRESULT hr = pD3D->CreateDevice(
    D3DADAPTER_DEFAULT,
    D3DDEVTYPE_HAL,
    hwnd,
    D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_DEBUG,
    &d3dpp,
    &pDevice);
  • D3DCREATE_DEBUGを指定すると、DirectXの内部チェックが強化され、APIの誤用やリソースリークなどを検出しやすくなります

デバッグ出力の確認

Visual Studioの出力ウィンドウやDebugViewなどのツールで、DirectXのデバッグメッセージを確認できます。

これにより、API呼び出しの失敗やパフォーマンス警告をリアルタイムで把握可能です。

注意点

  • デバッグランタイムはパフォーマンスが低下するため、リリースビルドでは外すことが推奨されます
  • 一部の環境ではデバッグランタイムのインストールが難しい場合もあるため、環境に応じて使い分けてください

GPUパフォーマンススナップショット

GPUのパフォーマンスを詳細に分析するために、DirectX9ではパフォーマンススナップショット機能を利用できます。

これにより、GPUの負荷や描画コールの詳細を取得し、ボトルネックの特定に役立ちます。

PIXツールの利用

MicrosoftのPIX(Performance Investigator for Xbox)ツールは、DirectX9のGPUパフォーマンス解析に広く使われています。

  • PIXを使ってアプリケーションを起動し、フレーム単位でGPUの動作をキャプチャします
  • 描画コール数、シェーダーの実行時間、テクスチャの使用状況などを詳細に分析可能です

GPUパフォーマンスカウンター

DirectX9のAPIでは直接GPUのパフォーマンスカウンターを取得する機能は限定的ですが、PIXなどの外部ツールを使うことで詳細な情報を得られます。

コード内での簡易計測

CPU側での描画処理時間を計測することで、GPU負荷の推測も可能です。

QueryPerformanceCounterstd::chronoを使い、フレームごとの描画時間を計測します。

#include <chrono>
auto start = std::chrono::high_resolution_clock::now();
// 描画処理
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
wchar_t buf[100];
swprintf_s(buf, L"Frame render time: %lld microseconds\n", duration);
OutputDebugString(buf);

スタッタリングの原因と対処

スタッタリングとは、フレームレートが不安定になり、画面がカクつく現象です。

ゲームの快適さを損なうため、原因を特定し適切に対処することが重要です。

主な原因

  1. CPU負荷の偏り

描画処理やゲームロジックの一部が重く、フレーム時間が不均一になります。

  1. GPU負荷の過剰

複雑なシェーダーや大量の描画コールでGPUが処理しきれない。

  1. 同期処理の問題

垂直同期(VSync)やバッファのスワップ待ちでフレームが遅延。

  1. リソースのロード遅延

実行中にテクスチャやモデルの読み込みが発生し、処理が一時停止。

対処法

  • 負荷分散

ゲームロジックや描画処理を複数フレームに分散し、一度に重い処理をしない。

  • 描画コールの削減

不要な描画を省き、バッチングやインスタンス描画を活用します。

  • シェーダーの最適化

複雑すぎるシェーダーを簡素化し、GPU負荷を軽減。

  • 非同期リソースロード

リソースの読み込みを別スレッドで行い、メインスレッドの処理を妨げない。

  • VSync設定の調整

垂直同期をオフにするか、フレームレート制限を設けて安定化を図ります。

  • フレームタイムの計測と監視

フレームごとの処理時間を計測し、どの処理がボトルネックかを特定します。

これらのデバッグ手法とパフォーマンス計測を活用することで、DirectX9アプリケーションの品質向上と快適な動作を実現できます。

問題の早期発見と適切な対策が、安定したゲーム体験の鍵となります。

マルチスレッドレンダリングの注意点

DirectX9でマルチスレッドを活用して描画処理を分散させることは、パフォーマンス向上のために有効な手法ですが、いくつかの制約や注意点があります。

ここでは、デバイス共有の制限とクリティカルセクションの最小化について詳しく説明します。

デバイス共有の制限

Direct3D9のIDirect3DDevice9はスレッドセーフではありません。

つまり、複数のスレッドから同時に同じデバイスオブジェクトを操作すると、予期せぬ動作やクラッシュの原因になります。

マルチスレッドレンダリングの基本的な制約

  • 単一スレッドでのデバイス操作推奨

描画コマンドの発行やリソースの作成・破棄は、基本的に1つのスレッド(通常はメインスレッド)で行うべきです。

  • IDirect3DDevice9::TestCooperativeLevelの呼び出しもスレッドセーフではない

デバイスの状態チェックはメインスレッドで行うことが望ましいです。

  • リソースの共有は制限あり

D3DPOOL_DEFAULTで作成したリソースはデバイスに依存し、スレッド間での共有はできません。

D3DPOOL_MANAGEDのリソースはシステムメモリに保持されるため、読み取りは可能ですが、描画はメインスレッドで行う必要があります。

マルチスレッド対応のためのAPIフラグ

Direct3D9にはマルチスレッド対応を助けるフラグD3DCREATE_MULTITHREADEDがあります。

これをCreateDevice時に指定すると、内部でデバイス操作の排他制御が行われます。

HRESULT hr = pD3D->CreateDevice(
    D3DADAPTER_DEFAULT,
    D3DDEVTYPE_HAL,
    hwnd,
    D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_MULTITHREADED,
    &d3dpp,
    &pDevice);
  • ただし、このフラグを使うとパフォーマンスが低下する可能性があるため、必要な場合のみ使用します
  • 完全なスレッドセーフを保証するわけではなく、適切な同期処理が依然として必要です

クリティカルセクションの最小化

マルチスレッド環境でDirect3Dデバイスを安全に操作するためには、クリティカルセクション(排他制御)を使って同時アクセスを防ぎます。

しかし、クリティカルセクションの範囲が広すぎると、スレッド間の待ち時間が増え、パフォーマンスが著しく低下します。

効率的なクリティカルセクションの設計

  • 必要最小限の範囲でロックを行う

描画コマンドの発行やリソース操作の直前から直後までの短時間だけロックし、他の処理はロック外で行います。

  • 描画処理とリソース管理を分離する

リソースのロードや準備は別スレッドで行い、描画はメインスレッドで行うことで、クリティカルセクションの競合を減らす。

  • バッチ処理を活用する

複数の描画コマンドをまとめて発行し、ロックの回数を減らす。

クリティカルセクションの例(C++)

#include <windows.h>
CRITICAL_SECTION csDeviceAccess;
void Initialize()
{
    InitializeCriticalSection(&csDeviceAccess);
}
void Cleanup()
{
    DeleteCriticalSection(&csDeviceAccess);
}
void ThreadSafeDraw(IDirect3DDevice9* pDevice)
{
    EnterCriticalSection(&csDeviceAccess);
    // デバイスへの描画コマンド
    pDevice->BeginScene();
    // 描画処理
    pDevice->EndScene();
    LeaveCriticalSection(&csDeviceAccess);
}
  • EnterCriticalSectionでロックを取得し、LeaveCriticalSectionで解放します
  • ロック中は他のスレッドは待機するため、処理はできるだけ短くします

ロックの粒度を小さくする工夫

  • 描画コマンドをまとめて一括で発行し、複数回のロック・アンロックを避けます
  • リソースの更新や状態変更は描画とは別スレッドで行い、描画スレッドとは同期を取ります

DirectX9でのマルチスレッドレンダリングは、デバイスのスレッドセーフでない特性を理解し、適切な同期とロックの設計が不可欠です。

デバイス共有の制限を守りつつ、クリティカルセクションの範囲を最小化することで、安定かつ効率的なマルチスレッド描画を実現できます。

フレームワークへの拡張

DirectX9を用いたゲームやグラフィックスアプリケーションの開発では、単純な描画処理だけでなく、複雑なシーン管理や柔軟な拡張性を持つ設計が求められます。

ここでは、シーン管理クラスの設計方法と、より高度な設計手法であるコンポーネントベースエンジンへの応用について詳しく説明します。

シーン管理クラスの設計

シーン管理は、ゲームやアプリケーションの状態(メニュー画面、ゲームプレイ画面、ポーズ画面など)を切り替え、管理する仕組みです。

これをクラスとして設計することで、コードの整理や拡張が容易になります。

シーン管理の基本構造

シーン管理クラスは、複数のシーンオブジェクトを保持し、現在アクティブなシーンの更新や描画を委譲します。

シーンは共通のインターフェースを持ち、切り替えや遷移がスムーズに行えるようにします。

class IScene
{
public:
    virtual ~IScene() {}
    virtual void Initialize() = 0;
    virtual void Update(float deltaTime) = 0;
    virtual void Render() = 0;
    virtual void Shutdown() = 0;
};
class SceneManager
{
private:
    IScene* currentScene = nullptr;
public:
    void ChangeScene(IScene* newScene)
    {
        if (currentScene)
        {
            currentScene->Shutdown();
            delete currentScene;
        }
        currentScene = newScene;
        if (currentScene)
        {
            currentScene->Initialize();
        }
    }
    void Update(float deltaTime)
    {
        if (currentScene)
            currentScene->Update(deltaTime);
    }
    void Render()
    {
        if (currentScene)
            currentScene->Render();
    }
    ~SceneManager()
    {
        if (currentScene)
        {
            currentScene->Shutdown();
            delete currentScene;
        }
    }
};

利点

  • 状態の分離

各シーンが独立して実装されるため、メンテナンスが容易。

  • 柔軟な切り替え

シーンの切り替えが明確で、遷移処理も管理しやすい。

  • 拡張性

新しいシーンを追加するだけで機能拡張が可能です。

使用例

class TitleScene : public IScene
{
public:
    void Initialize() override { /* 初期化処理 */ }
    void Update(float deltaTime) override { /* 入力処理など */ }
    void Render() override { /* 描画処理 */ }
    void Shutdown() override { /* 解放処理 */ }
};
int main()
{
    SceneManager sceneManager;
    sceneManager.ChangeScene(new TitleScene());
    while (true)
    {
        float deltaTime = 0.016f; // 仮のフレーム時間
        sceneManager.Update(deltaTime);
        sceneManager.Render();
    }
}

コンポーネントベースエンジンへの応用

近年のゲームエンジン設計では、オブジェクト指向の継承よりも「コンポーネントベース」の設計が主流です。

これは、ゲームオブジェクトを複数の機能的なコンポーネントの集合として扱い、柔軟かつ再利用性の高い設計を実現します。

コンポーネントベース設計の概要

  • ゲームオブジェクト(Entity)

識別子や基本情報を持つだけの軽量な存在。

  • コンポーネント(Component)

位置情報、描画、物理演算、スクリプトなどの機能を持つモジュール。

  • システム(System)

特定のコンポーネントを持つオブジェクトを処理するロジック。

クラス設計例

class Component
{
public:
    virtual ~Component() {}
    virtual void Update(float deltaTime) {}
    virtual void Render() {}
};
class TransformComponent : public Component
{
public:
    float x, y, z;
    TransformComponent() : x(0), y(0), z(0) {}
};
class RenderComponent : public Component
{
public:
    void Render() override
    {
        // 描画処理
    }
};
class GameObject
{
private:
    std::vector<Component*> components;
public:
    ~GameObject()
    {
        for (auto comp : components)
            delete comp;
    }
    template<typename T>
    T* GetComponent()
    {
        for (auto comp : components)
        {
            T* casted = dynamic_cast<T*>(comp);
            if (casted)
                return casted;
        }
        return nullptr;
    }
    void AddComponent(Component* comp)
    {
        components.push_back(comp);
    }
    void Update(float deltaTime)
    {
        for (auto comp : components)
            comp->Update(deltaTime);
    }
    void Render()
    {
        for (auto comp : components)
            comp->Render();
    }
};

利点

  • 柔軟な機能追加

必要な機能だけをコンポーネントとして追加可能です。

  • 再利用性の向上

コンポーネント単位での再利用が容易。

  • 継承の複雑さ回避

多重継承や複雑な継承階層を避けられます。

応用例

GameObject* player = new GameObject();
player->AddComponent(new TransformComponent());
player->AddComponent(new RenderComponent());
while (true)
{
    float deltaTime = 0.016f;
    player->Update(deltaTime);
    player->Render();
}

シーン管理クラスの設計によりアプリケーションの状態遷移を整理し、さらにコンポーネントベースの設計を取り入れることで、DirectX9を用いたゲーム開発の拡張性と保守性を大幅に向上させられます。

これらの設計手法は、規模の大きなプロジェクトや複雑なゲームロジックの実装に特に有効です。

実装例:回転する三角形

DirectX9で3D描画の基本を学ぶために、回転する三角形を描画するサンプルを紹介します。

ここでは、頂点データの設定と回転行列の生成方法を詳しく解説します。

頂点データの設定

三角形を描画するためには、まず頂点データを用意します。

Direct3D9では、頂点の位置や色、テクスチャ座標などを含むカスタム頂点構造体を定義し、頂点バッファに格納します。

以下は、位置と色を持つカスタム頂点構造体の例です。

#include <d3d9.h>
#include <d3dx9.h>
// カスタム頂点構造体(位置と色)
struct CUSTOMVERTEX
{
    FLOAT x, y, z;    // 頂点の3D座標
    DWORD color;      // 頂点カラー(ARGB)
};
// Flexible Vertex Formatの定義
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ | D3DFVF_DIFFUSE)

この構造体は、頂点の3D位置と色を持ちます。

D3DFVF_CUSTOMVERTEXはDirect3Dに頂点のフォーマットを伝えるための定数です。

次に、三角形の3頂点を設定します。

// 三角形の頂点データ
CUSTOMVERTEX vertices[] =
{
    { 0.0f,  1.0f, 0.0f, 0xFFFF0000 }, // 頂点1:赤色、上
    { 1.0f, -1.0f, 0.0f, 0xFF00FF00 }, // 頂点2:緑色、右下
    {-1.0f, -1.0f, 0.0f, 0xFF0000FF }  // 頂点3:青色、左下
};
  • 頂点は3つで三角形を構成します
  • 色はARGB形式で指定し、赤・緑・青の頂点を用意しています

頂点バッファを作成し、頂点データをコピーする例も示します。

IDirect3DVertexBuffer9* pVertexBuffer = nullptr;
// 頂点バッファの作成
HRESULT hr = pDevice->CreateVertexBuffer(
    sizeof(vertices),
    0,
    D3DFVF_CUSTOMVERTEX,
    D3DPOOL_MANAGED,
    &pVertexBuffer,
    nullptr);
if (FAILED(hr))
{
    MessageBox(nullptr, L"頂点バッファの作成に失敗しました。", L"エラー", MB_OK | MB_ICONERROR);
    return;
}
// バッファのロック
void* pVertices = nullptr;
hr = pVertexBuffer->Lock(0, sizeof(vertices), (void**)&pVertices, 0);
if (FAILED(hr))
{
    MessageBox(nullptr, L"頂点バッファのロックに失敗しました。", L"エラー", MB_OK | MB_ICONERROR);
    return;
}
// 頂点データのコピー
memcpy(pVertices, vertices, sizeof(vertices));
// バッファのアンロック
pVertexBuffer->Unlock();

このように頂点バッファに三角形の頂点データをセットします。

回転行列の生成

三角形を回転させるには、ワールド変換行列として回転行列を生成し、頂点の位置に適用します。

DirectX9のD3DXMatrixRotationZ関数を使うと、Z軸回転行列を簡単に作成できます。

D3DXMATRIX matWorld;
float angle = 0.0f; // 回転角度(ラジアン)
// 毎フレーム更新する例
void UpdateRotation(float deltaTime)
{
    angle += deltaTime; // 角度を増加(例:1秒で1ラジアン回転)
    if (angle > D3DX_PI * 2)
        angle -= D3DX_PI * 2;
    // Z軸回転行列の生成
    D3DXMatrixRotationZ(&matWorld, angle);
    // ワールド行列をデバイスにセット
    pDevice->SetTransform(D3DTS_WORLD, &matWorld);
}
  • angleは回転角度で、毎フレーム少しずつ増やします
  • D3DXMatrixRotationZはZ軸回転行列を生成します
  • 生成した行列をSetTransformでワールド行列として設定します

この回転行列を使うことで、三角形がZ軸を中心に回転して描画されます。

以上の頂点データ設定と回転行列生成を組み合わせることで、DirectX9で回転する三角形を描画できます。

これが3D描画の基本的な流れの一例となります。

実装例:テクスチャ付き四角形

DirectX9でテクスチャを貼り付けた四角形(クアッド)を描画する際には、ポリゴンの分割方法とUVマッピングの設定が重要です。

また、透明度を扱う場合はアルファブレンドの有効化も必要です。

ここでは、ポリゴン分割とUVマッピングの基本、そしてアルファブレンドの設定方法について詳しく解説します。

ポリゴン分割とUVマッピング

Direct3D9では四角形を直接描画するプリミティブは存在しないため、四角形は2つの三角形に分割して描画します。

これにより、GPUが効率的に処理できる形状になります。

頂点構造体の定義

テクスチャ付きの四角形を描くためには、頂点に位置情報とテクスチャ座標(UV座標)を持たせます。

以下は位置とUV座標を持つカスタム頂点構造体の例です。

struct CUSTOMVERTEX
{
    FLOAT x, y, z;   // 頂点の3D座標
    FLOAT u, v;      // テクスチャ座標
};
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ | D3DFVF_TEX1)

頂点データの設定

四角形の4頂点を定義し、UV座標を割り当てます。

UV座標はテクスチャのどの部分を頂点に対応させるかを示します。

通常、(0,0)がテクスチャの左上、(1,1)が右下です。

CUSTOMVERTEX vertices[] =
{
    { -1.0f,  1.0f, 0.0f, 0.0f, 0.0f }, // 左上
    {  1.0f,  1.0f, 0.0f, 1.0f, 0.0f }, // 右上
    { -1.0f, -1.0f, 0.0f, 0.0f, 1.0f }, // 左下
    {  1.0f, -1.0f, 0.0f, 1.0f, 1.0f }  // 右下
};

インデックスデータの設定

四角形を2つの三角形に分割するためのインデックスを設定します。

WORD indices[] = {
    0, 1, 2, // 三角形1(左上、右上、左下)
    2, 1, 3  // 三角形2(左下、右上、右下)
};

頂点バッファとインデックスバッファの作成

頂点バッファとインデックスバッファを作成し、上記のデータを転送します。

描画時にはこれらをセットして描画コールを行います。

// 頂点バッファ作成例(省略)
// インデックスバッファ作成例(省略)
pDevice->SetFVF(D3DFVF_CUSTOMVERTEX);
pDevice->SetStreamSource(0, pVertexBuffer, 0, sizeof(CUSTOMVERTEX));
pDevice->SetIndices(pIndexBuffer);
pDevice->DrawIndexedPrimitive(
    D3DPT_TRIANGLELIST,
    0,
    0,
    4,
    0,
    2);

UVマッピングのポイント

  • UV座標はテクスチャの範囲を0.0~1.0で指定します
  • テクスチャの一部だけを表示したい場合は、UV座標の範囲を調整します
  • UV座標が1.0を超えると、サンプラーステートの設定によりテクスチャが繰り返されることがあります

アルファブレンドの有効化

透明度を持つテクスチャや半透明の描画を行う場合は、Direct3Dのアルファブレンド機能を有効にする必要があります。

アルファブレンドの設定例

// アルファブレンドを有効にする
pDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
// ソースブレンドファクター(描画するピクセルのアルファ値を使用)
pDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
// デスティネーションブレンドファクター(既存のピクセルの逆アルファ値を使用)
pDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
  • D3DRS_ALPHABLENDENABLETRUEに設定してアルファブレンドを有効化します
  • SRCBLENDDESTBLENDでブレンドの計算方法を指定します。一般的にはソースのアルファ値と逆アルファ値を使う設定が多いです

描画時の注意点

  • 半透明のオブジェクトは描画順序が重要です。通常、不透明なオブジェクトを先に描画し、半透明オブジェクトを後から描画します
  • 深度バッファの書き込みを無効にすることもあります
pDevice->SetRenderState(D3DRS_ZWRITEENABLE, FALSE);
  • 描画後は元に戻すことを忘れないでください
pDevice->SetRenderState(D3DRS_ZWRITEENABLE, TRUE);
pDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);

これらの手順を組み合わせることで、DirectX9でテクスチャ付きの四角形を正しく描画し、透明度を活かした表現も可能になります。

ポリゴン分割とUVマッピングの理解は、より複雑なモデルやスプライト描画にも応用できます。

実装例:カメラ移動を伴う3Dシーン

3Dシーンの描画において、カメラの位置や向きを動的に変更することは、視点の移動や回転を実現するために不可欠です。

DirectX9では、ビュー行列と射影行列を適切に設定・更新することで、カメラ移動を伴う3Dシーンを表現できます。

ここでは、ビュー行列の更新方法と射影行列の調整について詳しく解説します。

ビュー行列の更新

ビュー行列は、ワールド空間のオブジェクトをカメラ視点に変換するための行列です。

カメラの位置(Eye)、注視点(At)、および上方向ベクトル(Up)を指定して生成します。

ビュー行列の生成

DirectX9ではD3DXMatrixLookAtLH関数を使って左手座標系のビュー行列を簡単に作成できます。

#include <d3dx9.h>
D3DXVECTOR3 eyePos(0.0f, 0.0f, -5.0f);    // カメラの位置
D3DXVECTOR3 lookAt(0.0f, 0.0f, 0.0f);     // 注視点(カメラが見る方向)
D3DXVECTOR3 upDir(0.0f, 1.0f, 0.0f);      // 上方向ベクトル
D3DXMATRIX matView;
D3DXMatrixLookAtLH(&matView, &eyePos, &lookAt, &upDir);
// ビュー行列をデバイスにセット
pDevice->SetTransform(D3DTS_VIEW, &matView);
  • eyePosはカメラの位置を表します。ここではZ軸方向に-5の位置に設定
  • lookAtはカメラが注視する点で、通常はシーンの中心など
  • upDirはカメラの上方向を示し、通常はY軸方向(0,1,0)を指定します

カメラ移動の実装例

カメラを動かすには、eyePoslookAtの値をフレームごとに更新します。

例えば、キーボード入力でカメラを前後左右に移動させる場合は以下のようにします。

void UpdateCamera(float deltaTime)
{
    float speed = 5.0f; // 移動速度
    // キーボード入力例(GetAsyncKeyStateを使用)
    if (GetAsyncKeyState('W') & 0x8000)
        eyePos.z += speed * deltaTime; // 前進
    if (GetAsyncKeyState('S') & 0x8000)
        eyePos.z -= speed * deltaTime; // 後退
    if (GetAsyncKeyState('A') & 0x8000)
        eyePos.x -= speed * deltaTime; // 左移動
    if (GetAsyncKeyState('D') & 0x8000)
        eyePos.x += speed * deltaTime; // 右移動
    // 注視点をカメラの前方に設定(単純な例)
    lookAt = D3DXVECTOR3(eyePos.x, eyePos.y, eyePos.z + 1.0f);
    // ビュー行列の再計算
    D3DXMatrixLookAtLH(&matView, &eyePos, &lookAt, &upDir);
    pDevice->SetTransform(D3DTS_VIEW, &matView);
}
  • deltaTimeは前フレームからの経過時間(秒)で、移動速度の調整に使います
  • GetAsyncKeyStateでキー入力を検知し、カメラ位置を更新
  • 注視点はカメラの前方に固定していますが、回転を加える場合は別途計算が必要です

射影行列の調整

射影行列は、3D空間の座標をスクリーン座標に変換するための行列で、視野角やアスペクト比、近クリップ面・遠クリップ面の距離を設定します。

射影行列の生成

DirectX9ではD3DXMatrixPerspectiveFovLH関数を使って透視投影行列を作成します。

float fovY = D3DXToRadian(45.0f); // 垂直方向の視野角(45度)
float aspectRatio = (float)windowWidth / (float)windowHeight; // アスペクト比
float nearZ = 0.1f;  // 近クリップ面
float farZ = 1000.0f; // 遠クリップ面
D3DXMATRIX matProj;
D3DXMatrixPerspectiveFovLH(&matProj, fovY, aspectRatio, nearZ, farZ);
// 射影行列をデバイスにセット
pDevice->SetTransform(D3DTS_PROJECTION, &matProj);
  • fovYは視野角で、広いほど視界が広がります
  • aspectRatioは画面の横幅と高さの比率で、歪みを防ぐために正確に設定します
  • nearZfarZは描画可能な距離範囲を決め、近すぎるものや遠すぎるものは描画されません

射影行列の動的調整

ウィンドウサイズが変わった場合や、視野角を変更したい場合は、射影行列を再計算して更新します。

void OnResize(int newWidth, int newHeight)
{
    float newAspect = (float)newWidth / (float)newHeight;
    D3DXMatrixPerspectiveFovLH(&matProj, fovY, newAspect, nearZ, farZ);
    pDevice->SetTransform(D3DTS_PROJECTION, &matProj);
}

ビュー行列の更新でカメラの位置や向きを動的に制御し、射影行列の調整で視野や画面比率を適切に設定することで、DirectX9で自由にカメラ移動を伴う3Dシーンを実装できます。

これにより、ユーザーが視点を操作できるインタラクティブな3D空間を構築可能です。

ユーザーインターフェイスの組み込み

ゲームや3Dアプリケーションにおいて、ユーザーインターフェイス(UI)は操作性や情報提示に欠かせません。

DirectX9環境でUIを実装する際には、2Dオーバーレイの描画技術や、手軽に導入できるIMGUI(Immediate Mode GUI)ライブラリの活用が効果的です。

ここでは、2Dオーバーレイの描画方法とIMGUIの導入案について詳しく解説します。

2Dオーバーレイの描画

3Dシーンの上に情報表示やメニューなどの2D要素を重ねるには、2Dオーバーレイ描画が必要です。

DirectX9では、ID3DXSpriteインターフェースを使うことで簡単に2Dスプライトを描画できます。

基本的な2Dオーバーレイ描画手順

  1. スプライトオブジェクトの作成

D3DXCreateSprite関数でID3DXSpriteオブジェクトを生成します。

  1. 描画開始

ID3DXSprite::Beginを呼び、アルファブレンドなどの描画状態を設定します。

  1. テクスチャの描画

ID3DXSprite::Drawでテクスチャを指定した位置に描画します。

  1. 描画終了

ID3DXSprite::Endで描画を完了します。

pSprite->Begin(D3DXSPRITE_ALPHABLEND);
// 例:画面左上にテクスチャを描画
D3DXVECTOR3 position(10.0f, 10.0f, 0.0f);
pSprite->Draw(pTexture, nullptr, nullptr, &position, D3DCOLOR_ARGB(255, 255, 255, 255));
pSprite->End();
  • D3DXSPRITE_ALPHABLENDを指定すると透明度を考慮した描画が可能です
  • UI要素は通常、3Dシーンの描画後にオーバーレイとして描画します

フォント描画との組み合わせ

ID3DXFontを使うとテキストも簡単に描画でき、スプライト描画と組み合わせてUIを構築できます。

RECT rect = { 50, 50, 0, 0 };
pFont->DrawText(nullptr, L"Score: 100", -1, &rect, DT_NOCLIP, D3DCOLOR_ARGB(255, 255, 255, 0));

注意点

  • 2DオーバーレイはZバッファを無効にして描画することが多いです
pDevice->SetRenderState(D3DRS_ZENABLE, FALSE);
// 描画処理
pDevice->SetRenderState(D3DRS_ZENABLE, TRUE);
  • 描画順序を管理し、UIが3Dオブジェクトの前面に表示されるようにします

IMGUIの導入案

IMGUI(Immediate Mode GUI)は、リアルタイムアプリケーション向けの軽量で使いやすいGUIライブラリです。

DirectX9にも対応しており、デバッグツールや開発中のUI構築に最適です。

IMGUIの特徴

  • 即時モードUI

毎フレームUIを再構築するため、状態管理が不要でシンプル。

  • 軽量で高速

描画負荷が低く、リアルタイム性が求められる環境に適しています。

  • 豊富なウィジェット

ボタン、スライダー、チェックボックス、テキスト入力など多彩なUI要素を提供。

DirectX9への組み込み手順概要

  1. IMGUIのソースコード取得

GitHubなどから公式リポジトリをクローン。

  1. DirectX9用バックエンドの追加

imgui_impl_dx9.cppimgui_impl_win32.cppをプロジェクトに追加。

  1. 初期化処理

Direct3Dデバイスとウィンドウハンドルを渡してIMGUIを初期化。

ImGui::CreateContext();
ImGui_ImplWin32_Init(hwnd);
ImGui_ImplDX9_Init(pDevice);
  1. フレームごとの更新と描画
ImGui_ImplDX9_NewFrame();
ImGui_ImplWin32_NewFrame();
ImGui::NewFrame();
// UI構築例
ImGui::Begin("Debug Window");
ImGui::Text("FPS: %.1f", ImGui::GetIO().Framerate);
ImGui::End();
ImGui::EndFrame();
ImGui::Render();
ImGui_ImplDX9_RenderDrawData(ImGui::GetDrawData());
  1. 終了処理
ImGui_ImplDX9_Shutdown();
ImGui_ImplWin32_Shutdown();
ImGui::DestroyContext();

利用上のポイント

  • IMGUIは開発中のデバッグUIやツールに最適ですが、ゲームの正式なUIとしてはカスタマイズが必要な場合があります
  • DirectX9のレンダリングループに組み込む際は、IMGUIの描画を3D描画の後、2Dオーバーレイとして行うのが一般的です

2Dオーバーレイ描画とIMGUIの導入により、DirectX9アプリケーションに柔軟で使いやすいユーザーインターフェイスを組み込めます。

これにより、操作性の向上や開発効率の改善が期待できます。

よくあるエラーと対処法

DirectX9での開発中に遭遇しやすいエラーには、API呼び出しの失敗やデバイスのロスト、シェーダーのコンパイルエラーなどがあります。

これらのエラーの原因と対処法を理解しておくことで、トラブルシューティングがスムーズになります。

ここでは、D3DERR_INVALIDCALL、ロストデバイス時の例外、シェーダーコンパイル失敗について詳しく解説します。

D3DERR_INVALIDCALL

D3DERR_INVALIDCALLは、Direct3Dの関数呼び出しが無効なパラメータや状態で行われた場合に返されるエラーコードです。

原因は多岐にわたり、APIの仕様違反やリソースの不整合などが考えられます。

主な原因例

  • デバイスが適切に初期化されていない状態での呼び出し

例えば、BeginSceneを呼ぶ前に描画コマンドを発行した場合。

  • 無効なパラメータの指定

頂点バッファやインデックスバッファのサイズやフォーマットが不正。

  • ロストデバイス状態での描画呼び出し

デバイスがロストしている間に描画処理を行った場合。

  • リソースの不適切な使用

既に解放されたリソースを使用しようとした場合。

対処法

  • APIの呼び出し順序を確認する

BeginSceneEndSceneの間で描画を行うことを徹底します。

  • パラメータの妥当性をチェックする

バッファサイズやフォーマット、ポインタの有効性を確認。

  • デバイスの状態をチェックする

TestCooperativeLevelでデバイスが正常か確認し、ロスト状態なら描画を控えます。

  • リソース管理を厳密に行う

解放済みのリソースを使わないようにし、参照カウントを適切に管理します。

  • デバッグランタイムを活用する

詳細なエラーメッセージを取得し、原因特定に役立てる。

ロストデバイス時の例外

Direct3D9のデバイスは、Alt+Tabや画面解像度の変更、スクリーンセーバーの起動などで「ロスト」状態になることがあります。

ロストデバイスとは、GPUリソースが一時的に利用不能になる状態で、描画やリソース操作が失敗し例外やエラーが発生します。

ロストデバイスの検出

IDirect3DDevice9::TestCooperativeLevelを呼び出し、戻り値で状態を判定します。

戻り値意味
D3DERR_DEVICELOSTデバイスはロスト状態で、まだリセットできない。描画不可。
D3DERR_DEVICENOTRESETデバイスはリセット可能です。リセット処理を行う必要あり。
D3D_OKデバイスは正常。描画可能です。

ロストデバイス時の例外発生例

ロスト状態で描画やリソース操作を行うと、D3DERR_INVALIDCALLE_FAILなどのエラーが返り、例外的な動作になることがあります。

対処法

  • 描画ループ内で状態を常にチェックする

ロスト状態なら描画処理をスキップ。

  • リセット可能になったらデバイスをリセットする

IDirect3DDevice9::Resetを呼び、D3DPRESENT_PARAMETERSを再設定。

  • D3DPOOL_DEFAULTのリソースを解放・再作成する

ロストデバイス時に破棄されるリソースは再作成が必要でしょう。

  • 例外処理を適切に行う

エラーコードをチェックし、例外的な状況に対応します。

ロストデバイス対応のサンプルコード例

HRESULT hr = pDevice->TestCooperativeLevel();
if (hr == D3DERR_DEVICELOST)
{
    // 描画不可。待機するなどの処理
    return;
}
else if (hr == D3DERR_DEVICENOTRESET)
{
    // リセット処理
    CleanupDefaultPoolResources(); // リソース解放
    hr = pDevice->Reset(&d3dpp);
    if (SUCCEEDED(hr))
    {
        RecreateDefaultPoolResources(); // リソース再作成
    }
}
else if (hr == D3D_OK)
{
    // 通常描画処理
}

シェーダーコンパイル失敗

HLSLシェーダーのコンパイル時にエラーが発生すると、シェーダーが正しく動作せず描画に支障をきたします。

コンパイル失敗は文法エラーや未定義の変数、型の不一致などが原因です。

エラーの検出方法

D3DXCompileShaderD3DXCompileShaderFromFileの呼び出し時に、エラーメッセージがID3DXBufferのエラーバッファに格納されます。

ID3DXBuffer* pErrorBuffer = nullptr;
HRESULT hr = D3DXCompileShader(
    shaderCode,
    strlen(shaderCode),
    nullptr,
    nullptr,
    "main",
    "ps_3_0",
    0,
    &pCode,
    &pErrorBuffer,
    nullptr);
if (FAILED(hr))
{
    if (pErrorBuffer)
    {
        OutputDebugStringA((char*)pErrorBuffer->GetBufferPointer());
        pErrorBuffer->Release();
    }
    // エラー処理
}

よくある原因例

  • シェーダーコードの文法ミス(セミコロン忘れ、括弧の不一致など)
  • 未定義の変数や関数の使用
  • 型の不一致(floatとintの混用など)
  • サポートされていないシェーダーモデルの指定
  • 入力・出力構造体の不整合

対処法

  • エラーメッセージを詳細に確認する

コンパイルエラーの内容と行番号が表示されるため、該当箇所を修正。

  • シェーダーモデルの適切な指定

使用しているGPUやDirectXバージョンに対応したシェーダーモデルを選択。

  • コードの段階的なテスト

複雑なシェーダーは小さな単位でコンパイルを試み、問題箇所を特定。

  • 公式ドキュメントやサンプルコードの参照

正しい構文やAPIの使い方を確認。

これらのエラーはDirectX9開発で頻繁に遭遇しますが、原因を正確に把握し適切に対処することで、安定したアプリケーション開発が可能になります。

デバッグランタイムやログ出力を活用し、問題解決に役立ててください。

今後の発展例

DirectX9は長年にわたり多くのゲームやアプリケーションで利用されてきましたが、最新のグラフィックス技術やパフォーマンス向上を求める場合は、より新しいAPIへの移行やクロスプラットフォーム対応が重要になります。

ここでは、DirectX11への移行ポイントと、クロスプラットフォームAPIとの比較について詳しく解説します。

DirectX11への移行ポイント

DirectX11はDirectX9の後継として多くの新機能と最適化を提供し、より高度なグラフィックス表現や効率的なリソース管理が可能です。

DirectX9からDirectX11へ移行する際の主なポイントは以下の通りです。

API設計の大幅な変更

  • DirectX9は固定機能パイプラインとシェーダープログラマブルパイプラインが混在していますが、DirectX11は完全にシェーダープログラマブルパイプラインに移行しています
  • リソース管理や描画コマンドの発行方法が大きく変わり、より明示的な制御が求められます

デバイスとコンテキストの分離

  • DirectX11ではID3D11DeviceID3D11DeviceContextが分離され、描画コマンドはコンテキストを通じて発行します
  • これによりマルチスレッドレンダリングが効率化され、複数のコンテキストを使った並列処理が可能です

シェーダーモデルの進化

  • DirectX11はシェーダーモデル5.0をサポートし、より複雑で高性能なシェーダーが記述可能です
  • テッセレーションやコンピュートシェーダーなど新しいシェーダーステージが追加されています

リソースバインディングの変更

  • DirectX9のFVFや固定機能ステートは廃止され、入力レイアウトやリソースビューを使ってリソースをバインドします
  • バッファやテクスチャの管理がより柔軟かつ複雑になっています

エラーハンドリングとデバッグ

  • DirectX11はHRESULTの返却に加え、デバッグレイヤーを使った詳細な検証が可能です
  • 開発時のトラブルシューティングが強化されています

移行のポイントまとめ

項目DirectX9DirectX11
パイプライン固定機能+シェーダー完全シェーダープログラマブル
デバイス単一オブジェクトデバイス+デバイスコンテキスト分離
シェーダーモデル2.0~3.05.0
リソース管理FVF、固定ステート入力レイアウト、リソースビュー
マルチスレッド制限あり高度な並列処理対応

DirectX9からDirectX11への移行は大規模なコード変更を伴いますが、最新のGPU性能を活かした高品質な描画が可能になります。

クロスプラットフォームAPIとの比較

近年、Windows以外のプラットフォームでも高性能なグラフィックスを実現するため、クロスプラットフォームAPIの利用が増えています。

代表的なAPIにはVulkanやOpenGL、Metal(Apple製品向け)があります。

DirectX9/11と比較した特徴を解説します。

Vulkan

  • 特徴

Khronos Groupが策定した低レベルグラフィックスAPIで、Windows、Linux、Androidなど多くのプラットフォームをサポート。

  • 利点

高いパフォーマンスとマルチスレッド対応、GPUリソースの細かな制御が可能です。

  • 課題

APIが複雑で学習コストが高いでしょう。

初期設定や管理が煩雑。

OpenGL / OpenGL ES

  • 特徴

長年使われてきたクロスプラットフォームAPI。

OpenGL ESはモバイル向け。

  • 利点

幅広いプラットフォーム対応と豊富なドキュメント。

  • 課題

ドライバ依存の挙動が多く、最新機能のサポートが遅れることがあります。

Metal

  • 特徴

Appleが提供する低レベルグラフィックスAPI。

iOSやmacOS専用。

  • 利点

Apple製品での高効率な描画が可能です。

  • 課題

WindowsやAndroidなど他プラットフォーム非対応。

DirectXとの比較

項目DirectX9/11VulkanOpenGLMetal
プラットフォームWindows中心クロスプラットフォームクロスプラットフォームApple製品限定
APIレベル中~低レベル低レベル中レベル低レベル
マルチスレッド対応DirectX11で強化高度に対応限定的高度に対応
学習コスト中程度高い低~中中程度
最新機能対応速い(特にWindows)速い遅れることあり速い(Apple製品)

クロスプラットフォーム開発のメリット

  • 複数OSやデバイスで同じコードベースを利用可能です
  • 将来的な移植性や市場拡大に有利

DirectXの強み

  • Windows環境での最適化と最新機能の迅速なサポート
  • 豊富な開発ツールとドキュメント

DirectX9からのステップアップとしてDirectX11への移行は、最新GPUの性能を活かす上で重要です。

一方で、将来的なクロスプラットフォーム展開を視野に入れるなら、VulkanやOpenGLなどのAPIも検討すべきです。

開発目的や対象プラットフォームに応じて最適なAPIを選択し、技術の発展に対応していくことが求められます。

まとめ

本記事では、DirectX9を用いたゲーム描画の基本から応用までを幅広く解説しました。

Win32ウィンドウの作成やDirect3Dデバイスの初期化、レンダリングループの実装、頂点・インデックスバッファの扱い、テクスチャやシェーダーの利用方法、さらに入力デバイス連携やサウンド再生、リソース管理のポイントも紹介しています。

加えて、デバッグ手法やマルチスレッドレンダリングの注意点、フレームワーク設計例、実践的な描画サンプルも網羅。

DirectX9の基礎をしっかり押さえつつ、今後の技術発展や他APIとの比較も理解できる内容です。

関連記事

Back to top button
目次へ