前回 、音感トレーニングのジャンプゲームをAIと作っている話を書きました。画面の横軸がピアノの鍵盤になっていて、和音の音の上だけに足場が出るあれです。あれからリリースに向けてあれこれ手を入れ続けているのですが、その試行錯誤の途中で思いついたのが、今回の到着ムービーです。ステージが島になっていて、新しい島に挑むたびに「飛行機で島へ運ばれて、パラシュートで降りて、砂浜に着く」というシーンが流れたら格好いいのではないか。
思いついたプロットはこうです。上空を俯瞰で飛ぶ飛行機。腹のドアが開いて、マスコットのえびふりゃが落ちる。落下の途中でパラシュートがバサッと開いて、ふわりと浮く。砂浜に着地して、勢い余って砂に埋まる。鉢巻きと尻尾だけ覗かせてブルブル震え、ポンッと飛び出して、さあ冒険だ。
先に、完成したものをお見せします。25秒・全8ショット・614フレーム。雲海の巡航からジャンプまでの通しです。
ここから先は、この25秒がすんなりできなかった、という話です。作り方は3D化のとき と同じで、Blenderを私は一切触らず、コードは全部AIに書かせます。舞台になる海辺は釣りの世界 で一度作ってあるので、今回は流用すればすぐ終わる──と思っていました。終わりませんでした。この記事は、その顛末の記録です。
また、海に負けかけた
まず舞台のビーチです。私は最初に釘を刺しておきました。海を作るのはすごく大変だったのだから、釣りゲームで作った海を活用してほしい、と。あの海は、サイン波を全部捨ててノイズにたどり着くまで三度負けた、いわくつきの海です。
AIは「海を移植しました」と、それらしいビーチを出してきました。ところが見ると、全然違う。うねりがゴロゴロした、彩度の高い、どこかで見たようなCGの海。しかも波打ち際には泡の玉が置物のように並んでいる。置物の泡は、釣りの海で「チートがバレましたね」と白状させた、まさにその却下案です。同じAIが、同じ人間相手に、同じ手を二度打ってきたわけです。
問い詰めると、原因はあっけないものでした。AIは釣りの記事の「文章」は読んでいたのに、記事に載っている海の「画像」を一度も見ていなかった。文章から仕組みだけ拾って、見た目は自分の癖で作っていたのです。画像を並べて比較させてようやく、波はほぼ平らでいい、色は淡い岸から水平線の濃い帯への3段だ、主役は細かいきらめきの粒だ、と伝わりました。

ついでに技術的な収穫もひとつ。色がどうしても濃く沈む問題の犯人は、色空間の変換漏れでした。人間が指定する色(sRGB)とレンダラーが計算に使う色(リニア)は別物で、変換せずに渡すと「深い紺」のつもりが「中途半端な青」になる。分かってしまえば一行の変換関数の話ですが、これを踏むまで、私は何度も「記事の海と全然違う」と言い続けることになりました。

波打ち際は釣りの海の結論をそのまま使っています。水際線を描くのではなく、海へ下る砂の斜面と水面の交わる線から「生まれさせる」方式。おかげで泡の白い線は、置物ではなく水面の高さの等高線として、勝手に有機的な曲線になります。

頼んでいない島が、生えてきた
事件はビーチの沖で起きました。あるとき水平線の手前に、緑色の饅頭が2個浮かんでいたのです。言っておきますが、私は島を頼んでいません。このゲームはステージを島単位で渡っていく作りなので、海の向こうに次の島が見えたら気が利いているだろう、とAIが自分で判断して置いたようです。

気持ちは分かります。でも饅頭にしか見えない。100メートルほど先にぽっかり浮かんでいるようにしか見えないのです。遠くの島というのはもっと霞んでいるものなので、置くならもっと遠くにリアルに霞ませろ、と直しを頼みました。AIは霞の色に寄せる、稜線をギザギザにする、切り絵のシルエットにする、と手を替え品を替えてきましたが、多少ましになるだけで、どうやっても「近くの何か」から抜けない。距離感というのは物体の描き込みではなく空気の描き方で出るものらしく、それを本気でやるのは大工事です。
そこで判断しました。ステージ到着の数秒のムービーのために、遠景の空気遠近を作り込むのは割に合わない。「島を表現するな。消せ」。消してみると、水平線だけの海で何も困りませんでした。AIが気を利かせて足した背景ほど、リアリティの請求書は高くつくという話です。
輸送機を、デザインし直す
キャラを運ぶ飛行機は、最初AIが小ぶりな単発のプロペラ機を出してきました。これが原案です。

可愛いのですが、これでは遊覧飛行です。私のイメージは、パラシュート部隊が飛び降りる、プロペラがいくつも付いた大型の輸送機。ただしミリタリー色に塗れという意味ではなく、色はパラシュートと同じ「みかん×生成り」のままで、シルエットだけ輸送機にしてほしい。
そう伝えて出てきたのが、この機体です。4発のプロペラ、太い胴体、高い位置の一枚翼、T字の尾翼。そして投下シーンの主役になる、腹の観音開きのドア。


作り直しの途中には珍事もありました。プロペラの羽根を回転軸の中心に置いた箱で作ったせいで、3枚羽根のつもりが中心から6本の腕が生えた風車になっていたのです。羽根は軸から一方向に伸びるものだと教えて、ようやくプロペラになりました。
世界の縁は、四角い
次の敵は世界の形でした。飛行機のシーンを作らせると、まず高度が低い。海面スレスレを飛ぶ輸送機を見せられて、これでは輸送機というより飛行艇です。

高度を上げさせると、今度はもっと根本的な問題が露出しました。カメラが高く上がった途端、海の四角い縁、つまりワールドの終端が画面に写り込んだのです。
3Dの世界は、実は板の上に作られています。地上すれすれのカメラなら板の縁は水平線の彼方ですが、上空からの俯瞰では板がただの板として見えてしまう。対策は身も蓋もなくて、海の板を巨大にするだけです。ただし芸のない巨大化はレンダリングが重くなるので、波の立つ詳細な海は岸の近くだけにして、その外側は起伏のない一枚板を敷く二層構えにしました。近くは波、遠くは色。人間の目は遠くの波など見ていないので、これで十分です。
高度のほうは、実際の高さの数値よりも記号が効きました。雲です。雲海を下に見て飛べば、それだけで「はるか上空」になる。クレイ調の平たい雲をいくつか浮かべ、輸送機はその上を飛ばすことにしました。

開傘は、カメラが落ち続ける
今回いちばん作りたかったのは、パラシュートが開く瞬間です。私の注文は「映画のスカイダイビング映像のリアル感」でした。落下するカメラがキャラと並走する。パラシュートが開いた瞬間、キャラだけが急減速する。カメラは速度を一切変えずに落ち続け、上を仰ぎながらキャラを見送る。するとキャラは、画面の中でぐんぐん上へ遠ざかって小さくなっていく。
実際に浮かび上がっているのではありません。減速しただけです。でも落ち続けるカメラから見れば、それは「急に浮いた」ように見える。相対運動だけで無重力感を作る、実写のスカイダイビング撮影がやっているのと同じ理屈です。

パラシュート自体も、最初から開いた状態を出すわけにはいきません。畳まれた束がまず細長いストリーマーになってはためき、それからバサッとドームに開く。開く瞬間には弾性の揺り戻しを入れて、布が空気を掴んだ感じを出します。

ここで表情の駄目出しをひとつ。開傘の喜びで口を開けた顔にさせたのはいいのですが、AIはその顔のまま延々と降下を続けさせました。いつまでも口を開けていたら不格好です。感情の表現は一瞬でいい。「すぐ元に戻せ」という指示で、口開きは開傘の0.5秒だけになりました。
緊張感がないんだよ
着地のシーンには、注文を重ねました。最初にAIが出してきた着地は、ふわふわとスローに降りてきて、そっと砂に埋まるものでした。可愛い。可愛いが、緊張感がない。勢い余って埋まるのだから、埋もれるくらい速くないと意味がない。
もうひとつ、細かいけれど許せなかったのが砂の土手です。突入でめくれた砂がクレーターの縁に盛り上がるのですが、AIはこの土手を着地する前から置いていました。埋まってから現れるなら分かる。まだ何も起きていない砂浜に、結果だけが先にある。ホラーです。
直した後の着地はこうなりました。0.5秒の急降下で砂に突入。衝撃でカメラが揺れ、土手はその瞬間に盛り上がって現れる。役目を終えたパラシュートは消えたりせず、空気が抜けてフニャーと萎み、横に流れて砂の上に横たわる。以降のシーンでも、萎んだ傘はずっと背景に残っています。

総計何時間かかったと思ってんの
制作の裏で、私はAIにこう聞きました。今回のレンダリング、総計何時間かかったと思ってるのか。AIは「1本25分」のような顔をしていましたが、実際に積み上げさせると約3時間。しかもその大半は、レンダラーが遅いからではなく、直すたびに全フレームを焼き直していたからでした。
ここから運用を変えさせました。まず、動画をショット単位の部品に分割する。巡航、投下、落下、開傘、降下、着地、飛び出し、歩き。修正が入ったら、そのショットのフレームだけレンダリングし直して、部品を差し替えて連結する。連結は再エンコードすらしないので一瞬です。確定したシーンは、二度とレンダリングしない。
もうひとつはGPUです。調べさせたら、スクリプトはGPU指定をしておらず、ずっとCPUだけで回っていました。Metal GPUを有効にし、さらにフレーム間でシーンの下準備を使い回す設定(persistent data)を入れて、1フレーム3.5秒が1.8秒に。仕上げの高解像度に至っては3倍速になりました。この2つで、「1ショット直すのに25分」が「2〜4分」になっています。
歩き出して、ジャンプで終わる
最後のシーンは、ゲームへの接続です。砂から飛び出したえびふりゃが、体を左右にくねらせながら、正面のカメラへ向かってペタペタ歩いてくる。目の前まで来たら、ぐっとためて、ジャンプ。画面いっぱいの笑顔で飛び上がったところでカットです。このジャンプがそのままゲームの1跳ね目につながる、という算段です。


通しの完成形は冒頭に貼った25秒です。ここまで読んだあとなら、開傘でカメラだけが落ち続けるところや、着地の瞬間に土手が現れるところも、作った側の目で見えるはずです。もう一度どうぞ。
今回の教訓
学びを並べておきます。ひとつ、AIに「参考にして」と資料を渡すときは、文章だけでなく画像を見たかまで確認すること。AIは読んだことと見たことを平気で混同します。ふたつ、AIが気を利かせて足したものには、リアリティの請求書が付いてくる。払う価値がなければ消していい。みっつ、演出のリアリティは物理の正確さではなく相対運動で作れる。カメラが落ち続けるだけで、キャラは浮かびます。よっつ、確定したシーンを焼き直さない仕組みは、レンダリングを速くするどんな設定より効く。
そして今回も結局、いちばん時間を食ったのは手戻りでした。AIの「できました」を信用せず、拡大して見て、比較して見て、動画にして見て、駄目なものは駄目と言う。この往復が遠回りに見えていちばん速いというのは、3本前の記事から一度も変わっていません。
余談ですが、この到着ムービーが繋がる先のジャンプゲームは、相対音感を鍛える音感トレーニングアプリ Harmonize の姉妹プロジェクトとして作っています。
次は島の選択マップです。島を消した男が、今度は島を並べる画面を作ることになります。
