DirectX9によるDirectGraphics実装
概要
DirectGraphicsはDirectXの描画関連を扱うAPIです。 3Dの描画がメインですが、2Dの描画も行うことが可能です。 DirectX9からプログラマシェーダを使用することができ、 10、11とバージョンがあがるごとに使用できるシェーダの種類も増えています。 このページではDirectX9を使ってDirectGraphicsを扱うための環境設定と 初期化、描画、終了に必要な処理を説明しています。
サンプル
サンプルはここからダウンロードできます。 環境については以下の内容となっています。
VSのバージョン | DirectXのバージョン |
---|---|
VisualStudio 2019 | DirectX9 |
環境設定方法
DirectGraphicsの機能を使用するためにVS上でいくつかの設定を行う必要があります。
パス設定
まず、DirectXのincludeファイルやlibファイルがどこにあるかの設定を行います。 メニューの「デバッグ」=>「プロジェクト名のプロパティ」からウィンドウを開き、 設定を行っていきます。 プロパティウィンドウが表示されるので「構成プロパティ」=> 「VC++ディレクトリ」を クリックしてインクルードディレクトリとライブラリディレクトリにパス設定を行います。
共通
パスの設定はまず設定したい項目をクリックすると「↓」アイコンが右端に 表示されるのでそちらをクリックします。 クリック後「編集」と書かれた項目が新しく表示されるので、クリックします。 パス設定をするウィンドウが新たに表示ます。 この画面の上部にある白い部分をダブルクリックすると右端に「…」が 表示されるのでそちらをクリックします。 参照先ディレクトリを決めるためのウィンドウが表示されるので、 DirectXが置かれている場所まで移動してパスを設定します。
インクルードディレクトリ
インクルードディレクトリではDirectXディレクトリの中にある Includeディレクトリを指定します。
ライブラリディレクトリ
ライブラリディレクトリではDirectXディレクトリの中にある Libファイルの中の「x86」か「x64」いずれかを指定します。 これはVisualStudio上段に「x86」または「x64」と書かれている項目があるので そちらを確認して書かれている方を指定してください。
libファイルの設定
パスの設定が完了したら次はlibファイルの設定を行います。 方法はプロパティで指定する方法とコードで指定する方法があります。
プロパティ指定
プロパティ指定はメニューの「デバッグ」=>「プロジェクト名のプロパティ」=> 「リンカ」=>「入力」項目の「追加の依存ファイル」で行います。 ここにDirectGraphicsでは「d3d9.lib」と「d3dx9.lib」を追加します。
コード指定
コード指定の場合はファイルに以下のコードを追加します。
#pragma comment(lib, "d3d9.lib")
#pragma comment(lib, "d3dx9.lib")
使用するincludeファイル
DirectGraphicsを使用する場合、以下のヘッダファイルをincludeして下さい。
#include <d3d9.h>
#include <d3dx9.h>
DirectGraphicsの構成
DirectGraphicsは二つのCOMインターフェースにより構成されています。 一つはDirectGraphicsの大元となるIDirect3D9、 もう一つは描画を行うための関数を持っているIDirect3DDevice9です。 ※インターフェースがわからない方はインターフェースの部分をクラスに置き換えて下さい。 DirectXを使用するだけならその認識で問題ないと思います。
IDirect3D9
上でも記述していますが、IDirect3D9はDirectGraphicsを使用するにあたり、 大元となるインターフェースです。 IDirect3DDevice9を作成するための関数やDirectXを使用するPCが どこまで機能を使いこなせるかをチェックする関数を持ちます。
IDirect3DDevice9
IDirect3DDevice9は描画関連のデバイス(GPUやディスプレイ)を 管理するインターフェースです。 このインターフェースにウィンドウに描画をするための関数があるので、 ゲーム中は主にこのインターフェースを使用することになります。
DirectGraphicsの初期化の流れ
DirectGraphicsを初期化する流れは以下の通りです。
①.IDirect3D9の作成 |
②.D3DPRESENT_PARAMETERS構造体の設定 |
③.IDirect3DDevice9の作成 |
①.IDirect3D9の作成
DirectGraphicsを使用する場合、最初にIDirect3D9を作成します。 IDirect3D9はDirectX9におけるDirectGraphicsの大元になりますが この後に作成するIDirect3DDevice9を作成した後はほとんど使用しません。
内容 | ||
---|---|---|
IDirect3D9を作成する関数 | ||
戻り値の型 | 説明 | |
IDirect3D9* | 作成が成功したらIDirect3D9*が返り、失敗したらnullptrが返る | |
引数の型 | 説明 | |
型 | UINT | DirectXのSDKバージョン |
名 | SDKVersion |
// 使用例
LPDIRECT3D9 direct3d9 = Direct3DCreate9(D3D_SDK_VERSION);
if (direct3d9 == nullptr)
{
// 作成失敗
}
これでIDirect3D9を作成できたので②に進みます。
②.D3DPRESENT_PARAMETERS構造体の設定
次に作成するIDirect3DDevice9インターフェースの取得時に D3DPRESENT_PARAMETERS構造体の設定を行う必要があります。 この構造体でバックバッファの設定などを行います。
内容 | ||
---|---|---|
IDirect3DDevice9を作成する際に 必要な設定するための項目をまとめた構造体 |
||
メンバ | 説明 | |
型名 | UINT | バックバッファの横幅 |
名前 | BackBufferWidth | |
型名 | UINT | バックバッファの縦幅 |
名前 | BackBufferHeight | |
型名 | D3DFORMAT | バックバッファの画面フォーマット情報 例:D3DFMT_R8G8B8は赤、緑、青を8bitずつで作成する |
名前 | BackBufferFormat | |
型名 | UINT | バックバッファの数 |
名前 | BackBufferCount | |
型名 | UINT | バックバッファの数 |
名前 | BackBufferCount | |
型名 | D3DMULTISAMPLE_TYPE | マルチサンプルの数 |
名前 | MultiSampleType | |
型名 | DWORD | マルチサンプルの品質レベル |
名前 | MultiSampleQuality | |
型名 | D3DSWAPEFFECT | フロントバッファとバックバッファの切り替え方法 |
名前 | SwapEffect | |
型名 | HWND | 画面を描画するウィンドウハンドル |
名前 | hDeviceWindow | |
型名 | BOOL | スクリーンモード trueがウィンドウモード |
名前 | Windowed | |
型名 | BOOL | 深度ステンシルバッファの有無 |
名前 | EnableAutoDepthStencil | |
型名 | D3DFORMAT | ステンシルバッファのフォーマット |
名前 | AutoDepthStencilFormat | |
型名 | DWORD | バックバッファからフロントバッファへ転送時のオプション |
名前 | Flags | |
型名 | UINT | フルスクリーンでのリフレッシュレート |
名前 | FullScreenRefreshRateInHZ | |
型名 | UINT | スワップエフェクトの書き換えタイミング |
名前 | PresentationInterval |
// バックバッファの数 => 一つ
present_param.BackBufferCount = 1;
// バックバッファのフォーマット => D3DFMT_UNKNOWN(フォーマットを知りません)
present_param.BackBufferFormat = D3DFMT_UNKNOWN;
/*
ウィンドウモード設定 => 定数で切り替え
true(ウィンドウ)、false(フルスクリーン)
*/
present_param.Windowed = true;
/*
スワップエフェクト => D3DSWAPEFFECT_DISCARD(自動設定)
スワップエフェクトとは:
バックバッファとフロントバッファへの切り替え方法
*/
present_param.SwapEffect = D3DSWAPEFFECT_DISCARD;
この他のメンバ変数には無効な値が設定されないように
ZeroMemoryやmemsetなどを使用してD3DPRESENT_PARAMETERS構造体の
0クリアは必ず行ってください。
これでD3DPRESENT_PARAMETERSの設定が完了したので③に進みます。
③.IDirect3DDeviceの作成
次はIDirect3DDeviceの作成をします。 IDirect3DDeviceはグラフィック関連のデバイス(グラフィックカード等の装置)を管理しており 画面への描画もIDirect3DDeviceが持っているメンバ関数を使用して行います。
内容 | ||
---|---|---|
IDirect3DDeviceを作成する関数 | ||
戻り値の型 | 説明 | |
HRESULT | 作成の結果が返る 成功:S_OK 失敗:S_OK以外 |
|
引数の型 | 説明 | |
型名 | UINT | ディスプレイアダプタの種類 デフォルトはD3DADAPTER_DEFAULTを指定 |
引数名 | Adapter | |
型名 | D3DDEVTYPE | デバイスの種類を設定 ・D3DDEVTYPE_HAL HALデバイスを使用して効率の良い描画処理を行う ・D3DDEVTYPE_REF ソフトウェアで描画処理を行う D3DDEVTYPE_HALに比べると処理が遅い |
引数名 | DeviceType | |
型名 | HWND | このデバイスが割り当てられるウィンドウハンドル |
引数名 | hFocusWindow | |
型名 | DWORD | デバイス制御の組み合わせ 基本的にはD3DCREATE_HARDWARE_VERTEXPROCESSINGか D3DCREATE_SOFTWARE_VERTEXPROCESSINGを使用する |
引数名 | BehaviorFlags | |
型名 | D3DPRESENT_PARAMETERS* | デバイスを設定するためのD3DPRESENT_PARAMETERS構造体 |
引数名 | pPresentationParameters | |
型名 | IDirect3DDevice9** | 作成されたIDirect3DDevice9を格納する IDirect3DDevice9のポインタのアドレス |
引数名 | ppReturnedDeviceInterface |
// DirectDeviceの作成
if (FAILED(g_pD3DInterface->CreateDevice(
D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
window_handle,
D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_MULTITHREADED,
&present_param,
&g_pD3DDevice)))
{
return false;
}
作成が成功すると第六引数の「ppReturnedDeviceInterface」に作成された
IDirect3DDevice9が保存されて、失敗するとnullptrが入っています。
失敗の対応
Deviceの作成の失敗でよくあるパターンが「HWND(Window Handle)がnullptr」と 「デバイスの種類と制御設定の組み合わせ」組み合わせです。
HWND(Window Handle)がnullptr
まずは第三引数のデバイスが割り当てられるWindowHandleによる失敗についてです。 Window作成の失敗をチェックしていない、引数での値の受け渡しを間違うなど、 この引数に正常な値が入っていないことによるエラーをよく見かけます。 作成に失敗した際は一度値を確認してみてください。 このチェックをして解決しないようなら、 次の「デバイスの種類と制御設定の組み合わせ」を確認してみてください。
デバイスの種類と制御設定の組み合わせ
この失敗はPCのデバイスがDirectXに対応していないことが主な原因ですので、 以下の設定の組み合わせパターンを上から試してみてください。
①.HAL * ハードウェア処理 | デバイスの種類 | D3DDEVTYPE_HAL |
制御設定 | D3DCREATE_HARDWARE_VERTEXPROCESSING | |
②.HAL * ソフトウェア処理 | デバイスの種類 | D3DDEVTYPE_HAL |
制御設定 | D3DCREATE_SOFTWARE_VERTEXPROCESSING | |
③.REF * ハードウェア処理 | デバイスの種類 | D3DDEVTYPE_REF |
制御設定 | D3DCREATE_HARDWARE_VERTEXPROCESSING | |
④.REF * ソフトウェア処理 | デバイスの種類 | D3DDEVTYPE_REF |
制御設定 | D3DCREATE_SOFTWARE_VERTEXPROCESSING |
ViewPort
ViewPort(ビューポート)とはバッファの使用範囲を設定する機能です。 基本はバッファのサイズを全て描画範囲に設定しますが、 この値を変更することで一部の範囲だけ描画することも可能です。
説明 | ||
---|---|---|
ViewPortの描画範囲で必要な設定情報をメンバに持つ構造体 | ||
メンバ | 説明 | |
型名 | DWORD | 描画領域のX座標(左上) |
名前 | X | |
型名 | DWORD | 描画領域のY座標(左上) |
名前 | Y | |
型名 | DWORD | 描画領域の横幅 |
名前 | Width | |
型名 | DWORD | 描画領域の縦幅 |
名前 | Height | |
型名 | float | クリップ領域の最小深度 基本は0.0f固定で問題ない |
名前 | MinZ | |
型名 | float | クリップ領域の最小深度 基本は1.0f固定で問題ない |
名前 | MaxZ |
// ビューポートパラメータ
D3DVIEWPORT9 view_port;
// ビューポートの左上座標
view_port.X = 0;
view_port.Y = 0;
// ビューポートのサイズ
view_port.Width = present_param.BackBufferWidth; // ウィンドウの幅
view_port.Height = present_param.BackBufferHeight; // ウィンドウの高さ
// ビューポート深度設定
view_port.MinZ = 0.0f;
view_port.MaxZ = 1.0f;
範囲設定の注意点
上の絵のようにViewPortの(x, y)を(0, 0)ではない値にした場合、 その位置からバッファの内容が描画されるということではない点に注意してください。 バッファの内容をどこからどこまでを使用するかという設定をViewPortで行います。
ViewPort設定関数
ViewPortを設定する関数はIDirectDevice9のメンバ関数である 「SetViewport関数」を使用します。
内容 | ||
---|---|---|
ViewPortの設定をする関数 | ||
戻り値の型 | 説明 | |
HRESULT | 設定の結果が返る 成功:S_OK 失敗:S_OK以外 |
|
引数の型 | 説明 | |
型名 | const D3DVIEWPORT9* | ViewPortの設定情報が保存されている D3DVIEWPORT9構造体のポインタ |
引数名 | pViewport |
// ビューポート設定
if (FAILED(g_pD3DDevice->SetViewport(&view_port)))
{
return false;
}
これでViewPortの設定が完了しましたので、DirectGraphicsの最低限の初期化が終了です。
DirectGraphicsの描画方法
DirectGraphicsの描画は描画開始と終了を関数で宣言します。 その間に描画処理を行うことでバックバッファに絵が描き込まれます。 ただし、終了関数を実行したとしてもそれは描画の終わりを宣言しただけで 画面にバックバッファの内容は反映されません。 反映はバックバッファの内容をフロントバッファに送る関数を実行することで完了します。
描画手順
以下は描画を行う手順で、この内容は必ず毎フレーム行ってください。
①.クリア |
②.描画開始 |
③.描画処理 |
④.描画終了 |
⑤.バックバッファ転送 |
①.クリア
描画を行う前にまずはバッファの内容をクリアします。 クリアを行わないと前に描き込まれた内容に上書きすることになるので そのフレームのみの描画結果にはなりません。 クリアには「Clear関数」を使用します。
内容 | ||
---|---|---|
バッファを引数の内容でクリアする ※クリアする範囲はビューポートで指定した範囲のみ |
||
戻り値の型 | 説明 | |
HRESULT | クリア結果 成功:S_OK 失敗:S_OK以外 |
|
引数の型 | 説明 | |
型名 | DWORD | 第二引数で使用するconst D3DRECT*の数 ※あまり使用しない |
引数名 | Count | |
型名 | const D3DRECT* | D3DRECTの配列 クリアする矩形の範囲を変更したい場合に使用 nullptrならビューポート全体をクリア ※あまり使用しない |
引数名 | pRects | |
型名 | DWORD | クリアするバッファの種類を決めるフラグの設定 バッファはバック、デプス、ステンシルの三種類 ※2Dの場合はD3DCLEAR_TARGETだけでも問題ない ・D3DCLEAR_TARGET レンダリングターゲットをクリア (これがバックバッファの設定) ・D3DCLEAR_STENCIL ステンシルバッファをクリア ・D3DCLEAR_ZBUFFER 深度バッファをクリア |
引数名 | Flags | |
型名 | D3DCOLOR | バックバッファをクリアする色情報 |
引数名 | Color | |
型名 | float | デプスバッファをクリアする値 未使用なら0.0f |
引数名 | Z | |
型名 | DWORD | ステンシルバッファをクリアする値 未使用なら0 |
引数名 | Stencil |
// ①.クリア
pD3D9Device->Clear(0,
NULL,
D3DCLEAR_TARGET,
D3DCOLOR_XRGB(0, 0, 0),
0.0f,
0);
2Dの場合は第三、第四引き数の指定をしっかり行えば問題ありません。
3Dの場合は第三引数にデプス、ステンシルのバッファ設定の追加と
第五、第六引数でクリアする値を指定したら大丈夫です。
これでクリアが完了したので描画開始に進みます。
②.描画開始
DirectX9では描画を始める際に「BeginScene関数」を実行して描画開始の宣言をします。 これを忘れるとこの後の手順を実行しても描画されないので注意してください。
内容 | ||
---|---|---|
描画の開始を宣言する | ||
戻り値の型 | 説明 | |
HRESULT | 宣言結果 成功:S_OK 失敗:S_OK以外 |
// ②.描画開始
if (D3D_OK == pD3D9Device->BeginScene())
{
}
BeginSceneが成功した場合S_OKが返りますので、毎フレーム結果を確認してから
次の描画処理に進むようにしてください。
③.描画処理
描画処理ではテクスチャの描画、3Dモデルの描画など画面に描画するために 必要な処理を全て実行します。 ここ以外で描画処理を実行しても描画が行われる保証はないので注意してください。 描画処理が全て終了したら描画の終了を宣言する必要があるので次に進みます。
④.描画終了
一フレーム内で行う描画処理がすべて完了したら「EndScene関数」を実行して 描画の終了を宣言します。 この関数はテクスチャ一枚、3Dモデル一体などを一つの対象を 描画するたびに実行する関数ではないので注意してください。
内容 | ||
---|---|---|
描画の終了を宣言する ※必ず描画の開始を宣言しておくこと |
||
戻り値の型 | 説明 | |
HRESULT | 宣言結果 成功:S_OK 失敗:S_OK以外 |
// ④.描画終了
pD3D9Device->EndScene();
これで、描画処理が完了しましたので最後の内容に進みます。
⑤.バックバッファ転送
描画が終了したらバックバッファに書き込まれた内容を 「Present関数」を使用してフロントバッファへ転送します。 ※Presentは引数の設定などでフロントバッファ以外のバッファにも転送できます。
内容 | ||
---|---|---|
バッファの内容を転送する | ||
戻り値の型 | 説明 | |
HRESULT | 転送結果 成功:S_OK 失敗:S_OK以外 |
|
引数の型 | 説明 | |
型名 | const RECT* | 転送元矩形 ※SWAP_EFFECTの種類がD3DSWAPEFFECT_COPYの場合の有効 D3DSWAPEFFECT_COPY以外はnullptr |
引数名 | pSourceRect | |
型名 | const RECT* | 転送先矩形 ※SWAP_EFFECTの種類がD3DSWAPEFFECT_COPYの場合の有効 D3DSWAPEFFECT_COPY以外はnullptr |
引数名 | pDestRect | |
型名 | HWND | 使用するWindow Handle ※nullptrを設定したらD3DPRESENT_PARAMETERSで設定した Window Handleが使用される |
引数名 | hDestWindowOverride | |
型名 | const RGNDATA* | 基本nullptr |
引数名 | pDirtyRegion |
// 4.バッファ転送
g_pD3DDevice->Present(nullptr, nullptr, nullptr, nullptr);
上のコードで書いてあるようにバックバッファをフロントバッファに転送する場合
全ての引数をnullptrに設定します。
これで描画の内容が転送されたので描画処理は全て終了です。
手順の実装コード
以下のコードは①~⑤をまとめたものになります。
// IDirect3DDevice9 => pD3D9Device
// ①.クリア
pD3D9Device->Clear(0,
nullptr,
D3DCLEAR_TARGET,
D3DCOLOR_XRGB(0, 0, 0),
0.0f,
0);
// ②.描画開始
if (D3D_OK == pD3D9Device->BeginScene())
{
// ③.描画処理実行
// ④.描画終了
pD3D9Device->EndScene();
// ⑤.バッファ転送
pD3D9Device->Present(nullptr, nullptr, nullptr, nullptr);
}
DirectGraphicsの終了
最後にDirectGraphicsを終了する場合の説明を行います。 DirectGraphicsを使用するために作成したDeviceなどのCOMインターフェースは きちんと解放する必要があります。 インターフェース解放は「Release関数」が用意してあるのでそちらを使用します。
内容 | ||
---|---|---|
解放する(参照カウンタを減らす) | ||
戻り値の型 | 説明 | |
ULONG | 減算後の参照カウンタ |
// IDirect3DDeviceの解放
pD3D9Device->Release();
COMインターフェースは参照カウンタというインターフェースを
保持しているオブジェクトの数をカウントする値を持っています。
これはCOMインターフェース作成時やAddRefなどにより加算されて、
Releaseなどを実行することで減算されます。
最終的にこの参照カウンタが0になったタイミングで解放が行われます。
解放の順番
DirectGraphicsを使用した場合、IDirect3D9とIDirect3DDevice9を使用しますが、
特別な理由がない限り作成した時と逆の順番で解放するようにしてください。
// IDirect3D9 => pD3D9
// IDirect3DDevice9 => pD3D9Device
pD3D9Device->Release();
pD3D9->Release();
これでDirectGraphicsの基本的な実装は全て終了です。
描画のための準備が完了したので次はポリゴンの描画に進みます。