スキンメッシュ描画

■バージョン

	DirectX
		DirectX9

	VisualStudio
		VisualStudio 2015

■サンプル

以下のリンクでサンプルをダウンロードできます。
※サンプルはアニメーション処理も入っています。

サンプル

■概要

このページでは階層有りスキンメッシュファイルの読み込み、更新、描画を行います。
スキンメッシュの概要は以下のリンクで確認できます。

3Dモデル -スキンメッシュ-

スキンメッシュは基本的に階層有りXFileの方法がベースになっているので
階層有りXFileの読み込み、更新、描画を知らない人は以下のリンクから確認をしてください。

階層有りXFile描画 -準備-

■メッシュコンテナ

スキンメッシュを使用する場合、D3DXMESHCONTAINERの継承データ構造に
以下のメンバを追加します。

追加メンバ:
	// ウェイトの数
	DWORD m_BoneWeightNum;

	// ボーンの数
	DWORD m_BoneNum;

	// ボーン情報のバッファ
	LPD3DXBUFFER m_BoneBuffer;

	// ボーンのマトリックスのポインタの配列
	D3DXMATRIX **m_BoneMatrix;

	// ボーンのオフセット行列
	D3DXMATRIX *m_BoneOffsetMatrix;

■読み込み

スキンメッシュを読み込むにはD3DXLoadMeshHierarchyFromX関数を使用します。
この関数で呼び出されるCreateMeshContainerの引数LPD3DXSKININFO skin_infoで
渡されているデータを使用してボーン情報の取得を行います。


// スキン情報があれば情報を取得する
if (skin_info != NULL)
{
	container->pSkinInfo = skin_info;
	skin_info->AddRef();
	// ボーンの数を取得
	DWORD bone_num = container->m_BoneNum = skin_info->GetNumBones();
	container->m_BoneOffsetMatrix = new D3DXMATRIX[bone_num];

	for (DWORD i =  0; i < bone_num; i++)
	{
		// オフセット行列をコピーする
		memcpy(&container->m_BoneOffsetMatrix[i], container->pSkinInfo->GetBoneOffsetMatrix(i), sizeof(D3DXMATRIX));
	}

	// ボーンの最適化
	if (container->pSkinInfo->ConvertToBlendedMesh(
		mesh,				// 入力メッシュ
		NULL,				// 未使用
		container->pAdjacency,		// メッシュの隣接データ(入力)
		NULL,				// メッシュの隣接データ(出力)
		NULL,				// ポリゴンの新規インデックスのバッファ
		NULL,				// 頂点の新規インデックスのバッファ
		&container->m_BoneWeightNum,	// 1つの頂点に影響を及ぼす重みの数
		&container->m_BoneNum,		// ボーンの数
		&container->m_BoneBuffer,	// ボーンデータが格納されたバッファ
		&container->MeshData.pMesh	// 変換後のメッシュ
	))
	{
		return E_FAIL;
	}
}


■行列領域の確保

	計算時に毎回取得しなくていいようにボーンの描画で使用する行列を保存して
	描画などの必要な場合に使用できるようにします。


	HRESULT SkinMeshFile::AllocateBoneMatrix(LPD3DXMESHCONTAINER container)
	{
		FrameData *pFrame=NULL;
		DWORD bone_num = 0;

		MeshContainer *original_container = (MeshContainer*)container;
		if (original_container->pSkinInfo == NULL)
		{
			return S_OK;
		}

		bone_num = original_container->pSkinInfo->GetNumBones();
		// ボーンのマトリクス分の領域を確保してデータを保存する
		original_container->m_BoneMatrix = new D3DXMATRIX*[bone_num];
		for (DWORD i = 0; i < bone_num; i++)
		{
			pFrame = (FrameData*)D3DXFrameFind( m_RootFrame, container->pSkinInfo->GetBoneName(i) );
			if (pFrame == NULL)
			{
				return E_FAIL;
			}
			original_container->m_BoneMatrix[i] = &pFrame->m_CombinedTransformationMatrix;
		}

		return S_OK;
	}

■描画

描画をするには頂点ブレンドを行ってメッシュの描画行列を作成する必要があります。


	if (original_container->pSkinInfo != NULL)
	{
		LPD3DXBONECOMBINATION bone_buffer = (LPD3DXBONECOMBINATION)original_container->m_BoneBuffer->GetBufferPointer();			// ボーンの数まわす
		for(DWORD i = 0; i < original_container->m_BoneNum; i++)
		{
			// ブレンドするボーンの数
			DWORD bone_blend_num = 0;

			// ボーンIDからブレンドする個数を割り出す
			for (DWORD j = 0; j < original_container->m_BoneWeightNum; j++)
			{
				if (bone_buffer[i].BoneId[j] != UINT_MAX)
				{
					bone_blend_num++;
				}
			}
			
			// 頂点ブレンドの設定
			// 第二引数には_D3DVERTEXBLENDFLAGSが使われているので
			// 対象となっているボーンの数と差異に注意
			g_pD3DDevice->SetRenderState(D3DRS_VERTEXBLEND, bone_blend_num - 1);

			for(DWORD j = 0; j < original_container->m_BoneWeightNum; j++) 
			{
				DWORD matrix_index = bone_buffer[i].BoneId[j];
				D3DXMATRIX matrix;

				if (matrix_index != UINT_MAX)
				{
					// オフセット行列(m_BoneOffsetMatrix) * ボーンの行列(m_BoneMatrix)で最新の位置を割り出す
					matrix = original_container->m_BoneOffsetMatrix[matrix_index] * (*original_container->m_BoneMatrix[matrix_index]);
					g_pD3DDevice->SetTransform( D3DTS_WORLDMATRIX(j), &matrix );
				}
			}

			g_pD3DDevice->SetMaterial( &original_container->pMaterials[bone_buffer[i].AttribId].MatD3D );
			g_pD3DDevice->SetTexture( 0, original_container->m_TextureList[bone_buffer[i].AttribId] );
			original_container->MeshData.pMesh->DrawSubset(i);	
		}
	} 


●ブレンド数の割り出し
	頂点ブレンドを行うためには重みの数を調べてSetRenderStateで設定する必要があります。
	
	・調査
		頂点ブレンドではブレンド対象となる頂点番号が配列の中に設定されています。
		もし、対象とならない場合はUINT_MAXがボーン番号の代わりに設定されているので、
		配列の中から対象となるボーンの数を算出します。


		// ブレンドするボーンの数
		DWORD bone_blend_num = 0;

		// ボーンIDからブレンドする個数を割り出す
		for (DWORD j = 0; j < original_container->m_BoneWeightNum; j++)
		{
			if (bone_buffer[i].BoneId[j] != UINT_MAX)
			{
				bone_blend_num++;
			}
		}


	・ブレンド設定
		頂点ブレンドはSetRenderStateでブレンドを行う個数を通知しておく必要があります。
		通知を忘れた場合、「●頂点ブレンド」のD3DTS_WORLDMATRIXの設定が無効になります。


		// 頂点ブレンドの設定
		// 第二引数には_D3DVERTEXBLENDFLAGSが使われているので
		// 対象となっているボーンの数と差異に注意
		g_pD3DDevice->SetRenderState(D3DRS_VERTEXBLEND, bone_blend_num - 1);


●頂点ブレンド
	頂点ブレンドはブレンド対象となっているオフセット行列とボーンの行列で最新の行列を作成し、
	SetTransformでワールド行列に設定します。
	この時、D3DTS_WORLDMATRIXに引数を指定することで、複数の行列から頂点ブレンドを行ってくれます。


	for(DWORD j = 0; j < original_container->m_BoneWeightNum; j++) 
	{
		DWORD matrix_index = bone_buffer[i].BoneId[j];
		D3DXMATRIX matrix;

		if (matrix_index != UINT_MAX)
		{
			// オフセット行列(m_BoneOffsetMatrix) * ボーンの行列(m_BoneMatrix)で最新の位置を割り出す
			matrix = original_container->m_BoneOffsetMatrix[matrix_index] * (*original_container->m_BoneMatrix[matrix_index]);
			g_pD3DDevice->SetTransform( D3DTS_WORLDMATRIX(j), &matrix );
		}
	}