その6 レイヤー要素を取得する:法線
前章まででメッシュとレイヤー、そして各要素が持つマッピング情報とリファレンス情報を見てきました。この章では早速レイヤーが持つ要素の一つである法線を取得してみます。
@ レイヤーから法線を得る
レイヤー(KFbxLayer)はメッシュから取得できます。レイヤーには要素を取得するためのメソッドが沢山よういされていますが、法線を取得するにはKFbxLayer::GetNormalsメソッドを用います:
頂点数と頂点座標配列を取得 KFbxLayerElementNormal* pNormalAry = pMesh->GetLayer(l)->GetNormals();
if ( pNormalAry )
{
// 法線取得処理
}
法線がレイヤーに必ず存在するとは言えません。必ずGetNormalsメソッドの戻り値をチェックして下さい。有効なポインタが返れば法線情報があります。
FBXの法線情報を取得するには、その格納形態を知っておく必要があります。通常法線は頂点に対して定義されています。つまり、法線の数=頂点の数です。一方で、法線が各ポリゴンの頂点ごとに定義されている場合もあります。この場合は法線の数=ポリゴン数×3になります。
これらはマッピングモードとリファレンスモードを見比べると判断ができます(忘れた方はこちらで確認)。表にしてまとめると次のようになります:
リファレンスモードの値 マッピングモードの値 法線情報の取得方法 eDIRECT eBY_CONTROL_POINT 頂点単位で取得 eBY_POLYGON_VERTEX インデックスを介して取得 eINDEX_TO_DIRECT eBY_CONTROL_POINT GetIndexArray配列を介して取得 eBY_POLYGON_VERTEX GetIndexArray配列を介して取得
少し分かりにくいのですが、eDIRECTの場合はKFbxLayerElementNormalはインデックス情報を持っておらず、内部に法線を直接配列形式で持っています。さらにリファレンスモードがeBY_CONTROL_POINTの時だけ法線の数と頂点の数が一致しています(頂点にただ1つ平均法線が定義)。それ以外の場合、法線の数はインデックスの数と一致しています。ただし、eDIRECTでeBY_POLYGON_VERTEXの場合はKFbxMeshからインデックス配列を取得しておく必要があります。・・・面倒なんですよね。
ある頂点p(頂点インデックス番号)に対応した法線を取得し平均化する関数は例えば次のようになります:
平均化法線を取得 /////////////////////////////////////////////////////////
// GetAverageNormal
// pIndex : KFbxMeshから得たインデックスバッファ
// indexCount : インデックス数
// pNormal : 法線Element
// ppOut : 法線バッファ出力
bool GetAverageNormal( unsigned short *pIndex, unsigned int indexCount, KFbxLayerElementNormal* pNormal, D3DXVECTOR3 **pOut )
{
unsigned int vertexNum = pNormal->GetDirectArray().GetCount();
unsigned int indexNum = pNormal->GetIndexArray().GetCount(); // 0より大きければインデックスが存在
D3DXVECTOR3 *pVec = pVec = new D3DXVECTOR3[ vertexNum ];
memset( pVec, 0, sizeof(D3DXVECTOR3) * vertexNum );
KFbxLayerElement::EMappingMode mode = pNormal->GetMappingMode();
KFbxLayerElement::EReferenceMode ref = pNormal->GetReferenceMode();
switch( ref )
{
case KFbxLayerElement::eDIRECT:
{
if ( mode == KFbxLayerElement::eBY_POLYGON_VERTEX ) {
// 法線の数はインデックスの数と一致しているが
// pNormalがインデックス情報を持っていないので
// KFbxMeshから得たインデックスバッファを使用
if ( !pIndex )
return false;
for ( unsigned int i = 0; i < indexCount; i++ ) {
unsigned index = pIndex[i];
pVec[index].x += (float)pNormal->GetDirectArray().GetAt(i)[0];
pVec[index].y += (float)pNormal->GetDirectArray().GetAt(i)[1];
pVec[index].z += (float)pNormal->GetDirectArray().GetAt(i)[2];
}
}
else if ( mode == KFbxLayerElement::eBY_CONTROL_POINT ) {
// 法線の数は頂点数と一致している
for ( unsigned int i = 0; i < vertexNum; i++ ) {
pVec[i].x = (float)pNormal->GetDirectArray().GetAt(i)[0];
pVec[i].y = (float)pNormal->GetDirectArray().GetAt(i)[1];
pVec[i].z = (float)pNormal->GetDirectArray().GetAt(i)[2];
}
}
break;
}
case KFbxLayerElement::eINDEX_TO_DIRECT:
{
// インデックスバッファがpNormalの内部に存在しているのでそれを利用
if ( mode == KFbxLayerElement::eBY_POLYGON_VERTEX ) {
// 法線の数はインデックスの数と一致しているので共通頂点の法線を合成
for ( unsigned int i = 0; i < indexNum; i++ ) {
unsigned int index = pNormal->GetIndexArray().GetAt(i);
pVec[index].x += (float)pNormal->GetDirectArray().GetAt(i)[0];
pVec[index].y += (float)pNormal->GetDirectArray().GetAt(i)[1];
pVec[index].z += (float)pNormal->GetDirectArray().GetAt(i)[2];
}
}
break;
}
}
// 法線を標準化
for ( unsigned int i = 0; i < vertexNum; i++ ) {
D3DXVec3Normalize( &pVec[i], &pVec[i] );
}
*pOut = pVec;
return true;
}
泥臭い実装になっていますが、こうせざるを得ません。この関数を通り抜けると1つの頂点に対応した平均化法線が取得できます。
法線があると頂点単位でライトを反映させる事ができます。平均化法線を用いればグローシェーディングが実現できます。何かと使うんです。