DirectX9によるDirectGraphics実装


概要

DirectGraphicsはDirectXの描画関連を扱うAPIです。
3Dの描画がメインですが、2Dの描画も行うことが可能です。
DirectX9からプログラマシェーダを使用することができ、
10、11とバージョンがあがるごとに使用できるシェーダの種類も増えています。
このページではDirectX9を使ってDirectGraphicsを扱うための環境設定と
初期化、描画、終了に必要な処理を説明しています。

サンプル

サンプルはここからダウンロードできます。
環境については以下の内容となっています。

開発環境
VSのバージョン DirectXのバージョン
VisualStudio 2019 DirectX9

環境設定方法

DirectGraphicsの機能を使用するためにVS上でいくつかの設定を行う必要があります。

パス設定

まず、DirectXのincludeファイルやlibファイルがどこにあるかの設定を行います。
メニューの「デバッグ」=>「プロジェクト名のプロパティ」からウィンドウを開き、
設定を行っていきます。

directx_0016

プロパティウィンドウが表示されるので「構成プロパティ」=> 「VC++ディレクトリ」を
クリックしてインクルードディレクトリとライブラリディレクトリにパス設定を行います。

directx_0017

共通

パスの設定はまず設定したい項目をクリックすると「↓」アイコンが右端に
表示されるのでそちらをクリックします。

directx_0018

クリック後「編集」と書かれた項目が新しく表示されるので、クリックします。

directx_0019

パス設定をするウィンドウが新たに表示ます。
この画面の上部にある白い部分をダブルクリックすると右端に「…」が
表示されるのでそちらをクリックします。

directx_0020

参照先ディレクトリを決めるためのウィンドウが表示されるので、
DirectXが置かれている場所まで移動してパスを設定します。

インクルードディレクトリ

インクルードディレクトリではDirectXディレクトリの中にある
Includeディレクトリを指定します。

directx_0021

ライブラリディレクトリ

ライブラリディレクトリではDirectXディレクトリの中にある
Libファイルの中の「x86」か「x64」いずれかを指定します。

directx_0022

これはVisualStudio上段に「x86」または「x64」と書かれている項目があるので
そちらを確認して書かれている方を指定してください。

directx_0023

libファイルの設定

パスの設定が完了したら次はlibファイルの設定を行います。
方法はプロパティで指定する方法とコードで指定する方法があります。

プロパティ指定

プロパティ指定はメニューの「デバッグ」=>「プロジェクト名のプロパティ」=>
「リンカ」=>「入力」項目の「追加の依存ファイル」で行います。

directx_0024

ここにDirectGraphicsでは「d3d9.lib」と「d3dx9.lib」を追加します。

directx_0025

コード指定

コード指定の場合はファイルに以下のコードを追加します。	
			
#pragma comment(lib, "d3d9.lib")
#pragma comment(lib, "d3dx9.lib")

DirectGraphicsの構成

DirectGraphicsは二つのCOMインターフェースにより構成されています。
一つはDirectGraphicsの大元となるIDirect3D9、
もう一つは描画を行うための関数を持っているIDirect3DDevice9です。
※インターフェースがわからない方はインターフェースの部分をクラスに置き換えて下さい。
 DirectXを使用するだけならその認識で問題ないと思います。

directx_0008

IDirect3D9

上でも記述していますが、IDirect3D9はDirectGraphicsを使用するにあたり、
大元となるインターフェースです。
IDirect3DDevice9を作成するための関数やDirectXを使用するPCが
どこまで機能を使いこなせるかをチェックする関数を持ちます。

IDirect3DDevice9

IDirect3DDevice9は描画関連のデバイス(GPUやディスプレイ)を
管理するインターフェースです。
このインターフェースにウィンドウに描画をするための関数があるので、
ゲーム中は主にこのインターフェースを使用することになります。

DirectGraphicsの初期化の流れ

DirectGraphicsを初期化する流れは以下の通りです。
①.IDirect3D9の作成
②.D3DPRESENT_PARAMETERS構造体の設定
③.IDirect3DDevice9の作成

①.IDirect3D9の作成

DirectGraphicsを使用する場合、最初にIDirect3D9を作成します。
IDirect3D9はDirectX9におけるDirectGraphicsの大元になりますが
この後に作成するIDirect3DDevice9を作成した後はほとんど使用しません。

Direct3DCreate9関数
内容
IDirect3D9を作成する関数
戻り値の型 説明
IDirect3D9* 作成が成功したらIDirect3D9*が返り、失敗したらnullptrが返る
引数の型 説明
UINT DirectXのSDKバージョン
SDKVersion
// 使用例 LPDIRECT3D9 direct3d9 = Direct3DCreate9(D3D_SDK_VERSION); if (direct3d9 == nullptr) { // 作成失敗 } これでIDirect3D9を作成できたので②に進みます。

②.D3DPRESENT_PARAMETERS構造体の設定

次に作成するIDirect3DDevice9インターフェースの取得時に
D3DPRESENT_PARAMETERS構造体の設定を行う必要があります。
この構造体でバックバッファの設定などを行います。

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
これだけの設定項目がありますが、全てに値を設定する必要はありません。 2Dゲームを開発する上で最低限必要な設定項目は以下の通りです。 ※3D開発を行う場合は、もう少し項目の追加が必要です。 // バックバッファの数 => 一つ 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が持っているメンバ関数を使用して行います。

CreateDevice関数
内容
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(ビューポート)とはバッファの使用範囲を設定する機能です。
基本はバッファのサイズを全て描画範囲に設定しますが、
この値を変更することで一部の範囲だけ描画することも可能です。

D3DVIEWPORT9構造体
説明
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;

範囲設定の注意点

directx_0007
上の絵のようにViewPortの(x, y)を(0, 0)ではない値にした場合、
その位置からバッファの内容が描画されるということではない点に注意してください。
バッファの内容をどこからどこまでを使用するかという設定をViewPortで行います。

ViewPort設定関数

ViewPortを設定する関数はIDirectDevice9のメンバ関数である
「SetViewport関数」を使用します。

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の描画は描画開始と終了を関数で宣言します。
その間に描画処理を行うことでバックバッファに絵が描き込まれます。
ただし、終了関数を実行したとしてもそれは描画の終わりを宣言しただけで
画面にバックバッファの内容は反映されません。
反映はバックバッファの内容をフロントバッファに送る関数を実行することで完了します。

描画手順

以下は描画を行う手順で、この内容は必ず毎フレーム行ってください。

①.クリア
②.描画開始
③.描画処理
④.描画終了
⑤.バックバッファ転送
各手順で関数を実行しますが、それらの関数は全てIDirect3DDevice9の関数なので、 関数実行の際はIDirect3DDevice9から使用するようにしてください。 ※サンプルコードを一緒に書いていますので参考にしてください。

①.クリア

描画を行う前にまずはバッファの内容をクリアします。
クリアを行わないと前に描き込まれた内容に上書きすることになるので
そのフレームのみの描画結果にはなりません。
クリアには「Clear関数」を使用します。

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関数」を実行して描画開始の宣言をします。
これを忘れるとこの後の手順を実行しても描画されないので注意してください。

BeginScene関数
内容
描画の開始を宣言する
戻り値の型 説明
HRESULT 宣言結果
成功:S_OK 失敗:S_OK以外
// ②.描画開始 if (D3D_OK == pD3D9Device->BeginScene()) { } BeginSceneが成功した場合S_OKが返りますので、毎フレーム結果を確認してから 次の描画処理に進むようにしてください。

③.描画処理

描画処理ではテクスチャの描画、3Dモデルの描画など画面に描画するために
必要な処理を全て実行します。
ここ以外で描画処理を実行しても描画が行われる保証はないので注意してください。
描画処理が全て終了したら描画の終了を宣言する必要があるので次に進みます。

④.描画終了

一フレーム内で行う描画処理がすべて完了したら「EndScene関数」を実行して
描画の終了を宣言します。
この関数はテクスチャ一枚、3Dモデル一体などを一つの対象を
描画するたびに実行する関数ではないので注意してください。

EndScene関数
内容
描画の終了を宣言する
※必ず描画の開始を宣言しておくこと
戻り値の型 説明
HRESULT 宣言結果
成功:S_OK 失敗:S_OK以外
// ④.描画終了 pD3D9Device->EndScene(); これで、描画処理が完了しましたので最後の内容に進みます。

⑤.バックバッファ転送

描画が終了したらバックバッファに書き込まれた内容を
「Present関数」を使用してフロントバッファへ転送します。
※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関数」が用意してあるのでそちらを使用します。
Release関数
内容
解放する(参照カウンタを減らす)
戻り値の型 説明
ULONG 減算後の参照カウンタ
// IDirect3DDeviceの解放 pD3D9Device->Release(); COMインターフェースは参照カウンタというインターフェースを 保持しているオブジェクトの数をカウントする値を持っています。 これはCOMインターフェース作成時やAddRefなどにより加算されて、 Releaseなどを実行することで減算されます。 最終的にこの参照カウンタが0になったタイミングで解放が行われます。

解放の順番

DirectGraphicsを使用した場合、IDirect3D9とIDirect3DDevice9を使用しますが、
特別な理由がない限り作成した時と逆の順番で解放するようにしてください。

// IDirect3D9 => pD3D9
// IDirect3DDevice9 => pD3D9Device

pD3D9Device->Release();
pD3D9->Release();

これでDirectGraphicsの基本的な実装は全て終了です。
描画のための準備が完了したので次はポリゴンの描画に進みます。