FBX2019.0
その3 FbxNodeのツリー構造
前章でFBXファイルをインポートしてFbxSceneオブジェクトを作りました。このオブジェクトの中にFBXファイルのすべての情報が格納されています。FBXは情報の巨大なツリー構造になっています。そのツリーは基本すべて「FbxNode」というノードが基本となっています。この章ではそのノードをたどる方法を見ていきましょう。
@ ルートノードの取得
巨大なツリーのトップは「ルートノード」と呼ばれます。そこの下にあらゆる情報がぶら下がっています。ルートノードを得るにはFbxScene::GetRootNodeメソッドを用います:
// ルートノードを取得
FbxNode *root = scene->GetRootNode();
if ( root != 0 ) {
// ルートにぶら下がっているノードを辿る
// ...
}
sceneはFBXファイルの情報をインポート済みのFbxSceneオブジェクトです。ノードはFbxNodeオブジェクトとして返されます。有効なノードが返れば、そこから下位のノードを辿っていく事が出来ます。
辿っていく事は出来るのですが、FBXのノード構造は複雑で、またどのようなノード構成になっているかは実際に辿らないと分からなかったりします。「このノードの下はこうなっている!」と決め打ちすると痛い目をみます。欲しいノードがちゃんとあるのか、想定するノード構成になっているかを知るためには、ノードの属性を得る必要があります。
A ノードの名前
FbxNodeオブジェクトはいくつか属性値を持っています。簡単な所ではノードには名前が定義されています。これは殆どがモデルを作成した人が付けたものです。ノードの名前はFbxNode::GetNameメソッドで取得できます。
例えば次のプログラムはルートノードにぶら下がるすべてのノード名を出力します:
void printSpace( int count ) {
for ( int i = 0; i < count; ++i )
printf( " " );
}
void enumNodeNames( FbxNode *node, int indent ) {
printSpace( indent );
const char *name = node->GetName();
printf( "%s\n", name );
int childCount = node->GetChildCount();
for ( int i = 0; i < childCount; ++i ) {
enumNodeNames( node->GetChild( i ), indent + 1 );
}
int main() {
...
// ルートノードを取得
FbxNode *root = scene->GetRootNode();
if ( root != 0 ) {
// ぶら下がっているノードの名前を列挙
enumNodeNames( root, 0 );
}
}
enumNodeNames関数は引数に渡された親ノードのGetNameメソッドから名前を取得し、それをprintfしています。ノードの下にぶら下がっている子ノードの数はFbxNode::GetChildCountメソッドで得られます。また子ノードはGetChildメソッドに子ノードのインデックスを渡すと取得できます。
上のコードはenumNodeNames関数を再帰的に呼ぶ事でルートノード以下の全ノードの名前を列挙しています。実際にUnity公認キャラである「Unityちゃん」のFBXデータを出力するとこんな感じになりました:
Unityちゃん(SD_unitychan_humanoid.fbx)ノード構成 RootNode
Character1_Reference
Character1_Hips
Character1_RightUpLeg
Character1_RightLeg
Character1_RightFoot
Character1_RightToeBase
J_R_knee
Character1_LeftUpLeg
Character1_LeftLeg
Character1_LeftFoot
Character1_LeftToeBase
J_L_knee
J_L_Skirt_00
J_L_Skirt_01
J_L_Skirt_02
J_R_Skirt_00
J_R_Skirt_01
J_R_Skirt_02
J_R_SkirtBack_01
J_R_SkirtBack_02
J_L_SkirtBack_01
J_L_SkirtBack_02
Character1_Spine
Character1_Spine1
Character1_Spine2
Character1_RightShoulder
Character1_RightArm
Character1_RightForeArm
Character1_RightHand
Character1_RightHandThumb1
Character1_RightHandThumb2
Character1_RightHandThumb3
Character1_RightHandThumb4
Character1_RightHandIndex1
Character1_RightHandIndex2
Character1_RightHandIndex3
Character1_RightHandMiddle1
Character1_RightHandMiddle2
Character1_RightHandMiddle3
Character1_RightHandRing1
Character1_RightHandRing2
Character1_RightHandRing3
Character1_RightHandPinky1
Character1_RightHandPinky2
Character1_RightHandPinky3
J_R_Elbow
J_R_ForeArm_00_tw
J_R_Sode_A00
J_R_Sode_A01
J_R_Sode_D00
J_R_Sode_D01
J_R_Sode_C00
J_R_Sode_C01
J_R_Sode_B00
J_R_Sode_B01
J_R_Arm_00_tw
J_R_Sode_E00
Character1_Neck
Character1_Head
J_L_HeadRibbon_00
J_L_HeadRibbon_01
J_L_HeadRibbon_02
J_L_HeadRibbon_03
J_R_HeadRibbon_00
J_R_HeadRibbon_01
J_R_HeadRibbon_02
J_R_HeadRibbon_03
bone_eye_L
bone_eye_R
J_L_HairTail_00
J_L_HairTail_01
J_L_HairTail_02
J_L_HairTail_03
J_R_HairTail_00
J_R_HairTail_01
J_R_HairTail_02
J_R_HairTail_03
J_L_HairSide_00
J_L_HairSide_01
J_L_HairSide_02
J_R_HairSide_00
J_R_HairSide_01
J_R_HairSide_02
J_L_HairFront_00
J_L_HairFront_01
J_R_HairFront_00
J_R_HairFront_01
J_L_HairSide2_00
J_L_HairSide2_01
J_R_HairSide2_00
J_R_HairSide2_01
_face
Character1_LeftShoulder
Character1_LeftArm
Character1_LeftForeArm
Character1_LeftHand
Character1_LeftHandThumb1
Character1_LeftHandThumb2
Character1_LeftHandThumb3
Character1_LeftHandThumb4
Character1_LeftHandPinky1
Character1_LeftHandPinky2
Character1_LeftHandPinky3
Character1_LeftHandRing1
Character1_LeftHandRing2
Character1_LeftHandRing3
Character1_LeftHandMiddle1
Character1_LeftHandMiddle2
Character1_LeftHandMiddle3
Character1_LeftHandIndex1
Character1_LeftHandIndex2
Character1_LeftHandIndex3
J_L_Sode_C00
J_L_Sode_C01
J_L_Sode_D00
J_L_Sode_D01
J_L_Sode_A00
J_L_Sode_A01
J_L_Sode_B00
J_L_Sode_B01
J_L_Elbow
J_L_ForeArm_00_tw
J_L_Arm_00_tw
J_L_Sode_E00
J_acce_00
J_acce_01
Mesh_SD_unitychan
_head
_body
_Fhair
_eye
_Fhair2
Unityちゃんはスキンメッシュやアニメーション等が含まれているため大変複雑な構造になっています。でもこのように名前を列挙すると少し状況がわかりますよね。ただ、ゲームなどで使う色々なFBXモデルのすべてが同じ名前のノード構成になる事はありません。ポリゴンの頂点情報がどこにあるのか、上の出力からはっきりはわかりません。名前は人の目には役に立ちますが、解析の手助けとしてはちょっと力不足です。
B ノードアトリビュート
名前の他に、ノードには「ノードアトリビュート」という属性もあります。これはそのノードが具体的にどういう情報を持っているかを表すオブジェクトで、FbxNodeAttributeクラスとして定義されています。一つのノードには大体一つのノードアトリビュートがあるのですが、まれに複数ある事もあります。ノードアトリビュートの数はFbxNode::GetNodeAttributeCountメソッドで、個々のアトリビュートはFbxNode::GetNodeAttributeByIndexメソッドにインデックスを渡すと得られます。
ノードアトリビュートには「タイプ」が列挙型として設定されています。これは例えば「eSkeleton(ボーン、スケルトン)」「eMesh(ポリゴンメッシュ)」「eCamera(カメラ)」「eLight(ライト)」など正にノードの具体的な物を表しています。ノードアトリビュートタイプはFbxNodeAttribute::GetAttributeTypeメソッドで取得できます。このタイプを判別すれば、タイプ別に情報を取得できるわけです。
先ほどの名前を列挙するenumNodeNames関数をenumNodeNamesAndAttributes関数とリネームし、中でノードアトリビュートを得てそのタイプの名前も列挙するように改良してみましょう:
void printSpace( int count ) {
for ( int i = 0; i < count; ++i )
printf( " " );
}
void enumNodeNameAndAttributes( FbxNode *node, int indent ) {
printSpace( indent );
const char *typeNames[] = {
"eUnknown", "eNull", "eMarker", "eSkeleton", "eMesh", "eNurbs",
"ePatch", "eCamera", "eCameraStereo", "eCameraSwitcher", "eLight",
"eOpticalReference", "eOpticalMarker", "eNurbsCurve", "eTrimNurbsSurface",
"eBoundary", "eNurbsSurface", "eShape", "eLODGroup", "eSubDiv",
"eCachedEffect", "eLine"
};
const char *name = node->GetName();
int attrCount = node->GetNodeAttributeCount();
if ( attrCount == 0 ) {
printf( "%s\n", name );
}
else {
printf( "%s (", name );
}
for ( int i = 0; i < attrCount; ++i ) {
FbxNodeAttribute *attr = node->GetNodeAttributeByIndex( i );
FbxNodeAttribute::EType type = attr->GetAttributeType();
printf( "%s", typeNames[ type ] );
if ( i + 1 == attrCount ) {
printf( ")\n" );
}
else {
printf( ", " );
}
}
int childCount = node->GetChildCount();
for ( int i = 0; i < childCount; ++i ) {
enumNodeNamesAndAttributes( node->GetChild( i ), indent + 1 );
}
}
int main() {
...
// ルートノードを取得
FbxNode *root = scene->GetRootNode();
if ( root != 0 ) {
// ぶら下がっているノード名とアトリビュートを列挙
enumNodeNamesAndAttributes( root, 0 );
}
}
で、Unityちゃんを出力してみるとこうなります:
Unityちゃん(SD_unitychan_humanoid.fbx)ノード構成 RootNode
Character1_Reference (eNull)
Character1_Hips (eSkeleton)
Character1_RightUpLeg (eSkeleton)
Character1_RightLeg (eSkeleton)
Character1_RightFoot (eSkeleton)
Character1_RightToeBase (eNull)
J_R_knee (eSkeleton)
Character1_LeftUpLeg (eSkeleton)
Character1_LeftLeg (eSkeleton)
Character1_LeftFoot (eSkeleton)
Character1_LeftToeBase (eNull)
J_L_knee (eSkeleton)
...
J_acce_00 (eSkeleton)
J_acce_01 (eNull)
Mesh_SD_unitychan (eNull)
_head (eMesh)
_body (eMesh)
_Fhair (eMesh)
_eye (eMesh)
_Fhair2 (eMesh)
長いので途中省略していますが、先ほどよりも具体的にノード構成が分かりました。Unityちゃんはルート下のCharacter1_Reference下から膨大なスケルトン(ボーン)が並んでいて、最後にメッシュ(eMesh)が並列に5つある事がわかりました。
このようにFBXはノードアトリビュートタイプで「何のノードであるか?」を知り、そのタイプ毎の解析を行うのが基本操作となります。では次の章から細かい情報の取得方法を見ていきましょう。