会社のサイトのトップでは、three.js で棒人間のキャラクターが動いています。コードで組んだ 3D のマスコットで、骨は無く、動きも手続きで作った簡単な物です。これをもっと生き生きと動かしたくて、画像から 3D を作る Pixal3D でマスコットを 3D にし、Mixamo で骨と既成の動きを付けられるかを技術検証しました。サイトには入れず、手元の確認ページだけで試しました。
その前に、画像から作った人物を整える検証をしていました。中の空洞を詰める直し(中空の殻を外から塗りつぶして詰める件)で表面が想定より凸凹し、色も参考画像どおりにはならず(4方向の絵を投影して色を貼る件)、見た目は粗いままでした。マスコットは形が単純なので、強くならして表面をなめらかにし、色の画像をやめて目を黒い粒にすれば、静止した見た目は整います(第2回で書きます)。ところが、動かすと崩れました。この連載は、その崩れを追った4回です。先に書いておくと、サイトに入れられる仕上がりにはならず、試して止めました。うまくいかなかった試しの話として読んでください。
第1回は、Pixal3D の 3D をそのまま Mixamo に上げて骨を入れ、three.js で動かすまでです。ここで最初の崩れ(腕を上げると脇が裂ける)に出会いました。
結論
- Mixamo は GLB を受け付けません。OBJ にして、UV と色の画像を付けて zip で上げると、目の黒が骨入れ後の FBX に埋め込まれ、three.js でそのまま出ました。
- 骨の名前が同じなので、動きだけの FBX を本体の AnimationMixer にそのまま当てられます。歩く・走るは、体の揺れや重心の移りがあり、手続きの動きより生き生きして見えました。
- 腕を上げる動き(喜ぶ・手を振る)で、脇の胴の面が腕に引っぱられて裂けました。元のモデルの腕が体に近いと、自動の骨入れが胴の面まで腕の骨に割り当てます。
- 腕を体から離した絵から作り直すと大きな裂けは消えましたが、脇の折れ目とお腹のしわが残りました。Mixamo の自動の骨入れだけでは足りず、重み(どの骨がどの面を動かすか)を手で直す必要があります。
試した環境
- 絵: Codex で描いたマスコットの絵
- 画像 → 3D: Pixal3D。AMD の GPU(ROCm)で動かすための移植版 trellis.cpp を使いました(移植版の作り方)。出力は GLB で、このマスコットは頂点 8.2 万・三角形 12.6 万でした。
- 骨入れと動き: Mixamo の自動の骨入れ。Adobe のアカウントでログインして使います。
- 再生: three.js の FBXLoader と AnimationMixer
流れは次の4段です。Mixamo では骨入れの後、本体と動きを別々の FBX で落とします。
動きを別の骨格に写す方法もありますが(Mixamo の動きを 2.6 頭身の人物に写す件)、ここでは骨入れまで Mixamo に任せました。
GLB を OBJ と色の画像にして上げる
Mixamo は GLB を受け付けない
Mixamo に上げられる形は FBX・OBJ・ZIP だけで、GLB は受け付けませんでした。手元の 3D が GLB なのは、使った移植版の出力の形がそうだからで、Pixal3D そのものの制約ではありません。
そこで、依存の無い Node のスクリプトで、GLB を OBJ・MTL・色の画像の3つに書き出しました。メッシュも材質も1つずつで、頂点の番号が 32 ビットの GLB を前提にした簡単な物です。要所は次の4つです。
- GLB の頭から JSON の長さを読み、その後ろにある BIN の始まりを出す
- 頂点を 100 倍して cm にし、いちばん低い点を 0 にして足を床に置く(Y が上、正面が +Z)
- UV の V を 1 から引いて、OBJ の向きに合わせる
- 色の画像(baseColorTexture)は、バイトをそのまま書き出して MTL から指す
const len = b.readUInt32LE(12);
const j = JSON.parse(b.slice(20, 20 + len).toString());
const binStart = 20 + len + 8;
const S = 100;
// 頂点: cm にして、足を 0 に
lines.push(`v ${(b.readFloatLE(o) * S).toFixed(4)} ${((b.readFloatLE(o + 4) - minY) * S).toFixed(4)} ${(b.readFloatLE(o + 8) * S).toFixed(4)}`);
// UV: V を反転
lines.push(`vt ${b.readFloatLE(o).toFixed(6)} ${(1 - b.readFloatLE(o + 4)).toFixed(6)}`);
// 色の画像を取り出し、MTL から指す
const texIndex = j.materials[0].pbrMetallicRoughness.baseColorTexture.index;
const image = j.images[j.textures[texIndex].source];
fs.writeFileSync(path.join(outDir, `${name}.mtl`), `newmtl body\nKd 1 1 1\nmap_Kd ${name}.${ext}\n`);
OBJ・MTL・色の画像を zip にまとめると 2.5MB でした。
目の黒は、色の画像を付けないと消える
形だけの OBJ を上げると、骨入れ後の FBX では目が凹みだけになりました。目の黒は色の画像の側にあるので、形だけでは凹みしか残りません。
UV と色の画像を付けて zip で上げると、Mixamo は skin あり(肌つき)の FBX に色の画像を埋め込み、three.js で読むとそのまま目の黒が出ました。本体の FBX は、色なしで 5MB、色つきで 9MB です。
骨を入れて、three.js で再生する
Mixamo の印(あご・手首・肘・膝・股)は、左右対称の設定にして片側に置けば済みました。骨入れは1分ほどです。本体は「T ポーズ・skin あり」、動きは「skin なし・30fps」で落とし、歩く・走るは「In Place」にしました。動きの FBX は1本 0.2〜0.35MB です。
再生は、FBXLoader で本体を読み、動きの FBX の animations[0] を同じ AnimationMixer に当てるだけです。本体と動きで骨の名前が同じなので、動きだけの FBX がそのまま使えます。
const loader = new FBXLoader();
const body = await loader.loadAsync('mascot.fbx'); // T ポーズ・skin あり
const mixer = new THREE.AnimationMixer(body);
const walk = await loader.loadAsync('walking.fbx'); // skin なし
mixer.clipAction(walk.animations[0]).play();
一つ迷いやすいのは、骨を入れ直した本体には、前の本体用に落とした動きが使えないことです。骨の休みの向きが変わるためです。骨を入れ直したら、Mixamo で本体を切り替えて、動きを落とし直しました。
動かして見ると、歩く・走るは、体の揺れや重心の移りがあり、手続きの動きより生き生きして見えました。既成の動きを借りること自体はできる、というのが最初の手応えです。


腕を上げると、脇の胴の面が裂けた
腕が体に近い見本では、扇のように裂ける
困ったのは腕を上げる動き(喜ぶ・手を振る)です。最初のモデルは腕が体に近い見本の絵から作った物で、喜ぶ動きでは脇の胴の面が腕に引っぱられて、扇のように大きく裂けました。

元のモデルの腕が体に近いと、自動の骨入れが胴の面まで腕の骨に割り当てます。腕を上げると、その胴の面まで腕と一緒に持ち上がります。Mixamo で動きを落とすときの Character Arm-Space(腕と体の間隔)を 82 にしてみましたが、待機の姿勢でも脇に穴が残りました。
腕を体から離した絵を頼む
それなら元の絵で腕を体から離せばよいと考え、Codex に今のマスコットの絵を添えて、同じ比率のまま描き直してもらいました。最初に「腕を真横に伸ばした T ポーズ」と頼むと、腕の上の縁が頭の下端に触れました。頭が大きく低いこの体型では、腕を真横にすると頭とぶつかります。腕を水平から約 20° 下げる指定にすると、頭から離れました。効いた指定は次の3つです。
- Both arms stretched straight out to the sides, fully extended, angled about 20 degrees downward from horizontal (a wide A-pose), palms facing down, mitten hands open and flat.
- The top of each arm starts clearly below the head: a visible band of background separates the top edge of each arm from the bottom of the head. The arms must not touch the head or the body anywhere; only the arm roots join the upper body.
- Legs straight, feet about shoulder width apart, with visible background between the legs.
ほかに、正面から胸の高さで、ほぼ平行投影で描くこと、無地の背景で影を付けないこと、文字を入れないことも添えました。腕と頭、腕と胴、脚どうしのあいだに背景が見えていることを、絵の段階で確かめておくのが要点です。

大きな裂けは消え、折れとしわが残る
この絵から Pixal3D で作り直し、同じ手順で骨を入れました。喜ぶ動きの大きな裂けは消えましたが、胴の脇に折れ目が残り、待機の姿勢でもお腹に横じわが出ました。

絵の頼み方で減らせるのは、腕が胴にくっついていることによる大きな裂けまでで、その先は Mixamo の自動の重みのままでは消えませんでした。きれいにするには、Blender などで重みを手で直す必要があります。
まとめ
- Mixamo は GLB を受け付けないので、OBJ・MTL・色の画像の zip にして上げる。色の画像を付けないと目の黒が消える。
- 動きだけの FBX は、骨の名前が同じなので本体の AnimationMixer にそのまま当たる。骨を入れ直したら動きは落とし直す。
- 腕が体に近いモデルでは、自動の骨入れが胴の面を腕の骨に割り当て、腕を上げると脇が裂ける。頭の大きい体型では、腕を水平から約 20° 下げた絵にすると頭とも胴とも離せる。
- それでも脇の折れ目とお腹のしわは残り、重みを手で直す必要がある。
確かめていないこと
- 道具を持つ・ロゴを押すなど、手の位置が決まった動きを、既成の動きと腕の逆算(IK)で作れるか。
おわりに
第1回で分かったのは、Pixal3D の 3D でも Mixamo の自動の骨入れと既成の動きは通り、歩く・走るなら手続きの動きより生き生きすること、ただし腕を上げる動きは自動の重みのままでは崩れることです。次回は、この重みを Blender で直していき、直すほど別の所が崩れた話を書きます。
※ Mixamo の受け付ける形や設定、three.js の振る舞いは執筆時点の情報で、いまは変わっている可能性があります。


