17
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

PythonAdvent Calendar 2019

Day 7

Blenderを使わずにPythonだけで3Dアニメーションを作ってみよう!【FBX SDK Python】

Last updated at Posted at 2019-12-06

20191201_moving_circle_cube.gif

これは Python Advent Calendar 2019 の7日目の記事です。
昨日は @ko-he-8 さんによる pythonのユニットテスト用ライブラリNoseのオプション紹介-19種類- でした。

Pythonのコードだけで3Dアニメーションを作りたい

Blenderのような3Dアニメーションソフトを使えば、便利なGUIで3Dアニメーションファイルを作ることができます!

そう!クリエイター・アニメーターならBlenderを使いましょう!!(え、Mayaがいい?それならMayaでどうぞ)

しかし!Qiitaの読者のほとんどは、プログラマーだと思います!

プログラマーなんだったら、プログラミングで3Dアニメーションを作りたいですよね!

いや、プログラミングで作るべきだ!

そんなあなたのために、 FBX SDK Python というものがあります!

これさえあれば! 大好きなPythonで3DアニメーションファイルFBXを生成することができるんです!

え?Pythonなんて別に好きじゃない?UnityならC#でFBXが作れる?

・・・

さぁ始めましょう!

今回の目標

以下のように、立方体が円運動するようなアニメーションを作ることが今回の目標です!

20191201_moving_circle_cube.gif

事前準備:FBX SDK Pythonをインストールする

まずはFBX SDK Pythonをインストールしましょう!

以前、いくつか記事を作成しましたので、ご自身の環境に合わせてご用意ください。

とりあえずサンプルコードを動かしてみる

まずは、サンプルコードを実際に動かしてみましょう!

以下にサンプルコードを置きましたので、ぜひダウンロードしてみてください。

GitHub / segurvita / fbx_sdk_python_sample

ダウンロードができたら、以下のコマンドをたたいてみましょう!

python generate_fbx/circle_anim.py resources/cube_ascii.fbx resources/moving_circle_cube_ascii.fbx

resources フォルダーに moving_circle_cube_ascii.fbx というファイルが生成されたと思います。

これが今回の成果物です!

Autodesk FBX Review というソフトで開けば、先ほどの動画のように、立方体が円運動している様子を眺めることができます!ぜひお試しください!

コマンド解説

さて、まだPythonについて一切解説していませんねw

まずは、以下のコマンドから解説をします。

python generate_fbx/circle_anim.py resources/cube_ascii.fbx resources/moving_circle_cube_ascii.fbx

generate_fbx/circle_anim.py というのが、今回使ったPythonのソースコードです。その次に2つの引数があります。これはそれぞれ

  • 入力ファイル: resources/cube_ascii.fbx

  • 出力ファイル: resources/moving_circle_cube_ascii.fbx

となっています。

Python解説

次に generate_fbx/circle_anim.py についてみていきましょう!

全体像はこんな感じですね!

circle_anim.py
import sys
import math
from fbx import *

fps = 30.0
rps = 0.5

def generate_anim_stack(scene):
    # 関数
def generate_anim_layer(scene, anim_stack, node):
    # 関数
def prot_circle(degree, time, curve_t_x, curve_t_y, curve_r_z):
    # 関数
def move_circle(node, anim_base_layer):
    # 関数
def get_ascii_format_id(manager):
    # 関数
def main(obj_path, fbx_path):
    # 関数

if __name__ == '__main__':
    # get argument
    args = sys.argv

    if len(args) < 2:
        print('Arguments are too short')
    else:
        main(args[1], args[2])

関数がいっぱいありますので、今回は、3Dアニメーションに関わる部分だけを解説していこうと思います。

main関数を見てみる

まずはmain関数ですね。アニメーションに関わる部分はこんな感じです。

def main(obj_path, fbx_path):
    # 各種インスタンスを生成する
    manager = FbxManager.Create()
    scene = FbxScene.Create(manager, "fbxScene")
    importer = FbxImporter.Create(manager, "")
    exporter = FbxExporter.Create(manager, "")

    # ファイルをシーンに読み込む
    importer.Initialize(obj_path, -1)
    importer.Import(scene)

    # シーン内にアニメーションスタックを作る
    anim_stack = generate_anim_stack(scene)

    # ルートノード(シーン内で最上位のノード)を取得する
    root_node = scene.GetRootNode()
    
    # ルートノードがあれば
    if (root_node):
        # ルートの直下の子ノードの数だけループする
        for i in range(root_node.GetChildCount()):
            # 子ノードを取得する
            node = root_node.GetChild(i)

            # アニメーションレイヤーを作る
            anim_base_layer = generate_anim_layer(scene, anim_stack, node)

            # アニメーションカーブを作る
            move_circle(node, anim_base_layer)

    # シーンをファイルに書き込む
    exporter.Initialize(fbx_path, get_ascii_format_id(manager))
    exporter.Export(scene)

    # インスタンスを破棄する
    exporter.Destroy()
    importer.Destroy()
    scene.Destroy()
    manager.Destroy()

聞きなれない用語がいくつかでてきましたね。

Scene : シーン

シーンとは、FBXファイルがもっている巨大な空間のようなものです。

このシーンの中に、アニメーションやメッシュといったさまざまなデータが格納されます。

(BlenderやUnityにもシーンという概念がありますが、それらとほとんど同じイメージでよいです!)

こんなコードで生成できます。

# シーンを作る
scene = FbxScene.Create(manager, "fbxScene")

Node : ノード

シーン内にはさまざまなデータがあります。その1つ1つがノードです。

ノードには親子関係があります。

たとえば、みなさん、自分の肩を回してみてください。

肩を回すと、肘の位置が変わりますよね?

つまり、 肘ノードは肩ノードの子 ってことです。子ノードは親ノードの影響を受けます。

反対に、肘だけを回しも、肩の位置は変わりませんよね?

親ノードは子ノードの影響は受けないんです。

FBXの場合、肘や肩といった関節もノードですし、アニメーションやメッシュ等もすべてノードになります。

Root Node : ルートノード

そんなノードの親をたどっていくと最終的に行き着くのが、ルートノードです。

シーンで最上位のノードですね!

こんなコードで取得できます。

# ルートノード(シーン内で最上位のノード)を取得する
root_node = scene.GetRootNode()

以下のように書けば、子ノードを順番に取得できます。

# ルートの直下の子ノードの数だけループする
for i in range(root_node.GetChildCount()):
    # 子ノードを取得する
    node = root_node.GetChild(i)

アニメーションスタックを作る

だんだん難しくなってきました・・・

アニメーションスタックというのは、アニメーションに関するデータをとりまとめるノードです。

これがないとアニメーションできません!

とりあえず、1つ作りましょう。

サンプルコードでは generate_anim_stack という関数でつくっています。

def generate_anim_stack(scene):
    # アニメーションスタックを作る
    anim_stack = FbxAnimStack.Create(scene, "stack")

    return anim_stack

これで作れます!

アニメーションレイヤーを作ろう

またまた謎の用語が・・・説明します。

3Dアニメーションといっても、いろんな動きがありますよね?

走る・歩く・ジャンプする・・・

そういった動き1つ1つをFBX用語では アニメーションレイヤー って呼ぶんです。(間違ってたらすみません)

今回は円運動をさせるだけなので、とりあえず1つ作りましょう!

def generate_anim_layer(scene, anim_stack, node):
    # ノード(立方体)の名前を取得
    node_name = node.GetName()

    # ノード(立方体)の位置と回転を0にする
    node.LclTranslation.Set(FbxDouble3(0.0, 0.0, 0.0))
    node.LclRotation.Set(FbxDouble3(0.0, 0.0, 0.0))

    # アニメーションレイヤーを作る(ノードの名前をつけて区別できるようにする)
    anim_base_layer = FbxAnimLayer.Create(scene, "layer_" + node_name)
    
    # アニメーションレイヤーをアニメーションスタック配下に加える
    anim_stack.AddMember(anim_base_layer)

    # アニメーションカーブノードを作る
    anim_curve_node = node.LclTranslation.GetCurveNode(
        anim_base_layer, True
    )
    
    # アニメーションレイヤーを返却する
    return anim_base_layer

これでアニメーションレイヤーが作れます!

・・・

なにやら途中でアニメーションカーブノードという謎の用語が登場しましたね・・・

これは、ノード(ここでは立方体)の位置や回転という情報をアニメーションで変化させるために必要なデータです。とりあえず必要なので作りましょう。(私もよくわかってません。すみません。)

アニメーションカーブを作ろう

あぁ!また謎の用語が!

実はノード(立方体)をアニメーションさせると、ひと口に言っても、

  • 位置を動かす
  • 回転させる
  • 大きさを伸縮させる
  • 等など

さまざまなアニメーションがありますよね?

たとえば、今回のように円運動をさせたいのであれば、

  • ノードの位置のx座標を動かす
  • ノードの位置のy座標を動かす
  • ノードの回転方向のz座標を動かす

みたいに3つの座標を動かすことになります。

この1つ1つが アニメーションカーブ です!

(BlenderやUnityのアニメーションカーブと概念はほとんど同じです)

今回は、3つのアニメーションカーブがあるんですね!

というわけで、以下がサンプルコードです。

def move_circle(node, anim_base_layer):
    # ノードの位置のx座標のアニメーションカーブを作る
    curve_t_x = node.LclTranslation.GetCurve(
        anim_base_layer, "X", True
    )
    
    # ノードの位置のy座標のアニメーションカーブを作る
    curve_t_y = node.LclTranslation.GetCurve(
        anim_base_layer, "Y", True
    )
    
    # ノードの回転方向のz座標のアニメーションカーブを作る
    curve_r_z = node.LclRotation.GetCurve(
        anim_base_layer, "Z", True
    )

    # 時間記録用の変数を用意する
    time = FbxTime()

    # アニメーションカーブの記録を開始する
    curve_t_x.KeyModifyBegin()
    curve_t_y.KeyModifyBegin()
    curve_r_z.KeyModifyBegin()

    # fps = 30 (1秒で30フレーム)
    # rps = 0.5 (1秒で円運動を半周)
    # fps/rps = 60 (60フレームで円運動を1周)
    # 60フレームを1フレーム毎に処理する。
    for frame in range(int(fps / rps)):
        # 経過時間を計算する。フレーム数/fps
        sec = float(frame) / fps
        
        # 経過時間を記録する
        time.SetSecondDouble(sec)
        
        # 回転角度を計算する。rps×経過時間×360°
        degree = rps * sec * 360.0
        
        # 回転角度に応じた値をアニメーションカーブにプロットする
        prot_circle(degree, time, curve_t_x, curve_t_y, curve_r_z)

    # アニメーションカーブの記録を終了する
    curve_t_x.KeyModifyEnd()
    curve_t_y.KeyModifyEnd()
    curve_r_z.KeyModifyEnd()

細かい計算式が色々でてきましたが、1フレーム毎に経過時間を記録して、円運動の回転角度を計算していることがわかると思います。

実際にアニメーションカーブへ値を記録するところは、 prot_circle という関数に記載しましたので、見ていきましょう。

アニメーションカーブにキーを追加する

アニメーションカーブに値を記録していくことを、 キーを追加する といったります。

以下が、キー追加処理のコードです。

def prot_circle(degree, time, curve_t_x, curve_t_y, curve_r_z):
    # 回転角度をラジアンに変換する
    radian = math.radians(degree)

    # 位置のx座標は cos(radian) で計算して、キー追加する
    key_index, key_last = curve_t_x.KeyAdd(time)
    curve_t_x.KeySet(
        key_index, time, math.cos(radian), FbxAnimCurveDef.eInterpolationLinear
    )

    # 位置のy座標は sin(radian) で計算して、キー追加する
    key_index, key_last = curve_t_y.KeyAdd(time)
    curve_t_y.KeySet(
        key_index, time, math.sin(radian), FbxAnimCurveDef.eInterpolationLinear
    )

    # 回転方向のz座標はラジアンではなく度数のまま、キー追加する
    key_index, key_last = curve_r_z.KeyAdd(time)
    curve_r_z.KeySet(
        key_index, time, degree, FbxAnimCurveDef.eInterpolationLinear
    )

この関数が1フレーム毎に呼ばれることで、3つのアニメーションカーブに1フレーム毎、キーが追加されていきます。

キーを追加しているのはこの部分です。

key_index, key_last = curve_t_x.KeyAdd(time)

これでキーが追加されます。

Pythonに慣れていない人のために、念のため補足すると、 KeyAdd という関数の戻り値が2つありまして、 key_indexkey_last にそれぞれ代入さているというコードになります。

このように2つの変数を並べて書くことをタプルと読んだりします。

今回は、 key_index に、キーの番号が代入されます。 key_last は使いませんので無視しましょう。

次のコードで、この key_index に対し、実際の値をセットしています。

curve_t_x.KeySet(
    key_index, time, math.cos(radian), FbxAnimCurveDef.eInterpolationLinear
)

以上で、Pythonの解説はおしまいです!おつかれさまでした!

さいごに

ここまでお読みいただき、ありがとうございました。

これで、Blenderを使わずに、Pythonだけで3Dアニメーションを作ることができましたね!!

本記事作成にあたり、以下のページを参考にさせていただきました。

17
22
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?