MMDの再生画面を動画・静止画で書き出す(ブラウザだけで)
作ったモーションを人に見せるには、動画か静止画にするのが確実です。ブラウザだけで完結させられますが、素朴に録画すると環境によってスローモーションの動画ができます。 何が原因で、どう避けるかをまとめます。
画面録画とフレーム単位の書き出しは別物
| 画面録画(MediaRecorder) | フレーム単位(WebCodecs) | |
|---|---|---|
| 撮り方 | 実時間で流しながら撮る | 再生ループを止めて1コマずつ描く |
| 描画が遅い環境 | スローモーションになる | 指定どおりの長さ・fpsになる |
| 音声 | 入る(BGMも一緒に録れる) | 入らない(映像のみ) |
| 対応状況 | 広い | VideoEncoder がある環境のみ |
画面録画は「今画面に出ているもの」をそのまま記録します。 1フレームの描画に50ミリ秒かかる環境で30fpsの動画を録ると、実時間は進むのに描けたコマが足りず、動きがゆっくりになります。MMDの再生はモデル1体でも重いので、これは珍しい状況ではありません。
フレーム単位の書き出しは、再生ループを止めて「固定のΔtで1コマ進める → 描く → エンコーダへ渡す」を繰り返します。 描画に何秒かかっても、タイムスタンプは自分で振るので結果は変わりません。書き出しに実時間の何倍かかっても、出てくる動画は正しい速度です。
VideoEncoderの出力はそのままでは動画ファイルにならない
WebCodecs の VideoEncoder が返すのは符号化されたフレームの列だけで、コンテナ(mp4やwebmの器)にはなりません。ブラウザにマルチプレクサは入っていないので、自分で組むか、ライブラリを足すことになります。
映像トラック1本だけなら、WebM(Matroska)の最小限のマルチプレクサは自前で書けます。 必要なのはEBMLのヘッダ・1つのトラック定義・キーフレームごとに区切ったクラスタだけです。音声を入れないと決めれば、実装はかなり小さくなります。
- VP8 / VP9 は幅と高さが偶数である必要があります。 canvasのサイズを偶数へ丸めてから始めてください(奇数のままだと設定の時点で弾かれます)。
- 対応コーデックは環境によって違います。
VideoEncoder.isConfigSupported()でVP9 → VP8の順に試して、通ったものを使うのが確実です。 - ビットレートは「幅 × 高さ × fps × 係数」で見当を付ければ十分です。
静止画は「大きく描き直してから」撮る
画面のスクリーンショットは表示解像度そのままなので、壁紙やイラストの素材には小さすぎます。レンダラのサイズを一時的に上げてから1枚描き、撮ってから元に戻すのが正解です。
- 先に画素密度を1へ固定してからサイズを指定する。 画素密度が掛かったままだと、指定した数字と実際のピクセル数が食い違います。
- 上限を決めておく。 このサイトは4096pxで頭打ちにしています。GPU無しの環境では、これ以上は確保に失敗しえます。
finallyで必ず画面用の設定へ戻す。 途中で失敗したときに戻し忘れると、プレビューが壊れたまま残ります(倍率・輪郭線・背景の3つとも)。
背景を透過させるには、レンダラを作るときに alpha: true を渡しておき、書き出しのときだけ背景をクリアします。検証は四隅と中央のアルファ値を読むこと — 見た目では透過しているかどうか分かりません(市松模様の背景に見えるのはビューアの機能です)。2倍・透過で書き出したとき、不透明な画素は全体の6.5%で、不透明な領域の範囲は不透過版と一致することを確認しました。
キャプチャが真っ黒になる原因
WebGLRenderer に preserveDrawingBuffer: true を渡していないと、canvas.toDataURL() が真っ黒な画像を返すことがあります。 WebGLは描画後にバッファをクリアしてよい仕様なので、レンダリングループの外(ボタンを押した瞬間)に読み出すと、既に消えていることがあるのです。
もう1つの原因はテクスチャの読み込み待ちです。モデルのローダのコールバックは「メッシュと材質が揃った」時点で呼ばれるだけで、テクスチャの取得はその後も非同期で続きます。 固定の待ち時間(1.5秒など)でキャプチャすると、テクスチャ22件のモデルで髪が破綻したサムネイルが保存されました(実測では取得完了まで7秒かかっていました)。未完了の件数を数えて、0になるまで待ってください。
保存する前に「絵が出ているか」を画素で確かめること。 背景色から離れた画素が全体の1%未満なら、それは失敗しているとみなして保存しない、という判定が効きます。真っ黒な画像が保存されてしまうと、後から見て原因が分かりません。
書き出しが正しいかを目で確かめない
この手の機能は「なんとなく出た」で終わらせると、破綻に気づけません。画素を数えて判定するのが確実です。
- 絵が出ているか → 背景色から離れた画素の割合(実測 8.7%)。
- 動いているか → 数秒あけて2枚撮り、画素を8×8に畳んだ指紋の差を見る(実測 2.4〜2.5%)。モデルが載っただけの静止画では差が0になるので、これで区別できます。
- 色が変わったか → 平均色の1成分だけを見ないこと。ある材質を純赤にすると「Rはそのまま、GとBが下がる」ので、Rの増加を見ていると「効いていない」と読み違えます。赤み(R ÷ GBの平均)で測るのが正しい(実測 0.902 → 1.000)。
再生中は画面を消させない
動画を書き出す前に、そもそも最後まで再生できるかという問題があります。4分のモーションは無操作なので、スマホでは途中で画面が消えます。Screen Wake Lock を取れば防げますが、タブを裏に回すとOSに解除されるので、表に戻ったときに取り直す必要があります。