ホーム < ゲームつくろー! < FBX習得編

その5 法線を取得する


 前章では3Dモデルの基本要素である頂点インデックスと頂点座標を取得しました。この章では頂点情報の1つである法線を取得してみます。



@ 法線情報を持つ「レイヤー」

 前章で紹介した頂点インデックスと頂点座標はKFbxMeshオブジェクトから直接得ることができました。この章で焦点を当てている法線は少し取得方法が異なります。KFbxMeshクラスにはGetNormalsというメソッドがあるのですが(元はKFbxGeometryBaseクラスのメソッド)、このメソッドを通しても大抵は法線を取得できません。実は法線情報はメッシュが持っている「レイヤー」というオブジェクトの中に格納されています。

(2008. 12. 17追記)
↑Nish様より2009.3ではKFbxMesh::GetNormalsメソッドでも法線が取れるとの情報を頂きました。
(了)

 メッシュ内に含まれているレイヤーの数はKFbxMesh::GetLayerCountメソッド、個々のレイヤーを得るにはKFbxMesh::GetLayerメソッドを用います:

レイヤーオブジェクトを取得
int layerNum = mesh->GetLayerCount();
for ( int i = 0; i < layerNum; ++i ) {
   KFbxLayer* layer = mesh->GetLayer( i );
}

 レイヤーは複数存在することがありますが、法線は大抵レイヤー0番に含まれています。それをちゃんと調べるには、KFbxLayer::GetNormalsメソッドでKFbxLayerElementNormalオブジェクトへのポインタをチェックします。有効なポインタが返って来た場合は法線が存在しています。

法線存在をチェック
int layerNum = mesh->GetLayerCount();
for ( int i = 0; i < layerNum; ++i ) {
   KFbxLayer* layer = mesh->GetLayer( i );
   KFbxLayerElementNormal* normalElem = layer->GetNormals();
   if ( normalElem == 0 ) {
      continue;   // 法線無し
   }

   // 法線あった!
   ...
}

 このようにして得たKFbxLayerElementNormalオブジェクトから法線ベクトルを得ることになります。



A マッピングモードとリファレンスモード

 法線ベクトルを得る時に注意することがあります。FBXにはいくつかの異なる方式で法線ベクトルが格納されています。方式を判断するには「マッピングモード」と「リファレンスモード」という2つのモードの組み合わせをチェックします。双方のモードを得るにはKFbxLayerElementNormal::GetMappingModeメソッドとGetReferenceModeメソッドを用います:

マッピングモードとリファレンスモードを取得
KFbxLayerElement::EMappingMode   mappingMode = normalElem->GetMappingMode();
KFbxLayerElement::EReferenceMode refMode     = normalElem->GetReferenceMode();

マッピングモード(KFbxLayerElement::EMappingMode)とリファレンスモード(KFbxLayerElement::EReferenceMode)にはいくつか種類がありますが、大抵は次の組み合わせになります:

マッピングモード リファレンスモード
eBY_CONTROL_POINT eDIRECT
eINDEX_TO_DIRECT
eBY_POLYGON_VERTEX eDIRECT
eINDEX_TO_DIRECT

 マッピングモードは法線がどのようにモデル表面に定義されているかを表します。eBY_CONTROL_POINTは「コントロール点」というモデルの表面を定義する点に対して法線が定義されています。一方のeBY_POLYGON_VERTEXはポリゴンの頂点に対して法線が定義されています。こう書くと「じゃあeBY_POLYGON_VERTEXの方が一般的だよね」と思ってしまうわけですが、実はeBY_CONTROL_POINT側で取得される事もあります。Mayaで適当にプリミティブを置いて、そのままFBXにエクスポートするとeBY_CONTROL_POINTになります。ですから両方に対応させる必要があります。

 リファレンスモードは法線がどのように配列に格納されているかを表します。eDIRECTは配列の中にコントロール点(頂点)の並び順で法線が格納されています。eINDEX_TO_DIRECTの場合は法線の並び順はインデックスで示されており、インデックスに対応した法線が配列に並んでいます。図で表すと次のようになっています:

 どちらのリファレンスモードでも、点に対して法線は等しい数だけ取得可能です。私が試した範囲では、法線に関してはリファレンスモードがeDIRECTだけ確認できましたので、その場合の法線の格納を以下に挙げます:

法線ベクトルを取得
// 法線の数・インデックス
int    normalNum          = normalElem->GetDirectArray().GetCount();
int    indexNum           = normalElem->GetIndexArray().GetCount();
D3DXVECTOR3* normalBuffer = new D3DXVECTOR3[ normalNum ];

// マッピングモード・リファレンスモード取得
KFbxLayerElement::EMappingMode mappingMode = normalElem->GetMappingMode();
KFbxLayerElement::EReferenceMode refMode = normalElem->GetReferenceMode();

if ( mappingMode == KFbxLayerElement::eBY_POLYGON_VERTEX ) {
   if ( refMode == KFbxLayerElement::eDIRECT ) {
      // 直接取得
      for ( int i = 0; i < normalNum; ++i ) {
         normalBuffer[ i ].x = (float)normalElem->GetDirectArray().GetAt( i )[ 0 ];
         normalBuffer[ i ].y = (float)normalElem->GetDirectArray().GetAt( i )[ 1 ];
         normalBuffer[ i ].z = (float)normalElem->GetDirectArray().GetAt( i )[ 2 ];
      }
   }
} else if ( mappingMode == KFbxLayerElement::eBY_CONTROL_POINT ) {
   if ( refMode == KFbxLayerElement::eDIRECT ) {
      // 直接取得
      for ( int i = 0; i < normalNum; ++i ) {
         normalBuffer[ i ].x = (float)normalElem->GetDirectArray().GetAt( i )[ 0 ];
         normalBuffer[ i ].y = (float)normalElem->GetDirectArray().GetAt( i )[ 1 ];
         normalBuffer[ i ].z = (float)normalElem->GetDirectArray().GetAt( i )[ 2 ];
      }
   }
}

 KFbxLayerElementNormal::GetDirectArrayメソッドは法線ベクトルの配列を取得できます。そのGetCountメソッドで配列サイズが返りますのでそれをnormalNumに格納しています。同様にKFbxLayerElementNormal::GetIndexArrayメソッドではインデックス配列を取得できますが、インデックスが無い場合(リファレンスモードがeDIRECT)は0が返ってきます。

 マッピングモードとリファレンスモードを調べてそれぞれの組み合わせに対応して法線ベクトルを取得します。上の例ではD3DXVECTOR3型に法線ベクトルをキャストしています(GetDirectArrayで取得できる配列はdouble型の3次元ベクトルです)。



 法線に限らず、レイヤーに含まれるほかの情報もマッピングモードとリファレンスモードでその格納方法が決まってきます。ここは実装としては面倒なのですが、確実に情報を得るためにも確実に押さえておきたい所です。