その6 UVを取得する
3Dモデルにテクスチャを貼り付ける時に必要なのがUV座標です。この章ではFBXファイルからUV情報を取得する方法について見ていきます。
@ UV取得
3Dモデリングツールで例えばディフューズテクスチャを貼り付けたとします。するとFBXファイルの中にディフューズテクスチャに対応したUVが書き込まれます。さらにスペキュラテクスチャを貼り付けると、「スペキュラテクスチャのUV」が追加されます。FBXの中では各テクスチャに対応したUVが個別に設定されているわけです。
FBX SDKを使ってUVを取り出す手順は、
・ KFbxMesh::GetLayerCountでレイヤー数を取得
・ KFbxMesh::GetLayerでレイヤー(KFbxLayer)を1枚ずつ取得
・ KFbxLayer::GetUVsでUVを格納しているKFbxLayerElementUVオブジェクトを取得
・ KFbxLayerElementUVからUV情報を取得
となります。UV座標は基本的には1つのレイヤーに1種類だけ収められています。
まず、KFbxLayerElementUVオブジェクトを取得するところまでは次のようになります:
KFbxLayerElementElementUVオブジェクトを取得 int layerCount = mesh->GetLayerCount(); // meshはKFbxMesh
for ( int i = 0; i < layerCount; ++i ) {
KFbxLayer* layer = mesh->GetLayer( i );
KFbxLayerElementUV* elem = layer->GetUVs();
if ( elem == 0 ) {
continue;
}
// UV情報を取得
...
}
これでUVを取得する準備が整いましたので、続けてUV座標を抽出します。前章の法線ベクトルの取得と同様に、UV座標もマッピングモードとリファレンスモードによって取得方法が違います。ただ、基本は法線の時と同じで、以下の組み合わせで判断します:
マッピングモード リファレンスモード eBY_CONTROL_POINT eDIRECT eINDEX_TO_DIRECT eBY_POLYGON_VERTEX eDIRECT eINDEX_TO_DIRECT
リファレンスモードがeDIRECTならば直接座標を得られますが、eINDEX_TO_DIRECTの場合はインデックス番号を介して座標を得ます。これも法線の時と同じです。取得部分はこうなります:
UV座標を取得 // UVの数・インデックス
int UVNum = elem->GetDirectArray().GetCount();
int indexNum = elem->GetIndexArray().GetCount();
int size = UVNum > indexNum ? UVNum : indexNum;
D3DXVECTOR2* buffer = new D3DXVECTOR2[ size ];
// マッピングモード・リファレンスモード別にUV取得
KFbxLayerElement::EMappingMode mappingMode = elem->GetMappingMode();
KFbxLayerElement::EReferenceMode refMode = elem->GetReferenceMode();
if ( mappingMode == KFbxLayerElement::eBY_POLYGON_VERTEX ) {
if ( refMode == KFbxLayerElement::eDIRECT ) {
// 直接取得
for ( int i = 0; i < size; ++i ) {
buffer[ i ].x = (float)elem->GetDirectArray().GetAt( i )[ 0 ];
buffer[ i ].y = (float)elem->GetDirectArray().GetAt( i )[ 1 ];
}
} else
if ( refMode == KFbxLayerElement::eINDEX_TO_DIRECT ) {
// インデックスから取得
for ( int i = 0; i < size; ++i ) {
int index = elem->GetIndexArray().GetAt( i );
buffer[ i ].x = (float)elem->GetDirectArray().GetAt( index )[ 0 ];
buffer[ i ].y = (float)elem->GetDirectArray().GetAt( index )[ 1 ];
}
}
}
この取得例では、最初にUV座標の数とインデックスの数を取得し、数の多い方をsizeとしています。要は頂点の並び分だけUV座標を展開しようと考えているわけです。UV座標の本体はKFbxLayerElementUV::GetDirectArrayメソッドで得られる配列の中に入っています。モードを調べた後にそこから座標を1つずつ取り出してバッファにコピーしています。memcpyをしたいとは思うのですが、配列の要素はdouble型なので仕方ありません。リファレンスモードがeINDEX_TO_DIRECTの場合は、一度インデックス番号を取り出し(KFbxLayerElementUV::GetIndexArray)、そこからUV座標をたぐり寄せます。
このように、UV座標自体は法線と同じ方法で取得できるわけです。
A ところでそれは誰のUVなのさ?
上記のように、FBXからUVを取得するのは難しくありません。ただ、先に述べた通りUVはテクスチャをモデルに貼り付ける度にFBX内に「個別に」設定されます。ですから、上の方法でレイヤーを1つずつチェックすると、貼り付けたテクスチャの枚数分だけUVが取得されます。
所が、私が調べた範囲でなのですが、どう頑張っても「取得したレイヤーがどのテクスチャ対応なのか」というUVとテクスチャを連結する情報を得ることができません。例えば、Layer0、Layer1、Layer2のそれぞれからUVが取得できたとします。でもこのどれがスペキュラテクスチャ用で、どれがバンプマップ用なのか判断する方法をどうしても見つけられませんでした。もっとも、すべてのテクスチャが同じUV座標を共有する場合は一番最初に取得できたUV座標を適用すれば良いだけなので問題はありませんが、すっきりしないのは確かです。もしこの辺りについてご存知の方がいらっしゃいましたら、是非とも教えて下さい。
(2008. 11. 12追記)
↑という問題があったのですが、解決方法がわかりました。
KFbxLayerElementUVオブジェクトからはやはり対応するマテリアル属性を得ることはできません。しかし、次章で説明する「テクスチャオブジェクト(KFbxTexture)」にあるUVSetというパブリックメンバに「UVセット名」が格納されており、ここからそのテクスチャがどのUVに対応しているかを判断できる事が判明しました(UVセット名というのは単純にUVの名前の事です)。
@で出てきたKFbxLayerElementUVクラスのGetNameメソッドを呼び出すと、そのUVセット名を取得する事ができます。これで、マテリアル属性にアタッチしているテクスチャとUVとの対応が出来たことになります:
レイヤーに対応するUVセット名を取得
レイヤー番号 UVセット UV座標 0 map1 (u1,v1)... 1 map1 (u1,v1)... ※レイヤー0と同じ 2 map2 (u2,v2)... ... ... ...
マテリアルに対応するUVセット名を取得
マテリアル属性 テクスチャ名 UVセット 対応レイヤー Diffuse DiffuseTex.jpg map1 0 Specular SpecularTex.jpg map1 0 Emissive - - - Ambient AmbientTex.jpg map2 2 ... ... ... ...
上の対応を確立するべく、次はテクスチャ情報の取得を見てみます。テクスチャはマテリアルとも直結していますので、両者合わせて次章でピックアップします。
B 謝辞
UVとマテリアル属性の関連付けの問題に付きましてアドバイスを頂きましたkoiさん及びJOHNさんに感謝申し上げます。