ポリゴン・骨格・動き・カメラを JSON で指定して、ブラウザだけで3Dキャラクターを動かし、WebM 動画に書き出します
vertices があればポリゴン、bones があれば骨格、tracks があれば動き、keys があればカメラ、actors があれば場面)。
ファイルはサーバーへ送られません。
座標は右手系・Y が上・単位はメートル相当(1.7 なら身長 170cm)。角度は度。時刻 t は秒。
{
"name": "hero",
"vertices": [[x, y, z], ...],
"faces": [[0, 1, 2], [4, 5, 7, 6], ...], // 三角形でも四角形でも。頂点番号を外から見て反時計回りに
"uvs": [[u, v], ...], // 頂点ごとの UV(省略可)
"faceUvs": [[[u,v],[u,v],[u,v],[u,v]], ...], // 面ごとの UV(uvs より優先。1枚の画像から区画を切って貼るとき)
"color": [r, g, b], // 0〜1。テクスチャが無いときの色(省略可)
"faceColors": [[r, g, b], ...], // 面ごとの色(省略可)
"repeat": true, // UV が 0〜1 を超えたら画像を繰り返す(床・壁)
"doubleSided": false
}
{
"bones": [
{"name": "hips", "parent": null, "head": [0, 0.95, 0]},
{"name": "spine", "parent": "hips", "head": [0, 1.10, 0]},
...
],
"weights": [ // 頂点番号の順。1頂点につき最大4本
[["thigh_l", 1]],
[["chest", 0.7], ["spine", 0.3]],
{"head": 1}, // この書き方でも可
...
] // 省略すると、一番近い骨2本に距離で自動で紐づける
}
{
"duration": 1.2, "loop": true, "ease": "smooth", // ease は smooth か linear
"tracks": {
"thigh_l": [{"t": 0, "rot": [-32, 0, 0]}, {"t": 0.6, "rot": [32, 0, 0]}, {"t": 1.2, "rot": [-32, 0, 0]}],
"hips": [{"t": 0, "pos": [0, 0, 0]}, {"t": 0.3, "pos": [0, -0.03, 0]}, ...] // pos は骨の位置のずらし
}
}
{
"fov": 50, "size": [640, 480], // size は書き出す動画の大きさ
"style": {"resolution": [320, 240], "nearest": true, "vertexSnap": true,
"background": "#07080c", "fog": ["#07080c", 3, 9]},
"keys": [
{"t": 0, "pos": [2.2, 2.6, 3.0], "look": [0, 0.9, 0]},
{"t": 4, "pos": [-2.6, 1.3, 2.6], "look": "head", "fov": 32} // look に骨や人物の名前も書ける
]
}
{
"world": {"background": "#07080c", "ambient": 0.55, "sun": [0.3, 1, 0.4], "fog": ["#07080c", 3.5, 10]},
"camera": { ...4 と同じ... },
"actors": [
{"name": "hero", "model": {...1...}, "skeleton": {...2...}, "animation": {...3...},
"texture": "data:image/png;base64,...", // 同梱するなら Data URL。ファイル名なら別に画像を読み込む
"position": [0, 0, 0], "rotation": [0, 180, 0], "scale": 1,
"path": [{"t": 0, "pos": [0, 0, 2.8], "rotY": 180}, {"t": 5, "pos": [0, 0, -3], "rotY": 180}]} // 移動の経路
],
"props": [{"name": "floor", "model": {...1...}, "texture": "data:..."}], // 骨の無い置き物
"duration": 5
}
最小はポリゴンの JSON 1つです。頂点の座標と面(頂点番号の並び)があれば表示できます。関節を動かすには骨格の JSON(骨の親子と位置、頂点ごとの重み)と、動きの JSON(骨ごとの角度のキーフレーム)を足します。カメラの JSON は位置・注視点・画角のキーフレームで、無ければマウスで自由に見られます。テクスチャは PNG や JPEG の画像ファイルをそのまま読み込みます。4つの JSON をまとめた「場面 JSON」にすると、複数の人物や小道具を1ファイルで持てます。
骨格 JSON に weights が無いときは、各頂点から一番近い骨(親の位置から子の位置までの線分)を探して自動で紐づけます。近い骨2本を距離で按分するので、関節付近の頂点は両方の骨に少しずつ引かれます。箱を組んだような低ポリゴンのモデルなら自動でほぼ足りますが、狙いどおりに曲げたい場合は weights を書いてください。
できます。カメラ JSON か場面 JSON の style で resolution(描画する解像度、例 320x240)、nearest(テクスチャを拡大してもぼかさない)、vertexSnap(頂点の位置を画面の格子に丸めて、初期の 3D ゲームのように頂点がぶれる)を指定します。画面上のチェックボックスでも切り替えられます。テクスチャの歪み(アフィン補間)までは再現していません。
WebM(VP9 または VP8)で保存します。実時間の録画ではなく、1コマずつ描いて確定させる方式なので、描画が重い端末でもコマが落ちず、指定したフレームレートと長さのとおりに書き出されます。大きさはカメラ JSON の size(例 640x480)で決まり、無ければ画面の大きさになります。この方式に対応していないブラウザでは実時間の録画に切り替わります。
いいえ。読み込み・描画・動画の書き出しは、すべてお使いのブラウザの中だけで行います。描画に使う three.js のライブラリだけを配信元(jsDelivr)から読み込みます。書き出した動画もそのまま端末に保存されます。