この業界、「どのくらいできるか」の指標に『アニメーションのプログラムがつくれるか?』というのがあります。
今まで、面倒くさくてプログラムしてなかったのですが、最近そのような質問をされて
『時間が無くてやってない。もちろんできるけど』なんて、誤魔化しました。
『プログラムできるなんてうそだろ』とバレる前にやっておきます。
まぁ、いつものように適当にファイルが入っています。
CSkinModel.h | スキンメッシュに関連したインターフェイス。 |
CSkinModel.cpp | スキンメッシュに関連したソースの寄せ集め。 |
draw.h | 描画の各関数の定義。 |
draw.cpp | メインの描画部分。 |
main.h | 基本的な定数など。 |
main.cpp | 描画に関係しないシステム的な部分。 |
font.h | フォント。fpsの表示に使用。 |
font.cpp | フォント。fpsの表示に使用。 |
resource.h | メニューなどのリソースのヘッダ。 |
あと、モデルと、実行ファイル及び、プロジェクトファイルが入っています。
今回、SDKに付属の tiny.x を入れているんですが、これっていいですかね。
実際には、ライブラリとして使いたい「くれくれ君」が多いでしょうから、関連するファイルを一つ(CSkinModel.cpp)にまとめました。
中身は、サンプルの SkinnedMesh のラッパーです。
ただし、単独のオブジェクトになるように、複数のメッシュが登録できる仕組みは取り除いて簡単にしました。
draw.cpp でオブジェクトを使用しています。
最初にすることですが、ヘッダファイルを読み込んでオブジェクトを生成します。
draw.cpp 0015: #include "CSkinModel.h" 0016: 0017: CSkinModel model;
CSkinModel が、実際に取り扱うクラスです。CSkinModel.h には、他にもクラスがありますが、 CSkinModel に必要なので定義されているためで、CSkinModel があれば十分に使えます。
次に必要なのが初期化です。
CSkinModel::Init(DWORD,LPDIRECT3DDEVICE8,D3DCAPS8) で初期化します。
CSkinModel は、内部で時間差をとって表示するフレームを決めるので、開始時間も初期化時に受け渡します。
次にCSkinModel::Load(TCHAR*) で、Xファイルをロードします。
draw.cpp 0027: HRESULT InitRender(LPDIRECT3D8 pD3D, LPDIRECT3DDEVICE8 pD3DDev) 0028: { 0029: HRESULT hr; 0030: DWORD time; 0031: 0032: timeBeginPeriod(10); // 10ms でタイマーを呼び出す 0033: time = timeGetTime(); 0034: 0035: // モデルの初期化 0036: D3DCAPS8 caps; 0037: pD3D->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps); 0038: model.Init(time, pD3DDev, caps); 0039: 0040: // もし、ファイルがなかったら、ディフォルトのファイルを読む 0041: if('\0' == model.GetFileName()[0]){ 0042: hr = model.Load("tiny.x"); 0043: } 0044: 0048: return S_OK; 0049: }
実際のレンダリングのときですが、
CSkinModel::FrameMove(DWORD) で、時間を進めて、CSkinModel::Render() で描画します。
ポーズしたいときは、CSkinModel::FrameMove(DWORD) だけ通さなければ、同じ姿勢で表示されます。
それ以外に、ワールド行列(に連動した回転や平行移動)や、ビュー行列を指定できます。
動かしたいときは、CSkinModel::SetTrans(D3DXMATRIXA16) を指定すれば、位置が動きます。
draw.cpp 0050: // ---------------------------------------------------------------------------- 0051: // Name: Render() 0052: // Desc: ポリゴンの描画 0053: //----------------------------------------------------------------------------- 0054: void Render(LPDIRECT3DDEVICE8 pD3DDev) 0055: { 0056: DWORD time = timeGetTime(); 0057: 0058: D3DXMATRIXA16 m; 0059: 0060: // 重心移動 0061: D3DXMatrixIdentity(&m); 0062: D3DXMatrixTranslation(&m, 0, -model.GetRadius() * 0.5f, 0); 0063: model.SetTrans(m); 0064: // 回転 0065: D3DXMatrixIdentity(&m); 0066: D3DXMatrixRotationYawPitchRoll(&m, rot.y, rot.x, rot.z); 0067: model.SetRot(m); 0068: 0069: // カメラの設定 0070: D3DXMatrixIdentity(&m); 0071: D3DXMatrixTranslation(&m, 0, 0, -model.GetRadius() * 2.5f); 0072: model.SetView(m); 0073: // モデルの表示 0074: model.FrameMove(time); 0075: model.Render(); 0076: }
最後は、お決まりの後片付けです。「Release()」を、呼んでください。
draw.cpp 0078: //----------------------------------------------------------------------------- 0079: // Name: CleanRender() 0080: // Desc: 後始末 0081: //----------------------------------------------------------------------------- 0082: void CleanRender(LPDIRECT3DDEVICE8 pD3DDev) 0083: { 0084: model.Release(); 0085: }
これぐらいなら、どなたでも使えるのではないでしょうか。
もっと複雑なことをされたい方やシェーダーを変えたい方は以降で説明する内部構造から、改良を加えていってください。
初期化で一番大事なことは、頂点シェーダーの作成です(後は、引数を保存しているだけです)。
今回は、なるべくファイル数を減らしたかったので、頂点シェーダーをプログラム中に埋め込んでいます。
頂点シェーダーでは4つまでの行列を合成してスキンの処理を行いますが、
現在の頂点シェーダーでは、分岐命令が使えないので、混ぜ合わせる行列の数に対応して、頂点シェーダーが4つに分かれます。
ここらへんは頂点シェーダー2.0になって、本質的に良くなる一つの点ですね。
今回は、「行列パレット」を使います。「行列パレット」とは、変換のためのローカル行列を配列として格納したものです。
実際にブレンドに使用する(4つ以下の)行列を「行列パレット」から引っ張ってきます。
絵を書くときにパレットから原色を取り出して色を作るように行列を混ぜ合わせて、実際に用いる行列を作ります。
行列を引っ張るのに、初登場のアドレスレジスタを使います。
アドレスレジスタは、定数レジスタに関して、配列のように番号を指定して
動的に参照する番号を変えるためのレジスタです。
この仕組みがないと、各頂点に関して異なる行列が引っ張ってこれないので、
サポートしていない頂点シェーダー1.0では、ソフトウェア的にしかシェーダーを実行できません。
行列パレットでは4x3の正方でない行列をつかいます。これは、行列パレットに使用する行列数が多いためで、 今回のパレット数では最大が29個(c9~c95で87個の定数レジスタから3列の行列を生成する)にもなるので、自明な4列目の行列は省略して少しでもレジスタを節約しています。
行列1つの(骨が入っていない)モデルの表示をする頂点シェーダーは次のとおりです。
まぁ、サンプルをそのまま引っ張ったので、コメントが多いですが、やっている事は短いです。
CSkinModel.cpp 0033: // ---------------------------------------------------------------------------- 0034: // v0 = 頂点の位置 0035: // v1 = 重み 0036: // v2 = 重みのインデックス 0037: // v3 = 法線 0038: // v4 = テクスチャー座標 0039: //------------------------------------------------------------------------------ 0040: // r0.w = Last blend weight 0041: // r1 = 重みのインデックス 0042: // r2 = 一時的な座標 0043: // r3 = 一時的な法線 0044: // r4 = カメラ空間での座標 0045: // r5 = カメラ空間での法線 0046: //------------------------------------------------------------------------------ 0047: // c9-c95 = 行列パレット 0048: // c8 = 平行光源 0049: // c7 = 環境光 0050: // c2-c5 = 射影行列 0051: // c1 = 光源方向 0052: // c0 = {1, ライトのべき乗, 0, 1020.01}; 0053: //------------------------------------------------------------------------------ 0054: // oPos = 位置 0055: // oD0 = 平行光 0056: // oD1 = すぺきゅらー 0057: // oT0 = テクスチャー座標 0058: //------------------------------------------------------------------------------ 0059: 0060: const char VertexShader0[] = { 0061: "vs.1.1 // シェーダ バージョン 1.1 \n"\ 0062: "mul r1,v2.zyxw,c0.wwww // Geforce3 で UBYTE4 がないのを補う \n"\ 0063: 0064: "mov a0.x,r1.x // 1 つめの行列を設定 \n"\ 0065: "m4x3 r4,v0,c[a0.x + 9] \n"\ 0066: "m3x3 r5,v3,c[a0.x + 9] \n"\ 0067: 0068: "mov r4.w,c0.x // 透視変換 \n"\ 0069: "m4x4 oPos,r4,c2 \n"\ 0070: 0071: "dp3 r5.w, r5, r5 // 法線の規格化 \n"\ 0072: "rsq r5.w, r5.w \n"\ 0073: "mul r5, r5, r5.w \n"\ 0074: 0075: "dp3 r1.x, r5, c1 // ライティング \n"\ 0076: "lit r1, r1 // \n"\ 0077: "mul r0, r1.y, c8 // 平行光源 \n"\ 0078: "add r0, r0, c7 // +環境光 \n"\ 0079: "min oD0, r0, c0.x // 1以下にクランプ \n"\ 0080: "mov oD1, c0.zzzz // すぺきゅらーの設定 \n"\ 0081: 0082: "mov oT0, v4 // テクスチャー座標のコピー " 0083: };
a0.x がアドレスレジスタです。65行目の m4x3 命令などで、行列パレットの最初の位置である定数レジスタ9をオフセットにして行列を引っ張ってきます。
ただし、係数や並び順が頂点情報に入れられて時点のものでは使いづらいので並び替えます。
後は、透視変換したり、法線は規格化して光源計算を行ったり、テクスチャー座標をコピーしてテクスチャーを張ります。
次に一気に飛ばして行列を4つ混ぜる場合を見ましょう。
1つの場合との違いはアドレスレジスタを4回変更して、4つの行列を合成していることです。
それらに重みの係数 v1 を掛けて、混ぜ合わせます。ただし、全ての係数の和が1になるように4つめの係数はそれ以外の3つの係数から求めました。
CSkinModel.cpp 0163: const char VertexShader3[] = { 0164: "vs.1.1 // シェーダ バージョン 1.1 \n"\ 0165: "mul r1,v2.zyxw,c0.wwww // Geforce3 で UBYTE4 がないのを補う \n"\ 0166: 0167: "dp3 r0.w,v1.xyz,c0.xxx // 最後の係数はウェイトの合計が1から算出 \n"\ 0168: "add r0.w,-r0.w,c0.x \n"\ 0169: 0170: "mov a0.x,r1.x // 1 つめの行列を設定 \n"\ 0171: "m4x3 r4,v0,c[a0.x + 9] \n"\ 0172: "m3x3 r5,v3,c[a0.x + 9] \n"\ 0173: "mul r4,r4,v1.xxxx // 係数をかけて合成する \n"\ 0174: "mul r5,r5,v1.xxxx \n"\ 0175: 0176: "mov a0.x,r1.y // 2 つめの行列を設定 \n"\ 0177: "m4x3 r2,v0,c[a0.x + 9] \n"\ 0178: "m3x3 r3,v3,c[a0.x + 9] \n"\ 0179: "mad r4,r2,v1.yyyy,r4 // 係数をかけて合成する \n"\ 0180: "mad r5,r3,v1.yyyy,r5 \n"\ 0181: 0182: "mov a0.x,r1.z // 3 つめの行列を設定 \n"\ 0183: "m4x3 r2,v0,c[a0.x + 9] \n"\ 0184: "m3x3 r3,v3,c[a0.x + 9] \n"\ 0185: "mad r4,r2,v1.zzzz,r4 // 係数をかけて合成する \n"\ 0186: "mad r5,r3,v1.zzzz,r5 \n"\ 0187: 0188: "mov a0.x,r1.w // 4 つめの行列を設定 \n"\ 0189: "m4x3 r2,v0,c[a0.x + 9] \n"\ 0190: "m3x3 r3,v3,c[a0.x + 9] \n"\ 0191: "mad r4,r2,r0.wwww,r4 // 係数をかけて合成する \n"\ 0192: "mad r5,r3,r0.wwww,r5 \n"\ 0193: 0194: "mov r4.w,c0.x // 座標変換 \n"\ 0195: "m4x4 oPos,r4,c2 \n"\ 0196: 0197: "dp3 r5.w, r5, r5 // 法線の規格化 \n"\ 0198: "rsq r5.w, r5.w \n"\ 0199: "mul r5, r5, r5.w \n"\ 0200: 0201: "dp3 r1.x, r5, c1 // ライティング \n"\ 0202: "lit r1, r1 // \n"\ 0203: "mul r0, r1.y, c8 // 平行光源 \n"\ 0204: "add r0, r0, c7 // +環境光 \n"\ 0205: "min oD0, r0, c0.x // 1以下にクランプ \n"\ 0206: "mov oD1, c0.zzzz // すぺきゅらーの設定 \n"\ 0207: 0208: "mov oT0, v4 // テクスチャー座標のコピー " 0209: };
行列が2つと3つの場合の合成はちょうどその中間のようになします。
シャーダープログラムの生成は次のようになりますが、特に見るべき点は無いので、適当に流してください。
CSkinModel.cpp 1475: //----------------------------------------------------------------------------- 1476: // 頂点シェーダーの生成 1477: //----------------------------------------------------------------------------- 1478: HRESULT CSkinModel::_CreateVertexShader() 1479: { 1480: LPD3DXBUFFER pCode; 1481: HRESULT hr; 1482: 1483: // インデックススキニングのための頂点シェーダーの生成 1484: DWORD dwIndexedVertexDecl1[] = { 1485: D3DVSD_STREAM( 0 ), 1486: D3DVSD_REG( 0, D3DVSDT_FLOAT3 ), // Position of first mesh 1487: D3DVSD_REG( 2, D3DVSDT_D3DCOLOR ), // 合成する行列の指定 1488: D3DVSD_REG( 3, D3DVSDT_FLOAT3 ), // 法線 1489: D3DVSD_REG( 4, D3DVSDT_FLOAT2 ), // テクスチャー座標 1490: D3DVSD_END() 1491: }; 1492: 1493: DWORD dwIndexedVertexDecl2[] = { 1494: D3DVSD_STREAM( 0 ), 1495: D3DVSD_REG( 0, D3DVSDT_FLOAT3 ), // Position of first mesh 1496: D3DVSD_REG( 1, D3DVSDT_FLOAT1 ), // 合成の重み 1497: D3DVSD_REG( 2, D3DVSDT_D3DCOLOR ), // 合成する行列の指定 1498: D3DVSD_REG( 3, D3DVSDT_FLOAT3 ), // 法線 1499: D3DVSD_REG( 4, D3DVSDT_FLOAT2 ), // テクスチャー座標 1500: D3DVSD_END() 1501: }; 1502: 1503: DWORD dwIndexedVertexDecl3[] = { 1504: D3DVSD_STREAM( 0 ), 1505: D3DVSD_REG( 0, D3DVSDT_FLOAT3 ), // Position of first mesh 1506: D3DVSD_REG( 1, D3DVSDT_FLOAT2 ), // 合成の重み 1507: D3DVSD_REG( 2, D3DVSDT_D3DCOLOR ), // 合成する行列の指定 1508: D3DVSD_REG( 3, D3DVSDT_FLOAT3 ), // 法線 1509: D3DVSD_REG( 4, D3DVSDT_FLOAT2 ), // テクスチャー座標 1510: D3DVSD_END() 1511: }; 1512: 1513: DWORD dwIndexedVertexDecl4[] = { 1514: D3DVSD_STREAM( 0 ), 1515: D3DVSD_REG( 0, D3DVSDT_FLOAT3 ), // Position of first mesh 1516: D3DVSD_REG( 1, D3DVSDT_FLOAT3 ), // 合成の重み 1517: D3DVSD_REG( 2, D3DVSDT_D3DCOLOR ), // 合成する行列の指定 1518: D3DVSD_REG( 3, D3DVSDT_FLOAT3 ), // 法線 1519: D3DVSD_REG( 4, D3DVSDT_FLOAT2 ), // テクスチャー座標 1520: D3DVSD_END() 1521: }; 1522: 1523: DWORD* dwIndexedVertexDecl[] = { 1524: dwIndexedVertexDecl1, 1525: dwIndexedVertexDecl2, 1526: dwIndexedVertexDecl3, 1527: dwIndexedVertexDecl4, 1528: }; 1529: 1530: char *filename[] = { 1531: "skinmesh1.vsh", 1532: "skinmesh2.vsh", 1533: "skinmesh3.vsh", 1534: "skinmesh4.vsh", 1535: }; 1536: 1537: const char *shader_program[] = { 1538: VertexShader0, 1539: VertexShader1, 1540: VertexShader2, 1541: VertexShader3, 1542: }; 1543: const int size[] = { 1544: sizeof(VertexShader0)-1, 1545: sizeof(VertexShader1)-1, 1546: sizeof(VertexShader2)-1, 1547: sizeof(VertexShader3)-1, 1548: }; 1549: 1550: // アドレス レジスタが使えないならソフトウェアT&L 1551: DWORD bUseSW = (m_d3dCaps.VertexShaderVersion < D3DVS_VERSION(1, 1)); 1552: 1553: for (DWORD i = 0; i < 4; ++i) { 1554: // シェーダープログラムの読み込み 1555: if ( FAILED(hr = D3DXAssembleShader( shader_program[i] , size[i], 0 , NULL , &pCode , NULL)) ) return hr; 1556: 1557: // 頂点シェーダーの生成 1558: if( FAILED( hr = m_pD3DDev->CreateVertexShader( dwIndexedVertexDecl[i], 1559: (DWORD*)pCode->GetBufferPointer(), 1560: &(m_dwIndexedVertexShader[i]) , bUseSW ? D3DUSAGE_SOFTWAREPROCESSING : 0 ) ) ) 1561: return hr; 1562: 1563: pCode->Release(); 1564: } 1565: 1566: return S_OK; 1567: }
では、いよいよXファイルのロードです。ここが一番しんどい部分です。
大雑把に「Xファイルのロード」と、「レンダリングステートなどの設定」分かれますが、
「Xファイルのロード」では、ファイルの構造を調べ、その構造にあったメッシュを生成し、アニメをロードし
親子関係を構築するなどのいろいろなことをしなくてはなりません。
使われている関数やメソッドをまとめると次のようになります。
しんどいですが、順次見ていきましょう。
呼びだされた関数の内部ですが、大まかに2つの部分に分かれます。
1574: //----------------------------------------------------------------------------- 1575: // ロード 1576: //----------------------------------------------------------------------------- 1577: HRESULT CSkinModel::[Load( const TCHAR *filename ) 1578: { 1579: HRESULT hr; 1580: 1581: strcpy(m_szPath, filename); 1582: 1583: // メッシュのロード 1584: if(FAILED(hr = this->_LoadMeshHierarchy(m_pD3DDev))) return hr; 1585: 1586: // SetRenderState の設定 1587: if (FAILED(hr = RestoreDeviceObjects(m_pD3DDev))) return hr; 1588: 1589: return S_OK; 1590: }
CSkinModel::_LoadMeshHierarchy が本質的なロード関数です。全体の流れを制御します。
0698: //----------------------------------------------------------------------------- 0699: // 読み込んだファイルを解読する 0700: //----------------------------------------------------------------------------- 0701: HRESULT CSkinModel::_LoadMeshHierarchy(LPDIRECT3DDEVICE8 pD3DDev) 0702: { 0703: HRESULT hr = S_OK; 0704: const TCHAR* pszFile = m_szPath; 0705: SDrawElement *pdeMesh = NULL; 0706: LPDIRECTXFILE pxofapi = NULL; // DirectXFile オブジェクト 0707: LPDIRECTXFILEENUMOBJECT pxofenum = NULL; // DirectXFile 列挙オブジェクト 0708: LPDIRECTXFILEDATA pxofobjCur = NULL; // DirectXFile データ オブジェクト 0709: DWORD dwOptions = 0; 0710: int cchFileName; 0711: 0712: if (pszFile == NULL) return E_INVALIDARG;// ありえないけど、一応・・・ 0713: 0714: pdeMesh = new SDrawElement(); 0715: delete pdeMesh->pframeRoot; 0716: pdeMesh->pframeAnimHead = NULL; 0717: 0718: pdeMesh->pframeRoot = new SFrame(); 0719: if (pdeMesh->pframeRoot == NULL) {// メモリ不足 0720: hr = E_OUTOFMEMORY; 0721: goto e_Exit; 0722: } 0723: 0724: cchFileName = strlen(m_szPath); 0725: if (cchFileName < 2) {// 有効なファイル名でないなら失敗扱い 0726: hr = E_FAIL; 0727: goto e_Exit; 0728: } 0729: 0730: // ------------------------------------------------------------------------ 0731: // XFile の実際の読み込み 0732: if (FAILED(hr = DirectXFileCreate(&pxofapi))) goto e_Exit; 0733: 0734: // カスタム テンプレートを登録する 0735: hr = pxofapi->RegisterTemplates((LPVOID)D3DRM_XTEMPLATES, D3DRM_XTEMPLATE_BYTES); 0736: if (FAILED(hr)) goto e_Exit; 0737: 0738: // 列挙オブジェクトを生成する 0739: // 列挙オブジェクトは、グローバル ユニーク識別子 (GUID) または名前でデータ オブジェクトを取得するのに使う 0740: if (FAILED(hr = pxofapi->CreateEnumObject( 0741: (LPVOID)m_szPath // DXFILELOAD_FROMFILE のときはファイル名 0742: , DXFILELOAD_FROMFILE // 「ファイル」からデータを読み取る 0743: , &pxofenum // 作成された列挙オブジェクト 0744: ))) goto e_Exit; 0745: 0746: 0747: // ------------------------------------------------------------------------ 0748: // データを読み込んで、フレームにがんがん登録していく 0749: while (SUCCEEDED(pxofenum->GetNextDataObject(&pxofobjCur))) { 0750: hr = pdeMesh->pframeRoot->LoadFrames(pxofobjCur, pdeMesh, dwOptions, *this); 0751: RELEASE(pxofobjCur); 0752: 0753: if (FAILED(hr)) goto e_Exit; 0754: } 0755: 0756: // ------------------------------------------------------------------------ 0757: // 骨構造の行列を構成する 0758: hr = pdeMesh->pframeRoot->FindBones(pdeMesh); 0759: if (FAILED(hr)) goto e_Exit; 0760: 0761: // ------------------------------------------------------------------------ 0762: // ファイル名を保存しておく 0763: delete []pdeMesh->szName; 0764: pdeMesh->szName = new char[cchFileName+1]; 0765: if (pdeMesh->szName == NULL) { 0766: hr = E_OUTOFMEMORY;// メモリ不足 0767: goto e_Exit; 0768: } 0769: memcpy(pdeMesh->szName, m_szPath, cchFileName+1); 0770: 0771: // ------------------------------------------------------------------------ 0772: // 現在登録されているモデルを削除し。読み込んだモデルを選択オブジェクトにする 0773: _DeleteSelectedMesh(); 0774: m_pdeMesh = pdeMesh; 0775: 0776: // ------------------------------------------------------------------------ 0777: // ときどき使うのでバウンディング球を作成しておく 0778: if (FAILED(hr = pdeMesh->CalculateBoundingSphere())) goto e_Exit; 0779: 0780: // 射影行列を設定しておく 0781: _SetProjectionMatrix(); 0782: 0783: // 現在の時間とループするフレーム数を設定する 0784: m_pdeMesh->fCurTime = 0.0f; 0785: 0786: // アニメをしないときの(全体移動の)行列を保存しておく 0787: D3DXMatrixTranslation(&m_pdeMesh->pframeRoot->matRot, 0788: -pdeMesh->vCenter.x, 0789: -pdeMesh->vCenter.y, 0790: -pdeMesh->vCenter.z); 0791: m_pdeMesh->pframeRoot->matRotOrig = m_pdeMesh->pframeRoot->matRot; 0792: 0793: e_Exit: 0794: // もう使わないものを開放 0795: RELEASE(pxofobjCur); 0796: RELEASE(pxofenum); 0797: RELEASE(pxofapi); 0798: if (FAILED(hr)) delete pdeMesh; 0799: 0800: return hr; 0801: }
説明すつ前に、少し構造を示しておきましょうか。
外の関数とのインターフェイスは、CSkinModel ですが、重要な属性は、SDrawElement *m_pdeMesh です。
彼が基点となり、骨の親子関係を構築する階層構造を作ります。
SDrawElement は、主なメンバーとして SFrame::pframeRoot、SFrame::pframeAnimHead を、もっています。
SFrame::pframeRoot は親子関係を保存するための木構造です。
SFrame::pframeAnimHead は、(時間の設定を容易にするための)アニメーション構造のツリー構造です。
SFrame に階層構造の情報が入ります。
いままで出てきた階層構造は、骨格を考えることになります。
例えば、下のような骨格を持つモデルが考えられます。
橙色の「へそ」の部分から全ての矢印は伸びていますが、ここを「親」とよびます。
親から直接伸びている部分が子供です。また、矢印が複数伸びている場合(「へそ」からは3本伸びていますね。
)それぞれの子供の関係は兄弟(sibling)と呼ばれます。
ただし、SFrame は、骨を表現する行列だけでなく、メッシュやアニメーションデータも格納します。
各親および子供から緑色の矢印が出ています。この矢印は骨の回転運動による基底行列の向きの変化を表しています(どこが基準の位置か明示されていませんが、それはアニメーションをしていないときの矢印の向きが自然な向きです)。
骨の行列の平行移動の部分は、白い矢印で表されます。
ワールド座標から見たときの子供の行列は、親の行列に子供自信の行列を掛けます。
また、掛けた行列をさらに子供(元の親から見て孫ですね)に渡すことによって、親の行列の情報が子孫に伝わっていきます。
この変換によって、腰を回したときに、肩も腕も同じように回ることが表現できます。
ここでも骨格は、あくまで説明用に適当に作ったものです。
実際にサンプルモデルの骨格がこのようになっているかはまったく知りませんし、
皆さんがどのようなワンスキンの骨格を作られているかはわかりません。
行列の合成に関しても、ここで見ておきましょう。
黄色い子供に注目します。
黄色い子供から出ている矢印は、紫色で囲まれた3つです。この3つの行列が「黄色い子供に関連付けられた」メッシュを混ぜ合わせる合成行列になります。
黄色い子供の行列自体はワールド行列に設定され、重心移動のような効果として用いられます。
さて、_LoadMeshHierarchy では、最初に CSkinModel::m_pdeMesh や、CSkinModel::pdeMesh->pframeRoot 等のメモリを確保します。
まぁ、使うためには必ず確保するものですし、特に問題はないでしょう。
次に DirectXFileCreate で、Xファイルを取り扱うための準備をします。
準備ができたら、RegisterTemplates で、「テンプレート」を読み込みます。
「テンプレート」とは、Xファイルを読み込むためのフォーマットが格納されたファイルです。
ここでは、いにしえのRMで規定されたフォーマットを読むように宣言します。
この「テンプレート」を変更すれば、プログラムの変更なしに独自ファーマットの読み込みができます
(が、誰もそんなことしないでしょうね)。
読み込むテンプレートが決まったら CreateEnumObject で、データを順次読み込むように設定し、
while (SUCCEEDED(pxofenum->GetNextDataObject(&pxofobjCur))) {...}
で、どんどん読み込んでいきます。どう読み込むかは後でやっていきましょう。
読み込みが終了したら、SFrame::FindBones で親子関係を経巡りながら、
それぞれのメッシュに骨の行列のポインタを確保していきます。
後々IKなどで個別に骨を動かしたくなったら、SMeshContainer::m_pBoneMatrix[i] を操作すればよいでしょう。
0802: //----------------------------------------------------------------------------- 0803: // 骨の構造の解読 0804: //----------------------------------------------------------------------------- 0805: HRESULT SFrame::FindBones(SDrawElement *pde) 0806: { 0807: HRESULT hr = S_OK; 0808: 0809: for(SMeshContainer *pMc=this->pmcMesh ; pMc ; pMc=pMc->pmcNext ){ 0810: if (pMc->m_pSkinMesh) { 0811: char** pBoneName = static_cast<char**>(pMc->m_pBoneNamesBuf->GetBufferPointer()); 0812: for (DWORD i = 0; i < pMc->m_pSkinMesh->GetNumBones(); ++i) { 0813: // 骨のフレームの行列を骨の行列に設定する 0814: pMc->m_pBoneMatrix[i] = &(pde->FindFrame(pBoneName[i])->matCombined); 0815: } 0816: } 0817: } 0818: 0819: // 子供や子供兄弟も作成する 0820: for(SFrame *pChild=this->pframeFirstChild ; pChild ; pChild = pChild->pframeSibling ){ 0821: if (FAILED(hr = pChild->FindBones(pde))) return hr; 0822: } 0823: 0824: return S_OK; 0825: }
_LoadMeshHierarchy では、FindBones が終了したらロードに成功したもほぼ同じなので、名前を保存したり、メッシュを登録します。
また、射影行列を設定したり、時間やアニメをしていないときの行列を保存するなどの変数の初期化を行います。
では、xファイルの中身のデータを見る部分にいきます。
xファイルの各データは、GetType によって得られるさまざまな種類のデータがあります。
TID_D3DRMMesh | メッシュ | 頂点情報など。いわゆるモデルデータ |
TID_D3DRMFrameTransformMatrix | 行列 | 各骨に関連した行列データ。 |
TID_D3DRMAnimationSet | アニメーションセット | その中にさらにアニメーションデータが入っている。 |
TID_D3DRMAnimation | アニメーション | 1つのアニメーションのデータが入っている |
TID_D3DRMFrame | フレーム | 子のフレームの情報が入っている(再帰的に子供のデータを読み込む) |
それらを適当な方法で読み込みます。
0826: //----------------------------------------------------------------------------- 0827: // フレームの読み込み 0828: //----------------------------------------------------------------------------- 0829: HRESULT SFrame::LoadFrames(LPDIRECTXFILEDATA pxofobjCur, SDrawElement *pde, 0830: DWORD options, CSkinModel &model) 0831: { 0832: HRESULT hr = S_OK; 0833: LPDIRECTXFILEDATA pxofobjChild = NULL; 0834: LPDIRECTXFILEOBJECT pxofChild = NULL; 0835: const GUID *type; 0836: DWORD cbSize; 0837: D3DXMATRIX *pmatNew; 0838: DWORD cchName; 0839: 0840: // タイプを調べて、種類に応じたロードを行う 0841: if (FAILED(hr = pxofobjCur->GetType(&type))) goto e_Exit; 0842: 0843: if(TID_D3DRMMesh==*type){ 0844: // ★D3D RM の頃からの伝統ある(?)メッシュファイル 0845: hr = this->_LoadMesh(pxofobjCur, options, model); 0846: if (FAILED(hr))goto e_Exit; 0847: }else 0848: if(TID_D3DRMFrameTransformMatrix==*type){ 0849: // ★姿勢行列 0850: hr = pxofobjCur->GetData(NULL, &cbSize, (PVOID*)&pmatNew); 0851: if (FAILED(hr)) goto e_Exit; 0852: // フレームを呼び出した(親の)フレームの行列を設定する 0853: this->matRot = *pmatNew; 0854: this->matRotOrig = *pmatNew; 0855: }else 0856: if(TID_D3DRMAnimationSet==*type){ 0857: // ★アニメーションの集合 0858: this->_LoadAnimationSet(pxofobjCur, pde, options, model); 0859: }else 0860: if(TID_D3DRMAnimation==*type){ 0861: // ★アニメーション 0862: this->_LoadAnimation(pxofobjCur, pde, options, model); 0863: }else 0864: if(TID_D3DRMFrame==*type){ 0865: // ★さらに新規フレーム 0866: // メモリ確保 0867: SFrame *pframeCur; 0868: if (NULL == (pframeCur = new SFrame())) { 0869: hr = E_OUTOFMEMORY; goto e_Exit; 0870: } 0871: // 名前があれば名前を読み込む 0872: if (FAILED(hr = pxofobjCur->GetName(NULL, &cchName))) goto e_Exit; 0873: if (cchName > 0) { 0874: if (NULL == (pframeCur->szName = new char[cchName])) { 0875: hr = E_OUTOFMEMORY; 0876: goto e_Exit; 0877: } 0878: if (FAILED(hr = pxofobjCur->GetName(pframeCur->szName, &cchName))) goto e_Exit; 0879: } 0880: // 新規フレームの追加 0881: this->AddFrame(pframeCur); 0882: 0883: // 子供のデータをがんがん読み込む 0884: while (SUCCEEDED(pxofobjCur->GetNextObject(&pxofChild))) { 0885: hr = pxofChild->QueryInterface(IID_IDirectXFileData,(LPVOID *)&pxofobjChild); 0886: if (SUCCEEDED(hr)) {// 子供に FileData を要求して、FileData があったら再起的に読み込む 0887: hr = pframeCur->LoadFrames(pxofobjChild, pde, options, model); 0888: if (FAILED(hr)) goto e_Exit; 0889: RELEASE(pxofobjChild); 0890: } 0891: RELEASE(pxofChild); 0892: } 0893: } 0894: 0895: e_Exit: 0896: RELEASE(pxofobjChild); 0897: RELEASE(pxofChild); 0898: return hr; 0899: }
それでは、それぞれの読み込みを見ましょう。
先ずはメッシュの読み込みです。
0900: //----------------------------------------------------------------------------- 0901: // モデルの読み込み 0902: //----------------------------------------------------------------------------- 0903: HRESULT SFrame::_LoadMesh(LPDIRECTXFILEDATA pxofobjCur, DWORD options, CSkinModel &model) 0904: { 0905: HRESULT hr = S_OK; 0906: SMeshContainer *pmcMesh = NULL; 0907: LPD3DXBUFFER pbufMaterials = NULL; 0908: LPD3DXBUFFER pbufAdjacency = NULL; 0909: DWORD cchName; 0910: UINT cFaces; 0911: LPDWORD pAdjacencyIn; 0912: 0913: // メモリの確保 0914: if (NULL == (pmcMesh = new SMeshContainer())) { 0915: hr = E_OUTOFMEMORY; goto e_Exit; 0916: } 0917: 0918: // 名前を調べる 0919: if (FAILED(hr = pxofobjCur->GetName(NULL, &cchName))) goto e_Exit; 0920: if (cchName > 0) { 0921: if (NULL == (pmcMesh->szName=new char[cchName])) { 0922: hr = E_OUTOFMEMORY; goto e_Exit; 0923: } 0924: if (FAILED(hr=pxofobjCur->GetName(pmcMesh->szName, &cchName))) goto e_Exit; 0925: } 0926: // XFile を読み込む 0927: hr = D3DXLoadSkinMeshFromXof(pxofobjCur, options, model.GetDevice(), &pbufAdjacency, &pbufMaterials, &pmcMesh->cMaterials, 0928: &pmcMesh->m_pBoneNamesBuf, &pmcMesh->m_pBoneOffsetBuf, &pmcMesh->m_pSkinMesh); 0929: if (FAILED(hr)) { 0930: if (hr == D3DXERR_LOADEDMESHASNODATA) hr = S_OK; 0931: goto e_Exit; 0932: } 0933: 0934: // 各ポリゴンに関する隣接面の情報をコピーする 0935: cFaces = pmcMesh->m_pSkinMesh->GetNumFaces(); 0936: pAdjacencyIn = static_cast<LPDWORD>(pbufAdjacency->GetBufferPointer()); 0937: if (NULL == (pmcMesh->m_rgiAdjacency=new DWORD[cFaces * 3])) { 0938: hr = E_OUTOFMEMORY; goto e_Exit; 0939: } 0940: memcpy(pmcMesh->m_rgiAdjacency, pAdjacencyIn, cFaces * 3 * sizeof(DWORD)); 0941: 0942: // スキンのデータを処理する 0943: if (pmcMesh->m_pSkinMesh->GetNumBones()) { 0944: // 骨の数の計算 0945: model.SetMaxBorns(max(pmcMesh->m_pSkinMesh->GetNumBones(), model.GetMaxBorns())); 0946: // 骨で使う行列分のメモリの確保 0947: pmcMesh->m_pBoneMatrix = new D3DXMATRIX*[pmcMesh->m_pSkinMesh->GetNumBones()]; 0948: if (pmcMesh->m_pBoneMatrix == NULL) goto e_Exit; 0949: pmcMesh->m_pBoneOffsetMat = reinterpret_cast<D3DXMATRIX*>(pmcMesh->m_pBoneOffsetBuf->GetBufferPointer()); 0950: // 実際に表示で使うための(取り扱いやすい)メッシュを構成する 0951: if (FAILED(hr = model.GenerateMesh(pmcMesh))) goto e_Exit; 0952: } else { 0953: pmcMesh->m_pSkinMesh->GetOriginalMesh(&(pmcMesh->pMesh)); 0954: pmcMesh->m_pSkinMesh->Release(); // もう使わないので開放 0955: pmcMesh->m_pSkinMesh = NULL; // 骨がないのでスキンメッシュではない 0956: pmcMesh->cpattr = pmcMesh->cMaterials; // 質感を引っ張る 0957: } 0958: 0959: if ((pbufMaterials == NULL) || (pmcMesh->cMaterials == 0)) { 0960: // 質感情報なければ適当なものを作成 0961: pmcMesh->rgMaterials = new D3DMATERIAL8[1]; 0962: if (pmcMesh->rgMaterials == NULL) { 0963: hr = E_OUTOFMEMORY; goto e_Exit; 0964: } 0965: memset(pmcMesh->rgMaterials, 0, sizeof(D3DMATERIAL8)); 0966: pmcMesh->rgMaterials[0].Diffuse.r = 0.5f; 0967: pmcMesh->rgMaterials[0].Diffuse.g = 0.5f; 0968: pmcMesh->rgMaterials[0].Diffuse.b = 0.5f; 0969: pmcMesh->rgMaterials[0].Specular = pmcMesh->rgMaterials[0].Diffuse; 0970: // ダミーテクスチャー 0971: pmcMesh->pTextures = new LPDIRECT3DTEXTURE8[1]; 0972: if (pmcMesh->pTextures == NULL) { 0973: hr = E_OUTOFMEMORY; goto e_Exit; 0974: } 0975: pmcMesh->pTextures[0] = NULL; 0976: } else { 0977: // 質感情報あれば読み込む 0978: pmcMesh->rgMaterials = new D3DMATERIAL8[pmcMesh->cMaterials]; 0979: pmcMesh->pTextures = new LPDIRECT3DTEXTURE8[pmcMesh->cMaterials]; 0980: if (pmcMesh->rgMaterials == NULL || pmcMesh->pTextures == NULL) { 0981: hr = E_OUTOFMEMORY; 0982: goto e_Exit; 0983: } 0984: 0985: LPD3DXMATERIAL pMaterials = (LPD3DXMATERIAL)pbufMaterials->GetBufferPointer(); 0986: 0987: for (UINT i = 0; i < pmcMesh->cMaterials; i++) { 0988: pmcMesh->rgMaterials[i] = pMaterials[i].MatD3D; 0989: // テクスチャーがあればロード 0990: pmcMesh->pTextures[i] = NULL; 0991: if (pMaterials[i].pTextureFilename != NULL) { 0992: hr = D3DXCreateTextureFromFile(model.GetDevice(), pMaterials[i].pTextureFilename, &(pmcMesh->pTextures[i])); 0993: if (FAILED(hr)) pmcMesh->pTextures[i] = NULL; 0994: } 0995: } 0996: } 0997: 0998: // 親のフレームにメッシュを登録する 0999: this->AddMesh(pmcMesh); 1000: pmcMesh = NULL; 1001: 1002: e_Exit: 1003: delete pmcMesh; 1004: 1005: RELEASE(pbufAdjacency); 1006: RELEASE(pbufMaterials); 1007: 1008: return hr; 1009: }
メッシュの情報は SMeshContainer に収められます。
SMeshContainer は、質感やテクスチャーの情報があります。
D3DXLoadSkinMeshFromXof で、LPDIRECTXFILEDATA にあるメッシュデータを読み込みます。
その後、各ポリゴンの隣接面の情報や骨の情報を保存した後、GenerateMesh で、メッシュを適当な構造に再構成します。
そして、質感情報やテクスチャーのロードなど、スキンでないメッシュでも行った手順を踏みます。
メッシュの再構成ですが、描画方法によって処理が異なります。
1420: //----------------------------------------------------------------------------- 1421: // メッシュの生成 1422: //----------------------------------------------------------------------------- 1423: HRESULT CSkinModel::GenerateMesh(SMeshContainer *pmcMesh) 1424: { 1425: HRESULT hr = S_OK; 1426: LPDIRECT3DDEVICE8 pD3DDev = NULL; 1427: DWORD cFaces = pmcMesh->m_pSkinMesh->GetNumFaces(); 1428: 1429: if (FAILED(hr=pmcMesh->m_pSkinMesh->GetDevice(&pD3DDev))) goto e_Exit; 1430: 1431: RELEASE(pmcMesh->pMesh); 1432: delete [] this->m_pBoneMatrices; 1433: 1434: pmcMesh->pMesh = NULL; 1435: this->m_pBoneMatrices = NULL; 1436: 1437: // 方法によって生成するものを変える 1438: switch(this->GetMethod()){ 1439: case D3DNONINDEXED: 1440: hr = _GenerateMeshNonIndexed(pmcMesh); 1441: break; 1442: case D3DINDEXEDVS: 1443: hr = _GenerateMeshIndexedVs(pmcMesh); 1444: break; 1445: case D3DINDEXED: 1446: hr = _GenerateMeshIndexed(pmcMesh); 1447: break; 1448: case SOFTWARE: 1449: hr = _GenerateMeshSoftware(pmcMesh); 1450: break; 1451: } 1452: if(SUCCEEDED(hr)) pmcMesh->m_Method = this->GetMethod(); 1453: 1454: e_Exit: 1455: 1456: RELEASE(pD3DDev); 1457: return hr; 1458: }
今回は、頂点シェーダーにしか興味がないので、_GenerateMeshIndexedVs(D3DINDEXEDVS)を見ます。
固定レジスタの数に限界があるので、行列パレットの最大値を制限します(あれ、ここで28個ですね。29個までいける気もするのですが…)。
次に頂点シェーダーのバージョンをチェックして、ハードウェアで実行できるか確認します。
だめならソフトウェアT&Lで実行です。
次に ConvertToIndexedBlendedMesh で、頂点パレットが使えるように変換します。
また、フレキシブル頂点フォーマットを確認して、混ぜ合わせる行列の数、すなわち頂点シェーダーの種類を確認します。
最後にフレキシブル頂点フォーマットにあったメッシュに再変換します。
1293: //----------------------------------------------------------------------------- 1294: // メッシュの生成(D3DINDEXEDVS) 1295: //----------------------------------------------------------------------------- 1296: HRESULT CSkinModel::_GenerateMeshIndexedVs(SMeshContainer *pmcMesh) 1297: { 1298: HRESULT hr = S_OK; 1299: // 行列パレットの数を設定する(最大28) 1300: pmcMesh->m_paletteSize = min(28, pmcMesh->m_pSkinMesh->GetNumBones()); 1301: 1302: // アドレス レジスタが使えないならソフトウェアT&L 1303: DWORD flags = D3DXMESHOPT_VERTEXCACHE; 1304: if (D3DVS_VERSION(1, 1) <= this->GetCaps()->VertexShaderVersion ) { 1305: pmcMesh->m_bUseSW = false; 1306: flags |= D3DXMESH_MANAGED; 1307: } else { 1308: pmcMesh->m_bUseSW = true; 1309: flags |= D3DXMESH_SYSTEMMEM; 1310: } 1311: 1312: // インデックス付きのブレンドされたメッシュへ変換する 1313: hr = pmcMesh->m_pSkinMesh->ConvertToIndexedBlendedMesh(flags, pmcMesh->m_rgiAdjacency, pmcMesh->m_paletteSize, NULL, 1314: &pmcMesh->cpattr, &pmcMesh->m_pBoneCombinationBuf, NULL, NULL, &pmcMesh->pMesh); 1315: if (FAILED(hr)) return hr; 1316: 1317: // 最大の混合する数を調べる 1318: if ((pmcMesh->pMesh->GetFVF() & D3DFVF_POSITION_MASK) != D3DFVF_XYZ) { 1319: pmcMesh->m_maxFaceInfl = ((pmcMesh->pMesh->GetFVF() & D3DFVF_POSITION_MASK) - D3DFVF_XYZRHW) / 2; 1320: } else { 1321: pmcMesh->m_maxFaceInfl = 1; 1322: } 1323: 1324: // FVF を変更して、それにあったメッシュを作成する 1325: DWORD newFVF = (pmcMesh->pMesh->GetFVF() & D3DFVF_POSITION_MASK) | D3DFVF_NORMAL | D3DFVF_TEX1 | D3DFVF_LASTBETA_UBYTE4; 1326: if (newFVF != pmcMesh->pMesh->GetFVF()) { 1327: LPD3DXMESH pMesh; 1328: hr = pmcMesh->pMesh->CloneMeshFVF(pmcMesh->pMesh->GetOptions(), newFVF, this->GetDevice(), &pMesh); 1329: if (!FAILED(hr)) { 1330: pmcMesh->pMesh->Release(); 1331: pmcMesh->pMesh = pMesh; 1332: pMesh = NULL; 1333: } 1334: } 1335: return hr; 1336: }
以上で、メッシュの読み込みは完了します。
次にアニメーションセットです。
アニメーションセットは、アニメーションセットを確認するフレームを追加してから、
登録されたアニメーションを読めるだけ読んでいきます。
1010: //----------------------------------------------------------------------------- 1011: // アニメーションの集合のロード 1012: //----------------------------------------------------------------------------- 1013: HRESULT SFrame::_LoadAnimationSet(LPDIRECTXFILEDATA pxofobjCur, SDrawElement *pde, 1014: DWORD options, CSkinModel &model) 1015: { 1016: SFrame *pframeCur; 1017: const GUID *type; 1018: HRESULT hr = S_OK; 1019: LPDIRECTXFILEDATA pxofobjChild = NULL; 1020: LPDIRECTXFILEOBJECT pxofChild = NULL; 1021: DWORD cchName; 1022: 1023: // 新規にフレームを追加 1024: if (NULL == (pframeCur=new SFrame())) { 1025: hr = E_OUTOFMEMORY; goto e_Exit; 1026: } 1027: this->AddFrame(pframeCur); 1028: 1029: pframeCur->bAnimationFrame = true; // アニメーションのフレームであることを宣言 1030: 1031: // 名前を読み込む 1032: if (FAILED(hr = pxofobjCur->GetName(NULL, &cchName))) goto e_Exit; 1033: if (cchName > 0) { 1034: if (NULL == (pframeCur->szName = new char[cchName])) { 1035: hr = E_OUTOFMEMORY; goto e_Exit; 1036: } 1037: if (FAILED(hr = pxofobjCur->GetName(pframeCur->szName, &cchName))) goto e_Exit; 1038: } 1039: 1040: // 読める限りのデータを読んで、アニメーションのデータをロードする 1041: while (SUCCEEDED(pxofobjCur->GetNextObject(&pxofChild))) { 1042: hr = pxofChild->QueryInterface(IID_IDirectXFileData,(LPVOID *)&pxofobjChild); 1043: if (SUCCEEDED(hr)) { 1044: if (FAILED(hr = pxofobjChild->GetType(&type))) goto e_Exit; 1045: 1046: if( TID_D3DRMAnimation == *type ) {// アニメのデータだけ 1047: hr = pframeCur->_LoadAnimation(pxofobjChild, pde, options, model); 1048: if (FAILED(hr)) goto e_Exit; 1049: } 1050: RELEASE(pxofobjChild); 1051: } 1052: RELEASE(pxofChild); 1053: } 1054: 1055: e_Exit: 1056: RELEASE(pxofobjChild); 1057: RELEASE(pxofChild); 1058: return hr; 1059: }
最後にアニメーションです。
ここも分岐があります。
実際のアニメーションデータ TID_D3DRMAnimationKey 以外にも、子供のフレームデータや「参照」として別のフレームのデータを引っ張ってきたりします。
まぁ、サンプル通りにしておきましょう。
IID_IDirectXFileDataReference | 別のフレームのデータの参照としてデータが存在する |
TID_D3DRMFrame | さらに子供のフレームがある |
TID_D3DRMAnimationOptions | オプション(今回は見ない) |
TID_D3DRMAnimationKey | キーフレームデータ |
キーフレームデータの中にも種類があります。
0 | 回転 | クオータニオンのデータで回転する |
1 | 拡縮 | 特定の時刻(キーフレーム)での値が入っています |
2 | 平行移動 | キーフレームでの値が入っています |
4 | 行列 | 行列のデータがそのまま入っています |
ちなみにキーフレーム(法)とは、アニメのデータを全ての時間持っているのでは無く、時間を空けて持ち、その間は適当な方法で補間する方法のことです。
同じアニメーションでも、キーフレームが少ないほうがデータが小さく、綺麗なアニメーションになる傾向があります。
以下にキーフレームの数を減らすかは、アニメータの腕、ないしはコンバーターを作る人の腕にかかっています。
1060: //----------------------------------------------------------------------------- 1061: // アニメのロード 1062: //----------------------------------------------------------------------------- 1063: HRESULT SFrame::_LoadAnimation(LPDIRECTXFILEDATA pxofobjCur, SDrawElement *pde, 1064: DWORD options, CSkinModel &model) 1065: { 1066: HRESULT hr = S_OK; 1067: SRotateKeyXFile *pFileRotateKey; 1068: SScaleKeyXFile *pFileScaleKey; 1069: SPositionKeyXFile *pFilePosKey; 1070: SMatrixKeyXFile *pFileMatrixKey; 1071: SFrame *pframeCur; 1072: LPDIRECTXFILEDATA pxofobjChild = NULL; 1073: LPDIRECTXFILEOBJECT pxofChild = NULL; 1074: LPDIRECTXFILEDATAREFERENCE pxofobjChildRef = NULL; 1075: const GUID *type; 1076: DWORD dwSize; 1077: PBYTE pData; 1078: DWORD dwKeyType; 1079: DWORD cKeys; 1080: DWORD iKey; 1081: DWORD cchName; 1082: char *szFrameName; 1083: 1084: // 新規にフレームを追加 1085: if (NULL == (pframeCur = new SFrame())) { 1086: hr = E_OUTOFMEMORY; goto e_Exit; 1087: } 1088: this->AddFrame(pframeCur); 1089: pde->AddAnimationFrame(pframeCur); // SDrawElement にアニメを追加 1090: 1091: pframeCur->bAnimationFrame = true; // アニメーションのフレームであることを宣言 1092: 1093: // 読める限りのデータを読んで、アニメーションのデータをロードする 1094: while (SUCCEEDED(pxofobjCur->GetNextObject(&pxofChild))) { 1095: // 参照をしらべる 1096: hr = pxofChild->QueryInterface(IID_IDirectXFileDataReference,(LPVOID *)&pxofobjChildRef); 1097: if (SUCCEEDED(hr)) { 1098: hr = pxofobjChildRef->Resolve(&pxofobjChild); 1099: if (SUCCEEDED(hr)) { 1100: // タイプを調べて、フレームの情報だけを抜き出す 1101: if (FAILED(hr = pxofobjChild->GetType(&type))) goto e_Exit; 1102: 1103: if( TID_D3DRMFrame == *type ) { 1104: if (pframeCur->pframeToAnimate != NULL) {// あるのか? 1105: hr = E_INVALIDARG; goto e_Exit; 1106: } 1107: 1108: // 名前を読み込む 1109: if (FAILED(hr = pxofobjChild->GetName(NULL, &cchName))) goto e_Exit; 1110: if (cchName == 0) { 1111: hr = E_INVALIDARG; goto e_Exit; 1112: } 1113: szFrameName = (char*)_alloca(cchName); 1114: if (szFrameName == NULL) { 1115: hr = E_OUTOFMEMORY; goto e_Exit; 1116: } 1117: if (FAILED(hr = pxofobjChild->GetName(szFrameName, &cchName))) goto e_Exit; 1118: 1119: // 名前からフレームを検索する 1120: pframeCur->pframeToAnimate = pde->FindFrame(szFrameName); 1121: if (pframeCur->pframeToAnimate == NULL) { 1122: hr = E_INVALIDARG; goto e_Exit; 1123: } 1124: } 1125: RELEASE(pxofobjChild); 1126: } 1127: RELEASE(pxofobjChildRef); 1128: } else { 1129: // 参照がなければデータを調べる 1130: hr = pxofChild->QueryInterface(IID_IDirectXFileData,(LPVOID *)&pxofobjChild); 1131: if (SUCCEEDED(hr)) 1132: { 1133: // タイプを調べて、種類に応じた処理を行う 1134: if (FAILED(hr = pxofobjChild->GetType(&type))) goto e_Exit; 1135: 1136: if ( TID_D3DRMFrame == *type ) {// フレーム 1137: hr = pframeCur->LoadFrames(pxofobjChild, pde, options, model); 1138: if (FAILED(hr)) goto e_Exit; 1139: } else if ( TID_D3DRMAnimationOptions == *type ) { 1140: // オプション(処理なし) 1141: } else if ( TID_D3DRMAnimationKey == *type ) { 1142: // アニメーションのキーフレームデータ 1143: if (FAILED(hr = pxofobjChild->GetData( NULL, &dwSize, (PVOID*)&pData ))) goto e_Exit; 1144: 1145: dwKeyType = ((DWORD*)pData)[0]; 1146: cKeys = ((DWORD*)pData)[1]; 1147: 1148: switch(dwKeyType){ 1149: case 0:// 回転 1150: if (pframeCur->m_pRotateKeys != NULL) { 1151: hr = E_INVALIDARG; goto e_Exit; 1152: } 1153: if (NULL==(pframeCur->m_pRotateKeys = new SRotateKey[cKeys])) { 1154: hr = E_OUTOFMEMORY; goto e_Exit; 1155: } 1156: pframeCur->m_cRotateKeys = cKeys; 1157: pFileRotateKey = (SRotateKeyXFile*)(pData + (sizeof(DWORD) * 2)); 1158: for (iKey = 0;iKey < cKeys; iKey++) { 1159: pframeCur->m_pRotateKeys[iKey].dwTime = pFileRotateKey->dwTime; 1160: pframeCur->m_pRotateKeys[iKey].quatRotate.x = pFileRotateKey->x; 1161: pframeCur->m_pRotateKeys[iKey].quatRotate.y = pFileRotateKey->y; 1162: pframeCur->m_pRotateKeys[iKey].quatRotate.z = pFileRotateKey->z; 1163: pframeCur->m_pRotateKeys[iKey].quatRotate.w = pFileRotateKey->w; 1164: pFileRotateKey++; 1165: } 1166: break; 1167: case 1:// 拡大縮小 1168: if (pframeCur->m_pScaleKeys != NULL) { 1169: hr = E_INVALIDARG; goto e_Exit; 1170: } 1171: if (NULL==(pframeCur->m_pScaleKeys = new SScaleKey[cKeys])) { 1172: hr = E_OUTOFMEMORY; goto e_Exit; 1173: } 1174: pframeCur->m_cScaleKeys = cKeys; 1175: pFileScaleKey = (SScaleKeyXFile*)(pData + (sizeof(DWORD) * 2)); 1176: for (iKey = 0;iKey < cKeys; iKey++) { 1177: pframeCur->m_pScaleKeys[iKey].dwTime = pFileScaleKey->dwTime; 1178: pframeCur->m_pScaleKeys[iKey].vScale = pFileScaleKey->vScale; 1179: pFileScaleKey++; 1180: } 1181: break; 1182: case 2:// 平行移動 1183: if (pframeCur->m_pPositionKeys != NULL) { 1184: hr = E_INVALIDARG; goto e_Exit; 1185: } 1186: if (NULL==(pframeCur->m_pPositionKeys = new SPositionKey[cKeys])) { 1187: hr = E_OUTOFMEMORY; goto e_Exit; 1188: } 1189: pframeCur->m_cPositionKeys = cKeys; 1190: pFilePosKey = (SPositionKeyXFile*)(pData + (sizeof(DWORD) * 2)); 1191: for (iKey = 0;iKey < cKeys; iKey++) { 1192: pframeCur->m_pPositionKeys[iKey].dwTime = pFilePosKey->dwTime; 1193: pframeCur->m_pPositionKeys[iKey].vPos = pFilePosKey->vPos; 1194: pFilePosKey++; 1195: } 1196: break; 1197: case 4:// 行列そのもの 1198: if (pframeCur->m_pMatrixKeys != NULL) { 1199: hr = E_INVALIDARG; goto e_Exit; 1200: } 1201: if (NULL==(pframeCur->m_pMatrixKeys = new SMatrixKey[cKeys])) { 1202: hr = E_OUTOFMEMORY; goto e_Exit; 1203: } 1204: pframeCur->m_cMatrixKeys = cKeys; 1205: pFileMatrixKey = (SMatrixKeyXFile*)(pData + (sizeof(DWORD) * 2)); 1206: for (iKey = 0;iKey < cKeys; iKey++) { 1207: pframeCur->m_pMatrixKeys[iKey].dwTime = pFileMatrixKey->dwTime; 1208: pframeCur->m_pMatrixKeys[iKey].mat = pFileMatrixKey->mat; 1209: pFileMatrixKey++; 1210: } 1211: break; 1212: default: 1213: hr = E_INVALIDARG; goto e_Exit; 1214: break; 1215: } 1216: } 1217: RELEASE(pxofobjChild); 1218: } 1219: } 1220: RELEASE(pxofChild); 1221: } 1222: 1223: e_Exit: 1224: RELEASE(pxofobjChild); 1225: RELEASE(pxofChild); 1226: RELEASE(pxofobjChildRef); 1227: return hr; 1228: }
と、このような長い経過を経て、ロードが終了します。
あと説明していないのは、レンダリングステートなどの設定ですね。
深度バッファを有効にしたり、ライトの構造体を設定したりします。
1611: //----------------------------------------------------------------------------- 1612: // シーンの初期化 1613: //----------------------------------------------------------------------------- 1614: HRESULT CSkinModel::RestoreDeviceObjects(LPDIRECT3DDEVICE8 pD3DDev) 1615: { 1616: HRESULT hr = S_OK; 1617: D3DLIGHT8 light; 1618: 1619: // レンダリングのための状態の設定 1620: pD3DDev->SetRenderState( D3DRS_DITHERENABLE, TRUE ); 1621: pD3DDev->SetRenderState( D3DRS_ZENABLE, TRUE ); 1622: pD3DDev->SetRenderState( D3DRS_SPECULARENABLE, FALSE ); 1623: pD3DDev->SetRenderState( D3DRS_NORMALIZENORMALS, TRUE ); 1624: 1625: pD3DDev->SetRenderState( D3DRS_CULLMODE, D3DCULL_CW ); 1626: pD3DDev->SetRenderState( D3DRS_LIGHTING, TRUE ); 1627: pD3DDev->SetTextureStageState( 0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR ); 1628: pD3DDev->SetTextureStageState( 0, D3DTSS_MINFILTER, D3DTEXF_LINEAR ); 1629: 1630: pD3DDev->SetRenderState( D3DRS_COLORVERTEX, FALSE ); 1631: 1632: if (m_pdeMesh != NULL) _SetProjectionMatrix(); 1633: 1634: 1635: // ソフトウェアT&Lのためのライトの設定 1636: ZeroMemory( &light, sizeof(light) ); 1637: light.Type = D3DLIGHT_DIRECTIONAL; 1638: light.Diffuse.r = 1.0; 1639: light.Diffuse.g = 1.0; 1640: light.Diffuse.b = 1.0; 1641: light.Specular.r = 0; 1642: light.Specular.g = 0; 1643: light.Specular.b = 0; 1644: light.Ambient.r = 0.25; 1645: light.Ambient.g = 0.25; 1646: light.Ambient.b = 0.25; 1647: light.Direction = D3DXVECTOR3( 0.0f, 0.0f, -1.0f); 1648: 1649: if (FAILED(hr = pD3DDev->SetLight(0, &light ))) return E_FAIL; 1650: if (FAILED(hr = pD3DDev->LightEnable(0, TRUE))) return E_FAIL; 1651: 1652: // 頂点シェーダーのライトの設定 1653: pD3DDev->SetVertexShaderConstant(1, &D3DXVECTOR4( 0.0f, 0.0f, 1.0f, 0.0f ), 1); 1654: 1655: return S_OK; 1656: }
ごくろうさまでした。。
描画部分では、時間を進める部分と設定された時間で描画する部分とに分かれています。
時間を進める部分では、全てのアニメーションのフレームにわたって時間を設定します。
時間を決定するのに 4.8 倍していますが、これはSDKのサンプルからそのまま引っ張ってきた値なのでどうしてか分かっていません。
もちろん、ここを変えればスローモーションになったり動きが速くなったりします。
1652: //----------------------------------------------------------------------------- 1653: // 動かす 1654: //----------------------------------------------------------------------------- 1655: HRESULT CSkinModel::FrameMove(DWORD time) 1656: { 1657: if(m_pdeMesh){ 1658: m_pdeMesh->fCurTime = 4.8f*(float)(time - m_1st_time);// 時間の設定 1659: 1660: for (SFrame* pFrame = m_pdeMesh->pframeAnimHead; pFrame ; pFrame=pFrame->pframeAnimNext) { 1661: pFrame->SetTime(m_pdeMesh->fCurTime); 1662: } 1663: } 1664: 1665: return S_OK; 1666: }
実際に時間を設定する部分は条件分岐の嵐です。
アニメーションの種類に応じて
行列 | 一番近い値を使う |
拡縮 | 線形補間 |
回転 | 球状平方補間 |
平行移動 | 線形補間 |
と、補間の仕方を変えます。データを行列で与えるのはデータが離散的になるので、なるべくやめたほうがよさそうですね。
0303: //----------------------------------------------------------------------------- 0304: // 時間の設定 0305: void SFrame::SetTime(float fGlobalTime) 0306: { 0307: UINT iKey; 0308: UINT dwp2; 0309: UINT dwp3; 0310: D3DXMATRIXA16 matResult; 0311: D3DXMATRIXA16 matTemp; 0312: float fTime1; 0313: float fTime2; 0314: float fLerpValue; 0315: D3DXVECTOR3 vScale; 0316: D3DXVECTOR3 vPos; 0317: D3DXQUATERNION quat; 0318: BOOL bAnimate = false; 0319: float fTime; 0320: 0321: if (m_pMatrixKeys ) { 0322: // 行列としてアニメが登録されていた場合 0323: 0324: // 時間にあったキーフレームを検索する 0325: fTime = (float)fmod(fGlobalTime, m_pMatrixKeys[m_cMatrixKeys-1].dwTime); 0326: for (iKey = 0 ;iKey < m_cMatrixKeys ; iKey++) { 0327: if ((float)m_pMatrixKeys[iKey].dwTime > fTime) { 0328: dwp3 = iKey; 0329: dwp2= (0<iKey)?(iKey - 1):iKey; 0330: break; 0331: } 0332: } 0333: fTime1 = (float)m_pMatrixKeys[dwp2].dwTime; 0334: fTime2 = (float)m_pMatrixKeys[dwp3].dwTime; 0335: // 時間の前後のキーフレームで近い方を選択する 0336: fLerpValue = ((fTime2 - fTime1) !=0)?( (fTime - fTime1) / (fTime2 - fTime1)):0; 0337: iKey = (0.5<fLerpValue)?dwp3:dwp2; 0338: pframeToAnimate->matRot = m_pMatrixKeys[iKey].mat; 0339: } else { 0340: D3DXMatrixIdentity(&matResult); 0341: // スケールのアニメが登録されていた場合 0342: if (m_pScaleKeys) { 0343: dwp2 = dwp3 = 0; 0344: 0345: // 時間にあったキーフレームを検索する 0346: fTime = (float)fmod(fGlobalTime, m_pScaleKeys[m_cScaleKeys-1].dwTime); 0347: for (iKey = 0 ;iKey < m_cScaleKeys ; iKey++) { 0348: if ((float)m_pScaleKeys[iKey].dwTime > fTime) { 0349: dwp3 = iKey; 0350: dwp2= (0<iKey)?(iKey - 1):iKey; 0351: break; 0352: } 0353: } 0354: fTime1 = (float)m_pScaleKeys[dwp2].dwTime; 0355: fTime2 = (float)m_pScaleKeys[dwp3].dwTime; 0356: 0357: // 線形補間して合成 0358: fLerpValue = ((fTime2 - fTime1) !=0)?( (fTime - fTime1) / (fTime2 - fTime1)):0; 0359: D3DXVec3Lerp(&vScale, 0360: &m_pScaleKeys[dwp2].vScale, 0361: &m_pScaleKeys[dwp3].vScale, 0362: fLerpValue); 0363: D3DXMatrixScaling(&matTemp, vScale.x, vScale.y, vScale.z); 0364: 0365: // 掛けた結果を保存する 0366: D3DXMatrixMultiply(&matResult, &matResult, &matTemp); 0367: 0368: bAnimate = true; 0369: } 0370: 0371: // 回転のアニメが登録されていた場合 0372: if (m_pRotateKeys ) { 0373: int i1 = 0; 0374: int i2 = 0; 0375: 0376: // 時間にあったキーフレームを検索する 0377: fTime = (float)fmod(fGlobalTime, m_pRotateKeys[m_cRotateKeys-1].dwTime); 0378: for (iKey = 0 ;iKey < m_cRotateKeys ; iKey++){ 0379: if (fTime < (float)m_pRotateKeys[iKey].dwTime){ 0380: i1 = (iKey > 0) ? iKey - 1 : 0; 0381: i2 = iKey; 0382: break; 0383: } 0384: } 0385: 0386: fTime1 = (float)m_pRotateKeys[i1].dwTime; 0387: fTime2 = (float)m_pRotateKeys[i2].dwTime; 0388: fLerpValue = ((fTime2 - fTime1) !=0)?( (fTime - fTime1) / (fTime2 - fTime1)):0; 0389: 0390: // 球状平方補間して合成 0391: int i0 = i1 - 1; 0392: int i3 = i2 + 1; 0393: 0394: if(i0 < 0) i0 += m_cRotateKeys; 0395: if(i3 >= (INT) m_cRotateKeys) i3 -= m_cRotateKeys; 0396: 0397: D3DXQUATERNION qA, qB, qC; 0398: D3DXQuaternionSquadSetup(&qA, &qB, &qC, 0399: &m_pRotateKeys[i0].quatRotate, &m_pRotateKeys[i1].quatRotate, 0400: &m_pRotateKeys[i2].quatRotate, &m_pRotateKeys[i3].quatRotate); 0401: 0402: D3DXQuaternionSquad(&quat, &m_pRotateKeys[i1].quatRotate, &qA, &qB, &qC, fLerpValue); 0403: 0404: quat.w = -quat.w; 0405: D3DXMatrixRotationQuaternion(&matTemp, &quat); 0406: // 掛けた結果を保存する 0407: D3DXMatrixMultiply(&matResult, &matResult, &matTemp); 0408: 0409: bAnimate = true; 0410: } 0411: 0412: // 平行移動のアニメが登録されていた場合 0413: if (m_pPositionKeys) { 0414: dwp2=dwp3=0; 0415: // 時間にあったキーフレームを検索する 0416: fTime = (float)fmod(fGlobalTime, m_pPositionKeys[m_cPositionKeys-1].dwTime); 0417: for (iKey = 0 ;iKey < m_cPositionKeys ; iKey++) { 0418: if ((float)m_pPositionKeys[iKey].dwTime > fTime) { 0419: dwp3 = iKey; 0420: dwp2= (0<iKey)?(iKey - 1):iKey; 0421: break; 0422: } 0423: } 0424: 0425: // 線形補間して合成 0426: fTime1 = (float)m_pPositionKeys[dwp2].dwTime; 0427: fTime2 = (float)m_pPositionKeys[dwp3].dwTime; 0428: fLerpValue = ((fTime2 - fTime1) !=0)?( (fTime - fTime1) / (fTime2 - fTime1)):0; 0429: D3DXVec3Lerp((D3DXVECTOR3*)&vPos, 0430: &m_pPositionKeys[dwp2].vPos, 0431: &m_pPositionKeys[dwp3].vPos, 0432: fLerpValue); 0433: D3DXMatrixTranslation(&matTemp, vPos.x, vPos.y, vPos.z); 0434: 0435: // 掛けた結果を保存する 0436: D3DXMatrixMultiply(&matResult, &matResult, &matTemp); 0437: bAnimate = true; 0438: } else { 0439: // 平行移動の場合はアニメが無ければディフォルトの位置を指定する 0440: D3DXMatrixTranslation(&matTemp 0441: , pframeToAnimate->matRotOrig._41 0442: , pframeToAnimate->matRotOrig._42 0443: , pframeToAnimate->matRotOrig._43); 0444: D3DXMatrixMultiply(&matResult, &matResult, &matTemp); 0445: } 0446: 0447: if (bAnimate) pframeToAnimate->matRot = matResult; 0448: } 0449: }
描画部分はもう少し入り組んでいます。
全体の回転や平行移動を決め、ビュー行列も確保した後、行列を決定しその行列で描画します。
1980: //----------------------------------------------------------------------------- 1981: // 描画 1982: //----------------------------------------------------------------------------- 1983: HRESULT CSkinModel::Render() 1984: { 1985: UINT cTriangles = 0; 1986: HRESULT hr; 1987: 1988: if(m_pdeMesh){ 1989: // 視点を設定する 1990: m_pdeMesh->pframeRoot->matRot = m_Rot; 1991: m_pdeMesh->pframeRoot->matTrans = m_Trans; 1992: 1993: // ソフトウェアT&Lのためにビュー行列を設定 1994: hr = m_pD3DDev->SetTransform(D3DTS_VIEW, (D3DMATRIX*)&m_View); 1995: if(FAILED(hr)) return hr; 1996: 1997: D3DXMATRIXA16 mCur; 1998: D3DXMatrixIdentity(&mCur); 1999: // 動かす 2000: if (FAILED(hr = m_pdeMesh->pframeRoot->UpdateFrames(mCur))) return hr; 2001: // 描画する 2002: if (FAILED(hr = _DrawFrames(m_pdeMesh->pframeRoot, cTriangles))) return hr; 2003: } 2004: 2005: return S_OK; 2006: }
SFrame::UpdateFrames は、アニメーションとして生成された行列を使って、
各骨の行列から親子関係を持ったローカル行列を作成します。
方法は親の骨の行列にさらに自分の骨の行列を掛けて親からの相対的な行列に変換します。
また、自分の骨を子供に受け渡して、さらに孫、曾孫のローカル座標を作らせます。
1674: //----------------------------------------------------------------------------- 1675: // 行列の生成 1676: //----------------------------------------------------------------------------- 1677: HRESULT SFrame::UpdateFrames(D3DXMATRIX &matCur) 1678: { 1679: HRESULT hr = S_OK; 1680: this->matCombined = matCur; 1681: D3DXMatrixMultiply(&this->matCombined, &this->matRot, &matCur); 1682: D3DXMatrixMultiply(&this->matCombined, &this->matCombined, &this->matTrans ); 1683: 1684: // 自分の行列を親として、子供たちを次々に行列変換する 1685: for( SFrame *p=this->pframeFirstChild ; p!=NULL ; p=p->pframeSibling ){ 1686: if (FAILED(hr = p->UpdateFrames(this->matCombined))) return hr; 1687: } 1688: 1689: return S_OK; 1690: }
もう一つ関数 CSkinModel::_DrawFrames() は、描画関数です。
作成した行列をワールド座標に設定して描画し、子供も次々に描画していきます。
1958: HRESULT CSkinModel::_DrawFrames(SFrame *pframe, UINT &cTriangles) 1959: { 1960: HRESULT hr = S_OK; 1961: LPDIRECT3DDEVICE8 pD3DDev = m_pD3DDev; 1962: 1963: // ワールド行列の生成 1964: if (pframe->pmcMesh != NULL){ 1965: hr = pD3DDev->SetTransform(D3DTS_WORLD, &pframe->matCombined); 1966: if(FAILED(hr)) return hr; 1967: } 1968: // 登録されているメッシュを表示 1969: for(SMeshContainer *it=pframe->pmcMesh ; it ; it=it->pmcNext){ 1970: if (FAILED(hr = _DrawMeshContainer(it)))return hr; 1971: cTriangles += it->pMesh->GetNumFaces(); 1972: } 1973: // 子供を次々に表示 1974: for(SFrame *pChild=pframe->pframeFirstChild ; pChild ; pChild=pChild->pframeSibling){ 1975: if (FAILED(hr = _DrawFrames(pChild, cTriangles))) return hr; 1976: } 1977: 1978: return S_OK; 1979: }
描画する関数は、さらにレンダリング方法によって分岐します。
今回は頂点シェーダーにのみ注目します。この時、_DrawMeshContainerIndexedVs が選ばれるので、その中身を見ていきます。
1934: //----------------------------------------------------------------------------- 1935: // 各メッシュの描画 1936: //----------------------------------------------------------------------------- 1937: HRESULT CSkinModel::_DrawMeshContainer(SMeshContainer *pmcMesh) 1938: { 1939: HRESULT hr = S_OK; 1940: 1941: // スキンじゃないモデル 1942: if (! pmcMesh->m_pSkinMesh) return _DrawMeshContainerNoSkin(pmcMesh); 1943: 1944: // 描画方法が切り変わったらメッシュを生成しなおす 1945: if (m_method != pmcMesh->m_Method) this->GenerateMesh(pmcMesh); 1946: 1947: // それぞれの手法に応じてそれぞれ描画 1948: if (m_method == D3DNONINDEXED) return _DrawMeshContainerNonIndexed(pmcMesh); 1949: if (m_method == D3DINDEXEDVS ) return _DrawMeshContainerIndexedVs (pmcMesh); 1950: if (m_method == D3DINDEXED ) return _DrawMeshContainerIndexed (pmcMesh); 1951: if (m_method == SOFTWARE ) return _DrawMeshContainerSoftware (pmcMesh); 1952: 1953: return S_OK; 1954: }
CSkinModel::_DrawMeshContainerIndexedVs では、最初と最後に頂点シェーダーのバージョン不足でHALが使えないときのソフトウェアT&L の切り替えを行います。
次に(スキンでない)メッシュを描画するときと同じように頂点バッファ、インデックスバッファ、頂点シェーダーの指定を行います。
ここで、混ぜ合わせに使う行列の数に応じて頂点シェーダーの切り替えが行われます。
次に骨の行列を固定レジスタに設定します。噂によると、定数レジスタを沢山切り替えるのは遅いようなので、
高速化したいときは、(行列の数を減らして)全ての行列を共通化して一モデルに一回ぐらいの更新に減らすと早くなるかもしれません(未検証)。
あとは、質感による色やテクスチャーの設定をして、DrawIndexedPrimitive で描画します。
1791: //----------------------------------------------------------------------------- 1792: // D3DINDEXEDVS 1793: //----------------------------------------------------------------------------- 1794: HRESULT CSkinModel::_DrawMeshContainerIndexedVs(SMeshContainer *pmcMesh) 1795: { 1796: UINT ipattr; 1797: LPDIRECT3DDEVICE8 pD3DDev = m_pD3DDev; 1798: LPD3DXBONECOMBINATION pBoneComb; 1799: HRESULT hr; 1800: 1801: D3DXVECTOR4 vConst( 1.0f, 0.0f, 0.0f, 765.01f ); 1802: LPDIRECT3DVERTEXBUFFER8 pVB; 1803: LPDIRECT3DINDEXBUFFER8 pIB; 1804: 1805: if (pmcMesh->m_bUseSW) {// 描画できない時はソフトウェアT&Lで描画 1806: m_pD3DDev->SetRenderState(D3DRS_SOFTWAREVERTEXPROCESSING, TRUE); 1807: } 1808: 1809: // 頂点、インデックスバッファの設定 1810: pmcMesh->pMesh->GetVertexBuffer(&pVB); 1811: pmcMesh->pMesh->GetIndexBuffer(&pIB); 1812: if(FAILED(hr = m_pD3DDev->SetStreamSource(0, pVB, D3DXGetFVFVertexSize(pmcMesh->pMesh->GetFVF()))))return hr; 1813: if(FAILED(hr = m_pD3DDev->SetIndices(pIB, 0)))return hr; 1814: pVB->Release(); 1815: pIB->Release(); 1816: 1817: // 混ぜている行列の個数に応じて、シェーダーを切り替える 1818: if(FAILED(hr = m_pD3DDev->SetVertexShader(m_dwIndexedVertexShader[pmcMesh->m_maxFaceInfl - 1])))return hr; 1819: 1820: pBoneComb = reinterpret_cast<LPD3DXBONECOMBINATION>(pmcMesh->m_pBoneCombinationBuf->GetBufferPointer()); 1821: for (ipattr = 0; ipattr < pmcMesh->cpattr; ipattr++){ 1822: // ビュー行列をかけて、ボーンの行列を設定する 1823: for (DWORD i = 0; i < pmcMesh->m_paletteSize; ++i){ 1824: DWORD matid = pBoneComb[ipattr].BoneId[i]; 1825: if (matid != UINT_MAX){ 1826: D3DXMATRIXA16 mat; 1827: D3DXMatrixMultiply(&mat, &pmcMesh->m_pBoneOffsetMat[matid], pmcMesh->m_pBoneMatrix[matid]); 1828: D3DXMatrixMultiplyTranspose(&mat, &mat, &m_View); 1829: m_pD3DDev->SetVertexShaderConstant(i*3 + 9, &mat, 3); 1830: } 1831: } 1832: 1833: // 質感の設定 1834: D3DXCOLOR ambEmm; 1835: D3DMATERIAL8 *pMaterial = &pmcMesh->rgMaterials[pBoneComb[ipattr].AttribId]; 1836: D3DXColorModulate(&ambEmm, &D3DXCOLOR(pMaterial->Ambient),&D3DXCOLOR(.25, .25, .25, 1.0)); 1837: ambEmm += D3DXCOLOR(pMaterial->Emissive); 1838: m_pD3DDev->SetVertexShaderConstant(8, &(pMaterial->Diffuse), 1);// 平行光源 1839: m_pD3DDev->SetVertexShaderConstant(7, &ambEmm, 1); // 環境光 1840: vConst.y = pMaterial->Power; 1841: m_pD3DDev->SetVertexShaderConstant(0, &vConst, 1);// すぺきゅらー 1842: // テクスチャーの設定 1843: m_pD3DDev->SetTexture(0, pmcMesh->pTextures[pBoneComb[ipattr].AttribId]); 1844: // 実際の描画 1845: hr = m_pD3DDev->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 1846: pBoneComb[ipattr].VertexStart, pBoneComb[ipattr].VertexCount, 1847: pBoneComb[ipattr].FaceStart * 3, pBoneComb[ipattr].FaceCount); 1848: if(FAILED(hr)) return hr; 1849: } 1850: 1851: // ソフトウェアT&Lの時を戻す 1852: if (pmcMesh->m_bUseSW) { 1853: m_pD3DDev->SetRenderState(D3DRS_SOFTWAREVERTEXPROCESSING, FALSE); 1854: } 1855: 1856: return S_OK; 1857: }
後片付けは確保したメモリを開放します。
階層構造になっているものはその子供、兄弟も順次開放していきます。
CSkinModel.cpp 2000: //----------------------------------------------------------------------------- 2001: // 最後の時やデバイスが変更されたとき等に呼ばれる 2002: //----------------------------------------------------------------------------- 2003: HRESULT CSkinModel::Release() 2004: { 2005: _InvalidateDeviceObjects(); 2006: _DeleteDeviceObjects(); 2007: 2008: return S_OK; 2009: } 2010: //----------------------------------------------------------------------------- 2011: // 最後の時やデバイスが変更されたとき等に呼ばれる 2012: //----------------------------------------------------------------------------- 2013: HRESULT CSkinModel::_InvalidateDeviceObjects() 2014: { 2015: if(m_pdeMesh) m_pdeMesh->pframeRoot->ReleaseDeviceDependentMeshes(); 2016: 2017: return S_OK; 2018: } 2019: //----------------------------------------------------------------------------- 2020: // 最後の時やデバイスが変更されたとき等に呼ばれる 2021: //----------------------------------------------------------------------------- 2022: HRESULT CSkinModel::_DeleteDeviceObjects() 2023: { 2024: if(m_pdeMesh) delete m_pdeMesh; m_pdeMesh = NULL; 2025: 2026: delete [] m_pBoneMatrices; 2027: 2028: return S_OK; 2029: } 2030: //----------------------------------------------------------------------------- 2031: // メッシュとして確保したメモリを開放する 2032: //----------------------------------------------------------------------------- 2033: void SFrame::ReleaseDeviceDependentMeshes() 2034: { 2035: if (this->pmcMesh != NULL){ 2036: for (SMeshContainer* pmcCurr = this->pmcMesh; pmcCurr != NULL; pmcCurr = pmcCurr->pmcNext){ 2037: if (pmcCurr->m_pSkinMesh != NULL){ 2038: RELEASE(pmcCurr->pMesh); 2039: 2040: pmcCurr->m_Method = NONE; 2041: } 2042: } 2043: } 2044: 2045: // 子供や子供兄弟を次々に削除 2046: if (this->pframeFirstChild != NULL) 2047: this->pframeFirstChild->ReleaseDeviceDependentMeshes(); 2048: 2049: if (this->pframeSibling != NULL) 2050: this->pframeSibling->ReleaseDeviceDependentMeshes(); 2051: }
やっとモーションを終えました。
オブジェクト化を進めようと思ったのですが、全然、まとまってなかったりして…
モーションは難しいことがない割に、ソースコードばかり長くなるので、説明はしたくないところですよね。
技術系のBBSで、『アニメーションするにはどうすればいいですか?』というのを、
就職活動の前の時期になると(?)見かけるのですが、最近では『skinned mesh のサンプルを見ろよ』で片付けられますね。
個人的には、その回答で良いと思うのですが、うざいのでやってみました。
モーションはフォーマットをどうするのかは考え物ですよね。
独自フォーマットで描画するのは楽ですが、コンバータを作ったり、メンテナンスをしなくちゃいけないし、
今回のように、よく知られたXファイルを使うときには、全てのXファイルが表示できないと文句いわれるし。
でもまぁ、仕事でやらない限りは、広がっているフォーマットを使うほうが(頭の良い人が考えているはずだから)効率もいいだろうし、データも沢山あるしでお勧めする方法だと思いますけどね。
ということで、今後『初心者なのですが、ファイルでアニメーションをするにはどうすればいいですか』という『あきらめろ』といいたくなるような質問が減ってくれればいいと思います。