ひにけにxnaさんのところでは、XNAを用いての解説があったので、
ここでは、普通のDirectX SDKとC++による組み合わせでこれをやってみたいと思います。
■ 使用するもの
- SkinnedMeshのサンプルプログラム
- ShaderModel 3.0に対応したグラフィックボード
- Radeon X1000シリーズを除く。これはVTFに対応していないため。
■ ソースコード
* 各準備
まずは、ボーンのマトリックスを格納するためのテクスチャを準備します。
ここではグローバルにおいて、手抜きしました。
また、毎回書き換えるので、ダイナミックテクスチャにしています。
// グローバル const int MaxMatricesPalette = 128; // ボーン用パレットの数 const int BoneTextureWidth = 128; // ボーン用テクスチャの横方向サイズ LPDIRECT3DTEXTURE9 g_pBoneTexture; // テクスチャの作成箇所 pd3dDevice->CreateTexture( BoneTextureWidth, 4, 1, D3DUSAGE_DYNAMIC, D3DFMT_A32B32G32R32F, D3DPOOL_DEFAULT, &g_pBoneTexture, NULL );
* ボーン用テクスチャの概要
ボーン用のテクスチャは、横方向に使用したいボーンの数、縦方向に4というサイズで作成しました。
というのも、行列は4x4のfloat要素で、今回のテクスチャはRGBA32Fという形式のため、1テクセルに4要素格納できます。
そのため、4テクセル使えば、1つの行列が生成できるということを目的にしています。
横方向(U方向)はボーンのインデックスを示すことにして、
V方向には行列の行を格納することにしました。
このほうが配列っぽく見えるかなと思っています。
* マトリックスパレット数調整のための変換部分
またついでに、ConvertToIndexedBlendedMesh を呼び出している部分も
これらの設定のボーン数をみるように修正が必要です。
pMeshContainer->NumPaletteEntries = min( // ここをボーン用テクスチャの横方向サイズとするなど MaxMatricesPalette, pMeshContainer->pSkinInfo->GetNumBones() );
* マトリックスパレットの設定部分の修正
マトリックスパレット設定部分を次のように変更します。
// first calculate all the world matrices for( iPaletteEntry = 0; iPaletteEntry < pMeshContainer->NumPaletteEntries; ++iPaletteEntry ) { iMatrixIndex = pBoneComb[iAttrib].BoneId[iPaletteEntry]; if( iMatrixIndex != UINT_MAX ) { D3DXMatrixMultiply( &matTemp, &pMeshContainer->pBoneOffsetMatrices[iMatrixIndex], pMeshContainer->ppBoneMatrixPtrs[iMatrixIndex] ); D3DXMatrixTranspose( &g_pBoneMatrices[iPaletteEntry], &matTemp ); } } { D3DLOCKED_RECT rc; hr = g_pBoneTexture->LockRect( 0, &rc, NULL, D3DLOCK_DISCARD ); if( SUCCEEDED(hr) ) { for( int i = 0; i < pMeshContainer->NumPaletteEntries; ++i ) { float* p = reinterpret_cast( rc.pBits ); p += i * 4; for( int j = 0; j < 4; ++j ) { for( int k = 0; k < 4; ++k ) { p[k] = g_pBoneMatrices[i].m[j][k]; } p+= rc.Pitch / sizeof(float); } } g_pBoneTexture->UnlockRect( 0 ); } g_pEffect->SetTexture( "gBoneTexture", g_pBoneTexture ); }
テクスチャにボーンのマトリックスを格納していきます。
注意点としてはテクスチャのY方向へずらすときは、ロックしたときのピッチバイト数でずらさないといけない点です。
それと、シェーダー内ではマトリックスとしては取れず、
float4の4つの組で取れる感じになります。
そのためシェーダーに送る際に転置しておきます。
こうすることで、内積を利用して計算が可能になります。
*プロジェクション用行列の設定修正
サンプルのSkinnedMeshのサンプルではmViewProjというシェーダー定数に
実はプロジェクション用の行列しか送っていないです。
ビュー行列はマトリックスパレットに対して適用していました。
この部分を変更します。
mViewProjにg_matProjを設定している部分で、g_matViewとの演算結果を格納するようにしておきます。
■シェーダーコード
ほとんどの部分はサンプルのそのままです。
変更点は以下の通り
- tex2Dlodにて該当するマトリックスパレット相当のテクセルから値を取得
- 浮動小数用にサンプラを設定
- VTF使うために、vs_3_0を設定。あわせてピクセルシェーダーも
// // Skinned Mesh Effect file (with VTF) // float4 lhtDir = {0.0f, 0.0f, -1.0f, 1.0f}; //light Direction float4 lightDiffuse = {0.6f, 0.6f, 0.6f, 1.0f}; // Light Diffuse float4 MaterialAmbient : MATERIALAMBIENT = {0.1f, 0.1f, 0.1f, 1.0f}; float4 MaterialDiffuse : MATERIALDIFFUSE = {0.8f, 0.8f, 0.8f, 1.0f}; float BoneTexWidthInv; // Matrix Pallette static const int MAX_MATRICES = 26; float4x3 mWorldMatrixArray[MAX_MATRICES] : WORLDMATRIXARRAY; float4x4 mViewProj : VIEWPROJECTION; texture gBoneTexture; sampler2D gBoneSampler = sampler_state { texture = gBoneTexture; MipFilter = NONE; MagFilter = POINT; MinFilter = POINT; AddressU = Clamp; AddressV = Clamp; }; /////////////////////////////////////////////////////// struct VS_INPUT { float4 Pos : POSITION; float4 BlendWeights : BLENDWEIGHT; float4 BlendIndices : BLENDINDICES; float3 Normal : NORMAL; float3 Tex0 : TEXCOORD0; }; struct VS_OUTPUT { float4 Pos : POSITION; float4 Diffuse : COLOR; float2 Tex0 : TEXCOORD0; }; float3 Diffuse(float3 Normal) { float CosTheta; // N.L Clamped CosTheta = max(0.0f, dot(Normal, lhtDir.xyz)); // propogate scalar result to vector return (CosTheta); } VS_OUTPUT VShade(VS_INPUT i, uniform int NumBones) { VS_OUTPUT o; float3 Pos = 0.0f; float3 Normal = 0.0f; float LastWeight = 0.0f; // Compensate for lack of UBYTE4 on Geforce3 int4 IndexVector = D3DCOLORtoUBYTE4(i.BlendIndices); // cast the vectors to arrays for use in the for loop below float BlendWeightsArray[4] = (float[4])i.BlendWeights; int IndexArray[4] = (int[4])IndexVector; for( int iBone = 0; iBone < NumBones-1; iBone++ ) { LastWeight = LastWeight + BlendWeightsArray[iBone]; float tx = (IndexArray[iBone]) * BoneTexWidthInv; float4 uv0 = float4( tx, 0, 0, 1 ); float4 uv1 = float4( tx, 1.0/ 4.0, 0, 1 ); float4 uv2 = float4( tx, 2.0/ 4.0, 0, 1 ); float4 vx = tex2Dlod( gBoneSampler, uv0 ); float4 vy = tex2Dlod( gBoneSampler, uv1 ); float4 vz = tex2Dlod( gBoneSampler, uv2 ); float w = BlendWeightsArray[ iBone ]; Pos.x += dot( vx.xyzw, float4(i.Pos.xyz,1) ) * w; Pos.y += dot( vy.xyzw, float4(i.Pos.xyz,1) ) * w; Pos.z += dot( vz.xyzw, float4(i.Pos.xyz,1) ) * w; Normal.x += dot( vx.xyz, i.Normal ) * w; Normal.y += dot( vy.xyz, i.Normal ) * w; Normal.z += dot( vz.xyz, i.Normal ) * w; } LastWeight = 1.0f - LastWeight; float tx = (IndexArray[NumBones-1]) * BoneTexWidthInv; float4 uv0 = float4( tx, 0, 0, 1 ); float4 uv1 = float4( tx, 1.0f/ 4.0, 0, 1 ); float4 uv2 = float4( tx, 2.0f/ 4.0, 0, 1 ); float4 vx = tex2Dlod( gBoneSampler, uv0 ); float4 vy = tex2Dlod( gBoneSampler, uv1 ); float4 vz = tex2Dlod( gBoneSampler, uv2 ); Pos.x += dot( vx.xyzw, float4(i.Pos.xyz,1) ) * LastWeight; Pos.y += dot( vy.xyzw, float4(i.Pos.xyz,1) ) * LastWeight; Pos.z += dot( vz.xyzw, float4(i.Pos.xyz,1) ) * LastWeight; Normal.x += dot( vx.xyz, i.Normal ) * LastWeight; Normal.y += dot( vy.xyz, i.Normal ) * LastWeight; Normal.z += dot( vz.xyz, i.Normal ) * LastWeight; // transform position from world space into view and then projection space o.Pos = mul(float4(Pos.xyz, 1.0f), mViewProj); // normalize normals Normal = normalize(Normal); // Shade (Ambient + etc.) o.Diffuse.xyz = MaterialAmbient.xyz + Diffuse(Normal) * MaterialDiffuse.xyz; o.Diffuse.w = 1.0f; // copy the input texture coordinate through o.Tex0 = i.Tex0.xy; return o; } int CurNumBones = 2; VertexShader vsArray[4] = { compile vs_3_0 VShade(1), compile vs_3_0 VShade(2), compile vs_3_0 VShade(3), compile vs_3_0 VShade(4) }; float4 mainPS( VS_OUTPUT _In ) : COLOR { return _In.Diffuse; } ////////////////////////////////////// // Techniques specs follow ////////////////////////////////////// technique t0 { pass p0 { VertexShader = (vsArray[CurNumBones]); PixelShader = compile ps_3_0 mainPS(); } }
■実行結果
こんな感じになります。
理由はピクセルシェーダーでディフューズ用のテクスチャ等を処理せず、
単純にライトの強度を出しているからです。
しかし、これがVTF利用して動いています。
今は、横方向128でやりましたが、これを256にすることで
ボーンの上限256個までを一度に使うことが出来るようになります。
コメント
シェーダ内のBoneTexWidthInvにはどのような値を入れるのでしょうか?
すいません解決しました。1.0/ボーンテクスチャ横サイズですね。