ここではDirect3D9の基本的な使い方と、板ポリを作って実際に画面に表示するまでの流れを説明します。
ここでは「2.窓メッセージ」で紹介したウインドウプログラムのソースコードを流用することで、
ウィンドウ関連を気にせずDirect3Dの処理だけを実装していきます。
まずはDirect3Dのテスト用に新規でプロジェクトを作成します。
なお、ここではプロジェクト名を「TestD3D9」とします。」
※VC2015を使用したプロジェクトの作り方についてはこちらを参考にしてください
次にウィンドウプログラムのソースファイルを今作成したプロジェクトのフォルダにコピーします。
※コピーするファイルはCWindows.cpp、CWindow.h、Main.cppの3つとなります
次にこれらのファイルをプロジェクトに登録します。
一度にファイルを登録したい場合は、以下のように登録したいファイルを選択してプロジェクトの名前部分にドラッグします。
問題が無ければ以下のようにプロジェクト内にファイルが登録されます。
この状態でビルドして実行すると、白いウィンドウが出るだけのアプリが完成ですが、
これをDirectXを使用するための最低限必要な状態と覚えておきましょう。
なお、この状態ではまだプロジェクトのプロパティを変更していないため、ビルド設定がデフォルト状態になっています。
デフォルト状態では使用するWindowsライブラリがDLL版となっているため、これを他のPCなどで実行させるには
VisualC++2015ランタイムをインストールしなければならないので、ユーザーにこれらのランタイムのインストールを
強制させたくない場合はプロジェクトの設定を変更してからビルドしてください。
※詳しくはこちらを参照
DirectX(3D、Sound含む)を使うにはDirectX SDKのヘッダとライブラリをプロジェクトで使えるように設定しなければなりません。
※昔のVCでは、全てのプロジェクトで同じヘッダとライブラリを使えるようにグローバル設定が可能でしたが、
現在のVCは必ずプロジェクトごとに設定が必要となっています(厳密には設定自体は可能ですが簡単に出来なくなっています)
まずはプロジェクトのプロパティを表示させます。
設定したい構成とプラットフォームを確認し「VC++ディレクトリ」を選択します。
次に右側のウィンドウ内のインクルードディレクトリの既存のパス文字列をクリックすると、
右側にボタンが出るのでこれを押します。
次に登録したいディレクトリを追加します。
以下のように何もない箇所をクリックするとその欄の入力状態となりますが、
さらにもう一度クリックすると右側に選択ボタンが表示されます。
→ |
選択ボタンを押すとディレクトリを選択する画面が表示されるので、
インストールしたDirectX SDKのIncludeディレクトリを選択します。
使用中のOSが32bit版OSの場合は「C:\Program Files\Microsoft DirectX SDK (June 2010)\Include」、
64bit版OSの場合は「C:\Program Files (x86)\Microsoft DirectX SDK (June 2010)\Include」となります。
※このダイアログは少々ややこしく、見た目はファイルを選ぶダイアログですが、
選ぶのはあくまでもディレクトリとなり実際に選択されるディレクトリはその中に入ってから
選択ボタンを押さなければなりません (上記ではIncludeフォルダの中に入ってからボタンを押しています)
選択したらそのパスが表示されるので、間違いがなければOKを押します。
※2行目をクリックすることでさらに追加でフォルダを追加することが出来ます
これでインクルードディレクトリに追加したパスが表示されます。
同じようにライブラリディレクトリにもDirectX SDKのライブラリを登録します。
ディレクトリの選択方法もインクルードと同じですが、ここでは作成するアプリが32bit版なのか
64bit版かによってディレクトリが異なるので、正しい方のディレクトリを指定します。
なお、現在設定中のアプリのプラットフォームは以下の部分で確認出来ます。
※Win32は32bit版、x64なら64bit版アプリの設定です
プラットフォームが32bit版なら「C:\Program Files\Microsoft DirectX SDK (June 2010)\Lib\x86」
もしくは「C:\Program Files (x86)\Microsoft DirectX SDK (June 2010)\Lib\x86」、
64bitなら「C:\Program Files \Microsoft DirectX SDK (June 2010)\Lib\x64」
または「C:\Program Files (x86)\Microsoft DirectX SDK (June 2010)\Lib\x64」となります。
以下のようにインクルードとライブラリが太字で表示されていたら完了なので、
OKボタンを押してプロパティページを閉じます。
※最終的には上部の構成(DebugかRelease)、プラットフォーム(Win32かx64)の4つ分それぞれの設定が必要です
これ以降このプロジェクトでDirectXを使うプログラムを作ることが出来ますが、
この作業は新規プロジェクトを作るごとに必要となるので覚えておきましょう。
ここでは上記のウィンドウサンプルのソースコードを改変しながらDirect3D9を構築する手順を説明します。
まずはDirect3D9を使うためにソースファイル(Main.cpp)に関連するヘッダをインクルードします。
#include "CWindow.h" // クラス定義ヘッダ #include <d3d9.h> #include <d3dx9.h> int WINAPI WinMain( HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpszCmdParam,int nCmdShow ) {
次はライブラリのリンク設定を行います。
ライブラリについて簡単に説明すると、通常のプログラムはhファイルとc(またはcpp)ファイルのセットとして作成しますが、
cのファイルを毎回コンパイルすると時間の無駄だったり、そもそも完成してあるソースコードを変更することも無いので、
こういったものは最初からコンパイルした状態にしておきます。
これをライブラリ化と言い、VCではコンパイル済みのコードとしてlibファイルを使用することが出来ます。
※ちなみにgcc系のコンパイラではaファイルとなり、これはVCとは互換性が無いためリンク出来ません
Direct3D9も同じく実際のコードはlibファイルにlibファイルにまとめられているので、
使用するlibをプロジェクトに登録して関数とリンク出来るようにしなければなりません。
Direct3D9に必要なライブラリは以下の3つです。
これらのライブラリをプロジェクトに登録するには2つの方法があります。
1つ目の方法はプロジェクトのプロパティとして追加する方法です。
プロジェクトのプロパティを開き「リンカー」内の「入力」を選択、右側の追加の依存ファイルに上記のライブラリを追加します。
なお、既存のライブラリの最後に記述するのではなく、右側のボタンを押して追加ダイアログで行わなければなりません。
既存のライブラリ文字列はデフォルトでグローバル設定にあるWinAPI用のライブラリが指定されており、
これを消去してしまうとWindows関連の関数のリンクに全て失敗することになり、
Direct3D以前にそもそもビルドがうまく通らないといったことになります。
追加ダイアログにて設定を行えば、登録したライブラリの後にグローバル設定のライブラリが引き継がれるようになるため、
正しくビルドを行うことが出来ます。
※複数のライブラリを追加するには1行に1つずつ記述します
多少面倒ですがこの設定を構成とプラットフォームごとに行います。
2つ目の方法は、以下のようにソースコード内にライブラリをリンクするための#pragmaを記述します。
#include "CWindow.h" // クラス定義ヘッダ #include <d3d9.h> #include <d3dx9.h> #pragma comment(lib, "d3d9.lib") #pragma comment(lib, "d3dx9.lib") #pragma comment(lib, "dxguid.lib") int WINAPI WinMain( HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpszCmdParam,int nCmdShow ) {
この方法には少し問題があり、#pragma指定は必ずビルド時にソースもしくはヘッダとして何らかの方法で読み込まれなければなりません。
このため、例えばこの#pragmaのあるソースファイルをライブラリ化してしまったりすると、
#pragmaはlibファイルに含まれないため、結果的にライブラリの指定が無かったことになります。
ここでは例としてMain.cppに記述していますが、通常はヘッダファイルなどコードとして提供するファイルに記述しておくべきです。
上記のどちらかの方法を使いライブラリを登録したら試しにビルドを行ってみましょう。
正しくビルドが通ればライブラリの指定は問題ありませんが、
リンクエラーなどが表示される場合はプロジェクトにDirectX SDKの
インクルードとライブラリのパスが正しく設定出来ていない可能性があるので、
この場合はもう一度プロジェクトの作成から行ってみてください。
ここから実際にDirect3Dのプログラムを行っていきます。
Direct3Dは基本的に最低1つのウインドウが必要になります。
これはタイトルバーだけが存在するウィンドウやそもそも何も枠のないウィンドウなど、
どのようなウインドウでもかまいませんが、例えばフルスクリーンのゲームを作る場合は、
ウィンドウサイズをフルで使うためメニューなどが無いウインドウを使ったり、
ウィンドウモードでDirect3Dを使う場合はタイトルバーやメニュー付きで作ったりします。
Direct3Dはこのウィンドウに対して動作するという仕組みのため、
例えばウィンドウプログラムで640x480のゲームを作る場合は最低640x480のウインドウが必要となります。
なお、正確にはタイトルバーを含めて640x480ではなく、ウィンドウ内の実際に表示される部分が640x480である必要があります。
この領域のことをクライアント領域と言いますが、WinAPIにはクライアント領域のサイズから実際のウィンドウサイズを
計算してくれる便利な関数が用意されています。
※AdjustWindowRect
ここではテストとして640x480のウィンドウモードで動作するプログラムを作成してみます。
ウィンドウサンプルのソースコードには既に空のウインドウが作成されているので、
今回はこれをDirect3D用のウィンドウとして使うことにします。
ではまず始めにDirect3D9に必要なインターフェースのポインタをグローバル変数として定義します。
また、これらはついでにNULLで初期化しておきます。
#include "CWindow.h" // クラス定義ヘッダ #include <d3d9.h> #include <d3dx9.h> #pragma comment(lib, "d3d9.lib") #pragma comment(lib, "d3dx9.lib") #pragma comment(lib, "dxguid.lib") // Direct3Dオブジェクトポインタ LPDIRECT3D9 lpD3D = NULL; // Direct3D LPDIRECT3DDEVICE9 lpD3DDevice = NULL; // Direct3DDevice int WINAPI WinMain( HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpszCmdParam,int nCmdShow ) {
LPDIRECT3D9はDirect3D全体を管理するためのオブジェクト、
LPDIRECT3DDEVICE9は実際のハードウェアデバイスを表すオブジェクトになります。
Direct3D9はこの2つがメインと言っても過言ではありません。
それでは次に実際にDirect3Dのデバイスを作成するプログラムを紹介します。
int WINAPI WinMain( HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpszCmdParam,int nCmdShow ) { CWindow win; if( !win.Create(hInstance,L"TestD3D9") ) return -1; // Direct3Dオブジェクトの取得 lpD3D = Direct3DCreate9( D3D_SDK_VERSION ); if( !lpD3D ) { MessageBoxW( win.hWnd,L"Direct3Dが作成出来ませんでした",L"エラー",MB_OK|MB_ICONHAND ); return -1; } // Direct3Dデバイスの設定をセット D3DPRESENT_PARAMETERS param; ZeroMemory( ¶m,sizeof(param) ); param.Windowed = TRUE; // ウィンドウモードか param.SwapEffect = D3DSWAPEFFECT_DISCARD; // 垂直同期でフリップ param.BackBufferCount = 1; // バックバッファの数 param.BackBufferWidth = 640; // 画面の幅 param.BackBufferHeight = 480; // 画面の高さ param.FullScreen_RefreshRateInHz = 0; // リフレッシュレート(0=デフォルトを使用) param.EnableAutoDepthStencil = TRUE; // 自動的にZバッファとステンシルバッファを作成する param.AutoDepthStencilFormat = D3DFMT_D24S8; // Zバッファとステンシルバッファのフォーマット // 現在のディスプレイのバックバッファフォーマットを取得 D3DDISPLAYMODE dm; HRESULT ret = lpD3D->GetAdapterDisplayMode( D3DADAPTER_DEFAULT,&dm ); // DEFAULT指定でプライマリアダプタを選択 if( FAILED(ret) ) { MessageBoxW( win.hWnd,L"Direct3Dが作成出来ませんでした",L"エラー",MB_OK|MB_ICONHAND ); return -1; } param.BackBufferFormat = dm.Format; // バックバッファのフォーマットをコピー // Direct3DDeviceを作成 ret = lpD3D->CreateDevice( D3DADAPTER_DEFAULT, // デフォルトの3Dデバイスを使用 D3DDEVTYPE_HAL, // ハードウェアを使用する win.hWnd, // ウインドウのハンドル D3DCREATE_HARDWARE_VERTEXPROCESSING, // ハードウェア頂点処理を行う(VGAがDX8に対応していない場合はSOFTWAREを設定) ¶m, // 上記で設定した構造体のポインタ &lpD3DDevice ); // 作成されたデバイスを受け取る変数のポインタ if( FAILED(ret) ) { // エラーなら終了 lpD3D->Release(); MessageBoxW( win.hWnd,L"Direct3DDeviceが作成出来ませんでした",L"エラー",MB_OK | MB_ICONHAND); return -1; } // メインループ MSG msg; while(1) { if( PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) ) { // プログラム内のメッセージ処理 if( msg.message==WM_QUIT ) break; // ALT+F4が押されたらメインを抜ける TranslateMessage(&msg); DispatchMessage(&msg); } Sleep(15); // CPUへのウエイト } // 開放処理 lpD3DDevice->Release(); lpD3D->Release(); win.Delete(); return 0; }
赤字部分が今回追加した内容です。
まず始めにDirect3D9のオブジェクトを作成する関数Direct3DCreate9()からLPDIRECT3D9のオブジェクトを取得します。
もしここでNULLが返った場合、Direct3D9に対応していないかDirectXのランタイムがインストールされていないということになるので、
その場合はエラーメッセージを表示して終了します。
エラー表示にはWinAPIのダイアログボックスを表示する関数MessageBoxWを使用します。
これを使うと簡単にダイアログを表示出来るため、起動時のエラーなどは全てこれで表示すると良いでしょう。
※関数の最後にWがついているものはUNICODE版となり、文字列は全てLで囲ったワイド文字列としなければなりません。
ちなみに通常のSJISなどのマルチバイトを使う場合は最後をAにします。
次に、Direct3Dデバイスを作成するために必要な情報をD3DPRESENT_PARAMETERS構造体にセットしています。
まずこの構造体を全て0で初期化したあとに値をセットしていきます。
この設定によりDirect3Dデバイスをどのように扱うかを指定しますが、
そのデバイスで使用できない設定を指定するとデバイスの構築に失敗してしまいます。
ここではウィンドウモード用の設定のため、バックバッファのフォーマットを現在のディスプレイアダプタから取得して設定しています。
この設定が現在のディスプレイと異なる場合、ウィンドウモードでの構築に失敗します。
以下はこの構造体の補足となります。
BackBufferCount | 通常描画を行う際には一度裏画面に全部を描画して、 最後に一気に表に全画面をコピーすることでチラツキを防ぎますが、 この裏画面のことをバックバッファと言います。 そして、この裏画面を何枚用意するかを指定しますが、 通常は裏画面は1つあれば良いのでここでは1を指定しています。 描画の安定をさらに求める場合にこれ以上を指定するということもありますが、 その分フレームが遅れてしまうため、特に理由が無い限りはバックバッファは1枚で十分です。 |
BackBufferWidth BackBufferHeight |
Direct3Dが描画に使用する画面の幅と高さを指定します。 通常は現在のウィンドウサイズを設定しますが、他のサイズを指定した場合は現在のウィンドウ全体に スケーリングされて表示させることが出来ます。 例えばウィンドウサイズを640x480としDirect3Dを320x240に設定した場合は、 2倍に拡大された状態でウィンドウ内に画像が描画されます。 このサイズはグラフィックカードと使用しているモニタにより指定可能な値が決まっています。 ここではとりあえずどのカードでも対応している640x480を指定しています。 |
FullScreen_RefreshRateInHz | フルスクリーン時の画面のリフレッシュレート(フレームレート)を指定します。 ウィンドウモードであればこの設定は無視されます。 これはグラフィックカードやドライバにより指定出来ないことがあるため、 通常はハードウェアのデフォルトを使用するということで0を指定します。 ※昔のGeForceにて60を指定しても起動出来ないことがありました |
BackBufferFormat | バックバッファの画像フォーマットを指定します。 フルスクリーンの場合、16ビットで起動したい場合はD3DFMT_R5G6B5を、 32ビットで起動したい場合はD3DFMT_X8R8G8B8を指定します。 一応24ビットでの起動にはD3DFMT_R8G8B8を指定したりしますが、 これらはグラフィックボードが対応していなければなりません。 ウィンドウモードの場合は現在のディスプレイ状態と一致している必要があります。 |
EnableAutoDepthStencil | ZバッファをDirect3Dが自動的に管理させるためにここではTRUEを設定します。 ここをTRUEにした場合は下のAutoDepthStencilFormatを正しく設定する必要があります。 |
AutoDepthStencilFormat | Zバッファとステンシルバッファのフォーマットを指定します。 通常なら16ビットのZバッファをサポートしているのでD3DFMT_D16を指定します。 さらにステンシルバッファも使うならD3DFMT_D24S8のようにSのついたフォーマットを選びます。 これもハードウェアが対応しているフォーマットである必要があります。 ※ここでは現状Zバッファもステンシルバッファも使わないので適当に設定しています |
これらの設定をまとめた構造体をデバイス作成関数のCreateDevice()に渡します。
以下はCreateDevice()に渡している引数について簡単に説明しています。
D3DADAPTER_DEFAULT | 現在メイン画面として設定されているモニタのグラフィックカードを デフォルトの3Dデバイスとして作成する |
D3DDEVTYPE_HAL | グラフィックボードのハードウェア機能を使う |
win.hWnd | ウインドウのハンドル |
D3DCREATE_HARDWARE_VERTEXPROCESSING | ハードウェアで頂点処理を行う |
¶m | 設定したD3DPRESENT_PARAMETERS構造体のポインタ |
&lpD3DDevice | 作成出来た3Dデバイスのオブジェクトが代入される変数のポインタ |
正しく3Dデバイスが作成された場合はlpD3DDeviceにそのポインタが返ります。
もし作成に失敗した場合は構築したオブジェクトを開放してから終了させています。
これでDirect3Dの構築は完了ですが、 重要なのはこのlpD3DDeviceが構築したグラフィックボードを
直接制御するためのインターフェースということになり、そのため実際の描画などの呼び出しは
全てこのlpD3DDeviceを経由して行うことになります。
次のwhileはメインループとなり、ウインドウメッセージを取ってそれがWM_QUIT、
つまりALT+F4で強制終了されたことを検出した時は、このループを抜けるようになっています。
ループを抜けたらDirect3Dを開放して終了となりますが、開放する順番には一応規約があり、
基本的にはあとに作成したものを先に開放する必要があります。
ここではDirect3D9オブジェクトの作成のあとにDirect3DDevice9のオブジェクトを作成しているため、
開放時には先にDirect3DDevice9を最後にDirect3D9を開放します。
これでDirect3Dの起動と終了の処理が出来たのでビルドして実行してみましょう。
ただしまだ描画処理などは入っていないので、今までと同じ白いウィンドウが表示されます。
このままではまだDirect3Dが本当に動作しているのか分からないので、
次から実際に描画を行うための処理を順番に実装していきます。
Direct3Dを使ったゲームというのは、基本的に1フレームごとに全ての画像を再描画することで画面を構成する方法となります。
対してWindowsのグラフィック機能(GDI)を使ったソフトでは、必要な時に更新された領域だけを
再描画するといったイベントが発生するので、これに合わせて必要最低限の部分だけの描画を行いますが、
Direct3Dでは常に毎フレーム全画面を書き直す必要があるため、
負荷が高くノートPCなどではバッテリーの減りが早くなったりします。
それではDirect3Dの描画に必ず必要な処理について説明します。
Direct3Dではバックバッファと言うものがあって、先にこちらに全ての描画を行ったあと、
その画面を一瞬で表側に切り替えることで画面を更新させます。
これをスワップまたはフリップといい、プログラム側で任意のタイミングで行うことが出来ます。
この一連の流れをメインループで毎回行うことで画面が更新されるわけですが、
以下はこの流れを最低限のプログラムで実装した例です。
// メインループ MSG msg; while(1) { if( PeekMessage(&msg,NULL,0,0,PM_REMOVE) ) { // プログラム内のメッセージ処理 if( msg.message==WM_QUIT ) break; // ALT+F4が押されたらメインを抜ける TranslateMessage(&msg); DispatchMessage(&msg); } // バックバッファのクリア lpD3DDevice->Clear( 0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER|D3DCLEAR_STENCIL, D3DCOLOR_XRGB(0,0,0), 1.0f, 0 ); // シーンの開始 lpD3DDevice->BeginScene(); // ここで描画処理を行う : // シーンの終了 lpD3DDevice->EndScene(); // フリップして実際に画面に反映 lpD3DDevice->Present( NULL,NULL,NULL,NULL ); Sleep(15); // CPUへのウエイト }
まず、フレームの最初に行うのがバックバッファの消去です。
Clear()メソッドを呼び出すと現在のバックバッファを指定した引数に合わせてクリアします。
ここでは画面全体を真っ黒、Zバッファを1.0f、ステンシルバッファを0でクリアしています。
次に、このバックバッファに描画を開始することを宣言するためにBeginScene()を呼び出します。
これを呼び出したあとにDirect3Dの描画メソッドなどが使用出来るようになりますが、
もしBeginScene()を呼び出さずに描画メソッドを呼び出してしまった場合は、
これらの描画メソッドは全てエラーを返します。
描画処理が一通り終わったら今度は描画の終了を宣言するためにEndScene()を呼び出します。
これでバックバッファへの描画は終了した状態となりますが、この段階ではまだ画面に繁栄されていない状態です。
最後にこのバックバッファを反映させるためにPresent()を呼び出します。
これで描画したものが実際に画面に表示されます。
なおPresent()には引数がありますが基本的に全てNULLでOKです。
上記のコードをビルドして実行すると真っ黒の画面が表示されます。
これでDirect3Dを使って画面を表示するための準備が整ったことになるので、
次は実際に描画を行うための実装をしていきます。
ちなみに実はClear()はシーンの開始関係なく呼び出すことが出来ます。
例えばシーンを開始した後にZバッファだけをもう一度クリアしたりといったことが可能です。
ここではテクスチャをロードする部分を作成してみましょう。
とりあえず以下のような画像をテクスチャとしてロードしてみます。
※上の画像はpng画像ですがサンプルプログラムの方はbmp画像となります (D3DXは実はpngも読み込むことが出来ますが、ここでは説明を簡単にするためbmpとtgaのみを扱います) |
まずはテクスチャを管理するためのテクスチャオブジェクトを定義します。
// Direct3Dオブジェクトポインタ LPDIRECT3D9 lpD3D = NULL; // Direct3D LPDIRECT3DDEVICE9 lpD3DDevice = NULL; // Direct3DDevice LPDIRECT3DTEXTURE9 lpTex = NULL; // Direct3DTexture
ここではテクスチャは1枚だけ使うためLPDIRECT3DTEXTURE9は1つだけ定義していますが、
Direct3Dではテクスチャを何枚もロードすることが出来るため、その場合はLPDIRECT3DTEXTURE9をその分だけ定義します。
次に画像をロードして、実際にテクスチャオブジェクトを作成します。
int WINAPI WinMain( HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpszCmdParam,int nCmdShow ) { // Direct3DDeviceを作成 (略) // テクスチャをファイルから作成 ret = D3DXCreateTextureFromFileExA( lpD3DDevice, // Direct3DDeviceのポインタ "BM.BMP", // ファイル名 D3DX_DEFAULT, // テクスチャの幅(画像から取得させる) D3DX_DEFAULT, // テクスチャの高さ(画像から取得させる) 1, // ミップマップテクスチャを作成するレベル(1だとこのテクスチャのみ作成される) 0, // レンダリングターゲットとはしない D3DFMT_UNKNOWN, // テクスチャのフォーマットを画像から取得 D3DPOOL_MANAGED, // テクスチャが消失しても自動的に退避させておく D3DX_FILTER_POINT, // テクスチャの描画時に一番近いドットを使用する D3DX_FILTER_NONE, // ミップマップテクスチャのフィルタを使用しない 0, // カラーキーを使用しない(アルファがあればそれを使う) NULL, // 作成されたテクスチャの情報を返さなくてもよい場合はNULL NULL, // パレット情報を返さなくてもよい場合はNULL &lpTex ); // 作成されたテクスチャを受け取る変数のポインタ if( FAILED(ret) ) { // 作成エラー lpD3DDevice->Release(); lpD3D->Release(); MessageBoxW( win.hWnd,L"テクスチャが作成出来ませんでした",L"エラーだにょ",MB_OK|MB_ICONHAND ); return -1; } // メインループ MSG msg; while(1) {
テクスチャをファイルから作成する関数がD3DXCreateTextureFromFileExA()です。
例のごとく最後にAと付いた関数はマルチバイト用の関数で、ここではファイル名がSJISのためA付きを使用していますが、
UNICODEのファイル名を使う場合はここをWに変える必要があります。
この関数の最後の引数には、先ほど定義したテクスチャオブジェクトのポインタを渡します。
ロードが成功するとここにテクスチャオブジェクトが渡されます。
それ以外の引数の詳細は上記のコードにあるのでそれを確認してください。
基本的には2D用の画像をロードするのであれば上記コードをそのまま使用すれば問題ありません。
この関数でテクスチャの作成に失敗した場合は、例によって今まで作成したオブジェクトを
削除してからプログラムを終了させています。
テクスチャのロードはたったこれだけで完了ですが、ついでに忘れないように
プログラムの終了時にこのテクスチャを開放する処理も入れておきます。
// 開放処理 lpTex->Release(); lpD3DDevice->Release(); lpD3D->Release();
当然ですがここもあとから作成したオブジェクトを先に開放しています。
これでテクスチャのロード処理は完了なのでビルドして実行してみましょう。
なお、まだこのテクスチャを描画する処理は一切入っていないので画面には何も表示されません。
次は板ポリゴンを作って実際にこのテクスチャを表示する処理を実装します。
ここではDirect3Dで板ポリを表示するために必要な頂点の定義について説明します。
板ポリについてはこちらを参照してください。
Direct3D8以降から頂点を独自で定義できるようになりました。
独自というのはDirectX7では決められた値をもった固定の構造体で、
これらは使わない情報が含まれることがあり、常にその分のメモリを必要としていましたが、
Direct3D8以降からはこのムダを省くため、必要な情報のみを持たせた構造体を、
自分で作って使用する形となりました。
しかし、このためDirect3D7に定義されていた構造体も全て定義されなくなったため、
以前と同じ頂点情報を扱う場合でも、その構造体とまったく同じ構造体を自分で作る必要があります。
さて頂点の構造体について説明するため、まずは以下の構造体を見てください。
// 頂点構造体 typedef struct _D3DTLVERTEX { float x,y,z; // 頂点のXYZ座標 float rhw; // 描画時にXYZから割られる数(小難しい話なのでここは常に1となると覚えよう) DWORD color; // 頂点の色(32bitアルファ値あり) float tu, tv; // 頂点に割り当てるテクスチャのUV値 } D3DTLVERTEX, *LPD3DTLVERTEX;
これが1つの頂点に含まれる情報となりますが、これ以外にも法線を追加したり、
テクスチャは2枚以上合成することが出来るためUV値をさらに増やしたりなど、
頂点構造体に自由に情報を追加することが出来るようになっています。
この構造体について簡単に説明すると、まず頂点に必要なのは当然3D空間上で位置を表すXYZの座標値があります。
次にrhwと言うものがありますが、これは3D空間からスクリーン座標に変換する処理をせず、
直接スクリーン座標として指定する場合に指定するものです。
これによりXYZ(Zは通常は0)で指定した座標が実際のスクリーン画面上でのXY座標そのままの位置で描画されるようになります。
※標準ではZ値が有効なため、同じZ値の画像を重ねると表示されないといった問題が発生するため、
通常は2D画像を表示する場合はZ値のチェックを行わないようにデバイスに指示をしておきます
次のcolorは頂点の色となります。
この色rはARGB形式として各8ビットずつ、つまり0xFFFFFFFFという形で定義を行うことで、
ポリゴンを表示する際にこの色が使われて表示されることになります。
また頂点カラーにはアルファ値、つまり透明度も指定出来るので、
例えば0x80FFFFFFとすると白の50%の半透明とすることも出来ます。
ちなみに頂点1つ毎に色が指定出来るので、各頂点にそれぞれ色をつけると下のようになかなか面白い効果になります。
※この例では分かりやすいように点を大きく描いていますが、実際にはこのような点は無くただの三角形となります
最後にtuとtvでその頂点が参照するテクスチャのUV値を指定します。(UVについてはこちら)
描画時には頂点から頂点までのUVに応じた色をテクスチャから抽出することで、
最終的に下のように絵が表示されることになり、これを一般的にテクスチャを貼ると言います。
ちなみに今回の頂点構造体ではUV値とcolorの2種類の色情報を持っていることになりますが、
これを実際に表示してみると以下のようにテクスチャの色と頂点カラーが合成されて表示されます。
※このような合成を行うにはハードウェアに合成方法をあらかじめ設定しておく必要があります
これにさらに頂点ごとに半透明も指定出来るため、かなり面白い効果を出すことも出来ます。
ちなみに上記の構造体の名前にTLと付けていますが、
これは「トランスフォーム」「ライティング」の入ったという意味で付けています。
トランスフォームとは頂点を3D上に変換することを、ライティングとは
ポリゴンに光が当たった時の色の計算をするということですが、
この構造体はこれらをすでに行ってあるという意味で付けています。
※もともとはDirect3D7で使われていた名残です
では頂点の定義方法が分かったところでこの構造体をMain.cppに追加してみます。
ついでにこの構造体がどのような情報を持っているのかというフラグも定数として定義しておきます。
#include "CWindow.h" // クラス定義ヘッダ #include <d3d9.h> #include <d3dx9.h> #pragma comment(lib, "d3d9.lib") #pragma comment(lib, "d3dx9.lib") #pragma comment(lib, "dxguid.lib") // 頂点構造体 typedef struct _D3DTLVERTEX { float x,y,z; // 頂点のXYZ座標 float rhw; // 描画時にXYZから割られる数(小難しい話なのでここは常に1となると覚えよう) DWORD color; // 頂点の色(32bitアルファ値あり) float tu, tv; // 頂点に割り当てるテクスチャのUV値 } D3DTLVERTEX, *LPD3DTLVERTEX; // 頂点の情報 #define D3DFVF_TLVERTEX (D3DFVF_XYZRHW|D3DFVF_DIFFUSE|D3DFVF_TEX1)
D3DFVF_TLVERTEX定数とは、定義した頂点構造体の中にどんな情報が入っているのかをまとめたものです。
このフラグはDirect3D9に定義されているD3DFVF_******という定数を組み合わせるのですが、
よく見るとこれらの定数と構造体の中身が一致しているのが分かると思います。
細かく見ると まずD3DFVF_DIFFUSE(ディフューズ)というのは反射色、つまり個の頂点の色という意味で、
次のD3DFVF_TEX1はテクスチャが1枚存在しそのUV値が存在しているという意味になります。
例えばもしテクスチャを2枚合成する必要がある場合はD3DFVF_TEX2を指定しますが、
D3DFVF_TEX?のフラグだけは組み合わせではなくどれか1つを設定しなければなりません。
またテクスチャを使わない場合はD3DFVF_TEX0とフラグを定義することになりますが、
実はこれは0x000と定義されているため、つまりテクスチャが無いのであれば省略が可能です。
これ以外にも構造体に情報があれば、それぞれDirect3Dで定義されたフラグを全て繋げるだけです。
この定数が必要なのはDirect3Dで実際に頂点を描画する場合、頂点にどのような情報が含まれているのかを
Direct3Dに教える必要があるためで、その時に毎回1つずつフラグを指定するのが面倒なのと、
構造体とそれに合った定数という形で名前を統一しておくことで、
表示の際に今回使っている頂点構造体には何が入っていたかを確認せずに済みます。
また常に定義した定数を使っているのであれば、あとで構造体を修正した場合にその定数も一緒に修正するだけで、
プログラムを直す手間もかからないといった利点があります。
ちなみに頂点構造体に定義出来る変数は型と定義する順番が決まっています。
例えば位置情報のx,y,zはfloat型で必ず最初に存在しなければなりません。
次にここでは2D用なのでfloat型のrhwを定義していますが、3D用で法線が必要ならfloat型のnx,ny,nzと定義します。
その次はカラー値、そして最後にuv値という形で、必ずこれらの順番を守って定義しなければなりません。
頂点の構造体が定義できたので実際に板ポリ用の頂点定義を行ってみましょう。
板ポリゴンは以下のように4つの頂点で定義出来ることを以前説明しました。
つまり四角形を描画するには頂点は4つ定義すればよいことになりますが、
これを実際にコード化すると以下のようになります。
// Direct3Dオブジェクトポインタ LPDIRECT3D9 lpD3D = NULL; // Direct3D LPDIRECT3DDEVICE9 lpD3DDevice = NULL; // Direct3DDevice LPDIRECT3DTEXTURE9 lpTex = NULL; // Direct3DTexture // 頂点データ D3DTLVERTEX v[4]; // 四角の4頂点分 int WINAPI WinMain( HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpszCmdParam,int nCmdShow ) {
※ここでは単純にグローバル変数として定義しています
次にこの構造体を初期化しますがその前に以下の図を見てください。
サンプルでは上のように画面中央に描画するという仕様で初期化します。
そのためここでは頂点配列を以下のように割り当てることにします。
v[0] | 1番目の頂点 |
---|---|
v[1] | 2番目の頂点 |
v[2] | 3番目の頂点 |
v[3] | 4番目の頂点 |
この頂点1つ1つに値を入れることで四角を作りますが実はこれは結構面倒な作業です。
以下は1つの頂点ごとに値を設定する例ですが、ここでは最適化などを考えずに1つずつ値を入れてみます。
// 頂点の初期化 ZeroMemory( &v,sizeof(v) ); v[0].x = 160.0f; v[0].y = 120.0f; v[0].z = 0.0f; v[0].rhw = 1.0f; v[0].color = 0xFFFFFFFF; v[0].tu = 0.0f; v[0].tv = 0.0f; v[1].x = 480.0f; v[1].y = 120.0f; v[1].z = 0.0f; v[1].rhw = 1.0f; v[1].color = 0xFFFFFFFF; v[1].tu = 1.0f; v[1].tv = 0.0f; v[2].x = 160.0f; v[2].y = 360.0f; v[2].z = 0.0f; v[2].rhw = 1.0f; v[2].color = 0xFFFFFFFF; v[2].tu = 0.0f; v[2].tv = 1.0f; v[3].x = 480.0f; v[3].y = 360.0f; v[3].z = 0.0f; v[3].rhw = 1.0f; v[3].color = 0xFFFFFFFF; v[3].tu = 1.0f; v[3].tv = 1.0f;
ちなみにこのサンプルではテクスチャ画像全体がこの四角形内に全て表示されるように定義しています。
テクスチャ自体は512x512の正方形ですが、頂点定義は正方形ではないため以下のように少し潰れたように表示されます。
最後に上記の構造体の初期化で気をつけなければならないこととして、
z値には0、rhwには1、さらに頂点カラーには白、アルファ値として100%(不透明)を指定しています。
上の方で説明したとおり、テクスチャの色と頂点カラーが合成される場合、
頂点カラーを白(RGBとも最大値)とすることで1倍を指定したことになり、
これによりテクスチャの色(RGB)を1倍することになるので、
結果的にテクスチャの色がそのまま表示されることになります。
もっと詳しく説明するとDirect3Dではカラー値0xFFは内部的に1.0として扱います。
つまり合成ということは掛け算を行うことなので計算式は色×1.0となるため、
色×255では無いことに気をつける必要があります。
ちなみにテクスチャの色も実際には0xFFを1.0として扱います。
同じようにUVも0~1として扱っていますが、このように3Dでは内部では常に
0~1で計算するような仕組みになっており、これを正規化した値と言います。
ここまでくればあとは画面に描画するだけです。
描画処理はメインループ内で毎回行いますが、上で説明した通り描画関数は
シーンを開始してから終了するまでの間でしか呼び出すことは出来ません。
以下はシーンの開始と終了の間で描画関数を呼び出すプログラムとなります。
// バックバッファのクリア lpD3DDevice->Clear( 0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER|D3DCLEAR_STENCIL, D3DCOLOR_XRGB(0,0,0), 1.0f, 0 ); // シーンの開始 lpD3DDevice->BeginScene(); // テクスチャのセット lpD3DDevice->SetTexture( 0,lpTex ); // 使用する頂点フォーマットのセット lpD3DDevice->SetFVF( D3DFVF_TLVERTEX ); // 頂点リストの描画 lpD3DDevice->DrawPrimitiveUP( D3DPT_TRIANGLESTRIP, 2, v, sizeof(D3DTLVERTEX) ); // シーンの終了 lpD3DDevice->EndScene(); // フリップして実際に画面に反映 lpD3DDevice->Present( NULL,NULL,NULL,NULL );
BeginScene()を呼び出すと描画関数が使用できるようになります。
まずはSetTexture()関数を使ってロードしておいたテクスチャをデバイスにセットします。
引数にはインデックスとテクスチャオブジェクトを指定しますが、
ここでは1枚目のテクスチャということでインデックスには0を指定しています。
2枚以上テクスチャを使う場合はこのインデックスを変えてデバイスにセットします。
設定できるテクスチャの最大数は使用しているグラフィックカードによって変わりますが、
2Dで使用する場合は基本的に1枚しか使用しないので、インデックスは常に0で問題ありません。
※今どきテクスチャが1枚も貼れないグラフィックカードは存在しないので、
特にエラーチェックなどは必要ありません
ちなみに描画時にテクスチャを貼りたくない場合は、
テクスチャオブジェクトにNULLを指定することで解除させることが出来ます。
またこの場合は頂点カラーだけの単純な四角形が表示されます。
裏技として頂点が本当に描画されているか確認したい時は、あえてテクスチャを解除するのも1つの手です。
例えばテクスチャのUVが間違って設定されており、たまたまテクスチャの透明部分が全頂点に選択されていた場合、
何も表示されないように見えるためです。
なお使い終わったテクスチャを削除したい場合、まだデバイスに指定されているのであれば先に解除しておく必要があります。
Direct3Dは最後に呼び出した関数の状態を保持しているため、一度テクスチャを設定してしまえば、
他のテクスチャを設定するかテクスチャを解除するまでそのテクスチャが永遠と使われてしまうため、
逆に意図しない箇所で以前のテクスチャが表示されてしまい画面がバグったように見えてしまうことがあります。
次にSetFVF()関数で今回描画に使用する頂点フォーマットのフラグをセットをしています。
これにより頂点データのフォーマットがDirect3Dデバイスに記憶され、
以降の描画関数に渡される頂点データの中身がそのフォーマットであるとDirect3Dが理解し、
それに合わせて描画を行うようになります。
もしこのフラグが間違っていると、頂点データ配列の位置が狂ってしまうため正しく描画が行えなくなります。
最後に自分で用意した頂点データを描画するためにDrawPrimitiveUP()関数を呼び出します。
この関数は先ほど設定した頂点フォーマットに合わせて頂点データを解析し、
これを指定した方法で画面に表示させます。
ここでは4頂点を使って3角形を2個描画するという仕様のD3DPT_TRIANGLESTRIP定数を指定しています。
また表示したい面の数が必要となるのでここでは2を引数として渡しています。
あとの引数は実際に描画させる頂点のデータと、この頂点データの1つ分の構造体のサイズとなります。
これで四角形1個の描画が完了となりEndScene()を呼び出してシーンを終了させます。
最後にこのプログラムを実行すると以下のような画面が表示されます。
上記のプログラムをすべて含んだサンプルを以下からダウンロード出来ます。
使用しているVCのバージョンに合わせて2種類のプロジェクトを用意したので、
該当する方をダウンロードしてください。
VisualStudio2010のプロジェクト ※VC2012、VC2013はこちら |
TestD3D9_vc2010.zip |
VisualStudio2015のプロジェクト | TestD3D9_vc2015.zip |