
VRChat World 「Chrono Cabin」 ができるまで
はじめに
先日 VRChatワールド「Chrono Cabin」を公開しました。
とある時計職人の山小屋をテーマにし、雑談や写真撮影、Chill、睡眠などに使えるよう設計しています。
この記事では、ワールドが完成するまでのプロセスや、デザインに込めたこだわりについて振り返ってみたいと思います。

制作プロセス
コンセプトの決定
「Chrono Cabin」の制作は、まずコンセプトの決定から始めました。あえて今まで作ったことのない時代・地域設定にすることで、手癖で作らず新しい技術を取り込みやすい環境を自分に強いることも目的としていました。
まずはChatGPTとの無限壁打ちを行い、「時計職人の山小屋」というコンセプトに決定しました。その上で一番見せたいいわゆる「hero shot」を決め、それを軸にワールドの構成も考え始めました。

また、コンセプトを軸に詳細なストーリーテリングを作成し、実現に必要な要素をスプレッドシートへリストアップしました。

実はめちゃくちゃストーリーテリングに時間をかけ、多くの設定(地域・年代・時間帯・この場所に住んでいる人の年齢/職業/目的・何が起きたか・今どういう状態か・etc.)があります。ワールド内にフレーバーテキストをちりばめることも考えましたが、なんだか謎解きワールドチックになりそうだったのと、何も考えず雰囲気をそのまま感じて欲しかったのでやめました。最終的にこれらの内容は表に出ませんでしたが、ここでしっかり設定を練ったことでワールド全体の一貫性を担保できたと思います。
制作の流れ
基本的には以下のような流れで進めました。
レイアウトの決定
グレーでVR内でのサイズ感確認
Unity Asset Storeから雰囲気・ディテール感のマッチするPropを探す
見つからない場合は自前でモデリング・テクスチャリング
用意したPropをいい感じに配置
ライティング
ギミック追加
最後に最適化
過去のワールドで痛い目を見てきた経験から、ちゃんと最初はグレーの状態でワールドを作り、VRで中に入ることでサイズ感の確認から始めました。最適化は途中でちまちまやるときりがないと思い、完成後にボトルネックとなっている箇所をまとめて潰す方針にしました。
また、前回のワールドはフルスクラッチで作りましたが、今回はその時よりもオブジェクト数を多くする想定だったので、Unity Asset Storeの活用も念頭に置いて作り始めました。雰囲気とディテール感がマッチしたものを探し出すのは思いの外骨の折れる作業だったので、途中で諦めて結局自前でモデリングするケースが多かったです。どれが自前でどれがアセットかがほぼわからない程度には、バランスが取れたんではないでしょうか。
使用したツールと技術
制作には Unity, Blender はもちろん、Substance 3D Painter, Illustrator, Marvelous Designer, Midjourney などをツールとして使用しました。テクスチャの多くはSubstance 3D Assetsを用いています。Marvelous Designerは本来服などのモデリングに使われる印象ですが、ベッドや布のモデリングに活用しています。Midjourneyはprop作成時のアイディア出しでも活躍してくれました。
作品としてのワールド作り
私にとってワールド制作は1つのVR空間作品としての位置付けが強く、中に入って体験できるあくまで作品としての側面も大切にしました。そのため、VRChatのワールドであることを想起させるようなUIっぽい要素は、極力減らすことを意識しています。
ミラースイッチ問題
VRChatのミラースイッチはまさにUI感の象徴であり、特にリアルな雰囲気を大事にする今回のワールドでは異物感が強く出ました。その対策として、ワールド内にあるいくつかのオブジェクト自体をスイッチにしてみました。しかし、当然ながらこの方法ではミラースイッチの位置に気づけないという大きな欠点がありました。

そこで、UdonSharpを書いてミラースイッチの方向を向いたタイミングでミラーアイコンを点滅させるようにしました。


一定時間経過で自動的に点滅が終了するので必ず気付ける仕組みではないですが、1人が気づきスイッチの位置を共有することで解決する問題ではありました。そのためこういった控えめな誘導を落としどころとしました。
何度も訪れたくなるための基本機能
作品として作りたいのはワールド制作者のエゴでしかないので、何度も訪れたくなるような「VRChatといえば」な基本要素も盛り込みました。ただし、異物感のあるUIとならないような工夫をしています。






空間デザインと機能
明確な溜まり場
以前作ったワールドでは「どこに溜まって雑談すれば良いかがいまいち不明瞭」と言う反省点が個人的にはありました。今回のワールドではそれを明確にすることも意識したレイアウトにしました。

複数のグループが同時に訪れても各々の会話が混ざらないよう、空間的に分離されたロフトエリアも設けました。これによりワールド全体のワンポイントにもなって良かったかなと思います。

ビジュアルと視覚体験
目に優しいビジュアル
Chillを謳うワールドなので、長時間滞在しても目が疲れないよう自然な空気感を重視したビジュアルを目指しました。Post ProcessingでのColor Gradingは便利ですし容易に雰囲気を演出できますが、今回はあえて使用せず、現実に近い自然な色彩で空間をデザインしています。
霧の表現
Unity標準のFogでは、プレイヤーから離れた位置の霧は表現できても、小屋内部に充満する霧を表現することはできませんでした。そこで今回はSilentさんの「Volumetric Fake Lights」を使用することで実現しました。

仕組み上リアルタイムライトが必要になるのでバッチ数が増えるデメリットはありますが、これがないと理想としている空気感を表現できなかったので採用しました。窓の外に霧が広がっている表現も、実はこのシェーダーのおかげで実現できています。


浮遊オブジェクト
ストーリーテリングの一環として浮遊するオブジェクトを取り入れましたが、常時存在しているとファンタジー感が強すぎたため、おまけ機能として実装しました。机の上に置かれた光る石をインタラクトすることで、オブジェクトが表示される仕掛けになっています。

アニメーションを用いて浮遊感を表現しようとしましたが、その場合ベイクできず見た目が浮いてしました。そのため、ランダムな方向にランダムなタイミングで頂点を動かすシェーダーを作成することでベイク対象にしています。

最初はAmplify Shader Editorで作成しましたが、z3yさんが公開しているVRChat向けShader GraphでBakeryのMono SHが使用できることを後から知り、そちらに乗り換えました。

(余談ですが、公開後のpublicで偶然z3yさんご本人に会えたので、感謝の言葉を拙い英語で伝えたりしました。うれしい。)
ライティング
矛盾するライティング要件
ベイクにはBakeryを使っています。全体的に見た目の調整は色々していますが、特にこだわったのは「小屋の明るさを保ちつつ、外の風景を影絵のような画作りにする」ことでした。これが思いの外難しく、というのも現実世界では矛盾するような以下のライティング要件を満たす必要がありました。
森を影絵のように見せるため、Skylight(環境光)は暗めにする
外自体は明るい印象を残す
室内もある程度の光量を担保し、真っ暗にならないようにする

外の表現は以下のようにすることで実現できました。
Skylight(環境光)を低めに設定
Skyboxを明るめに
Fogを森に充満させ、擬似的に明るさを表現
しかし、このままでは小屋の内部は環境光が足りず暗くなってしまいました。そこで、これを解決するため境目となる部分に見えないBakery Light Mesh(Area Light)を置いています。

ベイクの前後を比較すると、足りていない環境光をある程度自然に足せていることが分かるかと思います。


ロフトについても窓に見えないLight Meshを配置してベイクしています。

MonoSH
ベイクにはBakeryのDirectional ModeであるMonoSHを用いています。Reflection Probeによる反射表現のみでは物足りない箇所が、見る角度に応じてよりリアルに表現できます。

MonoSHについては過去に記事を書いているので、詳細が気になる方はこちらもぜひ。
最適化
早く公開したい気持ちを抑えつつ、最後にワールドの最適化にも時間を費やしました。とてつもなくバッチ数が多かったので、Frame Debuggerと一生にらめっこしていたような気がします。
基本的には以下の方針で最適化を行っています。少し技術的な話になるので、興味のある方以外は飛ばしていただいて大丈夫です。
GPU Instancing
→ 同一メッシュ & 同一マテリアルで大量に置いてあるオブジェクト。今回であれば本やボックス、歯車など。
Static Batching
→ 動かさず、ユニークなオブジェクト。
GPU Instancing
基本的には公式マニュアルに書いてある情報の通り、GPU Instancingが有効になる条件を守るように設定しました。
lightmapについては以下の記述があったので、同一lightmapにするため「1K lightmap 2枚」だったものを「2K lightmap 1枚」にして全体で共通のlightmapを参照するようにしました。
・One lightmap
Note: An instance can use multiple atlas regions in the lightmap.
また、私の環境においてはReflection Probeも同一Blendである必要がありました。見たところ問題ないかと思いきや、内部的には浮動小数点の関係か微妙に異なる値となっており、これが別バッチになる原因となっていました。

Shaderにおいて unity_SpecCube0_BoxMin.w でBlend率が取れるようになっているようで、Frame Debuggerで見ると 0.9999998 のようにズレていました。これにより、別バッチ扱いになっていました。


このケースが大量に発生していたため、Mesh Renderer → Probes → Anchor Overrideへ明示的にReflection Probeを指定することで問題を解決しました。

また、風の表現をするためTerrainのTreeに対してWind Zoneを使用しているのですが、これにより _Wind という内部の値がばらばらになり、GPU Instancingできなくなる問題にも直面しました。実は現状、木の本数だけBatchesが増えており一番のボトルネックになっています。ただ、これについてはどうしようもなさそうなので、木が揺れる独自のShaderを作成してPrefabを手植えする方針で対応中です。だからみんな「Terrainは重い」と口を揃えて言っていたんだとワールド公開直前になって身をもって理解。つらい。
2024/08/25 追記
その後無事対応が完了しました。
🕰️Chrono Cabin v1.0.2🕰️
— れいんつ (@laynts) August 22, 2024
パフォーマンス改善しました 🏃💨
主に森が軽くなっているはず!https://t.co/CyxEg7zR02
↓ 細かい内容
[ 森の木 ]
・LOD改善(遠くのものが正しくローポリ or 非表示化)
・Terrainへの配置をやめて手植え + 自作シェーダーで元の揺れを再現(GPU Instancing… pic.twitter.com/mg8KCD7dIo
環境音
今まで作ってきたワールドでは、環境音を用意していませんでした。ワールドBGMだけでごまかしていましたが、やはりリアルに寄せたワールドを作る上で、環境音の存在は没入感に大きく影響してくると思い、今回新たにチャレンジしました。
レイアウト
屋内と屋外とで別々の雨音をBGMとして流しています。小屋の外に出るタイミングで、屋内用と屋外用の雨音をクロスフェードするようAudio Sourceを配置しました。

また、ストーブの薪が燃えるパチパチ音も、Audio Sourceを配置することで聞こえるようにしています。

環境音の作り方についてはkenomoさんの記事がとても参考になりました。
【VRChat】ワールド制作サウンドTips
【VRChat】BOOTH Cafeのサウンドデザイン
サムネイル
ワールドを訪れる最初の入り口となるサムネイルはとても悩みました。全体感がわかるような構図にすることも考えましたが、今回はやはり雰囲気とリアル感を同時に伝えたかったのでこのようにしました。

ワールド名は書くか迷いましたが、雰囲気とリアル感がメインである状態は維持しつつ、控えめに配置しました。
余談ですが、週一イベント「ワールド制作雑談会」にて途中経過を共有していたところ、一足先に移動されていたEstyOctoberさんが「良さそうなサムネイルが見えたので戻ってきちゃいました」と言って戻ってきてくれたのも後押しとなりました。うれしい。
まとめ
「Chrono Cabin」の公開後、想像の何倍も多くの方が訪れてくれていたり、X上にも写真をたくさん投稿してくださっていて、とても嬉しく思うとともに、それ以上に驚いています。ただただ自分が作りたいから作っているワールドではありますが、その一方やはり反応が多いことで次回以降のワールド制作のモチベーションにも繋がっています。パフォーマンス改善が一段落したら次のワールド制作も始めようと思うので、完成した時はまたぜひ遊びに来てくれると嬉しいです。途中経過などは気まぐれにpostしていると思うので、良ければX(@laynts)のフォローもお願いします。それでは。