ひにけに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/ボーンテクスチャ横サイズですね。