Microsoft® Windows®ベースのDirect3D®保持モードアプリケーションを作成するためには、異なる2つの環境を設定しなくてはならない。1つは、デバイス、ビューポート、色解像度といったWindows環境であり、もう1つは、モデル、テクスチャ、照明、位置といった仮想環境である。このチュートリアルでは、簡単な保持モードアプリケーションの全コードを紹介している。次の図は、動作中のアニメーションのフレームの1つである。
このチュートリアルは、以下のセクションに分けられている。
このチュートリアルには、実際のDirect3D保持モードアプリケーションのすべてのコードが含まれている。以下の条件を満たすと、チュートリアルからこのコードを単一の.cファイルにコピーし、コンパイルして実行することができる。
- Direct3Dヘッダファイル(D3d.h、D3drm.hなど)をインクルードパスに設定していること。
- Winmm.lib、D3drm.lib、およびDdraw.lib静的ライブラリとリンクしていること。
- 使用するコンパイラが、Microsoft® DirectX® Software Development KitのMediaディレクトリ中のSphere3.xファイルを発見できること。
- Tutor.bmpファイルを供給すること。
このビットマップのピクセルサイズは、2の累乗でなければならないことに注意する。たとえば、16×16ピクセルビットマップ、128×1024ピクセルビットマップ、512×256ピクセルビットマップなどである。
このサンプルの中で、3D効果を受け持つコードの大半は、別個の関数に分割されているので、ユーザは、サンプルの一部を徐々に修正して実験することができる。Direct3Dのさらに複雑な機能を実装するには、DirectX SDKのサンプルを調べる必要がある。
以下の項目は、単純なDirect3D保持モードアプリケーションの開発全般に適用できる事項をいくつか説明するものである。
このチュートリアルには、Helworld.cのサンプルコードが含まれている。そのコードは、球を生成して、その球にテクスチャを適用し、ウィンドウの中で回転させる。これは、このアプリケーションを作成するのに必要な、ただ1つのCソースファイルである。他に必要なファイルは、Sphere3.x、DirectX SDKのMediaディレクトリの中で配布されている1つのメッシュファイル、ユーザが供給するビットマップのTutor.bmpだけである(ユーザのインクルードパス中にDirect3Dヘッダファイルを設定していること、Winmm.lib、D3drm.lib、およびDdraw.lib静的ライブラリにリンクしていることが前提であることに注意する)。
このチュートリアルは、DirectX SDKの一部であるGlobeサンプルを簡単にしたものである。Globeサンプルは、SDKのすべてのDirect3D保持モードサンプルと同様に、Rmmain.cppファイルといくつかのヘッダファイルをインクルードすることを必要とする。Helworld.cでは、Rmmain.cppの使用可能部分をC++からCに変換してソースコードに取り込んである。
このチュートリアルで示すコードは、実用コードと間違えてはいけない。プログラムと可能なユーザの対話型操作は、プログラムを起動、停止することと、実行中にウィンドウを最小化することに限定されている。コードを分かりやすくするため、エラーチェックの大半は除いてある。このサンプルの目的は、煩雑なことを最小限にして出力を行う、"Hello, world!"を画面上に表示する初歩のプログラムの目的に似ている。
ほとんどすべてのDirect3Dアプリケーションは、DirectDraw®を用いてスクリーンにグラフィックスを表示する。これらのアプリケーションは、DirectDrawの排他(フルスクリーン)モードまたは非排他(ウィンドウ)モードのいずれかを使用する。このドキュメントのコードはウィンドウモードを使っている。フルスクリーンモードはパフォーマンス上の利点や快適さを提供するが、ウィンドウモードで書かれたコードのほうがデバッグが簡単である。ほとんどの開発者はウィンドウモードでコードを書き、デバッグ終了後、フルスクリーンモードに移行させる。
以下は、Helworld.cサンプルの最初の数行である。このアプリケーションをビルドするのに他のCソースファイルは必要ないが、ファイルの拡張子は.cppではなく .cにする。Cコンパイラは、拡張子.cppの付いたファイルをC++のコードとみなし、lpVtbl ポインタと追加パラメータのthisを扱う。詳しくは、DirectX SDKドキュメントの「CからのCOMオブジェクトへのアクセス」を参照すること。このチュートリアルはCで書かれているため、明示的にlpVtbl ポインタをインクルードし、初期thisパラメータを追加している。このファイルの拡張子を.cppにすると、コンパイラは多数のコンパイルエラーを生成する。
INITGUIDは、他のインクルードやマクロ定義よりも前に定義されていなくてはならない。これは、DirectXを初めて操作する開発者が間違いやすい、大切なポイントである。
///////////////////////////////////////////////////////////////////// // // Copyright (C) 1996 Microsoft Corporation. All Rights Reserved. // // File: Helworld.c // // "Globe" SDKサンプルに基づいた、 // 簡単なDirect3D保持モードのサンプル。 // ///////////////////////////////////////////////////////////////////// #define INITGUID // 他のマクロ定義やインクルードの前に定義しなくてはならない #include <windows.h> #include <malloc.h> // memsetの呼び出しに必要 #include <d3drmwin.h> #define MAX_DRIVERS 5 // D3Dドライバの最大数 // グローバル変数 LPDIRECT3DRM lpD3DRM; // Direct3DRMオブジェクト LPDIRECTDRAWCLIPPER lpDDClipper;// DirectDrawClipperオブジェクト struct _myglobs { LPDIRECT3DRMDEVICE dev; // Direct3DRMデバイス LPDIRECT3DRMVIEWPORT view; // シーンが表示されるDirect3DRMビューポート LPDIRECT3DRMFRAME scene; // 他のオブジェクトが配置されるマスターフレーム LPDIRECT3DRMFRAME camera; // ユーザのPOVを示すフレーム GUID DriverGUID[MAX_DRIVERS]; // 有効なD3DドライバのGUID char DriverName[MAX_DRIVERS][50]; // 有効なD3Dドライバの名前 int NumDrivers; // 有効なD3Dドライバの数 int CurrDriver; // 現在使われているD3Dドライバの数 BOOL bQuit; // プログラムが終了しようとしている BOOL bInitialized; // すべてのD3DRMオブジェクトが初期化された BOOL bMinimized; // ウィンドウが最小化された int BPP; // 現在のディスプレイモードのビット深度 } myglobs; // 関数のプロトタイプ static BOOL InitApp(HINSTANCE, int); long FAR PASCAL WindowProc(HWND, UINT, WPARAM, LPARAM); static BOOL EnumDrivers(HWND win); static HRESULT WINAPI enumDeviceFunc(LPGUID lpGuid, LPSTR lpDeviceDescription, LPSTR lpDeviceName, LPD3DDEVICEDESC lpHWDesc, LPD3DDEVICEDESC lpHELDesc, LPVOID lpContext); static DWORD BPPToDDBD(int bpp); static BOOL CreateDevAndView(LPDIRECTDRAWCLIPPER lpDDClipper, int driver, int width, int height); static BOOL SetRenderState(void); static BOOL RenderLoop(void); static BOOL MyScene(LPDIRECT3DRMDEVICE dev, LPDIRECT3DRMVIEWPORT view, LPDIRECT3DRMFRAME scene, LPDIRECT3DRMFRAME camera); void MakeMyFrames(LPDIRECT3DRMFRAME lpScene, LPDIRECT3DRMFRAME lpCamera, LPDIRECT3DRMFRAME * lplpLightFrame1, LPDIRECT3DRMFRAME * lplpWorld_frame); void MakeMyLights(LPDIRECT3DRMFRAME lpScene, LPDIRECT3DRMFRAME lpCamera, LPDIRECT3DRMFRAME lpLightFrame1, LPDIRECT3DRMLIGHT * lplpLight1, LPDIRECT3DRMLIGHT * lplpLight2); void SetMyPositions(LPDIRECT3DRMFRAME lpScene, LPDIRECT3DRMFRAME lpCamera, LPDIRECT3DRMFRAME lpLightFrame1, LPDIRECT3DRMFRAME lpWorld_frame); void MakeMyMesh(LPDIRECT3DRMMESHBUILDER * lplpSphere3_builder); void MakeMyWrap(LPDIRECT3DRMMESHBUILDER sphere3_builder, LPDIRECT3DRMWRAP * lpWrap); void AddMyTexture(LPDIRECT3DRMMESHBUILDER lpSphere3_builder, LPDIRECT3DRMTEXTURE * lplpTex); static void CleanUp(void);
この項では、Helworld.cサンプルコードにインプリメントされている、Windowsプログラムの標準的なセットアップと初期化関数について説明する。
Helworld.cのWinMain関数には、DirectDrawやDirect3D保持モードを利用するアプリケーションに特有なコードが少しだけ含まれている。InitApp関数とCleanUp関数はWindowsプログラムの標準的な部分であるが、Helworld.cでは、さらにいくつかの特別なタスクを実行する。Direct3Dにおいて、WinMain関数が行う最も重要な処理は、RenderLoop関数の呼び出しである。RenderLoop関数は、アニメーションのそれぞれのフレームを描画するために必要である。RenderLoop関数について詳しくは「レンダリングループ」を参照すること。
///////////////////////////////////////////////////////////////////// // // WinMain // アプリケーションを初期化し、メッセージループを開始する。 // メッセージループは、終了メッセージを受け取るまでシーンをレンダリングする。 // ///////////////////////////////////////////////////////////////////// int PASCAL WinMain (HINSTANCE this_inst, HINSTANCE prev_inst, LPSTR cmdline, int cmdshow) { MSG msg; HACCEL accel = NULL; int failcount = 0; // RenderLoopが失敗した回数 prev_inst; cmdline; // ウィンドウを作成し、レンダリングを開始するために必要な // すべてのオブジェクトを初期化する。 if (!InitApp(this_inst, cmdshow)) return 1; while (!myglobs.bQuit) { // 処理するメッセージがなくなるまで、メッセージキュー // を監視する。 while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (!TranslateAccelerator(msg.hwnd, accel, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } // アプリケーションが最小化されておらず、終了しようともしておらず、かつ // D3DRMが初期化されていれば、レンダリングを開始する。 if (!myglobs.bMinimized && !myglobs.bQuit && myglobs.bInitialized) { // フレームのレンダリングを試みる。レンダリングが2回以上 // 失敗したら、実行を中断する。 if (!RenderLoop()) ++failcount; if (failcount > 2) { CleanUp(); break; } } } return msg.wParam; }
Helwold.cの初期化関数(InitApp)は、ほとんどのWindowsベースのアプリケーションと同様に、ウィンドウクラスを登録し、メインアプリケーションウィンドウを作成する。この後、InitApp関数はDirectDrawやDirect3Dを用いるアプリケーションに特有の処理を行う。
次に、InitApp関数は、現在のディスプレイのピクセル当たりのビット数を取得する。この値は、アプリケーションがレンダリングのクオリティを設定する際に使用される。詳しくは「レンダリングステートの設定」を参照すること。
その後、どのDirect3Dドライバが有効であるかを決定し、適切なドライバを選択するために、ローカル定義のEnumDrivers関数を使用する。ドライバの列挙について詳しくは、「デバイスドライバの列挙」を参照すること。
次に、Direct3DRMCreate関数を使って、IDirect3DRM3インターフェイスを作成する。このインターフェイスは、シーンやカメラフレームを作成したり、カメラをシーンに設置するため、IDirect3DRM3::CreateFrameやIDirect3DRMFrame3::SetPositionを呼び出すときに使用される。
DirectDrawClipperオブジェクトは、どの部分の3Dシーンを可視状態にするかを制御するクリップ面の管理を簡単にする。Helworld.cは、DirectDrawCreateClipper関数を使ってIDirectDrawClipperインターフェイスを生成した後、IDirectDrawClipper::SetHWndメソッドを使用して、クリップ情報を取得するためにウィンドウハンドルを設定する。
そして、ローカル定義のCreateDevAndView関数を使って、Direct3Dデバイスとビューポートを作成する。この関数について詳しくは「デバイスとビューポートの作成」を参照すること。
Direct3Dアプリケーションがサポートするすべての構造体の初期化が完了すると、3Dシーンを細かく構成することができるようになる。この処理はMyScene関数が行っている。MyScene関数の詳細については、「デバイスとビューポートの作成」を参照すること。
最後にInitApp関数は、標準的な初期化関数と同様に、ウィンドウの表示と更新を行う。
///////////////////////////////////////////////////////////////////// // // InitApp // ウィンドウを作成し、レンダリングを開始するために必要な // すべてのオブジェクトを初期化する。 // ///////////////////////////////////////////////////////////////////// static BOOL InitApp(HINSTANCE this_inst, int cmdshow) { HWND win; HDC hdc; WNDCLASS wc; RECT rc; // セットアップを行い、ウィンドウクラスを登録する。 wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WindowProc; wc.cbClsExtra = 0; wc.cbWndExtra = sizeof(DWORD); wc.hInstance = this_inst; wc.hIcon = LoadIcon(this_inst, "AppIcon"); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); wc.lpszMenuName = NULL; wc.lpszClassName = "D3DRM Example"; if (!RegisterClass(&wc)) return FALSE; // グローバル変数を初期化する。 memset(&myglobs, 0, sizeof(myglobs)); // ウィンドウを作成する。 win = CreateWindow ( "D3DRM Example", // ウィンドウクラス "Hello World (Direct3DRM)", // タイトルバー WS_VISIBLE | WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX, CW_USEDEFAULT, // 初期x座標 CW_USEDEFAULT, // 初期y座標 300, // 初期の幅 300, // 初期の高さ NULL, // 親ウィンドウ NULL, // メニューハンドル this_inst, // プログラムのインスタンスハンドル NULL // 生成パラメータ ); if (!win) return FALSE; // 現在のディスプレイのピクセル当たりのビット数を記憶する。 hdc = GetDC(win); myglobs.BPP = GetDeviceCaps(hdc, BITSPIXEL); ReleaseDC(win, hdc); // D3Dドライバを列挙し、1つを選択する。 if (!EnumDrivers(win)) return FALSE; // D3DRMオブジェクトとD3DRMオブジェクトを作成する。 lpD3DRM = NULL; Direct3DRMCreate(&lpD3DRM); // マスターシーンのフレームとカメラフレームを作成する。 lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, NULL, &myglobs.scene); lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, myglobs.scene, &myglobs.camera); myglobs.camera->lpVtbl->SetPosition(myglobs.camera, myglobs.scene, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0)); // DirectDrawClipperオブジェクトを作成し、 // ウィンドウと関連付ける。 DirectDrawCreateClipper(0, &lpDDClipper, NULL); lpDDClipper->lpVtbl->SetHWnd(lpDDClipper, 0, win); // 選択されたD3Dドライバを用いてD3DRMデバイスを作成する。 GetClientRect(win, &rc); if (!CreateDevAndView(lpDDClipper, myglobs.CurrDriver, rc.right, rc.bottom)) { return FALSE; } // レンダリングされるシーンを作成する。 if (!MyScene(myglobs.dev, myglobs.view, myglobs.scene, myglobs.camera)) return FALSE; myglobs.bInitialized = TRUE; // 初期化完了。 // ウィンドウを表示する。 ShowWindow(win, cmdshow); UpdateWindow(win); return TRUE; }
Helworld.cサンプルのメインウィンドウプロシージャは非常に単純である。実際は、このサンプルはユーザの入力を全く受け付けないアプリケーションとなる。
ウィンドウプロシージャは、WM_DESTROYメッセージを受け取るとCleanUp関数を使用する。
また、WM_ACTIVATEメッセージを受け取ったときには、ウィンドウプロシージャはIDirect3DRMWinDeviceを取得し、アクティブなレンダリングウィンドウの色を正しく表示するためにIDirect3DRMWinDevice::HandleActivateメソッドを使用する。同様に、WM_PAINTメッセージに応答して、ウィンドウプロシージャはIDirect3DRMWinDevice::HandlePaintメソッドを使用する。
///////////////////////////////////////////////////////////////////// // // WindowProc // メインウィンドウのメッセージハンドラ // ///////////////////////////////////////////////////////////////////// LONG FAR PASCAL WindowProc(HWND win, UINT msg, WPARAM wparam, LPARAM lparam) { RECT r; PAINTSTRUCT ps; LPDIRECT3DRMWINDEVICE lpD3DRMWinDev; switch (msg) { case WM_DESTROY: CleanUp(); break; case WM_ACTIVATE: { // このメッセージを処理する、ウィンドウ固有の // Direct3D保持モードウィンドウデバイスを作成する。 if (!myglobs.dev) break; myglobs.dev->lpVtbl->QueryInterface(myglobs.dev, &IID_IDirect3DRMWinDevice, (void **) &lpD3DRMWinDev); lpD3DRMWinDev->lpVtbl->HandleActivate(lpD3DRMWinDev, (WORD) wparam); lpD3DRMWinDev->lpVtbl->Release(lpD3DRMWinDev); } break; case WM_PAINT: if (!myglobs.bInitialized || !myglobs.dev) return DefWindowProc(win, msg, wparam, lparam); // このメッセージを処理する、ウィンドウ固有の // Direct3D保持モードウィンドウデバイスを作成する。 if (GetUpdateRect(win, &r, FALSE)) { BeginPaint(win, &ps); myglobs.dev->lpVtbl->QueryInterface(myglobs.dev, &IID_IDirect3DRMWinDevice, (void **) &lpD3DRMWinDev); lpD3DRMWinDev->lpVtbl->HandlePaint(lpD3DRMWinDev, ps.hdc); lpD3DRMWinDev->lpVtbl->Release(lpD3DRMWinDev); EndPaint(win, &ps); } break; default: return DefWindowProc(win, msg, wparam, lparam); } return 0L; }
Direct3Dを利用するアプリケーションは、必ず、有効なドライバを列挙し、必要な操作を行うために最適なドライバを選択しなければならない。以下のセクションでは、この処理を実行する関数について説明する。
EnumDrivers関数は、InitApp関数がアプリケーションのシーンとカメラを生成する直前に使用される。
IDirect3D COM(Component Object Model)インターフェイスは、実際にはDirectDrawオブジェクトに対するインターフェイスであるので、この列挙関数が最初にすることは、DirectDrawCreate関数を使ってDirectDrawObjectを作成することである。その後、EnumDriversがQueryInterfaceメソッドを使用してIDirect3Dインターフェイスを生成する。QueryInterfaceのCの実装が、単純に定数自体ではなく(C++の実装のように)、2番目のパラメータとしてインターフェイス識別子のアドレスを渡すことを要求することに注意する。
ドライバの列挙は、IDirect3D::EnumDevicesメソッドによって行われる。IDirect3D::EnumDevicesメソッドは、ローカルに定義されたenumDeviceFuncコールバック関数を利用する。このコールバック関数の詳細については、「enumDeviceFuncコールバック関数」を参照すること。
IDirect3D::EnumDevicesはDirect3Dメソッドであり、Direct3DRMメソッドではないことに注意が必要である。保持モードのAPIには、列挙を行うメソッドは存在しない。これは、1つのアプリケーションで保持モードと直接モードの両方を使用する場合の良い例である。
///////////////////////////////////////////////////////////////////// // // EnumDrivers // 有効なD3Dドライバを列挙し、1つを選択する。 // ///////////////////////////////////////////////////////////////////// static BOOL EnumDrivers(HWND win) { LPDIRECTDRAW lpDD; LPDIRECT3D lpD3D; HRESULT rval; // DirectDrawオブジェクトを作成し、ドライバの列挙に // 用いるDirect3Dインターフェイスを問い合わせる。 DirectDrawCreate(NULL, &lpDD, NULL); rval = lpDD->lpVtbl->QueryInterface(lpDD, &IID_IDirect3D, (void**) &lpD3D); if (rval != DD_OK) { lpDD->lpVtbl->Release(lpDD); return FALSE; } // enumDeviceFuncのドライバ選択コードを初期化する // ため、CurrDriverに-1を設定し、ドライバを列挙する。 myglobs.CurrDriver = -1; lpD3D->lpVtbl->EnumDevices(lpD3D, enumDeviceFunc, &myglobs.CurrDriver); // 少なくとも有効なドライバが1つはあることを保証する。 if (myglobs.NumDrivers == 0) { return FALSE; } lpD3D->lpVtbl->Release(lpD3D); lpDD->lpVtbl->Release(lpDD); return TRUE; }
enumDeviceFunk関数は、D3DENUMDEVICESCALLBACK型のコールバック関数である。 D3DENUMDEVICESCALLBACK型はヘッダファイルD3dcaps.hに定義されている。システムはこの関数に、インストールされている各Direct3Dドライバの識別子と名前、およびハードウェアとエミュレートされたドライバの能力を通知する。
コールバック関数は、D3DDEVICEDESC構造体のdcmColorModelメンバを使用して、ハードウェアと列挙されたドライバのどちらを調べるかを決定する。このメンバにハードウェアが設定されている場合、関数はハードウェアの性能を調べる。
次に、コールバック関数は、列挙されたドライバが現在のカラービット数でレンダリングを行うことができるかどうかを判定する。不可能な場合はD3DENUMRET_OKを返し、そのドライバに関する残りの処理をスキップして、次のドライバの列挙を続ける。コールバック関数は、ローカル定義のBPPToDDBD関数を用い、通知されたカラービット数と、InitApp関数でのGetDeviceCaps関数の呼び出しによって取得された色解像度とを比較する(BPPToDDBDはbits-per-pixel to DirectDraw bit-depthの略である)。BPPToDDBD関数のコードについては、「BPPToDDBDヘルパ関数」を参照すること。
列挙されたドライバについて簡単なテストを行った後は、D3DDEVICEDESC構造体の他のメンバを判定する。コールバック関数は、ソフトウェアエミュレーションよりもハードウェアを、RGB照明よりもモノクロ照明を選択する。
///////////////////////////////////////////////////////////////////// // // enumDeviceFunc // 使用可能なD3Dドライバの名前とGUIDを記憶するコールバック関数。 // ドライバを選択し、*lpContextに設定する。 // ///////////////////////////////////////////////////////////////////// static HRESULT WINAPI enumDeviceFunc(LPGUID lpGuid, LPSTR lpDeviceDescription, LPSTR lpDeviceName, LPD3DDEVICEDESC lpHWDesc, LPD3DDEVICEDESC lpHELDesc, LPVOID lpContext) { static BOOL hardware = FALSE; // 現在の開始ドライバはハードウェアである。 static BOOL mono = FALSE; // 現在の開始ドライバはモノクロ照明である。 LPD3DDEVICEDESC lpDesc; int *lpStartDriver = (int *)lpContext; // どのデバイス記述を調べるかを決定する。 lpDesc = lpHWDesc->dcmColorModel ? lpHWDesc : lpHELDesc; // 現在のディスプレイのビット深度では、このドライバがレンダリング // を行えない場合、ドライバをスキップして列挙を続行する。 if (!(lpDesc->dwDeviceRenderBitDepth & BPPToDDBD(myglobs.BPP))) return D3DENUMRET_OK; // このドライバの名前とGUIDを記憶する。 memcpy(&myglobs.DriverGUID[myglobs.NumDrivers], lpGuid, sizeof(GUID)); lstrcpy(&myglobs.DriverName[myglobs.NumDrivers][0], lpDeviceName); // ソフトウェアよりもハードウェアを、モノクロ照明よりもRGB照明を選択する。 if (*lpStartDriver == -1) { // これが最初の有効なドライバである。 *lpStartDriver = myglobs.NumDrivers; hardware = lpDesc == lpHWDesc ? TRUE : FALSE; mono = lpDesc->dcmColorModel & D3DCOLOR_MONO ? TRUE : FALSE; } else if (lpDesc == lpHWDesc && !hardware) { // このドライバはハードウェアであり、開始ドライバはソフトウェアである。 *lpStartDriver = myglobs.NumDrivers; hardware = lpDesc == lpHWDesc ? TRUE : FALSE; mono = lpDesc->dcmColorModel & D3DCOLOR_MONO ? TRUE : FALSE; } else if ((lpDesc == lpHWDesc && hardware ) || (lpDesc == lpHELDesc && !hardware)) { if (lpDesc->dcmColorModel == D3DCOLOR_MONO && !mono) { // このドライバと開始ドライバは同じ種類である。開始ドライバは // RGB照明だったのに対し、このドライバはモノクロである。 *lpStartDriver = myglobs.NumDrivers; hardware = lpDesc == lpHWDesc ? TRUE : FALSE; mono = lpDesc->dcmColorModel & D3DCOLOR_MONO ? TRUE : FALSE; } } myglobs.NumDrivers++; if (myglobs.NumDrivers == MAX_DRIVERS) return (D3DENUMRET_CANCEL); return (D3DENUMRET_OK); }
enumDeviceFuncコールバック関数は、BPPToDDBDヘルパ関数を用いて、現在のデバイスがサポートする色解像度を、列挙されたドライバのカラービット数と比較できる形式に変換する。enumDeviceFuncの詳細については、「enumDeviceFuncコールバック関数」を参照すること。
///////////////////////////////////////////////////////////////////// // // BPPToDDBD // ピクセル当たりのビット数をDirectDrawのビット深度に変換する。 // ///////////////////////////////////////////////////////////////////// static DWORD BPPToDDBD(int bpp) { switch(bpp) { case 1: return DDBD_1; case 2: return DDBD_2; case 4: return DDBD_4; case 8: return DDBD_8; case 16: return DDBD_16; case 24: return DDBD_24; case 32: return DDBD_32; default: return 0; } }
この項では、Helworld.cのコードのうち、3D環境を構築する部分について説明する。以下のセクションは、この処理を実行するための2つの関数について説明している。
これらの関数は、3D環境にオブジェクトやフレーム、光を配置するものではない。シーンの作成は、MyScene関数と、MyScene関数が使う関数群によって実現される。3D環境にシーンを設定する方法については、「シーンの作成」を参照すること。
Direct3Dデバイスとビューポートは、アプリケーションの初期化の一環として作成される。InitApp関数は、DirectDrawClipperオブジェクトを生成した後、DirectDrawClipperオブジェクトと選択されたドライバ、およびクライアント矩形の寸法をパラメータとして、CreateDevAndView関数を使用する。
CreateDevAndView関数は、列挙プロセスで選択されたドライバを使用して、IDirect3DRM3::CreateDeviceFromClipperメソッドによってDirect3DRMデバイスを作成する。このIDirect3DRMDevice3インターフェイスは、デバイスの幅および高さを取得するIDirect3DRMDevice3::GetWidthとIDirect3DRMDevice3::GetHeightメソッドの使用で用いられる。CreateDevAndView関数は、デバイスの幅や高さの情報を受け取った後、IDirect3DRM3::CreateViewportメソッドを使ってIDirect3DRMViewport2インターフェイスを取得する。
次に、CreateDevAndView関数が、IDirect3DRMViewport2::SetBackメソッドによってビューポートのバッククリッピングプレーンを設定すると、ローカル定義のSetRenderState関数が使われる。SetRenderState関数については、次の「レンダリングステートの設定」で説明する。
///////////////////////////////////////////////////////////////////// // // CreateDevAndView // 指定されたD3DドライバとサイズでD3DRMデバイスと // ビューポートを作成する。 // ///////////////////////////////////////////////////////////////////// static BOOL CreateDevAndView(LPDIRECTDRAWCLIPPER lpDDClipper, int driver, int width, int height) { HRESULT rval; // 指定されたD3Dドライバを用い、このウィンドウから // D3DRMデバイスを作成する。 lpD3DRM->lpVtbl->CreateDeviceFromClipper(lpD3DRM, lpDDClipper, &myglobs.DriverGUID[driver], width, height, &myglobs.dev); // カメラフレームを使ってD3DRMビューポートを作成する。 // 背景の深さを大きな数に設定する。幅と高さはだいたい // 調節されているので、デバイスからその値を取得する。 width = myglobs.dev->lpVtbl->GetWidth(myglobs.dev); height = myglobs.dev->lpVtbl->GetHeight(myglobs.dev); rval = lpD3DRM->lpVtbl->CreateViewport(lpD3DRM, myglobs.dev, myglobs.camera, 0, 0, width, height, &myglobs.view); if (rval != D3DRM_OK) { myglobs.dev->lpVtbl->Release(myglobs.dev); return FALSE; } rval = myglobs.view->lpVtbl->SetBack(myglobs.view, D3DVAL(5000.0)); if (rval != D3DRM_OK) { myglobs.dev->lpVtbl->Release(myglobs.dev); myglobs.view->lpVtbl->Release(myglobs.view); return FALSE; } // レンダリングのクオリティ、塗りつぶしモード、照明の状態、 // カラーシェーディングの情報を設定する。 if (!SetRenderState()) return FALSE; return TRUE; }
Direct3Dはステートマシンである。アプリケーションは、照明モジュール、レンダリングモジュール、および変換モジュールの状態を設定し、それらを通してデータを送出する。この仕組みを意識することは、直接モードにとっては不可欠であるが、保持モードでは部分的に隠蔽されている。保持モードアプリケーションは、SetRenderState関数によって、レンダリングステートを簡単に設定することができる。
まず、SetRenderState関数は、光をオン、塗りつぶしモードをソリッドにし、グーローシェーディングモードを用いるように指定して、IDirect3DRMDevice3::SetQualityメソッドを使う。このとき、ディザモードやテクスチャのクオリティを変更する必要があるアプリケーションは、IDirect3DRMDevice3::SetDitherメソッドやIDirect3DRMDevice3::SetTextureQualityメソッドを使うことができる。
///////////////////////////////////////////////////////////////////// // // SetRenderState // レンダリングのクオリティとシェーディング情報を設定する。 // ///////////////////////////////////////////////////////////////////// BOOL SetRenderState(void) { HRESULT rval; // レンダリングのクオリティ(照明のトグル、塗りつぶしモード、シェーディングモード)を設定する。 rval = myglobs.dev->lpVtbl->SetQuality(myglobs.dev, D3DRMLIGHT_ON | D3DRMFILL_SOLID | D3DRMSHADE_GOURAUD); if (rval != D3DRM_OK) { return FALSE; } // ディザモードを変更したい場合、ここでSetDitherを使う。 // テクスチャのクオリティをD3DRMTEXTURE_NEAREST(デフォルト)以外 // にしたい場合、ここでSetTextureQualityを使う。 return TRUE; }
WinMain関数は、次のフレームを描画するため、RenderLoop関数を使う。RenderLoop関数は、いくつかの単純な処理を実行する。
- IDirect3DRMFrame3::Move メソッドを使って、階層下のすべてのフレームの回転や速度の適用を行う。
- IDirect3DRMViewport2::Clearメソッドを使って、現在のビューポートを背景色でクリアする。
- IDirect3DRMViewport2::Renderメソッドを使って、現在のシーンをビューポートにレンダリングする。
- IDirect3DRMDevice3::Updateメソッドを使って、レンダリングされたイメージをスクリーンにコピーする。
///////////////////////////////////////////////////////////////////// // // RenderLoop // ビューポートをクリアして次のフレームをレンダリングし、ウィンドウを更新する。 // ///////////////////////////////////////////////////////////////////// static BOOL RenderLoop() { HRESULT rval; // 現在のシーンを確認する。 rval = myglobs.scene->lpVtbl->Move(myglobs.scene, D3DVAL(1.0)); if (rval != D3DRM_OK) { return FALSE; } // ビューポートをクリアする。 rval = myglobs.view->lpVtbl->Clear(myglobs.view); if (rval != D3DRM_OK) { return FALSE; } // シーンをビューポートにレンダリングする。 rval = myglobs.view->lpVtbl->Render(myglobs.view, myglobs.scene); if (rval != D3DRM_OK) { return FALSE; } // ウィンドウを更新する。 rval = myglobs.dev->lpVtbl->Update(myglobs.dev); if (rval != D3DRM_OK) { return FALSE; } return TRUE; }
3D環境のセットアップ(ドライバの選択、3Dデバイスとビューポートの作成、レンダリングステートの設定など)が完了すると、Helworld.cは、この3D環境にオブジェクトやフレーム、光を配置するための関数群を使用する。
Helworld.cのMyScene関数は、DirectX SDKのすべてのDirect3Dサンプルにインプリメントされている、BuildScene関数に相当する。アプリケーションのオブジェクトをテクスチャや照明効果とともに表示する処理は、すべてこの関数内で行われる。
MyScene関数は、作成されるシーンの各特性を設定する、ローカル定義の関数群を使用する。これらの関数を以下に示す。
これらの関数がビジュアルオブジェクトのセットアップを完了すると、MyScene関数はIDirect3DRMFrame3::AddVisualメソッドを呼び出し、オブジェクトを3D環境のworldフレームに追加する。その後は作成されたインターフェイスは不要になるため、繰り返しReleaseメソッドを使って、すべてのインターフェイスを解放することができる。
///////////////////////////////////////////////////////////////////// // // MyScene // フレーム、照明、メッシュ、テクスチャを作成する関数を使う。 // 完了したら、すべてのインターフェイスを解放する。 // ///////////////////////////////////////////////////////////////////// BOOL MyScene(LPDIRECT3DRMDEVICE dev, LPDIRECT3DRMVIEWPORT view, LPDIRECT3DRMFRAME lpScene, LPDIRECT3DRMFRAME lpCamera) { LPDIRECT3DRMFRAME lpLightframe1 = NULL; LPDIRECT3DRMFRAME lpWorld_frame = NULL; LPDIRECT3DRMLIGHT lpLight1 = NULL; LPDIRECT3DRMLIGHT lpLight2 = NULL; LPDIRECT3DRMTEXTURE lpTex = NULL; LPDIRECT3DRMWRAP lpWrap = NULL; LPDIRECT3DRMMESHBUILDER lpSphere3_builder = NULL; MakeMyFrames(lpScene, lpCamera, &lpLightframe1, &lpWorld_frame); MakeMyLights(lpScene, lpCamera, lpLightframe1, &lpLight1, &lpLight2); SetMyPositions(lpScene, lpCamera, lpLightframe1, lpWorld_frame); MakeMyMesh(&lpSphere3_builder); MakeMyWrap(lpSphere3_builder, &lpWrap); AddMyTexture(lpSphere3_builder, &lpTex); // マテリアルを作成する必要がある場合(たとえば、光り輝く面を作成 // する場合)、ここでCreateMaterialとSetMaterialを使用する。 // これでビジュアルオブジェクトが作成されたので、 // ワールドフレームに追加する。 lpWorld_frame->lpVtbl->AddVisual(lpWorld_frame, (LPDIRECT3DRMVISUAL) lpSphere3_builder); lpLightframe1->lpVtbl->Release(lpLightframe1); lpWorld_frame->lpVtbl->Release(lpWorld_frame); lpSphere3_builder->lpVtbl->Release(lpSphere3_builder); lpLight1->lpVtbl->Release(lpLight1); lpLight2->lpVtbl->Release(lpLight2); lpTex->lpVtbl->Release(lpTex); lpWrap->lpVtbl->Release(lpWrap); return TRUE; }
MyScene関数はMakeMyFrames関数を使って、Helworld.cで用いられるディレクショナル照明フレームやワールドフレームを作成する。MakeMyFrames関数は、IDirect3DRM3::CreateFrameメソッドを使って、この処理を実行する。
///////////////////////////////////////////////////////////////////// // // MakeMyFrames // シーンで使用するフレームを作成する。 // ///////////////////////////////////////////////////////////////////// void MakeMyFrames(LPDIRECT3DRMFRAME lpScene, LPDIRECT3DRMFRAME lpCamera, LPDIRECT3DRMFRAME * lplpLightFrame1, LPDIRECT3DRMFRAME * lplpWorld_frame) { lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, lpScene, lplpLightFrame1); lpD3DRM->lpVtbl->CreateFrame(lpD3DRM, lpScene, lplpWorld_frame); }
MyScene関数はMakeMyLights関数を使って、Helworld.cで用いられるディレクショナル照明やアンビエント照明を作成する。MakeMyLights関数はIDirect3DRM3::CreateLightRGBとIDirect3DRMFrame3::AddLightメソッドを使って、方向を持った明るい照明を生成し、それをフレームに追加する。また、薄暗いアンビエント照明を生成し、シーン全体に追加する(アンビエント照明は、つねにシーン全体に関連付けられる)。
///////////////////////////////////////////////////////////////////// // // MakeMyLights // シーンで使用する照明を作成する。 // ///////////////////////////////////////////////////////////////////// void MakeMyLights(LPDIRECT3DRMFRAME lpScene, LPDIRECT3DRMFRAME lpCamera, LPDIRECT3DRMFRAME lpLightFrame1, LPDIRECT3DRMLIGHT * lplpLight1, LPDIRECT3DRMLIGHT * lplpLight2) { lpD3DRM->lpVtbl->CreateLightRGB(lpD3DRM, D3DRMLIGHT_DIRECTIONAL, D3DVAL(0.9), D3DVAL(0.9), D3DVAL(0.9), lplpLight1); lpLightFrame1->lpVtbl->AddLight(lpLightFrame1, *lplpLight1); lpD3DRM->lpVtbl->CreateLightRGB(lpD3DRM, D3DRMLIGHT_AMBIENT, D3DVAL(0.1), D3DVAL(0.1), D3DVAL(0.1), lplpLight2); lpScene->lpVtbl->AddLight(lpScene, *lplpLight2); }
MyScene関数はSetMyPositions関数を使って、Helworld.cが使うフレームの位置と向きを設定する。SetMyPositions関数は、この処理をIDirect3DRMFrame3::SetPositionおよびIDirect3DRMFrame3::SetOrientationメソッドの使用によって実行する。IDirect3DRMFrame3::SetRotationメソッドは、球体が追加されるフレームに回転を設定する。
///////////////////////////////////////////////////////////////////// // // SetMyPositions // 照明、カメラ、ワールドフレームの位置と向きを設定する。 // 球体の回転を設定する。 // ///////////////////////////////////////////////////////////////////// void SetMyPositions(LPDIRECT3DRMFRAME lpScene, LPDIRECT3DRMFRAME lpCamera, LPDIRECT3DRMFRAME lpLightFrame1, LPDIRECT3DRMFRAME lpWorld_frame) { lpLightFrame1->lpVtbl->SetPosition(lpLightFrame1, lpScene, D3DVAL(2), D3DVAL(0.0), D3DVAL(22)); lpCamera->lpVtbl->SetPosition(lpCamera, lpScene, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0)); lpCamera->lpVtbl->SetOrientation(lpCamera, lpScene, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(1), D3DVAL(0.0), D3DVAL(1), D3DVAL(0.0)); lpWorld_frame->lpVtbl->SetPosition(lpWorld_frame, lpScene, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(15)); lpWorld_frame->lpVtbl->SetOrientation(lpWorld_frame, lpScene, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(1), D3DVAL(0.0), D3DVAL(1), D3DVAL(0.0)); lpWorld_frame->lpVtbl->SetRotation(lpWorld_frame, lpScene, D3DVAL(0.0), D3DVAL(0.1), D3DVAL(0.0), D3DVAL(0.05)); }
MyScene関数は、Helworld.cで使われる球状のメッシュをロードし、セットするためにMakeMyMesh関数を使用する。MekeMyMesh関数はIDirect3DRM3::CreateMeshBuilderメソッドを使ってIDirect3DRMMeshBuilder3インターフェイスを生成する。次に、IDirect3DRMMeshBuilder3::Load、IDirect3DRMMeshBuilder3::Scale、およびIDirect3DRMMeshBuilder3::SetColorRGBメソッドを使って、Sphere3.xファイルが示すメッシュを用意する(Sphere3.xファイルは、DirectX SDKに収録されている)。
///////////////////////////////////////////////////////////////////// // // MakeMyMesh // MeshBuilderオブジェクトを作成し、ロード、スケーリング、メッシュの色付けを行う。 // ///////////////////////////////////////////////////////////////////// void MakeMyMesh(LPDIRECT3DRMMESHBUILDER * lplpSphere3_builder) { lpD3DRM->lpVtbl->CreateMeshBuilder(lpD3DRM, lplpSphere3_builder); (*lplpSphere3_builder)->lpVtbl->Load(*lplpSphere3_builder, "sphere3.x", NULL, D3DRMLOAD_FROMFILE, NULL, NULL); (*lplpSphere3_builder)->lpVtbl->Scale(*lplpSphere3_builder, D3DVAL(2), D3DVAL(2), D3DVAL(2)); // 予想外のテクスチャブレンディングを避けるため、球体を白に設定する。 (*lplpSphere3_builder)->lpVtbl->SetColorRGB(*lplpSphere3_builder, D3DVAL(1), D3DVAL(1), D3DVAL(1)); }
MyScene関数はMakeMyWrap関数を使ってテクスチャ座標を作成し、MakeMyMesh関数がロードした球体に適用する。MakeMyWrap関数は、球体を含む境界ボックスを取得するためにIDirect3DRMMeshBuilder3::GetBoxメソッドを呼び出し、その境界ボックスのサイズをIDirect3DRM3::CreateWrapメソッドの呼び出し時に使用する。IDirect3DRM3::CreateWrapメソッドは円柱のテクスチャラップを作成し、IDirect3DRMWrapインターフェイスを取得する。テクスチャ座標を球体に適用するには、IDirect3DRMWrap::Applyメソッドの呼び出しを行う。
///////////////////////////////////////////////////////////////////// // // MakeMyWrap // ラップを作成し、テクスチャに適用する。 // ///////////////////////////////////////////////////////////////////// void MakeMyWrap(LPDIRECT3DRMMESHBUILDER sphere3_builder, LPDIRECT3DRMWRAP * lpWrap) { D3DVALUE miny, maxy, height; D3DRMBOX box; sphere3_builder->lpVtbl->GetBox(sphere3_builder, &box); maxy = box.max.y; miny = box.min.y; height = maxy - miny; lpD3DRM->lpVtbl->CreateWrap (lpD3DRM, D3DRMWRAP_CYLINDER, NULL, D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0), D3DVAL(0.0), D3DVAL(0.0), D3DVAL(1.0), D3DVAL(0.0), D3DDivide(miny, height), D3DVAL(1.0), D3DDivide(D3DVAL(1.0), height), lpWrap); (*lpWrap)->lpVtbl->Apply(*lpWrap, (LPDIRECT3DRMOBJECT) sphere3_builder); }
MyScene関数は、AddMyTexture関数を使ってテクスチャをロードし、球に関連付ける。AddMyTextureは、IDirect3DRM3::LoadTextureメソッドを使ってTutor.bmpビットマップをロードした後、IDirect3DRMMeshBuilder3::SetTextureメソッドを呼び出してそのビットマップを球に関連付ける。
///////////////////////////////////////////////////////////////////// // // AddMyTexture // ラップを作成し、テクスチャに適用する。 // ///////////////////////////////////////////////////////////////////// void AddMyTexture(LPDIRECT3DRMMESHBUILDER lpSphere3_builder, LPDIRECT3DRMTEXTURE * lplpTex) { lpD3DRM->lpVtbl->LoadTexture(lpD3DRM, "tutor.bmp", lplpTex); // デフォルト(16)以外のカラー深度が必要な場合、ここで // IDirect3DRMTexture::SetShadesを使う。 lpSphere3_builder->lpVtbl->SetTexture(lpSphere3_builder, *lplpTex); }
Helworld.cは、WM_DESTROYメッセージを受け取ったとき、またはRenderLoop関数の使用に何度か失敗したとき、CleanUp関数を使用する。
///////////////////////////////////////////////////////////////////// // // CleanUp // すべてのD3DRMオブジェクトを解放し、bQuitフラグをセットする。 // ///////////////////////////////////////////////////////////////////// void CleanUp(void) { myglobs.bInitialized = FALSE; myglobs.scene->lpVtbl->Release(myglobs.scene); myglobs.camera->lpVtbl->Release(myglobs.camera); myglobs.view->lpVtbl->Release(myglobs.view); myglobs.dev->lpVtbl->Release(myglobs.dev); lpD3DRM->lpVtbl->Release(lpD3DRM); lpDDClipper->lpVtbl->Release(lpDDClipper); myglobs.bQuit = TRUE; }
トップに戻る
© 1999 Microsoft and/or its suppliers. All rights reserved. Terms of Use.