Microsoft Visual C++ 2010 Express Microsoft DirectX SDK (June 2010) Direct3D 11.0 FBX SDK 2011.3 |
■FBX SDKを使う | Prev Top Next |
関連ページ:Direct3D 11.0 初期化 Direct3D 11.0 デバック用フォント描画 FBX SDKの準備 |
|
今回はFBXファイルから頂点情報を取得して、Direct3Dで描画するところまでやります。 FBXファイルのデータの解析はFBX SDKがやってくれますので、解析してくれたデータをDirect3Dで使用できるように 頂点の配列バッファに変換する処理を実装することになります。
FBX SDKから頂点データを取り出す方法は、サンプルPGを参考にするとよいでしょう。 当サイトのサンプル作成でもそうしました。
FBX SDK の初期化のサンプルソースは"examples\Common\Common.cxx"
頂点情報と取り出すサンプルソースは"examples\ImportScene\DisplayMesh.cxx"
を参照してください。
あと今回のサンプルではすべての頂点情報を取得しません。今後の展開で機能追加するかもしれませんが、とりあえず最低限必要だと思う機能のみ対応します。
ではソースを見ていきます。
---MeshUser.h---
#ifndef MESH_USER_H #define MESH_USER_H #include <vector> #include "DX11User.h" // メッシュ情報クラス class MESH_USER { public: // 頂点情報構造体 typedef struct _VERTEX_USER { D3DXVECTOR3 Vertex; // 頂点データ D3DXVECTOR3 Normal; // 法線ベクトル D3DXVECTOR2 UV; // テクセル }VERTEX_USER; // テクスチャー構造体 typedef struct _TEXTURE_USER { TCHAR MaterialName[512]; // テクスチャーの種別名 TCHAR TexturePath[512]; // 実行ファイルからテクスチャーまでの相対パス ID3D11ShaderResourceView* Texture; }TEXTURE_USER; TCHAR MeshName[512]; // メッシュ名 VERTEX_USER* Vertexes; // 頂点バッファ UINT VertexesCount; // 頂点バッファ数 UINT* Indexes; // インデックスバッファ UINT IndexesCount; // インデックスバッファ数 ID3D11Buffer* VertexBuffer; // 頂点バッファ ID3D11Buffer* IndexBuffer; // インデックスバッファ std::vector<TEXTURE_USER> Texture; // テクスチャー配列 MESH_USER() { MeshName[0] = _T('\0'); Vertexes = NULL; VertexesCount = 0; Indexes = NULL; IndexesCount = 0; VertexBuffer = NULL; IndexBuffer = NULL; Texture.clear(); } ~MESH_USER() { MeshName[0] = _T('\0'); SAFE_DELETE_ARRAY( Vertexes ); VertexesCount = 0; SAFE_DELETE_ARRAY( Indexes ); IndexesCount = 0; SAFE_RELEASE( VertexBuffer ); SAFE_RELEASE( IndexBuffer ); for( int i=0; i<(int)Texture.size(); i++ ) SAFE_RELEASE( Texture[i].Texture ); Texture.clear(); } // テクスチャーの種別名でシェーダーリソースビューを取得 void GetTexture( const TCHAR* pMaterialName, ID3D11ShaderResourceView** pSRV ) { (*pSRV) = NULL; for( int i=0; i<(int)Texture.size(); i++ ) { if( _tcscmp( Texture[i].MaterialName, pMaterialName ) == 0 ) { (*pSRV) = Texture[i].Texture; break; } } } }; // ベースとなるメッシュ情報クラス class BASE_MESH_USER { public: MESH_USER* MeshUser; // メッシュ情報クラスバッファ UINT MeshCount; // メッシュ情報クラスバッファ数 BASE_MESH_USER() { MeshUser = NULL; MeshCount = 0; } ~BASE_MESH_USER() { SAFE_DELETE_ARRAY( MeshUser ); } }; #endif頂点情報を格納するクラスです。VERTEX_USER構造体をみればわかるでしょうが、サンプルでは頂点の位置ベクトル、法線ベクトル、UVとテクスチャーをサポートします。
---FBXSDKMeshLoaderUser.h---
#ifndef FBXSDK_MESHLOADER_USER_H #define FBXSDK_MESHLOADER_USER_H // ワーニングがうっとうしい #pragma warning(disable: 4100) #pragma warning(disable: 4512) #include <tchar.h> #include <fbxsdk.h> #include <vector> #include "MeshUser.h" #pragma comment( lib, "wininet.lib" ) #pragma comment( lib, "advapi32.lib" ) #if defined(DEBUG) || defined(_DEBUG) #pragma comment( lib, "fbxsdk_mt2010d.lib" ) #else #pragma comment( lib, "fbxsdk_mt2010.lib" ) #endif class FBXSDK_MESHLOADER_USER { private: ID3D11Device* m_D3DDevice; KFbxSdkManager* m_pFBXSdkManager; KFbxScene* m_pFBXScene; private: // 頂点データを Direct3D で使える形式に変換するためのおおもとの関数 HRESULT ConvBaseMeshUser( BASE_MESH_USER* pBaseMesh ); // 頂点データを Direct3D で使える形式に変換するための処理の中でメッシュごとに処理するための関数 HRESULT ConvMeshUser( KFbxNode* pFBXNode, MESH_USER* pMeshUser ); // 頂点情報をセット HRESULT SetVertexBuffer( KFbxMesh* pFBXMesh, MESH_USER* pMeshUser, UINT IndexArray[6] ); // テクスチャーマップを取得 HRESULT GetBaseTextureMap( KFbxSurfaceMaterial* pMaterial, MESH_USER* pMeshUser ); // テクスチャーマップを取得 HRESULT GetTextureMap( KFbxSurfaceMaterial* pMaterial, MESH_USER* pMeshUser, const char* pMaterialName ); // FBX SDK 内部の文字列は char型配列 の Unicode っぽいので TCHAR に変換する void ConvertCHARToTCHAR( TCHAR pDestStr[512], const char* pSrcStr ); // Direct3D で使用するバッファ領域にセット void SetDirect3DBuffer( KFbxVector4* pFBXVector, D3DXVECTOR3* pD3DVector ); void SetDirect3DBuffer( KFbxVector2* pFBXVector, D3DXVECTOR2* pD3DVector ); // 頂点、インデックスバッファを作成する HRESULT CreateBuffer( ID3D11Buffer** pBuffer, void* pData, size_t size, D3D11_BIND_FLAG BindFlag ); public: FBXSDK_MESHLOADER_USER(); ~FBXSDK_MESHLOADER_USER(); // 初期化 HRESULT Initialize( ID3D11Device* pD3DDevice ); // 終了処理 void Invalidate(); // FBXファイルの読み込み HRESULT LoadMeshData( TCHAR* pFilename, BASE_MESH_USER** ppBaseMesh ); }; #endifFBX SDK が解析した頂点データを Direct3D で使えるデータに変換するクラス定義です。
---FBXSDKMeshLoaderUser.cpp---
#include "FBXSDKMeshLoaderUser.h" FBXSDK_MESHLOADER_USER::FBXSDK_MESHLOADER_USER() { m_pFBXSdkManager = NULL; m_pFBXScene = NULL; m_D3DDevice = NULL; } FBXSDK_MESHLOADER_USER::~FBXSDK_MESHLOADER_USER() { Invalidate(); } void FBXSDK_MESHLOADER_USER::Invalidate() { if( m_pFBXScene ) m_pFBXScene->Destroy(); m_pFBXScene = NULL; if( m_pFBXSdkManager ) m_pFBXSdkManager->Destroy(); m_pFBXSdkManager = NULL; } // 初期化処理( examples\Common\Common.cxx 参照 ) HRESULT FBXSDK_MESHLOADER_USER::Initialize( ID3D11Device* pD3DDevice ) { HRESULT hr = E_FAIL; m_D3DDevice = pD3DDevice; // FBX SDK マネージャーオブジェクトのインスタンス化 m_pFBXSdkManager = KFbxSdkManager::Create(); if( m_pFBXSdkManager == NULL ) { OutputDebugString( _T("■□■ KFbxSdkManager::Createでエラー ■□■\n") ); goto EXIT; } // 各種設定などを行うためのオブジェクトを作成 { KFbxIOSettings* ios = NULL; // IOSettings オブジェクトを作成。IOSROOTは階層的なプロパティ名に使用される定義らしいがよくわからん。 ios = KFbxIOSettings::Create( m_pFBXSdkManager, IOSROOT ); m_pFBXSdkManager->SetIOSettings( ios ); } // プラグインのディレクトリパスを設定。機能拡張に使用する? { // 実行ファイルが置いてあるディレクトリ KString lPath = KFbxGetApplicationDirectory(); // OS によって拡張子が異なるので指定する #if defined(KARCH_ENV_WIN) KString lExtension = "dll"; #elif defined(KARCH_ENV_MACOSX) KString lExtension = "dylib"; #elif defined(KARCH_ENV_LINUX) KString lExtension = "so"; #endif m_pFBXSdkManager->LoadPluginsDirectory(lPath.Buffer(), lExtension.Buffer()); } // シーンの作成 m_pFBXScene = KFbxScene::Create( m_pFBXSdkManager, "" ); hr = S_OK; EXIT: return hr; } // FBXファイルの読み込み HRESULT FBXSDK_MESHLOADER_USER::LoadMeshData( TCHAR* pFilename, BASE_MESH_USER** ppBaseMesh ) { HRESULT hr = E_FAIL; // FBX SDK のバージョン int lSDKMajor, lSDKMinor, lSDKRevision; // FBXファイルのバージョン int lFileMajor, lFileMinor, lFileRevision; // インポーター KFbxImporter* lImporter = NULL; // デバック用文字列 TCHAR DebugStr[512]; if( pFilename == NULL ) goto EXIT; (*ppBaseMesh) = new BASE_MESH_USER(); // FBX SDK のバージョン取得 KFbxSdkManager::GetFileFormatVersion( lSDKMajor, lSDKMinor, lSDKRevision ); // FBX ファイルを読み込むためのインポーターを作成 lImporter = KFbxImporter::Create( m_pFBXSdkManager, "" ); TCHAR FullName[512]; char FileName[512]; // FBXファイルへの絶対パス内に日本語文字が存在する場合インポーターの初期化に失敗するので、絶対パスを取得してFBX SDK内で正しく読めるように変換する { // FBXファイルがあるディレクトリのフルパスを取得 if( ::GetFullPathName( pFilename, MAX_PATH, FullName, NULL ) == 0 ) goto EXIT; // FBX SDK 内部の文字列は char型配列 の Unicode っぽいので変換する #ifdef _UNICODE // Unicode 文字コードを第一引数で指定した文字コードに変換する ::WideCharToMultiByte( CP_UTF8, 0, FullName, -1, FileName, (int)((wcslen(FullName)+1) * 2), NULL, NULL ); #else WCHAR str[512]; // 第一引数で指定した文字コードを Unicode 文字コードに変換する( CP_ACP は日本語WindowdではシフトJISコード ) ::MultiByteToWideChar( CP_ACP, 0, FullName, -1, str, (int)((strlen(FullName) + 1) * sizeof(WCHAR)) ); // Unicode 文字コードを第一引数で指定した文字コードに変換する ::WideCharToMultiByte( CP_UTF8, 0, str, -1, FileName, (int)((wcslen(str)+1) * 2), NULL, NULL ); #endif } // インポーターを使用してファイルロード const bool lImportStatus = lImporter->Initialize( FileName, // char型配列のUnicode形式で格納される FBX ファイルパス -1, // ファイルフォーマットは通常指定しない。指定しない場合、拡張子によってフォーマットを決定する。 m_pFBXSdkManager->GetIOSettings() // NULL の場合デフォルトの設定を使用するらしいが、デフォルトの設定ってなに? ); if( lImportStatus == false ) { _stprintf_s( DebugStr, 512, _T("■□■ FBXファイルロードエラー. FileName: [ %s ] ■□■\n"), pFilename ); OutputDebugString( DebugStr ); goto EXIT; } // 読み込んだFBXファイルのバージョンを取得 lImporter->GetFileVersion( lFileMajor, lFileMinor, lFileRevision ); // サンプルにはない処理だが、 FBX SDK がサポートするファイルバージョンと一致しないバージョンのファイルをロードした場合はエラーにする if( lSDKMajor != lFileMajor || lSDKMinor != lFileMinor || lSDKRevision != lFileRevision ) { _stprintf_s( DebugStr, 512, _T("■□■ ERROR: バージョン不一致. FileName: [ %s ] FBX SDK Version: [ %d. %d. %d ] ImportFile Version: [ %d. %d. %d ] ■□■\n"), pFilename, lSDKMajor, lSDKMinor, lSDKRevision, lFileMajor, lFileMinor, lFileRevision ); OutputDebugString( DebugStr ); goto EXIT; } // インポート対象とする要素を指定してると思うが、IMP_FBX_TEXTUREをfalseにしてもテクスチャーを普通にロードできる。意味不明。 m_pFBXSdkManager->GetIOSettings()->SetBoolProp(IMP_FBX_MATERIAL, true); m_pFBXSdkManager->GetIOSettings()->SetBoolProp(IMP_FBX_TEXTURE, true); m_pFBXSdkManager->GetIOSettings()->SetBoolProp(IMP_FBX_LINK, true); m_pFBXSdkManager->GetIOSettings()->SetBoolProp(IMP_FBX_SHAPE, true); m_pFBXSdkManager->GetIOSettings()->SetBoolProp(IMP_FBX_GOBO, true); m_pFBXSdkManager->GetIOSettings()->SetBoolProp(IMP_FBX_ANIMATION, true); m_pFBXSdkManager->GetIOSettings()->SetBoolProp(IMP_FBX_GLOBAL_SETTINGS, true); // シーンオブジェクトにインポートする { bool lStatus = lImporter->Import( m_pFBXScene ); if( lStatus == false ) { // サンプルPGではこのあたりでパスワード入力というなぞの処理を行っているが、使用目的不明につきはずした _stprintf_s( DebugStr, 512, _T("■□■ ERROR: インポートエラー. FileName: [ %s ] ■□■\n"), pFilename ); OutputDebugString( DebugStr ); goto EXIT; } } // 読み込んだ FBX ファイルの内容を MESH_USER にセットする hr = ConvBaseMeshUser( *ppBaseMesh ); if( FAILED( hr ) ) goto EXIT; hr = S_OK; EXIT: // インポーターオブジェクトを開放 if( lImporter ) { lImporter->Destroy(); lImporter = NULL; } return hr; } // 頂点データを Direct3D で使える形式に変換するためのおおもとの関数 // 1ファイル内に複数のメッシュが存在する場合、すべてのメッシュの情報を取得する // この辺の動作確認はサンプルのNormals.fbxでできる // FBXSDKのサンプルソースの examples\ImportScene\main.cxx 参照 HRESULT FBXSDK_MESHLOADER_USER::ConvBaseMeshUser( BASE_MESH_USER* pBaseMesh ) { HRESULT hr = E_FAIL; KFbxNode* lNode = m_pFBXScene->GetRootNode(); if(lNode) { // メッシュ数ごとのループ処理 int lChildCount = lNode->GetChildCount(); pBaseMesh->MeshUser = new MESH_USER[lChildCount]; pBaseMesh->MeshCount = lChildCount; for( int i=0; i<lChildCount; i++ ) { // メッシュの識別名をセット const char* Name = lNode->GetChild(i)->GetName(); // TCHARにコンバート ConvertCHARToTCHAR( pBaseMesh->MeshUser[i].MeshName, Name ); // メッシュの頂点情報をセット hr = ConvMeshUser( lNode->GetChild(i), &(pBaseMesh->MeshUser[i]) ); if( FAILED( hr ) ) goto EXIT; } } hr = S_OK; EXIT: return hr; } // 頂点、インデックスバッファを作成する HRESULT FBXSDK_MESHLOADER_USER::CreateBuffer( ID3D11Buffer** pBuffer, void* pData, size_t size, D3D11_BIND_FLAG BindFlag ) { HRESULT hr = E_FAIL; // バッファー リソース D3D11_BUFFER_DESC BufferDesc; // サブリソース D3D11_SUBRESOURCE_DATA* resource = NULL; // 初期値を設定する if( pData ) { resource = new D3D11_SUBRESOURCE_DATA(); resource->pSysMem = pData; resource->SysMemPitch = 0; resource->SysMemSlicePitch = 0; } // バッファの設定 ::ZeroMemory( &BufferDesc, sizeof( BufferDesc ) ); BufferDesc.ByteWidth = size; // バッファサイズ BufferDesc.Usage = D3D11_USAGE_DEFAULT; // リソース使用法を特定する BufferDesc.BindFlags = BindFlag; // バッファの種類 BufferDesc.CPUAccessFlags = 0; // CPU アクセス BufferDesc.MiscFlags = 0; // その他のフラグも設定しない // バッファを作成する hr = m_D3DDevice->CreateBuffer( &BufferDesc, resource, pBuffer ); if( FAILED( hr ) ) goto EXIT; hr = S_OK; EXIT: SAFE_DELETE( resource ); return hr; } // 頂点データを Direct3D で使える形式に変換するための処理の中でメッシュごとに処理するための関数 // 子要素の属性はメッシュのみ対応。他にもカメラとかライトとかスケルトンとか色々ある。 // FBXSDKのサンプルソースの examples\ImportScene\DisplayMesh.cxx 参照 HRESULT FBXSDK_MESHLOADER_USER::ConvMeshUser( KFbxNode* pFBXNode, MESH_USER* pMeshUser ) { HRESULT hr = E_FAIL; // 四角形ポリゴンを三角ポリゴンに変換する際に使用するインデックス // 分割方法は適当 UINT IndexArray[6] = { 0, 1, 2, 0, 2, 3 }; KFbxNodeAttribute* lNodeAttribute = pFBXNode->GetNodeAttribute(); if( lNodeAttribute ) { switch (lNodeAttribute->GetAttributeType()) { // メッシュを処理する case KFbxNodeAttribute::eMESH: KFbxMesh* lMesh = (KFbxMesh*)lNodeAttribute; // 頂点バッファの情報をセット hr = SetVertexBuffer( lMesh, pMeshUser, IndexArray ); if( FAILED( hr ) ) goto EXIT; break; } } // 頂点バッファを作成する hr = CreateBuffer( &pMeshUser->VertexBuffer , pMeshUser->Vertexes, pMeshUser->VertexesCount * sizeof( MESH_USER::VERTEX_USER ) , D3D11_BIND_VERTEX_BUFFER ); if( FAILED( hr ) ) goto EXIT; // インデックスバッファを作成する hr = CreateBuffer( &pMeshUser->IndexBuffer , pMeshUser->Indexes, pMeshUser->IndexesCount * sizeof( UINT ) , D3D11_BIND_INDEX_BUFFER ); if( FAILED( hr ) ) goto EXIT; hr = S_OK; EXIT: return hr; } // インデックスバッファを使用する場合の頂点情報をセット HRESULT FBXSDK_MESHLOADER_USER::SetVertexBuffer( KFbxMesh* pFBXMesh, MESH_USER* pMeshUser, UINT IndexArray[6] ) { HRESULT hr = E_FAIL; // 頂点バッファの配列数を取得 int VertexesCount = pFBXMesh->GetControlPointsCount(); // ポリゴン数を取得 int lPolygonsCount = pFBXMesh->GetPolygonCount(); // インデックスバッファの要素数を取得 UINT IndexesCount = 0; for( int i=0; i<lPolygonsCount; i++ ) { // 1ポリゴン内の頂点数を取得 int lPolygonSize = pFBXMesh->GetPolygonSize(i); switch( lPolygonSize ) { case 3: // 三角ポリゴン IndexesCount += 3; break; case 4: // 四角ポリゴン // 四角ポリゴンの場合三角ポリゴンに変換する IndexesCount += 6; break; default: // 5角形以上のパターンがあるけど、対応しない OutputDebugString( _T("■□■ 5角形以上のポリゴンを検出 ■□■\n") ); goto EXIT; break; } } // バッファの領域確保処理 { pMeshUser->VertexesCount = (UINT)VertexesCount; pMeshUser->Vertexes = new MESH_USER::VERTEX_USER[VertexesCount]; pMeshUser->IndexesCount = IndexesCount; pMeshUser->Indexes = new UINT[IndexesCount]; } // 頂点情報をセット { for( int i=0; i<VertexesCount; i++ ) { // コントロールポイントから頂点情報を取得 KFbxVector4 lVec4 = pFBXMesh->GetControlPointAt( i ); // 頂点データをセット SetDirect3DBuffer( &lVec4, &(pMeshUser->Vertexes[i].Vertex) ); } } // インデックスバッファをセット { UINT Index = 0; for( int i=0; i<lPolygonsCount; i++ ) { // 1ポリゴン内の頂点数を取得 int lPolygonSize = pFBXMesh->GetPolygonSize(i); switch( lPolygonSize ) { case 3: // 三角ポリゴン for( int j=0; j<3; j++ ) { // コントロールポイントのインデックスを取得 pMeshUser->Indexes[Index] = pFBXMesh->GetPolygonVertex( i, j ); Index++; } break; case 4: // 四角ポリゴン for( int j=0; j<6; j++ ) { pMeshUser->Indexes[Index] = pFBXMesh->GetPolygonVertex( i, IndexArray[j] ); Index++; } break; } } } // レイヤー数を取得 int lLayerCount = pFBXMesh->GetLayerCount(); // レイヤー数が 2 以上となるFBXファイルの作成方法がわからんのでサポートしない if( lLayerCount > 1 ) { OutputDebugString( _T("■□■ レイヤー数が2個以上存在した。 ■□■\n") ); goto EXIT; } // レイヤーなし以下の処理スキップ if( lLayerCount == 0 ) { hr = S_OK; goto EXIT; } // 法線ベクトルをセット { KFbxLayerElementNormal* leNormal = pFBXMesh->GetLayer(0)->GetNormals(); if( leNormal ) { switch( leNormal->GetMappingMode() ) { // ポリゴン単位で頂点情報がセットされている case KFbxLayerElement::eBY_POLYGON_VERTEX: switch( leNormal->GetReferenceMode() ) { // インデックスバッファを使用せず、直接法線ベクトルが格納されている case KFbxLayerElement::eDIRECT: { // 頂点ごとに対応する法線ベクトルを探す for( int i=0; i<VertexesCount; i++ ) { D3DXVECTOR3 Vec3 = D3DXVECTOR3( 0, 0, 0 ); UINT NormalIndex = 0; // 法線ベクトルの重複チェック用のバッファ std::vector<D3DXVECTOR3> NormalTempArray; NormalTempArray.clear(); // ポリゴンごとのループ for( int j=0; j<lPolygonsCount; j++ ) { // 1ポリゴン内の頂点数を取得 int lPolygonSize = pFBXMesh->GetPolygonSize(j); for( int k=0; k<lPolygonSize; k++ ) { // インデックスが同じなので処理対象 if( i == pFBXMesh->GetPolygonVertex( j, k ) ) { // 法線ベクトル取得 KFbxVector4 lVec4 = leNormal->GetDirectArray().GetAt( NormalIndex ); bool Find = false; for( int l=0; l<(int)NormalTempArray.size(); l++ ) { // すでに加算処理すみの法線ベクトルかチェックする if( NormalTempArray[l].x == lVec4.GetAt(0) && NormalTempArray[l].y == lVec4.GetAt(1) && NormalTempArray[l].z == lVec4.GetAt(2) ) { Find = true; break; } } // 法線ベクトルが完全に一致する頂点を再度加算処理することがあるので、 // 重複して加算しないようにする if( Find == false ) { NormalTempArray.push_back( D3DXVECTOR3( (float)lVec4.GetAt(0), (float)lVec4.GetAt(1), (float)lVec4.GetAt(2) ) ); Vec3 = D3DXVECTOR3( Vec3.x + (float)lVec4.GetAt(0), Vec3.y + (float)lVec4.GetAt(1), Vec3.z + (float)lVec4.GetAt(2) ); } } NormalIndex++; } } // 単位ベクトルの計算 D3DXVec3Normalize( &Vec3, &Vec3 ); // 法線ベクトルをセット ::CopyMemory( &(pMeshUser->Vertexes[i].Normal), &Vec3, sizeof( D3DXVECTOR3 ) ); } } break; // インデックス参照で法線ベクトルが格納されている // サンプルにここの処理が実装されているので処理されるパターンが存在すると思われるが、確認できなかった case KFbxLayerElement::eINDEX_TO_DIRECT: { OutputDebugString( _T("■□■ 法線ベクトルでeDIRECTでeINDEX_TO_DIRECT が処理されちゃった ■□■\n") ); goto EXIT; } } break; // 頂点単位で頂点情報がセットされている case KFbxLayerElement::eBY_CONTROL_POINT: if( leNormal->GetReferenceMode() == KFbxLayerElement::eDIRECT ) { for( int i=0; i<VertexesCount; i++ ) { KFbxVector4 lVec4 = leNormal->GetDirectArray().GetAt(i); SetDirect3DBuffer( &lVec4, &(pMeshUser->Vertexes[i].Normal) ); } } // サンプルにここの処理は実装されていない。 else { OutputDebugString( _T("■□■ 法線ベクトルでeBY_CONTROL_POINTでeDIRECT以外が処理されちゃった ■□■\n") ); goto EXIT; } break; default: OutputDebugString( _T("■□■ 不明な法線ベクトルデータ ■□■\n") ); goto EXIT; } } } // UV { KFbxLayerElementUV* leUV = pFBXMesh->GetLayer(0)->GetUVs(); if( leUV ) { switch( leUV->GetMappingMode() ) { // ポリゴン単位で頂点情報がセットされている case KFbxLayerElement::eBY_POLYGON_VERTEX: switch( leUV->GetReferenceMode() ) { // このパターンは確認できず case KFbxLayerElement::eDIRECT: OutputDebugString( _T("■□■ UVでeBY_POLYGON_VERTEXでeDIRECT が処理されちゃった ■□■\n") ); goto EXIT; break; // インデックス参照で法線ベクトルが格納されている case KFbxLayerElement::eINDEX_TO_DIRECT: { // 頂点バッファのループ for( int i=0; i<VertexesCount; i++ ) { UINT UVIndex = 0; // ポリゴンごとのループ for( int j=0; j<lPolygonsCount; j++ ) { // ポリゴン数を取得 int lPolygonSize = pFBXMesh->GetPolygonSize(j); // 1ポリゴン内の頂点ごとのループ for( int k=0; k<lPolygonSize; k++ ) { // インデックスが同じなので処理対象 if( i == pFBXMesh->GetPolygonVertex( j, k ) ) { // インデックスバッファからインデックスを取得する int lUVIndex = leUV->GetIndexArray().GetAt(UVIndex); // 取得したインデックスから UV を取得する KFbxVector2 lVec2 = leUV->GetDirectArray().GetAt(lUVIndex); // UV値セット SetDirect3DBuffer( &lVec2, &(pMeshUser->Vertexes[i].UV) ); } UVIndex++; } } } } } break; // 3DS MAX の場合、法線を頂点ごとに分割して出力するとここが処理される case KFbxLayerElement::eBY_CONTROL_POINT: switch (leUV->GetReferenceMode()) { // インデックスバッファを使用せず、直接法線ベクトルが格納されている case KFbxLayerElement::eDIRECT: for( int i=0; i<VertexesCount; i++ ) { KFbxVector2 lVec2 = leUV->GetDirectArray().GetAt(i); SetDirect3DBuffer( &lVec2, &(pMeshUser->Vertexes[i].UV) ); } break; // 確認できず case KFbxLayerElement::eINDEX_TO_DIRECT: OutputDebugString( _T("■□■ UVでeBY_CONTROL_POINTでeINDEX_TO_DIRECTが処理されちゃった ■□■\n") ); goto EXIT; break; default: OutputDebugString( _T("■□■ UVでeBY_CONTROL_POINTでeDIRECTでもeINDEX_TO_DIRECTでもない処理されちゃった ■□■\n") ); goto EXIT; } break; default: OutputDebugString( _T("■□■ 不明なUVデータ ■□■\n") ); goto EXIT; } } } // TEXTURE { // このサンプルでは同じ種類のテクスチャーは1つのみ使用可能とする。 // テクスチャーの種類については、FBXSDKのサンプルソースの examples\ImportScene\DisplayMesh.cxx 参照 // またポリゴン単位でどのテクスチャーを参照するかできそうな雰囲気だが考慮しない KFbxLayerElementMaterial* lLayerMaterial = pFBXMesh->GetLayer(0)->GetMaterials(); if( lLayerMaterial ) { switch( lLayerMaterial->GetMappingMode() ) { // 1つのメッシュに1つだけマッピングされている case KFbxLayerElement::eALL_SAME: { int lMatId = lLayerMaterial->GetIndexArray().GetAt(0); KFbxSurfaceMaterial* lMaterial = pFBXMesh->GetNode()->GetMaterial( lMatId ); if( lMatId >= 0 ) { // テクスチャーのロード GetBaseTextureMap( lMaterial, pMeshUser ); } } break; // ポリゴンごとにマッピングされている。 case KFbxLayerElement::eBY_POLYGON: for( int i=0; i<lPolygonsCount; i++ ) { for( int j=0; j<pFBXMesh->GetLayerCount(); j++ ) { KFbxLayerElementMaterial* lLayerMaterial = pFBXMesh->GetLayer(j)->GetMaterials(); if (lLayerMaterial) { KFbxSurfaceMaterial* lMaterial = NULL; int lMatId = -1; lMaterial = pFBXMesh->GetNode()->GetMaterial( lLayerMaterial->GetIndexArray().GetAt(i) ); lMatId = lLayerMaterial->GetIndexArray().GetAt(i); if(lMatId >= 0) { // テクスチャーのロード GetBaseTextureMap( lMaterial, pMeshUser ); } } } } break; } } } // FBXでは右手系座標となっているためDirect3Dで使用できるように座標系を変換する { for( int i=0; i<VertexesCount; i++ ) { pMeshUser->Vertexes[i].Vertex.z *= -1.0f; pMeshUser->Vertexes[i].Normal.z *= -1.0f; pMeshUser->Vertexes[i].UV.y = 1.0f - pMeshUser->Vertexes[i].UV.y; } } // 頂点情報をデバッガに出力 #if defined(DEBUG) || defined(_DEBUG) TCHAR s[512]; _stprintf_s( s, _T("■□■ 頂点情報出力[ %d ]\n"), pMeshUser->VertexesCount ); OutputDebugString( s ); for( UINT j=0; j<pMeshUser->VertexesCount; j++ ) { _stprintf_s( s, _T("■□■ Vertex[ %f,%f,%f ] Normal[ %f,%f,%f ] UV[ %f, %f ]\n"), pMeshUser->Vertexes[j].Vertex.x, pMeshUser->Vertexes[j].Vertex.y, pMeshUser->Vertexes[j].Vertex.z, pMeshUser->Vertexes[j].Normal.x, pMeshUser->Vertexes[j].Normal.y, pMeshUser->Vertexes[j].Normal.z, pMeshUser->Vertexes[j].UV.x, pMeshUser->Vertexes[j].UV.y ); OutputDebugString( s ); } _stprintf_s( s, _T("■□■ インデックスバッファ情報出力[ %d ]\n"), pMeshUser->IndexesCount ); OutputDebugString( s ); for( UINT j=0; j<pMeshUser->IndexesCount; j++ ) { _stprintf_s( s, _T("■□■ Indexes[ %d ]\n"), pMeshUser->Indexes[j] ); OutputDebugString( s ); } _stprintf_s( s, _T("■□■ マテリアル情報出力[ %d ]\n"), pMeshUser->Texture.size() ); OutputDebugString( s ); for( UINT j=0; j<pMeshUser->Texture.size(); j++ ) { _stprintf_s( s, _T("■□■ Texture[ %s, %s ]\n"), pMeshUser->Texture[j].MaterialName, pMeshUser->Texture[j].TexturePath ); OutputDebugString( s ); } #endif hr =S_OK; EXIT: return hr; } HRESULT FBXSDK_MESHLOADER_USER::GetBaseTextureMap( KFbxSurfaceMaterial* pMaterial, MESH_USER* pMeshUser ) { HRESULT hr = E_FAIL; if( m_D3DDevice == NULL ) goto EXIT; // 拡散反射マップを取得 hr = GetTextureMap( pMaterial, pMeshUser, KFbxSurfaceMaterial::sDiffuse ); if( FAILED( hr ) ) goto EXIT; // ほかの種類のテクスチャーを追加する... hr = S_OK; EXIT: return hr; } HRESULT FBXSDK_MESHLOADER_USER::GetTextureMap( KFbxSurfaceMaterial* pMaterial, MESH_USER* pMeshUser, const char* pMaterialName ) { HRESULT hr = E_FAIL; TCHAR DebugStr[512]; KFbxProperty lProperty = pMaterial->FindProperty( pMaterialName ); D3DX11_IMAGE_LOAD_INFO info; ::ZeroMemory( &info, sizeof( D3DX11_IMAGE_LOAD_INFO ) ); info.Width = D3DX11_DEFAULT; info.Height = D3DX11_DEFAULT; info.Depth = D3DX11_DEFAULT; info.FirstMipLevel = D3DX11_DEFAULT; // テクスチャーの最高解像度のミップマップ レベル。実際の使用方法不明。 info.MipLevels = 0; // ミップマップ数。0 または D3DX11_DEFAULT を使用するとすべてのミップマップ チェーンを作成する。 info.Usage = D3D11_USAGE_DEFAULT; info.BindFlags = D3D11_BIND_SHADER_RESOURCE; info.CpuAccessFlags = 0; info.MiscFlags = 0; info.Format = DXGI_FORMAT_FROM_FILE; info.Filter = D3DX11_FILTER_POINT; // テクスチャー読み込み時に使用するフィルター info.MipFilter = D3DX11_FILTER_POINT; // ミップマップ作成時に使用するフィルター info.pSrcInfo = NULL; // すでに同じ種類のテクスチャーをセットしたかをチェックする { ID3D11ShaderResourceView* Texture = NULL; TCHAR MaterialName[512]; ConvertCHARToTCHAR( MaterialName, pMaterialName ); pMeshUser->GetTexture( MaterialName, &Texture ); if( Texture != NULL ) { hr = S_OK; goto EXIT; } } int lLayeredTextureCount = lProperty.GetSrcObjectCount( KFbxLayeredTexture::ClassId ); if( lLayeredTextureCount == 0 ) { int lNbTextures = lProperty.GetSrcObjectCount(KFbxTexture::ClassId); if( lNbTextures == 1 ) { KFbxTexture* lTexture = KFbxCast <KFbxTexture> (lProperty.GetSrcObject( KFbxTexture::ClassId, 0 ) ); if( lTexture ) { MESH_USER::TEXTURE_USER tex; tex.MaterialName[0] = NULL; tex.TexturePath[0] = NULL; tex.Texture = NULL; // 画像ファイルへの相対パスを取得する ConvertCHARToTCHAR( tex.TexturePath, (char*)lTexture->GetRelativeFileName() ); // テクスチャーの種類を取得 ConvertCHARToTCHAR( tex.MaterialName, lProperty.GetName().Buffer() ); // シェーダーリソースビューの作成 hr = D3DX11CreateShaderResourceViewFromFile( m_D3DDevice, tex.TexturePath, &info, NULL, &(tex.Texture), NULL ); if( FAILED( hr ) ) { _stprintf_s( DebugStr, 512, _T("■□■ ERROR: テクスチャーロードエラー. TexturePath: [ %s ] ■□■\n"), tex.TexturePath ); OutputDebugString( DebugStr ); goto EXIT; } pMeshUser->Texture.push_back( tex ); } } // 2以上になるパターンがわからん else if( lNbTextures > 1 ) { OutputDebugString( _T("■□■ テクスチャー数が2個以上存在した。 ■□■\n") ); goto EXIT; } } // レイヤードテクスチャーが存在するパターンの FBX ファイルの作成方法がわからん else { OutputDebugString( _T("■□■ レイヤードテクスチャーが見つかった。 ■□■\n") ); goto EXIT; } hr = S_OK; EXIT: return hr; } // TCHARにコンバート void FBXSDK_MESHLOADER_USER::ConvertCHARToTCHAR( TCHAR pDestStr[512], const char* pSrcStr ) { #ifdef _UNICODE // 第一引数で指定した文字コードを Unicode 文字コードに変換する ::MultiByteToWideChar( CP_UTF8, 0, pSrcStr, -1, pDestStr, (int)((strlen(pSrcStr) + 1) * sizeof(WCHAR)) ); #else WCHAR str[512]; // 第一引数で指定した文字コードを Unicode 文字コードに変換する ::MultiByteToWideChar( CP_UTF8, 0, pSrcStr, -1, str, (int)((strlen(pSrcStr) + 1) * sizeof(WCHAR)) ); // Unicode 文字コードを第一引数で指定した文字コードに変換する( CP_ACP は日本語WindowdではシフトJISコード ) ::WideCharToMultiByte( CP_ACP, 0, str, -1, pDestStr, (int)((wcslen(str)+1) * 2), NULL, NULL ); #endif } // Direct3D で使用するバッファ領域にセット void FBXSDK_MESHLOADER_USER::SetDirect3DBuffer( KFbxVector4* pFBXVector, D3DXVECTOR3* pD3DVector ) { pD3DVector->x = (float)pFBXVector->GetAt( 0 ); pD3DVector->y = (float)pFBXVector->GetAt( 1 ); pD3DVector->z = (float)pFBXVector->GetAt( 2 ); } void FBXSDK_MESHLOADER_USER::SetDirect3DBuffer( KFbxVector2* pFBXVector, D3DXVECTOR2* pD3DVector ) { pD3DVector->x = (float)pFBXVector->GetAt( 0 ); pD3DVector->y = (float)pFBXVector->GetAt( 1 ); }重要なのは FBXSDK_MESHLOADER_USER::SetVertexBuffer 関数です。注意点がいくつかあるので説明していきます。
まず、マッピングモードとリファレンスモードについてです。 レイヤーから法線ベクトルなどのデータを取得するには、マッピングモードとリファレンスモードを考慮する必要があります。 この2つのモードはデータの格納方法を示します。これらのモードの内容をチェックし、データの取得方法を決定する必要があります。
●マッピングモード
1.KFbxLayerElement::eBY_POLYGON_VERTEX
ポリゴン単位でデータが格納されます。
図1
図1のように同じ頂点のデータが格納される場合があります。
2.KFbxLayerElement::eBY_CONTROL_POINT
頂点単位でデータが格納されます。
図2
図2のように頂点のデータが重複することはありません。
●リファレンスモード
1.KFbxLayerElement::eINDEX_TO_DIRECT
インデックスバッファを参照してデータが格納されます。
2.KFbxLayerElement::eDIRECT
インデックスバッファを使用せず、直接データが格納されます。
実際のデータの取得方法はサンプルソースを見てください。 残念ながらすべてのパターンを確認できていないため、虫食い状態になっています。 ありえないパターンもあるかもしれないし、正直なんともいえませんが必要になれば追加するかも。あとテクスチャーのようなマテリアル情報はこの限りでないっぽいです。
次に法線ベクトルの平均化処理です。マッピングモードが KFbxLayerElement::eBY_POLYGON_VERTEX の場合、
図1のように同一の頂点のデータが複数格納される場合があります。法線ベクトルはポリゴン面に垂直に設定されるため、
値の異なる法線ベクトルが同一頂点上に存在するようになります。この場合はシェーディングを滑らかにするために法線ベクトルを平均化します。
図3.同一頂点上に異なる値の法線ベクトルが存在する
さらに法線ベクトルの値が同じ場合は同じものとして扱うようにします。
図4.法線ベクトルの値が同じ
最後に座標系の変換処理です。Direct3Dでは左手座標系ですが、3ds Max でエクスポートした FBXファイル では右手座標系になってました。 3DCGソフトウェアによって座標系は変わるものなのかは知りませんが、必要に応じて変換する必要があります。 座標系については説明しているサイトがいっぱいあるのでここでは説明しません。
---main.cpp---
#include "../../USER/DX11User.h" #include "../../USER/D3D11User.h" #include "../../USER/DebugFontUser.h" // コンパイル済み頂点シェーダー #include "../../USER/HLSL/SimpleHLSL08_vs.h" // コンパイル済みピクセルシェーダー #include "../../USER/HLSL/SimpleHLSL08_ps.h" // FBX SDK 用 #include "../../USER/MeshUser.h" #include "../../USER/FBXSDKMeshLoaderUser.h" FBXSDK_MESHLOADER_USER* g_pMeshLoader = NULL; BASE_MESH_USER* g_pBoxMesh = NULL; // シェーダーオブジェクトを作成するとき、ファイルから読むか、メモリから読むかを切り替える #if defined(DEBUG) || defined(_DEBUG) #define UNCOMPILED_SHADER // ファイルを読み込んでコンパイルする #endif // アプリケーション名 TCHAR* AppName = _T("DX11_Tutrial08"); // Direct3D関連の自作クラス D3D11USER* g_pD3D11User = NULL; // デバッグ専用のテキスト描画する自作クラス CDebugFont* g_pDebugFontUser = NULL; // 頂点シェーダー ID3D11VertexShader* g_pVertexShader = NULL; // 入力レイアウト ID3D11InputLayout* g_pLayout = NULL; // 定数バッファ ID3D11Buffer* g_pConstantBuffers[2] = { NULL, NULL }; // ピクセルシェーダー ID3D11PixelShader* g_pPixelShader = NULL; // サンプラーステート ID3D11SamplerState* g_pSamplerState = NULL; // 深度ステンシルステート ID3D11DepthStencilState* g_pDepthStencilState = NULL; // 節電モードの制御に使用する変数。 bool Activate = true; // ウィンドウがアクティブか bool StandBy = false; // スタンバイ状態か bool ScreenShot = false; // スクリーンショットを作成するかフラグ D3DXVECTOR4 g_vecLight = D3DXVECTOR4( -0.5f, -0.5f, 0.5f, 0.0f ); typedef struct _CBUFFER { D3DXMATRIX g_matWVP; D3DXVECTOR4 g_vecLight; }CBUFFER; UINT DrawMeshIndex = 0; // リソースの初期化 HRESULT Init() { HRESULT hr = E_FAIL; // 頂点情報を取得する hr = g_pMeshLoader->LoadMeshData( _T("Res/F14.fbx"), &g_pBoxMesh ); if( FAILED( hr ) ) goto EXIT; // 頂点シェーダーを作成する D3D11_INPUT_ELEMENT_DESC layout[] = { { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 }, { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0 }, }; #ifndef UNCOMPILED_SHADER hr = g_pD3D11User->CreateVertexShaderFromMemory( &g_pVertexShader, g_VS_Main, sizeof( g_VS_Main ), &g_pLayout, layout, _countof( layout ) ); if( FAILED( hr ) ) goto EXIT; #else hr = g_pD3D11User->CreateVertexShaderFromFile( &g_pVertexShader, _T("../../USER/HLSL/SimpleHLSL08.hlsl"), "VS_Main", "vs_4_0", &g_pLayout, layout, _countof( layout ) ); if( FAILED( hr ) ) goto EXIT; #endif // ピクセルシェーダーを作成する #ifndef UNCOMPILED_SHADER hr = g_pD3D11User->CreatePixelShaderFromMemory( &g_pPixelShader, g_PS_Main, sizeof( g_PS_Main ) ); if( FAILED( hr ) ) goto EXIT; #else hr = g_pD3D11User->CreatePixelShaderFromFile( &g_pPixelShader, _T("../../USER/HLSL/SimpleHLSL08.hlsl"), "PS_Main", "ps_4_0" ); if( FAILED( hr ) ) goto EXIT; #endif // 定数バッファを作成する。 { hr = g_pD3D11User->CreateConstantBuffer( &g_pConstantBuffers[0], NULL, sizeof( D3DXMATRIX ), D3D11_CPU_ACCESS_WRITE ); if( FAILED( hr ) ) goto EXIT; hr = g_pD3D11User->CreateConstantBuffer( &g_pConstantBuffers[1], NULL, sizeof( D3DXVECTOR4 ), D3D11_CPU_ACCESS_WRITE ); if( FAILED( hr ) ) goto EXIT; } // サンプラーステートの設定 D3D11_SAMPLER_DESC samplerDesc; samplerDesc.Filter = D3D11_FILTER_ANISOTROPIC; // サンプリング時に使用するフィルタ。ここでは異方性フィルターを使用する。 samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP; // 0 〜 1 の範囲外にある u テクスチャー座標の描画方法 samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP; // 0 〜 1 の範囲外にある v テクスチャー座標の描画方法 samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP; // 0 〜 1 の範囲外にある w テクスチャー座標の描画方法 samplerDesc.MipLODBias = 0; // 計算されたミップマップ レベルからのバイアス samplerDesc.MaxAnisotropy = 16; // サンプリングに異方性補間を使用している場合の限界値。有効な値は 1 〜 16 。 samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS; // 比較オプション。 ::CopyMemory( samplerDesc.BorderColor, &D3DXVECTOR4( 0.0f, 0.0f, 0.0f, 0.0f ), sizeof( D3DXVECTOR4 ) ); // 境界色 samplerDesc.MinLOD = 0; // アクセス可能なミップマップの下限値 samplerDesc.MaxLOD = D3D11_FLOAT32_MAX; // アクセス可能なミップマップの上限値 hr = g_pD3D11User->m_D3DDevice->CreateSamplerState( &samplerDesc, &g_pSamplerState ); if( FAILED( hr ) ) goto EXIT; // 深度ステンシルステートを作成する D3D11_DEPTH_STENCIL_DESC ddsDesc; ::ZeroMemory( &ddsDesc, sizeof( ddsDesc ) ); ddsDesc.DepthEnable = TRUE; // 深度テストを使用する ddsDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL; ddsDesc.DepthFunc = D3D11_COMPARISON_LESS; ddsDesc.StencilEnable = FALSE; hr = g_pD3D11User->m_D3DDevice->CreateDepthStencilState( &ddsDesc, &g_pDepthStencilState ); if( FAILED( hr ) ) goto EXIT; hr = S_OK; EXIT: return hr; } // メモリ開放 void Invalidate() { SAFE_DELETE( g_pBoxMesh ); SAFE_DELETE( g_pMeshLoader ); SAFE_RELEASE( g_pDepthStencilState ); SAFE_RELEASE( g_pSamplerState ); SAFE_RELEASE( g_pVertexShader ); SAFE_RELEASE( g_pPixelShader ); for( int i=0; i<2; i++ ) SAFE_RELEASE( g_pConstantBuffers[i] ); SAFE_RELEASE( g_pLayout ); SAFE_DELETE( g_pDebugFontUser ); SAFE_DELETE( g_pD3D11User ); } // 描画処理 HRESULT Render() { HRESULT hr = E_FAIL; D3DXMATRIX matWorld; D3D11_MAPPED_SUBRESOURCE mappedResource; // クリア処理 { float ClearColor[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; // バックバッファをクリア g_pD3D11User->m_D3DDeviceContext->ClearRenderTargetView( g_pD3D11User->m_RenderTargetView, ClearColor ); // 深度バッファをクリア if( g_pD3D11User->m_DepthStencilView ) g_pD3D11User->m_D3DDeviceContext->ClearDepthStencilView( g_pD3D11User->m_DepthStencilView, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0 ); } // 頂点関連のデータをデバイスコンテキストにセット { // 頂点バッファ設定 UINT stride = sizeof( MESH_USER::VERTEX_USER ); UINT offset = 0; g_pD3D11User->m_D3DDeviceContext->IASetVertexBuffers( 0, 1, &g_pBoxMesh->MeshUser[0].VertexBuffer, &stride, &offset ); // インデックスバッファ設定 g_pD3D11User->m_D3DDeviceContext->IASetIndexBuffer( g_pBoxMesh->MeshUser[0].IndexBuffer, DXGI_FORMAT_R32_UINT, 0 ); // 頂点レイアウト設定 g_pD3D11User->m_D3DDeviceContext->IASetInputLayout( g_pLayout ); } // プリミティブ タイプおよびデータの順序に関する情報を設定 //g_pD3D11User->m_D3DDeviceContext->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP ); g_pD3D11User->m_D3DDeviceContext->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST ); // シェーダーをデバイスにセット { // 頂点シェーダーをデバイスに設定する。 g_pD3D11User->m_D3DDeviceContext->VSSetShader( g_pVertexShader, NULL, 0 ); // ハルシェーダーを無効にする。 g_pD3D11User->m_D3DDeviceContext->HSSetShader( NULL, NULL, 0 ); // ドメインシェーダーを無効にする。 g_pD3D11User->m_D3DDeviceContext->DSSetShader( NULL, NULL, 0 ); // ジオメトリシェーダーを無効にする。 g_pD3D11User->m_D3DDeviceContext->GSSetShader( NULL, NULL, 0 ); // ピクセルシェーダーをデバイスに設定する。 g_pD3D11User->m_D3DDeviceContext->PSSetShader( g_pPixelShader, NULL, 0 ); g_pD3D11User->m_D3DDeviceContext->PSSetSamplers( 0, 1, &g_pSamplerState ); // コンピュートシェーダーを無効にする。 g_pD3D11User->m_D3DDeviceContext->CSSetShader( NULL, NULL, 0 ); } // 深度テストを有効にする g_pD3D11User->m_D3DDeviceContext->OMSetDepthStencilState( g_pDepthStencilState, 0 ); // アルファブレンディングを無効 D3D11_RENDER_TARGET_BLEND_DESC BlendDesc; BlendDesc = g_pD3D11User->GetDefaultBlendDesc(); g_pD3D11User->SetBlendState( &BlendDesc, 1, FALSE ); // 頂点シェーダー { D3DXMATRIX matView, matProj, matWVP; D3DXMATRIX matScale, matRotation, matTranslation; D3DXMATRIX* mat; // 射影行列 D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/4.0f, 4.0f / 3.0f, 1.0f, 250.0f ); // ビュー行列 D3DXMatrixLookAtLH( &matView, &D3DXVECTOR3( 0.0f, 20.0f, -100.0f ), &D3DXVECTOR3( 0.0f, 0.0f, 0.0f ), &D3DXVECTOR3( 0.0f, 1.0f, 0.0f ) ); // ワールド行列 D3DXMatrixScaling( &matScale, 10.0f, 10.0f, 10.0f ); static float r = 0.0f; r += 0.02f; D3DXMatrixRotationY( &matRotation, D3DXToRadian( r ) ); D3DXMatrixTranslation( &matTranslation, 0.0f, 0.0f, 0.0f ); matWorld = matScale * matRotation * matTranslation; // 行列の合成 matWVP = matWorld * matView * matProj; hr = g_pD3D11User->m_D3DDeviceContext->Map( g_pConstantBuffers[0], 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource ); if( FAILED( hr ) ) goto EXIT; mat = (D3DXMATRIX*)(mappedResource.pData); // シェーダー内では列優先にしているので転置行列を作成する。 D3DXMatrixTranspose( mat, &matWVP ); g_pD3D11User->m_D3DDeviceContext->Unmap( g_pConstantBuffers[0], 0 ); // 頂点シェーダーに定数バッファを設定する。 g_pD3D11User->m_D3DDeviceContext->VSSetConstantBuffers( 0, 1, &g_pConstantBuffers[0] ); } // ピクセルシェーダー { D3DXMATRIX matInv; D3DXVECTOR4 v; hr = g_pD3D11User->m_D3DDeviceContext->Map( g_pConstantBuffers[1], 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource ); if( FAILED( hr ) ) goto EXIT; D3DXVECTOR4* pVec = (D3DXVECTOR4*)(mappedResource.pData); // 逆行列 // ワールド行列の逆行列を作成 D3DXMatrixInverse( &matInv, NULL, &matWorld ); D3DXVec4Transform( &v, &g_vecLight, &matInv ); D3DXVec3Normalize( (D3DXVECTOR3*)&v, (D3DXVECTOR3*)&v ); ::CopyMemory( pVec, &v, sizeof( D3DXVECTOR4 ) ); g_pD3D11User->m_D3DDeviceContext->Unmap( g_pConstantBuffers[1], 0 ); // ピクセルシェーダーに定数バッファを設定する。 g_pD3D11User->m_D3DDeviceContext->PSSetConstantBuffers( 1, 1, &g_pConstantBuffers[1] ); // ピクセルシェーダーにテクスチャーリソースを設定する ID3D11ShaderResourceView* pSRV = NULL; g_pBoxMesh->MeshUser[DrawMeshIndex].GetTexture( _T("DiffuseColor"), &pSRV ); g_pD3D11User->m_D3DDeviceContext->PSSetShaderResources( 0, 1, &pSRV ); } // インデックスバッファを使用した描画 g_pD3D11User->m_D3DDeviceContext->DrawIndexed( g_pBoxMesh->MeshUser[DrawMeshIndex].IndexesCount, 0, 0 ); if( g_pDebugFontUser ) { // デバッグ専用フォント描画 g_pDebugFontUser->RenderFPS( g_pD3D11User->m_D3DDeviceContext, 0, 0 ); } // レンダリングされたイメージを表示。 hr = g_pD3D11User->m_SwapChain->Present( 0, 0 ); if( ScreenShot ) { // スクリーンショット作成 g_pD3D11User->CreateScreenShot(); ScreenShot = false; } EXIT: return hr; } // 節電処理および描画処理 HRESULT PowerSavingAndRender() { HRESULT hr = E_FAIL; switch( StandBy ) { // スタンバイモード case true: // テストのみ行い、描画処理は行わない。 hr = g_pD3D11User->m_SwapChain->Present( 0, DXGI_PRESENT_TEST ); switch( hr ) { // いまだスタンバイ中。。。 case DXGI_STATUS_OCCLUDED: // 電源管理によるスリープ状態の場合ここにくる。 // フルスクリーンモード時にスクリーンセーバーが起動時した場合は、表示モードが強制的にウィンドウモードに変更されるためここにこない。 goto EXIT; break; case S_OK: // フルスクリーンモード時にスクリーンセーバーが起動時した場合は表示モードが強制的にウィンドウモードに変更される。 // ウィンドウモードの場合スタンバイから復帰してしまうため、ウィンドウがアクティブになったときに復帰するようにする。 if( Activate == true ) { // たまにウィンドウが表示されないときがあるので表示するようにする ::ShowWindow( g_pD3D11User->m_hWnd, SW_SHOW ); StandBy = false; } break; default: goto EXIT; break; } break; // スタンバイモードでない case false: // 描画処理 hr = Render(); if( FAILED( hr ) ) goto EXIT; switch( hr ) { case DXGI_STATUS_OCCLUDED: // スタンバイモードへ移行 // フルスクリーンモード時のスクリーンセーバー起動時、 // スリープ状態に移行した時に発生する。 StandBy = true; goto EXIT; break; case S_OK: break; default: goto EXIT; break; } break; } hr = S_OK; EXIT: return hr; } // ウィンドウプロシージャ LRESULT CALLBACK WndProc( HWND hWnd, UINT msg, UINT wParam, LONG lParam ) { switch( msg ) { case WM_KEYUP: // アプリ終了 if( wParam == VK_ESCAPE ) ::DestroyWindow( hWnd ); // F2キーを押すと、ウィンドウモードを切り替える。 // 自動的にウィンドウモードを切り替える機能もあるが、ウィンドウスタイルを自由に変更するために自分で実装することにした。 if( wParam == VK_F2 ) { g_pD3D11User->ChangeWindowMode(); } // スクリーンショットを作成する if( wParam == VK_SNAPSHOT ) ScreenShot = true; break; case WM_ACTIVATE: Activate = true; break; case WM_DESTROY: Invalidate(); ::PostQuitMessage(0); break; default: return ::DefWindowProc( hWnd, msg, wParam, lParam ); } return 0L; } // メイン関数 int APIENTRY _tWinMain( HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPTSTR /*lpCmpLine*/, INT /*nCmdShow*/ ) { // メモリリーク検出 _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF | _CRTDBG_CHECK_EVERY_1024_DF); HRESULT hr = E_FAIL; MSG msg; ::ZeroMemory(&msg, sizeof(MSG)); // 表示モードを記述するための構造体。 DXGI_MODE_DESC sd; // Direct3D 関連自作クラスのインスタンスを作成 g_pD3D11User = new D3D11USER(); // ディスプレイモード一覧を取得する。 // 取得した値はクラス内部に保持される。 hr = g_pD3D11User->GetDisplayMode(); if( FAILED( hr ) ) { ::MessageBox( NULL, _T("ディスプレイモード取得エラー"), _T("初期化エラー"), MB_OK ); goto EXIT; } // とりあえず最初に見つかったディスプレイモードを選択する CopyMemory( &sd, &g_pD3D11User->m_DisplayModeDesc[0], sizeof( DXGI_MODE_DESC ) ); // ウィンドウの作成およびDirect3D の初期化 hr = g_pD3D11User->InitD3D11( AppName, hInstance, WndProc, &sd, TRUE, TRUE, TRUE, TRUE ); if( FAILED( hr ) ) { ::MessageBox( NULL, _T("Direct3D 11.0 初期化エラー"), _T("初期化エラー"), MB_OK ); goto EXIT; } // デバッグ専用フォント出力クラスの作成処理 // デバックコンパイル時のみ使用する #if defined(DEBUG) || defined(_DEBUG) g_pDebugFontUser = new CDebugFont(); hr = g_pDebugFontUser->Create( g_pD3D11User->m_D3DDevice, 0.015f, 0.05f ); if( FAILED( hr ) ) { ::MessageBox( NULL, _T("デバックフォントクラス初期化エラー"), _T("初期化エラー"), MB_OK ); goto EXIT; } #endif // 自作のメッシュローダーの初期化 g_pMeshLoader = new FBXSDK_MESHLOADER_USER(); g_pMeshLoader->Initialize( g_pD3D11User->m_D3DDevice ); // リソースの初期化 hr = Init(); if( FAILED( hr ) ) { ::MessageBox( NULL, _T("リソース初期化エラー"), _T("初期化エラー"), MB_OK ); goto EXIT; } ::ShowWindow(g_pD3D11User->m_hWnd, SW_SHOW); ::UpdateWindow(g_pD3D11User->m_hWnd); // メッセージループ do { if( ::PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) ) { ::TranslateMessage(&msg); ::DispatchMessage(&msg); } else { hr = PowerSavingAndRender(); if( FAILED( hr ) ) ::DestroyWindow( g_pD3D11User->m_hWnd ); } }while( msg.message != WM_QUIT ); EXIT: if( g_pD3D11User && g_pD3D11User->m_hWnd ) ::DestroyWindow( g_pD3D11User->m_hWnd ); ::UnregisterClass( AppName, hInstance ); return msg.wParam; }main関数です。起動直後にメモリリークを検出するための _CrtSetDbgFlag()関数 を追加しています。スタティックライブラリ内でメモリリークが発生した場合でも検出できます。
あとは、プリミティブ タイプを D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST に変更したくらいです。
---SimpleHLSL08.hlsl---
// ワールド行列 × ビュー × 射影行列 cbuffer cbMatrixWVP : register( b0 ) { // 列優先 column_major float4x4 g_matWVP : packoffset( c0 ); }; cbuffer cbVecLight : register( b1 ) { float4 g_vecLight : packoffset( c0 ); }; // テクスチャー Texture2D g_Tex : register( t0 ); // サンプラーステート SamplerState g_Sampler : register( s0 ); // 頂点シェーダーの入力パラメータ struct VS_IN { float3 pos : POSITION; // 頂点座標 float3 normal: NORMAL; // 法線 float2 texel : TEXCOORD; // テクセル }; // 頂点シェーダーの出力パラメータ struct VS_OUT { float4 pos : SV_POSITION; float3 normal : NORMAL; float2 texel : TEXCOORD0; }; // 頂点シェーダー VS_OUT VS_Main( VS_IN In ) { VS_OUT Out; Out.pos = mul( float4( In.pos, 1 ), g_matWVP ); Out.normal = In.normal; Out.texel = In.texel; return Out; } // ピクセルシェーダ float4 PS_Main( VS_OUT In ) : SV_TARGET { // ランバート拡散照明 float Color = dot( -g_vecLight.xyz, normalize( In.normal ) ); return g_Tex.Sample( g_Sampler, In.texel ) * max( Color, 0.2f ); }シェーダーは説明しなくてもいいでしょう。
さて今回の修正で D3D11USER クラスも修正しています。ポリゴンの表面の設定です。 Direct3D9 では時計回りがデフォルトだったためその設定をデフォルト値に設定してたんですが、FBX SDK では反時計回りで頂点データが格納されているため変更する必要があります。 今回追加する FBXSDK_MESHLOADER_USER クラス側で対応しようかとも思いましたが、 COLLADAもおそらく反時計回りを採用しているでしょうし、反時計回りをデフォルトに設定してたほうがなにかと都合がよさそうなので反時計回りに統一することにしました。 D3D11USER クラス参照している方がいたらごめんなさい。