[Astro] #185 トップページの世界に風・雪・夜を足す 01 — 髪・霧・雪を 1 つの風でつなぎ、調整パネルで決める

[Astro] #185 トップページの世界に風・雪・夜を足す 01 — 髪・霧・雪を 1 つの風でつなぎ、調整パネルで決める

はじめに

前回は、.git の中身を調べるツール、Git Inspector を作りました。

今回は、ツールではなく、トップページの 3D の世界の話です。先日、トップページに霧を足しました。ところが、霧は薄すぎて、最初は「出ていない」と思ったほどでした。設定の数字(JSON)を直して、やっと見えるようになりました。

霧が見えるようになると、次を足したくなります。

  • 風: VRM の髪は、揺れやすい作りです。風を感じられるはず。ただし、風は目に見えません。霧と連動させれば、見えるようになります
  • 雪: もうすぐ冬です
  • 夜: 時間に合わせて、暗くなったり明るくなったりする。ライティングは大事です
  • スポットライト: 夜は、アバターが暗くなるので、照らしたくなります

1 回では終わらない内容なので、シリーズにします。この記事は、その 1 回目です。風と雪は、実機で良い見た目になりました。夜とスポットライトは、仕組みはできたものの、見た目はまだ決まっていません。 決まっていないところも、そのまま書きます。

作ったもの

どれも、data/weather.json という 1 つの JSON で、設定します。値を直して再読み込みすれば、反映されます。

  • 風: 風の強さと向きを 1 か所で持ち、髪・霧・雪が、同じ風を読みます
  • 雪: 粒(Points)を、カメラのまわりの箱の中に降らせます
  • 時間帯: 時刻から、画面全体の明るさ、環境光、空の色を変えます。時刻は、JSON に書けば固定(テスト用)、書かなければ今の時刻(PC の時計)です
  • スポットライト: 夜に、アバターを照らす光(途中です)
  • 右クリックメニューのスイッチ: 霧・風・雪・昼夜を、見る人が ON / OFF できます。値は記憶せず、再読み込みで JSON の設定に戻ります
  • 調整パネル: スライダーで値を動かしながら、画面を見て決める道具。今回の、いちばんの収穫でした

(画像: 風が吹いて、髪が揺れ、霧が流れている画面)

トップページ。霧が出ていて、アバターの髪が風で揺れている

風

髪は、スプリングボーンで揺れる

VRM の髪や服の揺れは、スプリングボーン(揺れ物の物理)で動いています。three-vrm では、vrm.springBoneManager.joints の中に、揺れ物の関節が入っていて、それぞれに gravityDir(重力の向き)と gravityPower(重力の強さ)があります。

風は、この重力に、風のベクトルを足して、実現しました。

  • モデルを読み込んだとき、全部の関節の、元の重力の向きと強さを覚えます
  • 毎フレーム、「元の重力 + 風」のベクトルを、向きと強さに直して書き込みます
  • 風を止めたとき(OFF、または ROOM 以外のモード)は、覚えておいた元の値に戻します

スプリングボーンの更新そのものは、これまでの場所で行われているので、設定の値だけを書き換える作りにしました。

最初の風は、不自然でした

最初は、一定の強さで、右から吹かせました。髪は動きましたが、傾いたまま止まりました。強さを 0.5 から上げても、見た目は変わりませんでした。揺れ物の硬さで、頭打ちになるからだと考えています(確認はしていません)。

「風に動きがないので、不自然か」と気づいて、風の強さを時間で変えました。

  • 周期の違う 3 つの波を混ぜて、規則的に見えないようにします
  • 真ん中を急にして、凪ぎ(ほぼ 0)と突風(ほぼ最大)を、はっきりさせます
  • 計算上、約 27% の時間が凪ぎ(強さが 0.25 未満)、約 36% が強め(0.75 超)で、強弱の切り替わりは 1 秒に 1 回ほどです

凪ぎが入ったことで、髪が戻って、また吹かれる、という動きになりました。「0 と 0.5 を、不規則に入れ替えれば、風の揺れになるのでは」という、見ていて出た思いつきが、そのまま正解でした。

風の向きも、少しゆらがせます。最初は ±25° にしましたが、「左から右だけだね」と言われました。広げる値(swayDeg)を、調整できるようにしました。

霧と連動させて、風を見える化

霧は、半径の違う球の殻に、ノイズで濃淡をつけたものです。ノイズを引く座標を、風の向きにずらして行くと、霧の模様が風下へ流れます。髪と霧が、同じ風を読むので、髪が右に揺れるとき、霧も右に流れます。

実機では、「風があって、髪の動きとシンクロしてる」と、確認できました。

ただし、霧が出ていないとき(晴れ)には、風は髪でしか見えません。「霧がある時だけでいい」ということにしました。

雪

雪は、Points(粒)です。

  • 粒は 2,500 個です。位置は、頂点シェーダーの中で計算します。CPU が毎フレーム渡すのは、時間と、落ちた量と、風で流された量くらいです
  • 粒は、カメラのまわりの、12 × 8 × 12 メートルの箱の中にあります。箱の外に出たら、反対側から入るように、余りの計算(mod)をします。カメラが動いても、箱はついてくるので、ずっと降り続けます
  • 横への流れは、風から受け取ります。風が止まれば、まっすぐ落ちます
  • 粒ごとに、揺れの位相と速さを変えて、ひらひらさせます
  • 箱の端と、カメラの近くでは、薄くします。粒が突然出たり消えたり、目の前に大きな粒が出たりするのを、防ぐためです
  • 人・ステージの後ろの粒は、深度テストで隠れます

数値の検証として、単体の WebGL で、「量が 0 のとき、粒が 0 個」「量に比例して増える」「遠くへ動かしたカメラにも、ついてくる」ことを確認しました。

実機では、「これはいいわ」と、見た目を確認できました。

(画像: 雪が降っている画面)

夜の空の下で、雪が降っているトップページ

時間帯(夜)

動かすもの

画面を暗くするだけなら、画面全体の明るさを下げれば済みます。ただ、それでは、空が青くなりません。そこで、次を動かすことにしました。

  • 画面全体の明るさ: レンダラーの toneMappingExposure
  • 環境光: シーンにあった ambientLight を、時間帯の倍率と色を掛ける部品に置き換えました
  • 空の色: カメラを中心にした球の殻を、背景の手前に置いて、色を掛け算します(画面の色 × 殻の色)。人・ステージの手前には出ません
  • 環境マップの強さ: scene.environmentIntensity

時刻は、JSON で決める

時刻は、JSON の time.override に "22:30" と書けば、その時刻に固定されます。空にする(または書かない)と、PC の時計に従います。「時間を消すと、今の時間」という使い方です。

時刻ごとの値は、キーフレームとして、JSON に並べます。

  • 時刻(at)、画面の明るさ(exposure)、環境光の強さと色、空の色、スポットライトの強さ、環境マップの強さ
  • キーの間は、なめらかに補間します。24 時間でつながるので、夜から翌朝へも、途切れません
  • 朝焼けと夕焼けは、そのときのキーの色で作ります(06:30 と 17:30)

補間のテストは、「24 時間を 1 分ずつ見て、急な変化がない」(1 分あたりの最大の変化は 0.012)、「時刻が壊れたキーは無視する」「キーが順不同でも動く」を確認しました。

実機では、時刻を 22:30 に固定して、夜の青い空になることを、確認しました。夕方に開いたら、夕方の薄暗さがそのまま出ました。時刻が実際の時間と連動しているので、リアルに夕方は薄暗い、という当たり前のことに、あとで気づきました。

(画像: 夜にしたトップページ)

時刻を 22:30 にした夜のトップページ。空は濃い青で、雪が降っている

スポットライト — ここが、まだ決まっていません

夜になると、アバターが暗くなります。そこで、アバターを照らすスポットライトを足しました。

  • アバターの子として置くので、アバターが動いても、光がついていきます
  • 強さは、「基準の強さ × 時間帯の倍率」です。夜は 1、夕方は 0.5、昼は 0 です
  • 灯りの数が変わると、全部の素材が作り直されて重くなるので、いつも置いておき、消すときは強さを 0 にします

ところが、見た目が、思うようになりませんでした。

床に、光が届かない

ステージの素材を、コンソールに出して調べました。結果は、MeshStandardMaterial が 3 個、MeshPhysicalMaterial が 12 個でした。光を受ける素材です。ところが、metalness(金属度)が 1 でした。

金属は、光が当たって明るくなる分(拡散光)を持たず、まわりの映り込みでしか見えません。そのため、スポットライトを当てても、床に光だまりが出ません。

そこで、光が床に当たる場所に、ぼかした光の円を重ねることにしました(疑似の光だまり)。光の位置から、照らす点を通る直線が、床と交わる場所を計算して、加算で重ねます。単体の WebGL で、円が描かれることは確認しましたが、実際の床との重なりは、まだ確かめていません。

アバターに、陰影が出ない

もう 1 つの問題は、アバターの陰影でした。左から光を当てると、右側の体が暗くなるはずです。実際は、全体が一様に明るく見えました。

最初は、「空の環境マップが、全方向から当たっているせいだ」と考えて、夜の環境マップの強さを下げました。これは、外れでした。 アバターの素材(MToon)が環境マップの光を受けているか、単体の WebGL で調べたところ、環境マップだけで照らした球は、上でも下でも同じ色でした。MToon は、環境マップの光を受けていませんでした(単体のテストでの結果です。実機のモデルでは、確かめていません)。環境マップを下げて、暗くなったのは、床の金属だけでした。

主な原因は、環境光(ambient)でした。環境光は、金属のステージには効きません。そこで、環境マップは元に戻し、夜の環境光だけを大きく下げました。

それでも、実機の見た目は「全然だめ」でした。暖色の光で、肌がオレンジに寄りすぎたり、暗い側が黒すぎたりします。数字は変えられますが、どう見えたら良いかを決めるのは、見ている人の美的感覚です。ここで、調整パネルを作りました。

調整パネル

コンソールに、1 行ずつ値を打って試す方法は、1 回の試行に、時間がかかります。数字を当てずっぽうに変えるのも、疲れます。そこで、値をスライダーで動かしながら、画面を見て決めるパネルを作りました。

  • 右クリックメニューの SYS に、TUNE_PANEL のボタンを足しました。localhost か、URL に ?tune を付けたときだけ表示します。公開の訪問者には、見せません
  • 時刻: スライダーで 0〜24 時を動かせます。REAL で今の時刻に戻ります
  • キーフレーム: 時刻を選ぶと、その時刻に切り替わり、明るさ・環境光・空の色などを動かせます。同じ値のキー(夜の 3 つ)は、まとめて動きます
  • スポットライト・風・雪: すべての値が、スライダーです。スポットの位置は、x / y / z で動かせます
  • COPY: セクションごとに、weather.json に貼れる形で、クリップボードにコピーします。貼って、再読み込みすれば、同じ見た目になります

パネルの値は、記憶しません。再読み込みで、weather.json の値に戻ります。気に入った値は、必ず COPY で残す、という作りです。

ヘッドレスのブラウザで、「開閉」「スライダーで値が変わる」「夜のキー 3 つが連動する」「COPY が、正しい JSON になる」を確認しました。実機では、「動いてる、これは便利」と確認できました。

(画像: 調整パネルを開いた画面)

左に調整パネル(TIME、SPOT、WIND、SNOW のスライダー)を開いた、トップページ

詰まったこと

  • 一定の風は、不自然でした。髪が傾いたまま止まりました。凪ぎと突風を入れました
  • 風向きのゆらぎが小さすぎました。±25° では、ずっと同じ向きに見えました
  • 「環境マップを下げれば、陰影が出る」は、外れでした。MToon は、環境マップを受けていません。確かめずに、先に直していました
  • 床が光を受けない理由は、推測で語る前に、調べるべきでした。調べると、金属でした
  • コンソールに貼る例を、まとめて貼られました。1 行ずつ選ぶ前提で書いたのに、poolEnabled = false まで一緒に実行されて、床の評価が、無効になりました。例の書き方の問題です
  • 光の調整は、当てずっぽうでは決まりません。数字を変えるのは簡単でも、見た目の良し悪しは、画面を見て決めるものでした

「ライティングは大事だね」と言って始めましたが、数字ではなく、自分の目で決める作業でした。ノイズの記事を書いていたころと、結局、何も変わっていません。数字を触って、見て、直す。この繰り返しを、早くするものが、調整パネルでした。

未確認のこと・制限

  • スポットライトの見た目は、まだ決まっていません。位置・強さ・色・光だまりの値を、調整パネルで決めて、次回に載せます
  • 光だまりと、実際の床の重なりは、確かめていません。床の高さが 0 でないときは、高さの値(poolY)を合わせる必要があります
  • VR / MR では、霧・雪・時間帯・スポットライトを、出さない作りにしています。VR でどう見せるかは、決めていません
  • 処理の重さは、測っていません。雪の 2,500 粒、スポットライト 1 灯(影なし)で、見た目に問題はありませんでしたが、数字では確かめていません
  • Chrome(Windows)でだけ、確認しました。スマホは、確認していません
  • 風の強さの初期値(髪の揺れの大きさ)は、実機の見た目で決めた値です。モデルによって、合うかどうかは確認していません
  • 霧は、調整パネルに、まだ入っていません

おわりに

風は、髪を揺らすだけでなく、霧と雪を、同じ風でつなぐことで、世界に 1 つの空気ができました。髪の動きと、霧の流れが合っているのを見たとき、風が「ある」ように感じました。

いちばん大きな収穫は、実は風でも雪でもなく、調整パネルでした。見た目の良し悪しは、数字ではなく、人の目で決めます。ならば、目で決める作業を、できるだけ早くする道具が要る。Inspector(中身を見る道具)を作り続けてきた理由と、同じだと思いました。

次回は、調整パネルで決めた値で、夜とスポットライトを仕上げます。その次は、霧をパネルに足して、全体の見た目を整えます。