SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

Direct3D Mobileによる3DCGプログラミング

Windows Mobile 5.0で動作する3Dアプリケーションの開発


プログラムの解説

 ここからサンプルプログラムの解説をしていきます。DirectX 8.0以降のDirect3Dの開発経験がある方であれば、Direct3D Mobile APIの使い方はすぐに覚えられると思います。Direct3D Mobile以外のウィンドウ周りの処理に関しても、Win32での開発経験がある方であればほとんど同じですので、すぐに開発ができると思います。

プログラムの準備

リンクするライブラリ

  • d3dm.lib
  • d3dmx.lib
  • aygshell.lib

 今回のサンプルでは、以上の3つのライブラリとリンクしています。「d3dm.lib」「d3dmx.lib」がDirect3D Mobileのライブラリで、前者は基本的なインターフェイスのライブラリ、後者はベクトルや行列、テクスチャローダー関数などヘルパー関数などのライブラリです。「aygshell.lib」はPocketPCのUIなどのシェルまわりのライブラリです。

インクルードファイル

#include <windows.h>
#include "resource.h"
#include <commctrl.h>
#include <d3dm.h.>
#include <d3dmx.h>
#include <aygshell.h>

 今回のサンプルでは上記のヘッダーファイルをインクルードしています。「d3dm.h」「d3dmx.h」がDirect3D Mobileのヘッダーファイルです。「aygshell.h」はシェルのヘッダーファイルです。

マクロ

#define SAFE_RELEASE(o) {if(o){o->Release();o = NULL;}}
#define SAFE_DELETE(o) {if(o){delete o;o = NULL}}
#define SAFE_DELETE_ARRAY(o) {if(o){delete[] o;o = NULL;}}

 この辺は、メモリやインターフェイスの解放用に定義しておきます。

グローバル変数

// グローバル変数:
HINSTANCE g_hInst;            // インスタンス
HWND      g_hWnd;             // ウィンドウハンドル

bool      g_bWindowed = true; // ウィンドウモードフラグ
INT g_iScreenWidth  = 0;      // スクリーン幅
INT g_iScreenHeight = 0;      // スクリーン高さ

 Direct3D Mobileで使うインターフェイス以外のグローバル変数は以上です。

 ウィンドウモードフラグは、trueだとDirect3D Mobileでの描画はウィンドウのクライアント領域になり、falseだと全画面に描画するフルスクリーンモードで起動するようにしています。

WinMain()関数からの流れ

WinMain()関数

int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPTSTR    lpCmdLine,
                   int       nCmdShow)
{
    MSG msg;
    g_hInst = hInstance;

    // 画面が回転してるのであれば起動しない
    if(IsScreenRotated())
        return 0;

    // ウィンドウの生成
    if(FAILED(InitWindow()))
        return 0;

    // Direct3D Mobileの初期化
    if(FAILED( InitD3DM()))
        return 0;

    // 頂点のセットやテクスチャの読み込み
    if(FAILED( InitApp()))
    {
        CleanupD3DM();
        return 0;
    }

    ShowWindow(g_hWnd, nCmdShow);
    UpdateWindow(g_hWnd);

    memset( &msg, 0, sizeof(msg) );

    // アプリケーションループ
    while( msg.message!=WM_QUIT )
    {
        if( PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) )
        {
            TranslateMessage( &msg );
            DispatchMessage( &msg );
        }
        else
            Render();
    }

    return (int) msg.wParam;
}

 WinMain()関数からの流れは、以下の様になります。

  1. 画面が回転してるかどうかのチェック
  2. アプリケーションのウィンドウの生成
  3. Direct3D Mobileの初期化
  4. 頂点データのセットやテクスチャのロード
  5. アプリケーションループ
  6. 描画
  7. 終了

 となります。最初の画面の回転のチェックでは、PocketPC機には画面の回転機能がありますが、デバイスによっては回転時にDirect3D Mobileを起動できない機種もありますのでチェックしています(rx1950のように回転していても起動できる機種もあるようです)。続いて、アプリケーションのウィンドウの生成を行い、Direct3D Mobileの初期化を行います。初期化ができたら頂点データやテクスチャのロードを行います。アプリケーションループの中で描画処理を書き、終了となります。

ウィンドウプロシージャ

WndProc()関数
LRESULT CALLBACK WndProc(HWND hWnd, UINT message,
                         WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_LBUTTONUP:
        // 画面をタップすると終了
        PostMessage(hWnd, WM_CLOSE, 0, 0);
        break;
    case WM_CLOSE:
        CleanupD3DM();
        break;
    case WM_DESTROY:
        PostQuitMessage( 0 );
        return 0;
    }

    return DefWindowProc(hWnd, message, wParam, lParam);
}

 ウィンドウに対するイベントを処理するウィンドウプロシージャは上記のようにしています。今回は画面のクライアント領域(フルスクリーンなら画面内)をタップする(タッチパネルを押す)と、アプリケーションが終了します。WM_CLOSEでは、Direct3D Mobileの各種インターフェイスをメモリ上から解放する処理を行う関数を呼び出します。

画面の回転のチェック

BOOL IsScreenRotated()
{
    DEVMODE devMode  = {0};
    devMode.dmSize   = sizeof(DEVMODE);
    devMode.dmFields = DM_DISPLAYORIENTATION;

    ChangeDisplaySettingsEx(NULL, &devMode, NULL, CDS_TEST, NULL);

    if (devMode.dmDisplayOrientation != DMDO_0)
    {
        MessageBox(
            NULL,
            TEXT("画面が回転した状態では起動できません"),
            TEXT("警告"),
            MB_OK | MB_ICONINFORMATION | MB_SETFOREGROUND
            );

        return TRUE;
    }

    return FALSE;
}

 ここでは画面が回転しているかどうかをチェックしてます。回転している状態であれば、メッセージボックスを出力してTRUEを返します。デバイスによっては画面回転時にDirect3D Mobileの初期化ができないものがありますので、このチェックをします。

ウィンドウの生成

HRESULT InitWindow()
{
    HRESULT hr = S_OK;

    SHInitExtraControls();

    WNDCLASS wc = { 0L, WndProc, 0L, 0L,
                    g_hInst, NULL, NULL, NULL, NULL,
                    TEXT("D3DMのサンプル") };
    RegisterClass( &wc );

    // ウィンドウモード
    g_hWnd = CreateWindow(
        TEXT("D3DMのサンプル"), TEXT("D3DMのサンプル"),
        WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
        CW_USEDEFAULT, NULL, NULL, g_hInst, NULL);

    // 画面幅と高さ
    g_iScreenWidth = GetSystemMetrics(SM_CXSCREEN);
    g_iScreenHeight = GetSystemMetrics(SM_CYSCREEN);

    // フルスクリーンかウィンドウモードかで生成するウィンドウを変える
    if(g_bWindowed)
    {
        // ウィンドウモード
        // SIPのバーを消去
        SHFullScreen(g_hWnd, SHFS_HIDESIPBUTTON);
    }
    else
    {
        // フルスクリーン
        // タスクバーとSIPのバーを消去
        SHFullScreen(g_hWnd, SHFS_HIDESIPBUTTON | SHFS_HIDETASKBAR);
    }

    return hr;
}

 このあたりは、Win32での開発経験があれば基本的には同じですが、SH~()関数というのがありますが、これはPocketPCのメニューなどのUIの部分に関連する関数です。

 SHFullScreen()を呼ぶことで、SIPのバー(手書き入力やソフトウェアキーボードの呼び出しの部分)やウィンドウのタイトルなどが表示されるタスクバーを表示しないようにできます。今回は、ウィンドウモードフラグに応じて変えています。

Direct3D Mobileのインターフェイス

グローバルに記述
// Direct3D Mobileのインターフェイス
IDirect3DMobile*             g_pd3d     = NULL; // Direct3Dオブジェクト
IDirect3DMobileDevice*    g_pd3dmDevice = NULL; // Direct3Dデバイス
IDirect3DMobileVertexBuffer* g_pVB      = NULL; // 頂点バッファ
IDirect3DMobileTexture*      g_pTexture = NULL; // テクスチャ

 IDirect3D~ではじまるインターフェイスはDirect3D Mobileのインターフェイスになります。今回は、絶対に必要なDirect3D Mobileオブジェクトとデバイスの他に、ポリゴンの頂点データを管理する頂点バッファインターフェイスと、ポリゴン上の模様にあたるテクスチャのインターフェイスを利用します。

Direct3D Mobileの初期化

Direct3D Mobileオブジェクトの生成

InitD3DM()関数内
if( NULL == ( g_pD3DM = Direct3DMobileCreate( D3DM_SDK_VERSION ) ) )
    return E_FAIL;

 ここでは、OS側にサポートするDirect3D Mobileのバージョン(D3DM_SDK_VERSIONはバージョンをあらわす定数)を問い合わせて、これから動かそうとするバージョンが実行可能であればDirect3D Mobileオブジェクトを生成します。実行可能では無い場合は、g_pD3DにはNULLが入ります。

Direct3D Mobileデバイスの作成

InitD3DM()関数内
D3DMPRESENT_PARAMETERS d3dmpp;
memset( &d3dmpp, 0, sizeof(d3dmpp) );
d3dmpp.Windowed = g_bWindowed;
d3dmpp.SwapEffect = D3DMSWAPEFFECT_DISCARD;

// フルスクリーンの場合
if(!g_bWindowed)
{
    // バックバッファの幅と高さを設定
    d3dmpp.BackBufferHeight = g_iScreenHeight;
    d3dmpp.BackBufferWidth = g_iScreenWidth;
}

// このフォーマットをサポートするか?
hr = g_pd3d->CheckDepthStencilMatch( 0, D3DMDEVTYPE_DEFAULT,
     D3DMFMT_R5G6B5, D3DMFMT_R5G6B5, D3DMFMT_D24S8);
if(SUCCEEDED(hr))
{
    // Zバッファが24bitでステンシルバッファが8bit
    d3dmpp.BackBufferFormat = D3DMFMT_R5G6B5;
    d3dmpp.EnableAutoDepthStencil = TRUE;
        d3dmpp.AutoDepthStencilFormat = D3DMFMT_D24S8;
}
else
{
    // Zバッファが16bit
    d3dmpp.BackBufferFormat = D3DMFMT_R5G6B5;
    d3dmpp.EnableAutoDepthStencil = TRUE;
        d3dmpp.AutoDepthStencilFormat = D3DMFMT_D16;
} 

// デバイスの生成
hr = g_pd3d->CreateDevice( D3DMADAPTER_DEFAULT, D3DMDEVTYPE_DEFAULT,
    g_hWnd, 0, &d3dmpp, &g_pd3dmDevice );

return hr;

 デバイスの生成は、D3DMPRESENT_PARAMETERS構造体に設定項目のフラグをセットしていくことからはじめます。設定は、フルスクリーンやウィンドウモードでの起動やバックバッファフォーマットなどを決めます。この辺の流れはPCのDirect3Dと似た感じです。使えるフラグの組み合わせはハードウェアごとに異なるので注意が必要です。

 CheckDepthStencilMatch()では、バックバッファフォーマットと深度バッファフォーマットの組み合わせが、実行するハードウェア上でサポートするかどうか問い合わせます。深度バッファフォーマットに関しては、ハードウェアによってかなりサポートのされ方が違うようなので、注意が必要です。

 余談ですが筆者が実験した結果、rx1950ではD3DMFMT_D16をサポートしていなく、W-ZERO3はD3DMFMT_D24S8をサポートしていませんでした。

頂点バッファ

 頂点バッファとは、メモリ上に置くポリゴンの頂点データの配列で、構造体の定義の仕方によって任意のデータ要素を持たせることができます。

頂点データの構造体

グローバルに記述
struct VERTEX
{
    FLOAT    x, y, z;  // 位置座標
    DWORD    dwColor;  // 頂点色
    FLOAT    tu, tv;   // テクスチャ座標

    static const DWORD    FVF;
};
const DWORD VERTEX::FVF =
    D3DMFVF_XYZ_FLOAT | D3DMFVF_DIFFUSE | D3DMFVF_TEX1;

 Direct3D Mobileでは、PCのDirect3Dと同じく柔軟な頂点フォーマットを使って、ポリゴンの頂点のデータ型を定義します。

 これは、頂点によって位置座標や色、テクスチャ座標、法線などを必要に応じて組み合わせます。レンダリングパイプラインがこれらのパラメータの意味を理解するために、FVFにフラグを設定します。

頂点バッファの作成

InitApp()関数内
// VRAMに頂点バッファを置くことが可能かどうか?
D3DMPOOL vbpool;
if (caps.SurfaceCaps & D3DMSURFCAPS_VIDVERTEXBUFFER)
{
    vbpool = D3DMPOOL_VIDEOMEM;
}
else
{
    vbpool = D3DMPOOL_SYSTEMMEM;
}

// 頂点バッファの作成
if(FAILED(g_pd3dmDevice->CreateVertexBuffer( sizeof(VERTEX)*4,
    0, VERTEX::FVF, vbpool, &g_pVB )))
    return E_FAIL;

 ここでは、頂点バッファの作成をします。

 まず最初にVRAMを備えたハードウェアなどで、VRAM上に頂点バッファを作成できるかどうかチェックしています。頂点バッファはメモリ上に置かれたポリゴンの頂点配列だと思って下さい。その操作と管理を行うのが、g_pVBです。今回は、四角形のポリゴンを描画したいので。頂点データを4つ置けるサイズを確保しておきます。

頂点配列のセット

InitApp()関数内
VERTEX    V[] =
{
    -1.0f, 1.0f, 0.0f, 0xffffffff, 0.0f, 0.0f,
    1.0f, 1.0f, 0.0f, 0xffffffff, 1.0f, 0.0f,
    -1.0, -1.0, 0.0f, 0xffffffff, 0.0f, 1.0f,
    1.0, -1.0, 0.0f, 0xffffffff, 1.0f, 1.0f,
};

VOID*    pV;
if(FAILED(g_pVB->Lock(0, 0, &pV, 0)))
    return E_FAIL;

// 頂点データのコピー
memcpy(pV, V, sizeof(V));

g_pVB->Unlock();

 今回はこのようにして、XY平面上に正方形を描画できるようにVにデータをセットします。

 この段階では、先に作成した頂点バッファ上にあるわけではないので、データをコピーする必要があります。頂点バッファをLock()することでコピーをします。また、Lock()が終ったら必ずUnlock()を行います。

テクスチャ

 テクスチャはポリゴン表面に貼られる模様や素材となる画像です。ビットマップファイルなどを読み込んで生成します。今回は直接ファイルパスから参照するのではなく、リソースファイルにして読み込む方法を紹介します。

リソースファイルの作成

リソースの追加
リソースの追加

 リソースの追加は、[ソリューション エクスプローラ]の[リソース ファイル]のフォルダを右クリックするか、[プロジェクト]メニューの[リソースの追加]などから上のダイアログを呼び出すことで行えます。今回は、インポートで64x64ピクセルのビットマップを追加しました。このビットマップをテクスチャとして使います。

 リソースが作成できるとプロジェクトに「resource.h」というヘッダーファイルができるので、インクルードしておきます。

 テクスチャですが、PocketPCはPCに比べると解像度が低く、メモリも少ないことには気をつけなければいけません。

テクスチャのロード

リソースからテクスチャの読み込み
// VRAMにテクスチャを置くことが可能であるかどうか?
D3DMPOOL texpool;
if (caps.SurfaceCaps & D3DMSURFCAPS_VIDTEXTURE)
{
    // VRAMを利用する
    texpool = D3DMPOOL_VIDEOMEM;
}
else
{
    // システムRAMを利用する
    texpool = D3DMPOOL_SYSTEMMEM;
}

// テクスチャのロード
// リソースからの読み込み
if( FAILED( D3DMXCreateTextureFromResourceEx( g_pd3dmDevice,
    GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_TEXTURE),
    D3DMX_DEFAULT,D3DMX_DEFAULT, 1, 0, D3DMFMT_UNKNOWN,
    texpool, D3DMX_FILTER_POINT, D3DMX_FILTER_POINT,
        0, NULL, NULL, &g_pTexture ) ) )
{
    return E_FAIL;
}

 テクスチャをロードする前にcaps.SurfaceCapsフラグを調べて、VRAMにテクスチャを置けるかどうかを確かめます。

 D3DMXCreateTextureFromResourceEx()関数を使うことでリソースからテクスチャを読み込みます。読み込まれたテクスチャはg_pTextureで管理と操作をします。今回はリソースから読み込んでいますが、ファイルパスを指定してテクスチャを読み込む関数としてD3DMXCreateTextureFromFileEx()関数があります。

 リソースではなく外部ファイルを使う場合には、実行ファイルと一緒に実行環境に配置する必要がありますが、その設定はプロジェクトのプロパティで[構成プロパティ]の[配置]にある[追加ファイル]に記述します。

追加ファイルの設定箇所
追加ファイルの設定箇所

次のページ
描画

この記事は参考になりましたか?

特集記事連載記事一覧

もっと読む

この記事の著者

高橋 誠史(タカハシ マサフミ)

僻地の大学院生です。GPUプログラミングとそのニュースを集めたサイトShader.jpを開いています。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/316 2006/03/08 16:14

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー