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

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はノードアトリビュートタイプで「何のノードであるか?」を知り、そのタイプ毎の解析を行うのが基本操作となります。では次の章から細かい情報の取得方法を見ていきましょう。