Microsoft Visual Studio .NET 2003
 Microsoft DirectX 9.0 SDK (December 2004)
 シェーダーモデル 2.0

■頂点ブレンド Prev  Top  Next
関連ページ:なし

無料レンタルサーバー

今回は頂点ブレンドをやります。リップシンク(いわゆる口パク)などのアニメーションで使用します。リップシンクを静止画で見せてもしょうがないので画像なしです。 頂点ブレンドはキーフレームの状態のモデルを作成し、それらのモデルの頂点を合成してアニメーションします。 リップシンクの場合次の6パターンとなります。

1.口を閉じてる
2.あ
3.い
4.う
5.え
6.お

まあ口を閉じてる状態と母音の口の状態です。ですが、実際には表情によるアニメーションもあるのでそれ以上必要になります。

---DXUTMesh.h---


#define LIPSYNC_VBUFFER_NUM 128
#define LIPSYNC_ID_NUM 10

class CDXUTMeshLipSync
{
public:
   int m_IDSrc, m_IDDest;                                             //変化前と変化後のメッシュのID
   float   m_Timer;                                                   //変化量

private:
   LPDIRECT3DDEVICE9 m_pd3dDevice;

   typedef struct _PNT_VERTEX
   {
      D3DXVECTOR3 p; // 頂点
      D3DXVECTOR3 n; // 法線
      D3DXVECTOR2 t; // テクセル
   }PNT_VERTEX;

   typedef struct _PN_VERTEX
   {
      D3DXVECTOR3 p; // 頂点
      D3DXVECTOR3 n; // 法線
   }PN_VERTEX;

   CDXUTMesh* m_pMesh;                                                  //マテリアル情報を格納

   LPDIRECT3DVERTEXBUFFER9 m_pVertexBufferArray[LIPSYNC_VBUFFER_NUM];   //頂点バッファの配列
   LPDIRECT3DINDEXBUFFER9  m_pIndexBuffer;                              //インデックスバッファ
   LPDIRECT3DVERTEXDECLARATION9   m_pDecl;                              //頂点宣言
   
   D3DVERTEXELEMENT9 m_pElement[128];                                   //LPDIRECT3DVERTEXDECLARATION9の中身
   UINT m_numElements;                                                  //LPDIRECT3DVERTEXDECLARATION9の配列数

   UINT m_dwNumVertices, m_dwNumFaceFaces;                              //頂点数、三角ポリゴン数
   
   WORD m_Index;                                                        //メッシュのファイル名のロード時に使用するインデックス
   WORD m_numStreams;                                                   //ストリーム番号の最大値
   
   char m_FileName[LIPSYNC_VBUFFER_NUM][100];                           //メッシュのファイル名

public:
   CDXUTMeshLipSync( LPDIRECT3DDEVICE9 pd3dDevice );
   ~CDXUTMeshLipSync();

   //頂点バッファを開放
   HRESULT Destroy();

   //頂点宣言を設定
   HRESULT CreateVertexDeclaration( D3DVERTEXELEMENT9 pDecl[] );
   
   //メッシュのファイル名を追加
   HRESULT AddMeshFileName( LPCTSTR strFilename );
   
   //頂点バッファなどを作成
   HRESULT Create();

   //変化後のメッシュのIDを設定
   void SetID( int ID );
   
   //アニメーション用のタイマーを更新
   void NextFrame();

   //テクスチャーセット
   HRESULT SetTexture( DWORD Sampler, DWORD IndexMaterials );
   
   //レンダリング
   HRESULT Render();
};

---DXUTMesh.cpp---


//コンストラクタ
CDXUTMeshLipSync::CDXUTMeshLipSync( LPDIRECT3DDEVICE9 pd3dDevice )
{
   m_Index = 0;
   m_numStreams = 0;

   m_pd3dDevice = pd3dDevice;

   for( int i=0; i<LIPSYNC_VBUFFER_NUM; i++ )
      m_pVertexBufferArray[i] = NULL;

   m_IDSrc = 0;
   m_IDDest = 0;
   m_Timer = 1.0f;

   m_pIndexBuffer = NULL;
   m_pDecl = NULL;
   m_pMesh = NULL;
}

CDXUTMeshLipSync::~CDXUTMeshLipSync()
{
   Destroy();

   SafeRelease( m_pDecl );
}

//頂点バッファとインデックスバッファはデバイスロスト時に開放する必要があるので、Destroy()で開放する
HRESULT CDXUTMeshLipSync::Destroy()
{
   for( int i=0; i<LIPSYNC_VBUFFER_NUM; i++ )
      SafeRelease( m_pVertexBufferArray[i] );

   SafeRelease( m_pIndexBuffer );

   SafeDelete( m_pMesh );
   
   return S_OK;
}

//頂点宣言を設定
HRESULT CDXUTMeshLipSync::CreateVertexDeclaration( D3DVERTEXELEMENT9 pDecl[] )
{
   HRESULT hr = E_FAIL;

   //頂点宣言を作成
   hr = m_pd3dDevice->CreateVertexDeclaration( pDecl, &m_pDecl );

   m_numStreams = 0;
   m_Index = 0;

   //設定した頂点宣言からストリーム数を取得する
   hr = m_pDecl->GetDeclaration( m_pElement, &m_numElements);
   m_numElements--;
   for( WORD i=0; i<m_numElements; i++ )
   {
      if( m_pElement[i].Stream > m_numStreams )
         m_numStreams = m_pElement[i].Stream;
   }
   m_numStreams++;

   hr = S_OK;

   return hr;
}

//キーフレームとなるメッシュのファイル名をどんどん追加
HRESULT CDXUTMeshLipSync::AddMeshFileName( LPCTSTR strFilename )
{
   HRESULT hr = E_FAIL;

   strcpy( m_FileName[m_Index], strFilename );

   m_Index++;

   hr = S_OK;

   return hr;
}

//追加したメッシュのファイル名をもとに頂点バッファを作成する
HRESULT CDXUTMeshLipSync::Create()
{
   HRESULT hr = E_FAIL;
   CDXUTMesh* Mesh = NULL;
   LPVOID pDst, pSrc;

   LPDIRECT3DVERTEXBUFFER9 VertexBuffer = NULL;
   LPDIRECT3DINDEXBUFFER9 IndexBuffer = NULL;

   for( WORD i=0; i<m_numStreams; i++ )
   {
      DWORD FVF = 0;

      //FVFの取得
      for( WORD j=0; j<m_numElements; j++ )
      {
         if( i == m_pElement[j].Stream )
         {
            switch( m_pElement[j].Usage )
            {
            case D3DDECLUSAGE_POSITION:
               FVF |= D3DFVF_XYZ;
               break;
            case D3DDECLUSAGE_NORMAL:
               FVF |= D3DFVF_NORMAL;
               break;
            case D3DDECLUSAGE_TEXCOORD:
               FVF |= D3DFVF_TEX1;
               break;
            }
         }
      }

      //Xファイルをロード
      Mesh = new CDXUTMesh();
      hr = Mesh->Create( m_pd3dDevice, m_FileName[i] );
      hr = Mesh->SetFVF( m_pd3dDevice, FVF );

      //メッシュの頂点数を取得
      m_dwNumVertices = Mesh->m_pSysMemMesh->GetNumVertices();

      //頂点バッファを取得
      hr = Mesh->m_pSysMemMesh->GetVertexBuffer( &VertexBuffer );

      int size = 0;

      //1頂点のメモリサイズを取得
      if( ( FVF & D3DFVF_TEX1 ) == 0 )
         size = sizeof(PN_VERTEX);

      else
         size = sizeof(PNT_VERTEX);

      //頂点バッファの作成
      hr = m_pd3dDevice->CreateVertexBuffer(
                                             m_dwNumVertices * size,
                                             D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC,
                                             0,
                                             D3DPOOL_DEFAULT,
                                             &m_pVertexBufferArray[i],
                                             NULL
                                           );

      //頂点をセットアップ
      
      //頂点バッファを参照するためにロック
      hr = m_pVertexBufferArray[i]->Lock( 0, 0, &pDst, D3DLOCK_NOOVERWRITE );

      //頂点バッファに書き込むためにロック
      hr = VertexBuffer->Lock( 0, 0, &pSrc, 0 );

      //コピー
      CopyMemory( pDst, pSrc, m_dwNumVertices * size );

      hr = VertexBuffer->Unlock();
      
      hr = m_pVertexBufferArray[i]->Unlock();

      //
      if( ( FVF & D3DFVF_TEX1 ) && m_pIndexBuffer == NULL )
      {
         //マテリアル情報をロード
         m_pMesh = new CDXUTMesh();
         hr = m_pMesh->Create( m_pd3dDevice, m_FileName[i] );
         hr = m_pMesh->SetFVF( m_pd3dDevice, FVF );

         // インデックス バッファを作成 
         
         //三角ポリゴン数を取得
         m_dwNumFaceFaces = Mesh->m_pSysMemMesh->GetNumFaces();
         
         //インデックスバッファを取得
         Mesh->m_pSysMemMesh->GetIndexBuffer( &IndexBuffer );

         hr = m_pd3dDevice->CreateIndexBuffer(
                                               m_dwNumFaceFaces * 3 * sizeof(WORD),
                                               D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC,
                                               D3DFMT_INDEX16, D3DPOOL_DEFAULT,
                                               &m_pIndexBuffer,
                                               NULL
                                             );

         //インデックスバッファを参照するためにロック
         hr = m_pIndexBuffer->Lock( 0, 0, (void**)&pDst, D3DLOCK_NOOVERWRITE );

         //インデックスバッファに書き込むためにロック
         hr = IndexBuffer->Lock( 0, 0, (void**)&pSrc, 0 );

         //コピー
         CopyMemory( pDst, pSrc,  m_dwNumFaceFaces * 3 * sizeof(WORD) );

         hr = IndexBuffer->Unlock();

         hr = m_pIndexBuffer->Unlock();

         SafeRelease( IndexBuffer );
      }

      SafeRelease( VertexBuffer );
      SafeDelete( Mesh );
   }

   hr = S_OK;

   SafeRelease( VertexBuffer );
   SafeDelete( Mesh );

   return hr;
}

//変化後のメッシュのIDを設定
void CDXUTMeshLipSync::SetID( int ID )
{
   if( m_Timer == 1.0f )
   {
      m_IDSrc = m_IDDest;
      m_IDDest = ID;
      m_Timer = 0.0f;
   }
}

//アニメーションの更新
void CDXUTMeshLipSync::NextFrame()
{
   if( m_Timer < 1.0f )
   {
      //とりあえず変化量は固定で
      m_Timer+=0.1f;
      
      if( m_Timer > 1.0f )
         m_Timer = 1.0f;
   }
}

//テクスチャーの設定
HRESULT CDXUTMeshLipSync::SetTexture( DWORD Sampler, DWORD IndexMaterials )
{
   HRESULT hr = E_FAIL;

   hr = m_pd3dDevice->SetTexture( Sampler, m_pMesh->m_pTextures[IndexMaterials] );

   hr = S_OK;

   return hr;
}

//レンダリング
HRESULT CDXUTMeshLipSync::Render()
{
   HRESULT hr = E_FAIL;

   //頂点バッファの設定
   int size = 0;
   for ( WORD i = 0; i<m_numStreams; i++ )
   {
      size = 0;
      for( WORD j=0; j<m_numElements; j++ )
      {
         if( i == m_pElement[j].Stream )
         {
            if( m_pElement[j].Usage == D3DDECLUSAGE_TEXCOORD )
            {
               size = sizeof(PNT_VERTEX);
               break;
            }
         }
      }
      if( j >= m_numElements )
         size = sizeof(PN_VERTEX);

      hr = m_pd3dDevice->SetStreamSource( i, m_pVertexBufferArray[i], 0, size );
   }

   //インデックスバッファの設定
   hr = m_pd3dDevice->SetIndices( m_pIndexBuffer );

   //頂点宣言の設定
   hr = m_pd3dDevice->SetVertexDeclaration( m_pDecl );

   //レンダリング
   m_pd3dDevice->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, 0, 0, m_dwNumVertices, 0 , m_dwNumFaceFaces );

   //多分これでストリームソースを無効にする
   for ( WORD i = 0; i<m_numStreams; i++ )
      hr = m_pd3dDevice->SetStreamSource( i, NULL, 0, sizeof(PNT_VERTEX) );

   hr = S_OK;

   return hr;
}

リップシンクアニメーションつきXファイルクラスです。みづらくなるのでエラーチェックはしていません。 できるだけ汎用的に設計しましたが、それでもバンプマッピングに対応していないなど未対応の部分もあるので必要に応じて修正してください。 あとSetID()とNextFrame()関数も不完全です。なにせアニメーションしてる途中のときはSetID()による設定を受け付けませんから。必要なら修正してください。

---LipSync.fx---


float4x4 m_WVPP;
float4 m_LightDir;

int   m_SrcID;    //変更元のオブジェクトID
int   m_DestID;   //変更先のオブジェクトID
float m_Timer;    //アニメーションの変化量

//オブジェクトのテクスチャー
sampler tex0 : register(s0);

struct VS_OUTPUT
{
   float4 Pos    : POSITION;
   float2 Tex    : TEXCOORD0;
   float3 Normal : TEXCOORD1;
};
VS_OUTPUT VS( 
              float4 PosN    : POSITION0,
              float4 NormalN : NORMAL0,
              float2 Tex     : TEXCOORD0,

              float4 PosA    : POSITION1,
              float4 NormalA : NORMAL1,
              
              float4 PosI    : POSITION2,
              float4 NormalI : NORMAL2,

              float4 PosU    : POSITION3,
              float4 NormalU : NORMAL3,

              float4 PosE    : POSITION4,
              float4 NormalE : NORMAL4,

              float4 PosO    : POSITION5,
              float4 NormalO : NORMAL5
            )
{
   VS_OUTPUT Out;
   
   float4 P[6];   
   P[0] = PosN;
   P[1] = PosA;
   P[2] = PosI;
   P[3] = PosU;
   P[4] = PosE;
   P[5] = PosO;

   float4 N[6];   
   N[0] = NormalN;
   N[1] = NormalA;
   N[2] = NormalI;
   N[3] = NormalU;
   N[4] = NormalE;
   N[5] = NormalO;

   Out.Pos    = mul( lerp( P[m_SrcID], P[m_DestID], m_Timer ), m_WVPP );
   Out.Tex    = Tex;
   Out.Normal = normalize( lerp( N[m_SrcID], N[m_DestID], m_Timer ).xyz );
    
   return Out;
}

float4 PS( VS_OUTPUT In ) : COLOR0
{
   //ライティングはハーフランバートで
   float4 Out;
   float p = dot(In.Normal, -m_LightDir.xyz);
   p = p * 0.5f + 0.5;
   p = p * p;
      
   //色情報をRGBに格納する
   Out = p * tex2D( tex0, In.Tex );

   return Out;
}

technique TShader
{
   pass P0
   {
      VertexShader = compile vs_1_1 VS();
      PixelShader  = compile ps_2_0 PS();
   }
}

頂点と法線を線形合成しているだけのシェーダーです。

---Lambert.h---


class LIPSYNC
{
private:
   LPD3DXEFFECT m_pEffect;
   D3DXHANDLE m_pTechnique, m_pWVPP, m_pLightDir, m_pSrcID, m_pDestID, m_pTimer;
   D3DXMATRIX m_matView, m_matProj;
   LPDIRECT3DDEVICE9 m_pd3dDevice;

public:
   LIPSYNC( LPDIRECT3DDEVICE9 pd3dDevice );
   ~LIPSYNC();
   void Invalidate();
   void Restore();
   HRESULT Load();
   void SetParameters( int SrcID, int DestID, float Timer );
   void Begin();
   void BeginPass();
   void SetMatrix( D3DXMATRIX* pMatWorld, D3DXVECTOR4* pLightDir );
   void CommitChanges();
   void EndPass();
   void End();
   BOOL IsOK();
   LPD3DXEFFECT GetEffect(){ return m_pEffect; };
};

---Lambert.cpp---


LIPSYNC::LIPSYNC( LPDIRECT3DDEVICE9 pd3dDevice )
{
   m_pd3dDevice = pd3dDevice;
   m_pEffect = NULL;
}

LIPSYNC::~LIPSYNC()
{
   SafeRelease( m_pEffect );
}

void LIPSYNC::Invalidate()
{
   if( m_pEffect )
      m_pEffect->OnLostDevice();
}

void LIPSYNC::Restore()
{
   if( m_pEffect )
      m_pEffect->OnResetDevice();
}

HRESULT LIPSYNC::Load()
{
   D3DCAPS9 caps;

   m_pd3dDevice->GetDeviceCaps( &caps );
   if( caps.VertexShaderVersion >= D3DVS_VERSION( 1, 1 ) && caps.PixelShaderVersion >= D3DPS_VERSION( 2, 0 ) )
   {
      LPD3DXBUFFER pErr = NULL;
      HRESULT hr = D3DXCreateEffectFromFile( m_pd3dDevice, _T("LipSync.fx"), NULL, NULL, 0, NULL, &m_pEffect, &pErr );
      if( FAILED( hr ) )
         return -2;

      m_pTechnique = m_pEffect->GetTechniqueByName( "TShader" );
      m_pWVPP      = m_pEffect->GetParameterByName( NULL, "m_WVPP" );
      m_pLightDir  = m_pEffect->GetParameterByName( NULL, "m_LightDir" );
      m_pSrcID     = m_pEffect->GetParameterByName( NULL, "m_SrcID" );
      m_pDestID    = m_pEffect->GetParameterByName( NULL, "m_DestID" );
      m_pTimer     = m_pEffect->GetParameterByName( NULL, "m_Timer" );

      m_pEffect->SetTechnique( m_pTechnique );
   }

   else
   {
      return -3;
   }

   return S_OK;
}

void LIPSYNC::SetParameters( int SrcID, int DestID, float Timer )
{
   if(  m_pEffect )
   {
      m_pEffect->SetInt( m_pSrcID,  SrcID );
      m_pEffect->SetInt( m_pDestID, DestID );
      m_pEffect->SetFloat( m_pTimer,  Timer );
   }
}

void LIPSYNC::Begin()
{
   if(  m_pEffect )
   {
      m_pd3dDevice->GetTransform( D3DTS_VIEW, &m_matView );
      m_pd3dDevice->GetTransform( D3DTS_PROJECTION, &m_matProj );

      m_pEffect->Begin( NULL, 0 );
   }
}

void LIPSYNC::BeginPass()
{
   if( m_pEffect )
   {
      m_pEffect->BeginPass( 0 );
   }
}

//ローカル座標系
void LIPSYNC::SetMatrix( D3DXMATRIX* pMatWorld, D3DXVECTOR4* pLightDir )
{
   if( m_pEffect )
   {
      D3DXMATRIX m;
      D3DXVECTOR4 LightDir;
      D3DXVECTOR4 v;

      m = (*pMatWorld) * m_matView * m_matProj;
      m_pEffect->SetMatrix( m_pWVPP, &m );

      //Light
      LightDir = *pLightDir;
      D3DXMatrixInverse( &m, NULL, pMatWorld );
      D3DXVec4Transform( &v, &LightDir, &m );
      D3DXVec4Normalize( &v, &v );
      m_pEffect->SetVector( m_pLightDir, &v );
   }

   else
      m_pd3dDevice->SetTransform( D3DTS_WORLD, pMatWorld );
}

void LIPSYNC::CommitChanges()
{
   if( m_pEffect )
      m_pEffect->CommitChanges();
}

void LIPSYNC::EndPass()
{
   if( m_pEffect )
   {
      m_pEffect->EndPass();
   }
}

void LIPSYNC::End()
{
   if( m_pEffect )
   {
      m_pEffect->End();
   }
}

BOOL LIPSYNC::IsOK()
{
   if( m_pEffect )
      return TRUE;

   return FALSE;
}

リップシンクアニメーションするときはこのクラスを使用してレンダリングします。

---Main.cpp---


LPDIRECT3D9 m_pdirect3d9 = NULL;
LPDIRECT3DDEVICE9 m_pd3dDevice = NULL;
D3DPRESENT_PARAMETERS m_d3dParameters;

D3DCAPS9 Caps;

//シーンのメッシュ
CDXUTMeshLipSync* m_pMeshLipSync = NULL;

//リップシンクシェーダークラスの宣言
LIPSYNC* m_pLipSync = NULL;

//スクリーンの解像度
UINT nWidth = 1024;
UINT nHeight = 768;

//太陽の位置ベクトル
//光源の位置はカメラの視線方向にある
D3DXVECTOR4 LightPos = D3DXVECTOR4( 200.0f, 400.0f, -500.0f, 1.0f );

//平行光源の光の方向ベクトル
D3DXVECTOR4 LightDir;

//視点の位置ベクトル
D3DXVECTOR4 EyePos   = D3DXVECTOR4( 0.0f, 0.0f, 0.0f, 1.0f );

bool RenderOK = false;

int APIENTRY WinMain( HINSTANCE hInstance,
                      HINSTANCE /*hPrevInstance*/,
                      LPSTR     /*lpCmpLine*/,
                      INT       /*nCmdShow*/)
{  
   char* AppName = "Tutrial";

   MSG msg;
   ZeroMemory(&msg, sizeof(MSG));
   HWND hWnd = NULL;

   WNDCLASSEX wc;
   wc.cbSize      = sizeof(WNDCLASSEX);
   wc.style      = CS_VREDRAW | CS_HREDRAW;
   wc.lpfnWndProc   = (WNDPROC)WndProc;
   wc.cbClsExtra   = 0;
   wc.cbWndExtra   = sizeof(DWORD);
   wc.hCursor      = LoadCursor(NULL, IDC_ARROW);
   wc.hIcon      = NULL;
   wc.hIconSm      = NULL;
   wc.lpszMenuName    = NULL;
   wc.lpszClassName = AppName;
   wc.hbrBackground = (HBRUSH)GetStockObject( BLACK_BRUSH );
   wc.hInstance    = hInstance;
   RegisterClassEx(&wc);


   //****************************************************************
   //ここでウィンドウの作成処理
   //****************************************************************


   //****************************************************************
   //ここでDirect3Dの初期化を行う。
   //****************************************************************

   m_pd3dDevice->GetDeviceCaps(&Caps);

   //リップシンククラスの初期化
   m_pLipSync = new LIPSYNC( m_pd3dDevice );
   m_pLipSync->Load();

   //顔
   m_pMeshLipSync = new CDXUTMeshLipSync( m_pd3dDevice );
   //頂点宣言
   //テクセルはすべてのストリームソースの頂点で共有できるので一箇所のみ設定
   //キーフレームの増減はここで設定すること
   D3DVERTEXELEMENT9 decl[] =
   {
      //n_face
      {0,  0, D3DDECLTYPE_FLOAT3,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0},
      {0, 12, D3DDECLTYPE_FLOAT3,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL,   0},
      {0, 24, D3DDECLTYPE_FLOAT2,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0},

      //a_face
      {1,  0, D3DDECLTYPE_FLOAT3,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 1},
      {1, 12, D3DDECLTYPE_FLOAT3,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL,   1},

      //i_face
      {2,  0, D3DDECLTYPE_FLOAT3,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 2},
      {2, 12, D3DDECLTYPE_FLOAT3,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL,   2},

      //u_face
      {3,  0, D3DDECLTYPE_FLOAT3,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 3},
      {3, 12, D3DDECLTYPE_FLOAT3,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL,   3},

      //e_face
      {4,  0, D3DDECLTYPE_FLOAT3,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 4},
      {4, 12, D3DDECLTYPE_FLOAT3,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL,   4},

      //o_face
      {5,  0, D3DDECLTYPE_FLOAT3,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 5},
      {5, 12, D3DDECLTYPE_FLOAT3,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL,   5},

      D3DDECL_END()
   };
   m_pMeshLipSync->CreateVertexDeclaration( decl );
   //メッシュのファイル名を追加
   m_pMeshLipSync->AddMeshFileName( _T("res\\face_n.x") );
   m_pMeshLipSync->AddMeshFileName( _T("res\\face_a.x") );
   m_pMeshLipSync->AddMeshFileName( _T("res\\face_i.x") );
   m_pMeshLipSync->AddMeshFileName( _T("res\\face_u.x") );
   m_pMeshLipSync->AddMeshFileName( _T("res\\face_e.x") );
   m_pMeshLipSync->AddMeshFileName( _T("res\\face_o.x") );


   //平行光源の位置ベクトルから方向ベクトルを計算する
   LightDir = D3DXVECTOR4( -LightPos.x, -LightPos.y, -LightPos.z, 0.0f );
   D3DXVec3Normalize( (D3DXVECTOR3*)&LightDir, (D3DXVECTOR3*)&LightDir );

   RenderOK = true;

   //デバイス消失後にリストアする必要があるオブジェクトの初期化
   Restore();

   ::ShowWindow(hWnd, SW_SHOW);
   ::UpdateWindow(hWnd);

   do
   { 
      if( ::PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) )
      {
         ::TranslateMessage(&msg); 
         ::DispatchMessage(&msg); 
      }
      else
      {
         if( MainLoop(hWnd) == FALSE )
            ::DestroyWindow( hWnd );
      }
   }while( msg.message != WM_QUIT );

   ::UnregisterClass( AppName, hInstance );

   return msg.wParam;
}

//デバイスのリセット前に開放すべきオブジェクト
void Invalidate()
{
   m_pLipSync->Invalidate();

   m_pMeshLipSync->Destroy();
}

//デバイスのリセット後に初期化すべきオブジェクト
void Restore()
{
   m_pLipSync->Restore();

   m_pMeshLipSync->Create();


   //固定機能パイプラインライティングを設定する
   D3DLIGHT9 Light;
   ZeroMemory(&Light, sizeof(D3DLIGHT9));
   Light.Type = D3DLIGHT_DIRECTIONAL;
   Light.Direction = D3DXVECTOR3( LightDir.x, LightDir.y, LightDir.z );
   Light.Ambient   = D3DXCOLOR( 1.0f, 1.0f, 1.0f, 1.0f );
   Light.Diffuse   = D3DXCOLOR( 1.0f, 1.0f, 1.0f, 1.0f );
   Light.Specular  = D3DXCOLOR( 0.0f, 0.0f, 0.0f, 0.0f );
   m_pd3dDevice->SetLight(0, &Light);
   m_pd3dDevice->LightEnable(0, TRUE);

   D3DMATERIAL9 Material;
   ZeroMemory( &Material, sizeof( Material ) );
   Material.Diffuse.r = 1.0f;
   Material.Diffuse.g = 1.0f;
   Material.Diffuse.b = 1.0f;
   Material.Diffuse.a = 1.0f;
   m_pd3dDevice->SetMaterial( &Material );
}

//メッセージループからコールされる関数
BOOL MainLoop( HWND HWnd )
{
   HRESULT hr;
   
   //レンダリング不可能
   if( RenderOK == false )
   {
      hr = m_pd3dDevice->TestCooperativeLevel();
      switch( hr )
      {
      //デバイスは消失しているがReset可能
      case D3DERR_DEVICENOTRESET:

         //開放
         Invalidate();

         //デバイスをリセットする
         hr = m_pd3dDevice->Reset( &m_d3dParameters );
         
         switch( hr )
         {
         //デバイスロスト
         case D3DERR_DEVICELOST:
            break;

         //内部ドライバーエラー
         case D3DERR_DRIVERINTERNALERROR:
            return FALSE;
            break;

         //メソッドの呼び出しが無効です
         case D3DERR_INVALIDCALL:
            return FALSE;
            break;

         case S_OK:

            //初期化
            Restore();

            RenderOK = true;
         }
         break;
      }
   }

   //レンダリング可能
   else
   {
      D3DXMATRIX matWVP, matPProj, matView, matWorld, matTiger, matScaling, matTranslation, matRotation;

      //遠近射影座標変換
      //クリップ面はアプリケーションごとに調整すること
      D3DXMatrixPerspectiveFovLH( &matPProj,
                                  D3DX_PI/4.0f,
                                  4.0f / 3.0f,
                                  30.0f,
                                  700.0f );
      m_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matPProj );

      //ビュー座標変換
      D3DXMatrixIdentity( &matView );
      m_pd3dDevice->SetTransform( D3DTS_VIEW, &matView );

      m_pd3dDevice->BeginScene();

      m_pd3dDevice->SetSamplerState( 0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR );
      m_pd3dDevice->SetSamplerState( 0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR );
      m_pd3dDevice->SetSamplerState( 0, D3DSAMP_MIPFILTER, D3DTEXF_NONE );

      m_pd3dDevice->Clear( 0L,
                           NULL,
                           D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
                           0x00A0A0FF,                            
                           1.0f,
                           0L
                         );

      //リップシンクアニメーションの更新
      m_pMeshLipSync->NextFrame();
      
      //レンダリング
      m_pMeshLipSync->SetTexture( 0, 0 );
      m_pLipSync->SetParameters( m_pMeshLipSync->m_IDSrc, m_pMeshLipSync->m_IDDest, m_pMeshLipSync->m_Timer );
      m_pLipSync->Begin();
      m_pLipSync->BeginPass();
      D3DXMatrixIdentity( &matWorld );
      m_pLipSync->SetMatrix( &matWorld, &LightDir );
      m_pMeshLipSync->Render();
      m_pLipSync->EndPass();
      m_pLipSync->End();

      m_pd3dDevice->EndScene();

      hr = m_pd3dDevice->Present( NULL, NULL, NULL, NULL );
      
      //デバイスロストのチェック
      switch( hr )
      {
      //デバイスロスト
      case D3DERR_DEVICELOST:
         RenderOK = false;
         break;

      //内部ドライバーエラー
      case D3DERR_DRIVERINTERNALERROR:
         return FALSE;
         break;

      //メソッドの呼び出しが無効です
      case D3DERR_INVALIDCALL:
         return FALSE;
         break;
      }
   }

   return TRUE;
}

以上です。今回はモデリングが面倒です。なにせキーフレームの数だけモデリングする必要がありわけですから。 でモデリング上の注意点としてですが、
1.頂点数はすべてのキーフレームモデルで同じであること。
2.インデックスバッファを共有するため、頂点の順序を変えないこと。
があげられます。
まあ筆者は業界人でもCG屋でもないですけど、 基本となるモデルを作成して、それをベースにして頂点を動かしてキーフレームモデルをそれぞれ作成していくのが一般的でしょうか? 今回のサンプル作成ではそのようにして作成しました。

しかし、自分でやってみて思いましたがリップシンクは難しいです。モデルが悪いというのもありますが、口の動きがきもい(笑)。 まあ正直言うとこれが原因でサンプルイメージのせなかったんですけどね。 ゲームしてるときはあんまり意識しないんですけど、これってすごい技術だよなと今更思いました。

Prev  Top  Next
inserted by FC2 system