trellis.cpp(Pixal3D の C++ への移植版)で、Radeon でも画像1枚から色付きの 3D が作れるかを技術検証したところ、できた GLB をビューアで開くとキャラクターが仰向けに寝ていました。形も色も合っているのに向きだけが違うので、ビューアやモデルの読み込みの設定を疑いたくなるところです。

結論

  • trellis.cpp は、TRELLIS.2 用の軸の変換(Z が上 → glTF の Y が上)を、Pixal3D にもそのまま当てます。そのため GLB は、頭が glTF の -Z、顔が +Y を向いて寝ています。
  • 頂点を (x, y, z) → (x, −z, y) と回すと立ちます。回転なので、面の巻き(表と裏)は変わりません。
  • 回すのは POSITION・NORMAL・TANGENT の xyz です。POSITION の min/max も書き直します。
  • 三面図を受け取る fork(pixal3d.cpp)の出力は、-Z が正面です。こちらは Y 軸まわりに 180°、(x, y, z) → (−x, y, −z) と回します。

試した環境

  • trellis.cpp v0.8.1(HIP でビルド)、複数視点の fork の pixal3d.cpp
  • Radeon RX 7900 XT(gfx1100、VRAM 20 GB)、Ubuntu、ROCm 7.2.4
  • 後始末は、trellis-cli を呼ぶ自作の Python スクリプトで行います(numpy と Pillow だけ)

何が起きていたか

寝ている向きを見ると、頭が glTF の -Z、顔が +Y でした。trellis.cpp は TRELLIS.2 の移植から始まった物で、TRELLIS.2 用の「Z が上の座標を、glTF の Y が上に直す変換」を持っています。この変換が Pixal3D の出力にもそのまま当たるため、1回余計に倒れた形で書き出されていました。

次の図は、1枚の方式と複数視点の方式の出力を、それぞれの回し方で同じ向き(+Z が正面)にそろえる流れです。

2つの方式の出力を同じ向きにそろえる

頂点を回して立たせる

直し方は単純で、頂点の座標を回すだけです。

def upright_glb(src, dst):
    # 頂点を (x, y, z) → (x, -z, y) と回して立たせる(POSITION・NORMAL と TANGENT の xyz)。
    # POSITION の min/max も直す。
    turn_glb(src, dst, lambda x, y, z: (x, -z, y))

def face_front_glb(src, dst):
    # pixal3d.cpp の --views の出力(glTF の -Z が正面)を Y 軸まわりに 180° 回し、
    # 1枚の方式と同じ向き(+Z が正面)にする。
    turn_glb(src, dst, lambda x, y, z: (-x, y, -z))

turn_glb では、GLB の JSON チャンクと BIN チャンクを自分で読みます。float32 の POSITION・NORMAL・TANGENT を、その場で書き換えます。気をつけた点は次の3つです。

  • 位置だけでなく、法線(NORMAL)と接線(TANGENT の xyz)も同じように回します。位置だけ回すと、光の当たり方がずれます。
  • glTF のアクセサには POSITION の min/max が書かれているので、回した後の値に直します。
  • ノードに matrix・rotation・scale・translation が付いていたら、処理を止めます。想定していない形で、頂点を回すだけでは向きが合わないからです。

(x, y, z) → (x, −z, y) は鏡映ではなく回転です。そのため、三角形の頂点の並び(巻き)を入れ替える必要はありません。

複数視点の fork は別の向きで出る

三面図を渡せる fork の pixal3d.cpp(--views)の出力は、1枚の方式とまた違い、glTF の -Z が正面でした。こちらは Y 軸まわりに 180° 回して、1枚の方式と同じ +Z が正面の向きにそろえています。同じ trellis.cpp の系統でも、fork によって向きが違うことがあります。新しい fork を試すときは、まず向きを確かめるのがよさそうです。

横たわった人形と立った人形が並び、そのあいだに回転を表す弧の矢印

立ったことをどう確かめたか

後始末の最後に、4方向のプレビューを CPU で描いています(29 万面で 2 秒)。このプレビューで、正面・横・背面の向きを目で確かめます。

なお、色を入力に合わせる処理では、立たせた形を入力のカメラ(+Z の側、距離 0.5/tan(fov/2))から投影しています。このときキャラのパーツ 7 本の輪郭は、切り抜きと IoU 0.976〜0.987 で重なりました。この重なりは向きを確かめるために測った値ではありません。前後が逆(Y 軸まわりに 180°)でも正面の輪郭は左右が反転するだけなので、前後は上の4方向のプレビューで見ています。

確かめていないこと

  • 本家の Python 版の出力と、向きや形を比べてはいません。
  • ノードに変換が付いた GLB は、止める扱いにしているだけで、直し方は作っていません。

おわりに

trellis.cpp で Pixal3D の GLB が寝て出てきても、壊れているわけではありません。頂点・法線・接線を (x, −z, y) に回し、min/max を直せば立ちます。

※ trellis.cpp と pixal3d.cpp の出力の向きは、執筆時点の版(v0.8.1 と、試した commit)での振る舞いで、いまは変わっている可能性があります。