今回の記事では3D上に板を表示して回転する方法について書いていきます。
『3Dの座標系と座標変換』の内容を実際に実装していくことになります。
前提条件として必要な知識なので先に読むことを推薦します。
又、『Scene管理』で書いたシーン管理システムを使ってシーンを作ります。
では早速実装の方法について説明していきます。
3D用の頂点初期化
先ずは頂点フォーマットを用意します。
#define FVF_CUSTOM3D (D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1) struct SimpleVertex { float x, y, z; DWORD color; float u, v; };
以前はD3DFVF_XYZRHWを使っていましたが、RHWはトランスフォーム済み(座標変換済み)という意味です。
3Dでは座標変換が必要になるのでD3DFVF_XYZを使います。
他はカラーとテクスチャーを持たせています。
次に初期化です。
static TextureData g_texData; static IDirect3DVertexBuffer9* g_pVertexBuffer; // 初期化 static bool SceneInit(void) { // assetsフォルダ内のbridge.pngをテクスチャーとして読み込み if( !LoadTexture(TEXT("assets/bridge.png"), &g_texData) ) { return false; } float aspect = (float)g_texData.srcWidth / (float)g_texData.srcHeight; // 高さを1.0として幅を画像のアスペクト比に合わせます。 float rect[] = { -0.5f * aspect, 0.5f, 0.5f * aspect, -0.5f, }; // テクスチャーサイズから画像サイズのUVを取得(画像が2の累乗であれば1.0fになる) float u = (float)g_texData.srcWidth / (float)g_texData.width; float v = (float)g_texData.srcHeight / (float)g_texData.height; // 画像サイズに合わせてUVも追加した頂点データ(Z値は原点におく) const SimpleVertex vertices[4] = { // 1つ目の矩形 { rect[0], rect[1], 0.0f, 0xffffffff, 0.0f, 0.0f }, { rect[2], rect[1], 0.0f, 0xffffffff, u, 0.0f }, { rect[0], rect[3], 0.0f, 0xffffffff, 0.0f, v }, { rect[2], rect[3], 0.0f, 0xffffffff, u, v }, }; // 頂点バッファの生成 g_pVertexBuffer = CreateVertexBuffer(vertices, sizeof(vertices)); if( g_pVertexBuffer == NULL ) { return false; } return true; } // 解放 static void SceneFinal(void) { ReleaseTexture(&g_texData); SAFE_RELEASE(g_pVertexBuffer); }
これで頂点とテクスチャーの初期化が出来ました。
頂点のサイズが1.0と小さくなった以外は特に2Dと変わりませんね。
-0.5から0.5で配置しているのは原点を中心にくるようにするためです。
この頂点バッファの位置をローカル座標といいます。
3Dモデルの回転処理
次に更新処理を更新処理では板を回転させる計算を実装していきます。
DirectXでは行列等の計算をするヘルパー関数が用意されています。
今回はその関数を使用して実装していきます。
static float g_angle = 0.0f; // ワールド行列 static D3DXMATRIX g_WorldMatrix; // ビュー行列 static D3DXMATRIX g_ViewMatrix; // プロジェクション行列 static D3DXMATRIX g_ProjectionMatrix; // 更新 static void SceneUpdate(void) { g_angle += D3DXToRadian(1.0f); D3DXMATRIX trans, rot, scale; // 拡縮、回転、移動の行列を作成 D3DXMatrixScaling(&scale, 1.0f, 1.0f, 1.0f); D3DXMatrixRotationY(&rot, g_angle); D3DXMatrixTranslation(&trans, 0.0f, 0.0f, 0.0f); // 拡縮、回転、移動の行列を合成 D3DXMatrixMultiply(&g_WorldMatrix, &scale, &rot); D3DXMatrixMultiply(&g_WorldMatrix, &g_WorldMatrix, &trans); // カメラの位置と視点、上方向のベクトルを元にビュー行列の作成 D3DXVECTOR3 cameraPos(0.0f, 0.5f, -3.0f); D3DXVECTOR3 targetPos(0.0f, 0.0f, 0.0f); D3DXVECTOR3 upVec(0.0f, 1.0f, 0.0f); D3DXMatrixLookAtLH(&g_ViewMatrix, &cameraPos, &targetPos, &upVec); // プロジェクション行列の作成 D3DXMatrixPerspectiveFovLH( &g_ProjectionMatrix, D3DXToRadian(45.0f), // 画角45° 640.0f / 480.0f, // 画面比率 0.01f, // Nearクリップの設定 1000.0f // Farクリップの設定 ); }
これで各座標系に変換する行列が完成です。
g_WorldMatrixはワールド座標系に変換するためのワールド行列が完成です。
今回は拡縮と移動はしないので1.0と0.0fで埋めてあります。
g_ViewMatrixのビュー行列はコメントにある通りカメラの位置と視点、そして上を指すベクトルを元にビュー座標系に変換する行列として作成しています。
g_ProjectionMatrixは画角と画面比率、NearとFarの距離を設定することで視錐台の範囲をプロジェクション座標系に変換する行列を作成しています。
最後に実際に描画していきます。
// 描画 static void SceneRender(void) { IDirect3DDevice9* pDevice = GetDirect3DDevice(); // View, Projection行列の設定 pDevice->SetTransform(D3DTS_VIEW, &g_ViewMatrix); pDevice->SetTransform(D3DTS_PROJECTION, &g_ProjectionMatrix); // World行列の設定 pDevice->SetTransform(D3DTS_WORLD, &g_WorldMatrix); // 通常書き込みモードに設定 SetRenderMode(ERenderMode::Normal, false); // 裏も見えるように両面描画にする pDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); // 今回はライトはないのでライトを無効にする pDevice->SetRenderState(D3DRS_LIGHTING, FALSE); // テクスチャーの設定 pDevice->SetTexture(0, g_texData.pTexture); // 頂点バッファの設定 pDevice->SetStreamSource(0, g_pVertexBuffer, 0, sizeof(SimpleVertex)); // 頂点フォーマットの指定 pDevice->SetFVF(FVF_CUSTOM3D); // 描画 pDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2); }
依然と違う場所が数点ほどあります。
先ずはSetTransformです。
それぞれ第一引数で用途を指定してビュー、プロジェクション、ワールドの行列を設定しています。
次にSetRenderStateです。
3Dで回転させるとなると裏面が無くては裏返った時に見えません。
なのでカリングモードのNONEで設定することで両面描画にしています。
あとはライトの設定をオフにすることでライティングを無効にしています。
これで実行した結果がこちらです。
↑は画像が回転して確度が変わっていくところが分かります。
回転していくと裏返って画像が反転していきます。
今回の記事の実装まで行ったサンプルプロジェクトを下記にアップしてあります。
プロジェクトファイル