ホーム < ゲームつくろー! < DirectX技術編
その61 完全ホワイトボックスなスキンメッシュアニメーションの解説:サンプルプログラム
DirectX技術編その61「完全ホワイトボックスなスキンメッシュアニメーションの解説」で説明した内容を踏まえたサンプルプログラムです。実行するとワイヤーフレームで描かれたサンプルモデルがうねうねと動きます。
サンプルスクリーンショット。うねうねです。
このサンプルはスキンメッシュアニメーションを完全にホワイトボックス(暗黙コードや外部ファイルが無い)で実現しています。メッシュ自体もプログラム上で作成しています。描画には固定機能ではなくてシェーダを使っています。シェーダもソース内に書かれています。よって描画方法もホワイトボックスです。最低限の実装ではありますが、スキンメッシュに必要な事を全部行っている状態です。
メッシュの設計図やサンプルコードについてはDirectX技術編その61で死ぬほど詳しく説明しています。これでスキンメッシュアニメーションの壁を超えましょう〜(^-^)/
サンプルを動かすために必要なファイルはありません。空のプロジェクトを立ち上げて下のコードを全部コピペすれば動きます。うまく動かない場合は掲示板にご連絡下さい。
// 完全ホワイトボックスなスキンメッシュアニメーションテストプログラム // 作成者: IKD // HP : http://marupeke296.com #pragma comment(lib, "dxguid.lib") #pragma comment(lib, "d3d9.lib") #pragma comment(lib, "d3dx9.lib") #include <windows.h> #include <tchar.h> #include <d3d9.h> #include <d3dx9.h> _TCHAR gName[100] = _T("完全ホワイトボックスなスキンメッシュアニメーションテストプログラム"); int SkinMeshAppMain(LPDIRECT3DDEVICE9 g_pD3DDev); // 最低限の頂点情報 // 座標と各ボーンの重みそしてボーン行列番号があればスキンメッシュはできます! struct Vertex { D3DXVECTOR3 coord; D3DXVECTOR3 weight; unsigned char matrixIndex[4]; }; // ボーン構造体 struct Bone { int id; // ボーンID(通し番号) Bone *firstChild; // 第1子ボーン Bone *sibling; // 次の兄弟ボーン D3DXMATRIX offsetMat; // ボーンオフセット行列 D3DXMATRIX initMat; // 初期姿勢行列 D3DXMATRIX boneMat; // ボーン姿勢行列 D3DXMATRIX *combMatAry; // 合成姿勢行列配列へのポインタ Bone() : id(), firstChild(), sibling(), combMatAry() { D3DXMatrixIdentity(&initMat); D3DXMatrixIdentity(&offsetMat); D3DXMatrixIdentity(&boneMat); } }; // 最低限のシェーダ // 重み係数と頂点を動かすためのワールド変換行列の配列を渡します const char *vertexShaderStr = "float4x4 view : register(c0);" "float4x4 proj : register(c4);" "float4x4 world[12] : register(c8);" " " "struct VS_IN {" " float3 pos : POSITION;" " float3 blend : BLENDWEIGHT;" " int4 idx : BLENDINDICES;" "};" "struct VS_OUT {" " float4 pos : POSITION;" "};" "VS_OUT main( VS_IN In ) {" " VS_OUT Out = (VS_OUT)0;" " float w[3] = (float[3])In.blend;" " float4x4 comb = (float4x4)0;" " for ( int i = 0; i < 3; i++ )" " comb += world[In.idx[i]] * w[i];" " comb += world[In.idx[3]] * (1.0f - w[0] - w[1] - w[2]);" " " " Out.pos = mul( float4(In.pos, 1.0f), comb );" " Out.pos = mul( Out.pos, view );" " Out.pos = mul( Out.pos, proj );" " return Out;" "}"; // ピクセルシェーダは至って適当 // 好きなように点を穿って下さい。 const char *pixelShaderStr = "struct VS_OUT {" " float4 pos : POSITION;" "};" "float4 main( VS_OUT In ) : COLOR {" " return float4(1.0f, 1.0f, 1.0f, 1.0f);" "}" ""; // メイン int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { // ウィンドウプロシージャ struct WP { static LRESULT CALLBACK WndProc(HWND hWnd, UINT mes, WPARAM wParam, LPARAM lParam){ if(mes == WM_DESTROY) {PostQuitMessage(0); return 0;} return DefWindowProc(hWnd, mes, wParam, lParam); } }; // アプリケーションの初期化 HWND hWnd; WNDCLASSEX wcex ={sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW, WP::WndProc, 0, 0, hInstance, NULL, NULL, (HBRUSH)(COLOR_WINDOW+1), NULL, (_TCHAR*)gName, NULL}; if(!RegisterClassEx(&wcex)) return 0; RECT rect; const int width = 800, height = 600; SetRect( &rect, 0, 0, width, height ); AdjustWindowRect( &rect, WS_OVERLAPPEDWINDOW, FALSE); if(!(hWnd = CreateWindow(gName, gName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, rect.right - rect.left, rect.bottom - rect.top, NULL, NULL, hInstance, NULL))) return 0; // Direct3Dの初期化 LPDIRECT3D9 g_pD3D; LPDIRECT3DDEVICE9 g_pD3DDev; if( !(g_pD3D = Direct3DCreate9( D3D_SDK_VERSION )) ) return 0; D3DPRESENT_PARAMETERS d3dpp; d3dpp.BackBufferWidth = width; d3dpp.BackBufferHeight = height; d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8; d3dpp.BackBufferCount = 1; d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE; d3dpp.MultiSampleQuality = 0; d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; d3dpp.hDeviceWindow = NULL; d3dpp.Windowed = TRUE; d3dpp.EnableAutoDepthStencil = TRUE; d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8; d3dpp.Flags = 0; d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT; d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_ONE; if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &d3dpp, &g_pD3DDev ) ) ) if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &g_pD3DDev ) ) ) { g_pD3D->Release(); return 0; } // ウィンドウ表示 ShowWindow( hWnd, SW_SHOW ); // ボーンを動かすぞ SkinMeshAppMain(g_pD3DDev); g_pD3DDev->Release(); g_pD3D->Release(); return 0; } // スキンメッシュするぞー! int SkinMeshAppMain(LPDIRECT3DDEVICE9 g_pD3DDev) { // ポリゴンの頂点定義 // 頂点数は15個 Vertex vtx[15] = { { D3DXVECTOR3(-0.5000f, -2.2887f, 0.0f), D3DXVECTOR3(1.00f, 0.00f, 0.00f), {2, 0, 0, 0} }, { D3DXVECTOR3(-0.5000f, -1.2887f, 0.0f), D3DXVECTOR3(0.50f, 0.50f, 0.00f), {1, 2, 0, 0} }, { D3DXVECTOR3(-0.5000f, -0.2887f, 0.0f), D3DXVECTOR3(1.00f, 0.00f, 0.00f), {0, 0, 0, 0} }, { D3DXVECTOR3(-1.3660f, 0.2113f, 0.0f), D3DXVECTOR3(0.50f, 0.50f, 0.00f), {3, 4, 0, 0} }, { D3DXVECTOR3(-2.2321f, 0.7113f, 0.0f), D3DXVECTOR3(1.00f, 0.00f, 0.00f), {4, 0, 0, 0} }, { D3DXVECTOR3(-1.7321f, 1.5774f, 0.0f), D3DXVECTOR3(1.00f, 0.00f, 0.00f), {4, 0, 0, 0} }, { D3DXVECTOR3(-0.8660f, 1.0774f, 0.0f), D3DXVECTOR3(0.50f, 0.50f, 0.00f), {3, 4, 0, 0} }, { D3DXVECTOR3( 0.0000f, 0.5774f, 0.0f), D3DXVECTOR3(1.00f, 0.00f, 0.00f), {0, 0, 0, 0} }, { D3DXVECTOR3( 0.8660f, 1.0774f, 0.0f), D3DXVECTOR3(0.50f, 0.50f, 0.00f), {5, 6, 0, 0} }, { D3DXVECTOR3( 1.7321f, 1.5774f, 0.0f), D3DXVECTOR3(1.00f, 0.00f, 0.00f), {6, 0, 0, 0} }, { D3DXVECTOR3( 2.2321f, 0.7113f, 0.0f), D3DXVECTOR3(1.00f, 0.00f, 0.00f), {6, 0, 0, 0} }, { D3DXVECTOR3( 1.3660f, 0.2113f, 0.0f), D3DXVECTOR3(0.50f, 0.50f, 0.00f), {5, 6, 0, 0} }, { D3DXVECTOR3( 0.5000f, -0.2887f, 0.0f), D3DXVECTOR3(1.00f, 0.00f, 0.00f), {0, 0, 0, 0} }, { D3DXVECTOR3( 0.5000f, -1.2887f, 0.0f), D3DXVECTOR3(0.50f, 0.50f, 0.00f), {1, 2, 0, 0} }, { D3DXVECTOR3( 0.5000f, -2.2887f, 0.0f), D3DXVECTOR3(1.00f, 0.00f, 0.00f), {2, 0, 0, 0} }, }; // 頂点インデックス // ポリゴン数は13枚で三角形リストなので13×3 = 39個あります WORD idx[39] = { 0, 1, 14, 1, 13, 14, 1, 2, 13, 2, 12, 13, 2, 7, 12, 2, 6, 7, 2, 3, 6, 3, 5, 6, 3, 4, 5, 7, 8, 12, 8, 11, 12, 8, 9, 11, 9, 10, 11, }; // インデックスを辿って三角形リストを作成 Vertex v[39]; for ( int i = 0; i < 39; i++ ) v[i] = vtx[idx[i]]; // 頂点宣言 D3DVERTEXELEMENT9 declAry[] = { {0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0}, {0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_BLENDWEIGHT, 0}, {0, 24, D3DDECLTYPE_UBYTE4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_BLENDINDICES, 0}, D3DDECL_END() }; IDirect3DVertexDeclaration9 *decl = 0; g_pD3DDev->CreateVertexDeclaration( declAry, &decl ); ////////////////////////////////////////// // ボーン情報の作成 // 必要なのは // ・ボーンオフセット行列 // ・ボーン行列 ////////////// Bone *bones = new Bone[7]; // ボーン確保 // 親子関係を構築します // 今回は手でべたべたとやります bones[0].firstChild = &bones[1]; bones[1].firstChild = &bones[2]; bones[3].firstChild = &bones[4]; bones[5].firstChild = &bones[6]; bones[1].sibling = &bones[3]; bones[3].sibling = &bones[5]; // 初期姿勢の計算 // まずはローカル姿勢を設定して // 最終的に自分の親からの相対姿勢に直します。 D3DXMatrixRotationZ(&bones[0].initMat, D3DXToRadian(-90.0f)); D3DXMatrixRotationZ(&bones[1].initMat, D3DXToRadian(-90.0f)); D3DXMatrixRotationZ(&bones[2].initMat, D3DXToRadian(-90.0f)); D3DXMatrixRotationZ(&bones[3].initMat, D3DXToRadian(150.0f)); D3DXMatrixRotationZ(&bones[4].initMat, D3DXToRadian(150.0f)); D3DXMatrixRotationZ(&bones[5].initMat, D3DXToRadian( 30.0f)); D3DXMatrixRotationZ(&bones[6].initMat, D3DXToRadian( 30.0f)); bones[0].initMat._41 = 0.0000f; bones[0].initMat._42 = 0.0000f; bones[1].initMat._41 = 0.0000f; bones[1].initMat._42 = -1.0000f; bones[2].initMat._41 = 0.0000f; bones[2].initMat._42 = -2.0000f; bones[3].initMat._41 = -0.6830f; bones[3].initMat._42 = 0.3943f; bones[4].initMat._41 = -1.5490f; bones[4].initMat._42 = 0.8943f; bones[5].initMat._41 = 0.6830f; bones[5].initMat._42 = 0.3943f; bones[6].initMat._41 = 1.5490f; bones[6].initMat._42 = 0.8943f; // ボーンオフセット行列を計算しておきます。 // オフセット行列は各ボーンの「ローカル姿勢」の逆行列です。 D3DXMATRIX *combMat = new D3DXMATRIX[7]; // 合成変換行列。これがシェーダに渡ります for ( int i = 0; i < 7; i++ ) { bones[i].id = i; bones[i].combMatAry = combMat; D3DXMatrixInverse(&bones[i].offsetMat, 0, &bones[i].initMat); } // 初期姿勢を親の姿勢からの相対姿勢に直します。 // まず子の末端まで下りて、自分のローカル空間での初期姿勢 × 親のボーンオフセット行列で相対姿勢が出ます // 親子関係を辿るので再起関数が必要です。 struct CalcRelativeMat { static void run(Bone* me, D3DXMATRIX *parentoffsetMat) { if ( me->firstChild ) run( me->firstChild, &me->offsetMat ); if ( me->sibling ) run( me->sibling, parentoffsetMat ); if ( parentoffsetMat ) me->initMat *= *parentoffsetMat; } }; CalcRelativeMat::run( bones, 0 ); ///////////////////////////////////////// // シェーダのコンパイルとシェーダ作成 ////// ID3DXBuffer *shader, *error; IDirect3DVertexShader9 *vertexShader; IDirect3DPixelShader9 *pixelShader; HRESULT res = D3DXCompileShader( vertexShaderStr, (UINT)strlen(vertexShaderStr), 0, 0, "main", "vs_3_0", D3DXSHADER_PACKMATRIX_ROWMAJOR, &shader, &error, 0 ); if ( FAILED(res) ) { OutputDebugStringA( (const char*)error->GetBufferPointer() ); return 0; }; g_pD3DDev->CreateVertexShader( (const DWORD*)shader->GetBufferPointer(), &vertexShader ); shader->Release(); res = D3DXCompileShader( pixelShaderStr, (UINT)strlen(pixelShaderStr), 0, 0, "main", "ps_3_0", D3DXSHADER_PACKMATRIX_ROWMAJOR, &shader, &error, 0 ); if ( FAILED(res) ) { OutputDebugStringA( (const char*)error->GetBufferPointer() ); return 0; }; g_pD3DDev->CreatePixelShader( (const DWORD*)shader->GetBufferPointer(), &pixelShader ); shader->Release(); // 各種行列初期化 D3DXMATRIX view, proj; D3DXMatrixLookAtLH( &view, &D3DXVECTOR3(0.0f, -5.0f, -4.0f), &D3DXVECTOR3(0.0f, 0.0f, 0.0f), &D3DXVECTOR3(0.0f, 1.0f, 0.0f)); D3DXMatrixPerspectiveFovLH( &proj, D3DXToRadian(30), 64.0f/48, 1.0f, 10000.0f); //////////////////////////////////// // ボーン描画用オブジェクト // 本質ではないので気にしない〜(^-^; ///////// ID3DXMesh *boneObj[7]; float len[7] = {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}; for ( int i = 0; i < 7; i++ ) D3DXCreateCylinder(g_pD3DDev, 0.0f, 0.1f, len[i], 16, 1, &boneObj[i], 0); //////////////////////////////////////////// // 描画ループ部 // 毎フレームのボーンの姿勢更新がここで行われます ///////// MSG msg; float val = 0.0f; float a[7] = {0}; do{ val += 0.03f; while( PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) ) { DispatchMessage(&msg); } g_pD3DDev->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL, D3DCOLOR_XRGB(40,40,80), 1.0f, 0 ); g_pD3DDev->BeginScene(); /////////////////////////////////////// // ボーンの姿勢を更新 // 最終的には、 // [ボーンオフセット行列] × [ワールド空間でのボーンの姿勢] // を計算します。 // 各ボーンの初期姿勢からの差分姿勢(親空間ベース)を更新 // これは適当にぐにぐに動かして構わない部分です D3DXMATRIX defBone[7]; D3DXMatrixIdentity(&defBone[0]); for ( int i = 1; i < 7; i++ ) { D3DXMATRIX tmp; D3DXMatrixRotationY(&defBone[i], D3DXToRadian(sinf(val) * 70.0f)); } // 各ボーン行列の親空間ベースでの姿勢を更新 // 差分姿勢×初期姿勢(共に親空間ベース)です。 for ( int i = 0; i < 7; i++ ) bones[i].boneMat = defBone[i] * bones[i].initMat; // 親空間ベースにある各ボーン行列をローカル空間ベースの姿勢に変換 // ここは親子関係に従って行列を掛ける必要があります // 掛ける順番は 子 × 親 です。 D3DXMATRIX global; D3DXMatrixRotationZ( &global, val * 0.1f ); struct UpdateBone { static void run( Bone* me, D3DXMATRIX *parentWorldMat ) { me->boneMat *= *parentWorldMat; me->combMatAry[me->id] = me->offsetMat * me->boneMat; if ( me->firstChild ) run( me->firstChild, &me->boneMat ); if ( me->sibling ) run( me->sibling, parentWorldMat ); }; }; UpdateBone::run( bones, &global ); // シェーダ設定 // 変数を書き込むレジスタ位置はシェーダに書いてありますよ。 g_pD3DDev->SetVertexShader( vertexShader ); g_pD3DDev->SetPixelShader( pixelShader ); g_pD3DDev->SetVertexShaderConstantF(0, (const float*)&view, 4); g_pD3DDev->SetVertexShaderConstantF(4, (const float*)&proj, 4); g_pD3DDev->SetVertexShaderConstantF(8, (const float*)combMat, 4 * 7); // ポリゴン描画 // ワイヤーフレーム描画でカリング無しで g_pD3DDev->SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME); g_pD3DDev->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); g_pD3DDev->SetVertexDeclaration( decl ); g_pD3DDev->DrawPrimitiveUP(D3DPT_TRIANGLELIST, 13, &v, sizeof(Vertex)); //////////////////////////////// // ボーンオブジェクト描画 // 本質ではないので気にしない部分です(^-^; /////// { D3DMATERIAL9 material = {{1.0f, 1.0f, 1.0f, 1.0f}}; material.Power = 10.0f; D3DLIGHT9 light = {D3DLIGHT_DIRECTIONAL, {1.0f, 0.7f, 0.5f, 1.0f}}; light.Direction = (D3DVECTOR)D3DXVECTOR3(1.0f, 1.0f, 1.0f); g_pD3DDev->SetLight( 0, &light ); g_pD3DDev->LightEnable( 0, TRUE) ; g_pD3DDev->SetRenderState( D3DRS_LIGHTING, TRUE ); g_pD3DDev->SetMaterial( &material ); g_pD3DDev->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID); g_pD3DDev->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW); g_pD3DDev->SetVertexShader( 0 ); g_pD3DDev->SetPixelShader( 0 ); g_pD3DDev->SetTransform( D3DTS_VIEW, &view ); g_pD3DDev->SetTransform( D3DTS_PROJECTION, &proj ); D3DXMATRIX boneObjRot; D3DXMatrixRotationY( &boneObjRot, D3DXToRadian(-90.0f) ); boneObjRot._41 = 0.5f; for ( int i = 0; i < 7; i++ ) { g_pD3DDev->SetTransform( D3DTS_WORLD, &(boneObjRot * bones[i].boneMat) ); boneObj[i]->DrawSubset( 0 ); } } g_pD3DDev->EndScene(); g_pD3DDev->Present( NULL, NULL, NULL, NULL ); }while(msg.message != WM_QUIT); for ( int i = 0; i < 7; i++ ) boneObj[i]->Release(); vertexShader->Release(); pixelShader->Release(); decl->Release(); delete[] bones; delete[] combMat; return 0; }