見出し画像

サンリオVfes2023 beyond_a_bit 担当箇所の技術解説と感想

まだ暑かった8月下旬、EstyOctoberさんから「次回のサンリオVfesでのライブ演出作成の依頼が来ました。一緒にやりませんか?」というお誘いを頂きました。まずサンリオVfesがまたあるということに歓喜しつつ、それに関われるというのは願ってもないお話だったので、参加させて頂きました。
別件、Metamorphosisのお話を頂いたのは9月末だったのでリリースは前後していますが、これが私がVRChatを始めて最初のいわゆる企業案件でした。

ロールとしては Technical Artist / Shader Architectということで、シェーダーを用いた演出作成を主に行いました。
またHoudiniを使ってフロアを破壊(!)したり、適宜Editor拡張やUdon Sharpを用いたギミックを作成したりもしました。
今後もこういったお話を頂けると大変ありがたいので、私が何ができるのかわかるよう担当した部分について解説を残しておこうと思います。Vfesの記憶を残しておきたいので、あわせて感想も書いておきます。

なお2日間の開催でしたので、見逃してしまった方もいると思います。
次の土日、1月29日と30日(日本時間で日曜と月曜の午前中から昼にかけて)にもアーカイブ上演として同じ演出が見られるらしいので、そちらでぜひ見て頂きたいです。
以降の解説・感想にはB4フロア各演出の内容を含みますので、アーカイブ視聴予定の方はご注意ください。

※2023/3/23にbeyond a bit単体でワールドが公開され、いつでも見られるようになりました。下記ツイート内リンクからワールドを開くことができます。


音に連動したシェーダー演出

ライブ演出ということで、音楽と連動した演出は半ば必要不可欠です。
AudioLinkを使うという方法もありますが、厳密にある音が鳴っているタイミングで動作させるような仕組みではありません。
一般的なパーティクルライブでは手付けのアニメーションでタイミングを合わせると思いますが、そうではなく正確に連動したものをやりたいという話です。

そこでMIDIファイルを画像に変換し、そこから毎フレーム必要な値をシェーダー内で取得することでオーディオリアクティブな演出を行うシステムを作成しました。以下はAudioLinkとの比較です。
要は「鳴っている音を拾う」か「楽曲の譜面と合わせる」かの違いです。

AudioLink:
ワールドで鳴っている音をリアルタイムに解析し、音の高さを元に4段階に分類する仕組み。それぞれの高さの音量をそのまま使ったり、閾値を超える音量のときに光らせたりできる。リアルタイムに解析するため、使用するワールドや音源を問わず簡単に導入できる。

今回作成したシステム:
事前に楽曲のMIDIファイルを画像に変換し、マテリアル上で指定したトラックの音量、高さ、何番目の音かといった情報を取得する仕組み。任意の楽器が鳴るタイミングで正確に動作させたり、画像ファイルを加工することで演出を細かくコントロールすることが出来る。MIDIファイルが必要になるためどんなワールドでも使えるわけではないが、楽曲をチーム内で制作するケースであれば問題ない。

なかなか大変でしたが、その分解説し甲斐もある内容となりました。

①MIDI > PNGの変換(Unity C# エディタ拡張)

まず、Unityの標準機能ではMIDIファイルを読み込むことができません。
自作することも検討しましたが、Takahashi KeijiroさんがMidiAnimationTrackというライブラリを公開されていたのでこちらを利用させて頂きました。

これで読み込んだMIDIファイルの中身を試しにコンソールに出力すると、以下のような(何拍目でどの高さの音が何ボリュームで鳴り始めた/鳴り終わった)ことを示すデータが得られることがわかります。

各トラック(=各楽器)でこのようなデータが得られる

このデータを配列に格納し、画像ファイルとして出力するエディタ拡張を作成しました。
RGBのうちRに音の強さ、Gに音の高さ、Bにその音が何番目になった音かを格納するようにしました。
音ゲーの譜面のようなものをイメージしてもらえれば近いと思います。
またこのテクスチャを作成する際、トラック毎にカーブを設定して音の始まりから終わりに向かってボリュームをどう減衰させるかを決めています。

左下から順に並んでいて、1トラック終わると次のトラックのデータが並ぶ

なおこのようにテクスチャを色ではなく数値のデータとして扱うようなケースでは、以下のようにUnityのインポート設定を無圧縮にし補完も切る必要があります。

sRGB : Off
Generate Mip Maps : Off
Filter Mode : Point (no filter)
Compression : None

②画像の読み込みとSetGlobalFloat(Shader+Udon#)

作成したPNGファイルを専用シェーダーの各マテリアルに割り当て、毎フレームその色を読み込みます。
そのためには現在楽曲のどの位置が再生されているか知る必要があるので、その値を昨秋にリリースされたUdon Sharpの新機能「VRCGraphics.Set Global」でセットしています。要はシーン上のどのマテリアルからでも取得できる共通の変数です。
当初は画像の読み込みまでUdon側で行ったうえでシェーダーで使用したい値をそれぞれSetGlobalFloatしていたのですが、制作が進むにつれ使用するトラック数や演出パターンがどんどん増加しほぼマテリアル毎に異なる値を使うこととなったため、再生位置のみをSetGlobalIntegerするよう変更しました。

        public int TotalPixel;
        private Renderer _renderer;
        private MaterialPropertyBlock _MaterialPropertyBlock;

        void Start()
        {
            _renderer = GetComponent<MeshRenderer>();
            _MaterialPropertyBlock = new MaterialPropertyBlock();
        }

        void Update()
        {

            _renderer.GetPropertyBlock(_MaterialPropertyBlock);
            // Udonの変数にアニメーションが付けられないので
            // マテリアル側に楽曲の再生中に0~1で増加する値を設定、
            // 1トラックのpixel数と掛けてintで渡した。
            float musicPos = _MaterialPropertyBlock.GetFloat("MusicPosition");
            int _UdonAudioPixelTime = Mathf.FloorToInt(musicPos * TotalPixel);

            int id = VRCShader.PropertyToID("_UdonAudioPixelTime");
            VRCShader.SetGlobalInteger(id, _UdonAudioPixelTime);
        }

そうして設定した再生位置の値と、どのトラックのデータを利用するかというマテリアル上の設定値を利用して、以下のような関数で値を読み込みます。

float3 getMidiTexture(int trackNo, int pixelTime)
{
    float2 uv = float2(pixelTime % _MidiTex_TexelSize.z + 0.5, floor(pixelTime / _MidiTex_TexelSize.z) + trackNo * _MidiRowOfOneTrack + 0.5) / _MidiTex_TexelSize.z;
    float3 color = tex2Dlod(_MidiTex, float4(uv, 0, 0)).rgb;
    return color;
}

これを利用することで、音に正確にリンクしたタイミングでオブジェクトを光らせることができるようになります。

③シェーダー側の演出パターン


以下のパターンを用意しました。

(1)シンプルに光らせるもの

序盤の花や惑星等。
同時にフロアの色も戻している。

(2)音に合わせて距離ベースで波紋が広がっていくもの

序盤のフロアに沿って広がっていく波紋
(距離に応じて過去の値を読む)

(3)モデルのUV2の値と組み合わせて波紋が広がっていくもの

画像の巨大な花や生物、他にも多くの箇所で使用。
花はキック音、生物は電子音と別々の音に連動している。
生物が何かと交信をしているようにも見える。

(4)ノイズ音に合わせて画面の水平方向にメッシュをグリッチのようにズラすもの

視線方向とupベクトルの外積方向に頂点をズラすことで、どこから見てもグリッチになる。
QuestではポスプロやGrabPassが使えないので、メッシュを変形させて実現する。

(5)パーティクルのCVS(CustomVertexStreams)で割り当てたランダム値と音の順番が一致するときに光るもの

妖精が撒くパーティクルなど。
全てを同時に光らせず、1フレーズで1ループするように光るグループが変わっていく。

その他、草原やラストの花についても専用の演出を作成しました。

音に合わせて波が迫ってきたり、光る部位が増えたりする。

これまでの知識を活かして色々作れましたが、もう少し(4)のような頂点シェーダーを活かしたダイナミックな動きを多く入れられるとより良かったかなと思います。
また、シェーダーだけでなくタイムライン上の操作等も連動させられるようなエディタ拡張を作ることができれば、より演出の幅を広げつつ効率的に制作が進められそうです。

Questに対応したメッシュのパーティクル化(Shader + Houdini)

メッシュが崩れ、パーティクルになって消えていく。

メッシュがパーティクルになって消えていくというのはVirtualな演出として私は真っ先にイメージしますが、VRChatでは意外と少ない気がします。そんなことはなく特に今回のVfesではB4各演目やオープニングパレードでも使われており、以前のNakayoku Connectでもたくさん使われていました(1日目を見たあとに訂正)。
普通に実装しようとすると、各ポリゴンの重心座標を取ったり、複数ポリゴンで共有されている頂点を切り離した新しいメッシュを作るためにジオメトリシェーダーを使うことになります。
今回はそれに加えQuest単体版VRChatにも対応させる必要があるため、そのジオメトリシェーダーを使うことができないという制約があります。

頂点シェーダーのみでメッシュのパーティクル化を行えるようにするため、Houdini上でメッシュを加工して対応しました。

ポリゴン毎に同じ方向に移動させるためには、その重心座標(正確な重心である必要はなく、平均など原点として利用できる座標であれば十分)が必要です。これらは通常頂点シェーダーで利用できないため、Houdini上でUV3とUV4に座標を入れておくことで対応しました。
Houdini × Shaderでは、UV座標は単なるfloat2としてよく使います。UV4までで足りないときはテクスチャにベイクしています。

HoudiniとUnityで座標系が異なるためx軸を反転する。
Primitive毎に実行し、このあとAttributePromoteSOPでVertexに転送する。
複数ポリゴンで共有された頂点を切り離す必要もあるため、
事前にtriangulateとsplitpointを通しておく。

中心座標を元に移動後の座標が計算できるようになります。ノイズを取得するシード値も、移動前の中心座標を用いることで各ポリゴンで同一になります。

ラストの全てが箱に吸い込まれていく演出も、同様の仕組みでメッシュをパーティクル化させ、中心座標の制御方法を変えたものになります。

なおこういったモデルが移動する演出をシェーダーで行う場合、Bounding Boxを適切に広げなければカメラ方向によってオブジェクトが突然消えたりします。Skinned Meshではない場合はスクリプトから操作してBoundsを拡張する必要があり面倒なのですが、以前カーテンシェーダーを販売したときにエディタ拡張を作成し同梱していたため、それを利用して対応しました。
ただしカメラ外にあるときも描画負荷が発生するためfpsの低下に注意する必要があります。今回はラストシーンの負荷が結構危うく、制作終盤に地道な負荷軽減を行いました。

フロアの破壊(Houdini)

サンリオVfesのB4フロアを破壊してしまう演出を作成しました。お気に入り。

破壊自体はHoudiniのVoronoiFractureとDOPを利用すれば比較的容易にできるのですが、今回は花が咲く箇所に事前に穴を開けたり、Quest対応で負荷軽減を意識したり、破壊する前のメッシュで行われたライトベイクの内容を反映したりする必要がありました。

花が咲く箇所の位置データはEstyさんがCSVファイルとして書き出してくれたので、それを利用しています。

CSVを読み込み、対応する位置から棒が生えてくるようにしてシミュレーションを行った。


Voronoi Fractureを行う際、花周辺に多く点を配置することで破片の大きさに差を付けている。

負荷軽減は、破壊した建物をSkinned Meshとして1つの書き出すことで対応しました。
通常通りLabs RBD to FBXで書き出すと破片の1つ1つでSetPassCallが呼ばれ、負荷が高くなってしまいます。
破片に対応したボーンを作成し、ROP FBX Character Outputから書き出すことで負荷を軽減しています。VRChatなどリアルタイムの環境で細かい破片を動かしたい場合には特に有用な方法だと思います。

Skinned Meshにしてしまったことでライトベイクが行えなくなってしまったため、別途元のオブジェクトでベイクしたテクスチャをマテリアル上で読み込み、TillingとOffsetを合わせて反映しています。
(今回は数がそれほど多くなかったので手作業でTillingOffsetを合わせていますが、エディタ拡張で自動的に設定できるようにしたかったな…という反省があります)

今回、最終版のフロアデータ受領が11月になっている中で先行して破壊部分の演出を作る必要がありました。
仮モデルを読み込んで演出を作成しておき、最終版のデータ受領後にノードを差し替えるだけで良かったため、Houdiniの強みが出た部分でした。

その他の細かい演出

フロアの暗転(Shader)

フロアを暗転し、その後演出用の暗転していないオブジェクトを出すというのは意外と面倒です。暗転したいフロア側のオブジェクトにも、Emissionがあるものも多いためライトを消すだけでは対応できません。
今回は前述のSetGlobalで、フロア側のオブジェクト全てに同じ値を乗算して対応しました。

Quest版での色味調整(Shader)

今回はHDRカラーを用いて白飛びさせ、ポスプロのBloomでグラデーションを作るという絵作りが基本になっているわけですが、Questから見ると全て真っ白に見えます。これは #if defined(SHADER_API_MOBILE) を使ってQuest版専用の色を割り当てることで対応しました。もう少しいいアプローチがあるような気がするので今後も考えておきたいですが、反応を見る限りは概ね気にならない程度の差に収まったようです。
私はこれまでQuest対応の経験が少なかったため今回得られた知見は多かったです。特に色の階調が少ないために発生するカラーバンディングや、Bloomが使えない問題はなかなか難しく、今回直接的な対策は用意できていませんが、知っておくことで今後の対策を考えられると思います。(基本的には構成段階で回避する方向になってしまうと思います)

サンリオVfes2023 B4 Chill Parkの感想

一旦自分の制作以外の話になります。

最高でしたね。
近しい人たちが関わっていたということを抜きにしても。私はあのレベルのVR体験を、こんな立て続けに味わったことは無いはずです。
どの演出もハイレベルでしたが、TORIENAさんキヌさんの質がやはり突き刺さりました。

TORIENAさんチームの演出はこれまでちゅーたなさんのテストワールド等で見てきたものの集大成で、しかも単に寄せ集められたのではなくアーティストを引き立てる演出として昇華されたものだと感じました。制作メンバーが掛け合わされた結果だというのを感じます。純粋に手数が多く、作業量も相当だったのではないでしょうか。「デコイ」は曲自体も良すぎて今も聞きながらVfesの余韻を感じています。2曲の振り幅の広さも、アーティストがどちらも出来るということを示していて良かったです。

見覚えのある電流。
完全にマッチする形で取り込まれていました。

キヌさんチームは…言語化が難しいです。とにかく私は殴られています。制作期間の短さなどの話も別途伺ったのですが信じていません。今回のB4
があるのは1年前のキヌさんが基準を引き上げたからだと思っているので、最大限のリスペクトで構えていたつもりでしたがそれをメタ的に踏まえた内容で超えてきた形でした。演出の意味がよく考えられていて、音楽が良く、技術がそれらを支えている、と書き並べれば当たり前のことですがVRではなんでもできる・どこからでも見れる故に求められること・考えるべきことが余りに多く、それらを満たすことは本当に難しいです。メッセージ自体の力強さ、視聴者を引き込む圧力なんかはもう見ていた人には言うまでもないですね。本当に良かったです。受け取ったのでまた頑張っていきます。

全部良かったですが、ここから「拡世」への流れが特に。
メッセージ自体とビジュアル、どちらも良かったし、
「拡世」の2文字自体が刺さります。拡げていきたいです。

今回VRC Houdini勉強会からはbeyond_a_bitのEstyさん、wata23さん、私。
キヌさんチームに勉強会主催のtanittaさん
TORIENAさんチームにちゅーたなさん、三日坊主さんという形で、多数のHoudinistがB4フロアに参加しChill ParkはHoudini Parkと化していました。

そして示し合わせたかのようにフロアを破壊しています。自分の破壊に反省もありながら(花の形状を正しく反映したり、Houdiniのポテンシャルならより高度な破壊が出来たはず)、フロアに収まりきらないスケール感を表現したり、独自の世界観に移行する演出として必須になってきているように思いました。いきなり消しても良いんですが、どうしても没入感や説得力という観点で差が出ると思います。
私も「どう破壊するか」「それをどう活かすか」をもっとよく考えなければいけないですね。(全ての演出においてそうです)

破壊し切らず枠を使ったり、破片を戻したりするTORIENAさんチームの破壊が特に良かったです。

また破壊のみならず様々な演出がHoudiniからのアニメーション出力、またはシェーダーとHoudiniの組合せによって実現されていて、シェーダー単体で出来る演出の先に進むにはHoudiniというツールが半ば必須になってきているようにも感じました。リアルタイムのライブ演出として他にも多数のオブジェクトを表示しながら鑑賞に耐える負荷を実現するには、データのメッシュ側の仕込みやテクスチャへのベイクができると助かる場面が非常に多いです。

生物の飛行、歩行、揺れなどのアニメーションは全てHoudini製。
(Estyさんが作成)

ちょうど1年前に周りの方々の影響でVRC Houdini勉強会に行くようになったのですが、みんな慧眼だったな…と思います。同時期に始めた方も多く、おかげでこの非常に強力ながら入門しにくいツールと1年間付き合えています。そこそこの年齢になっても横並びの意識を持ちながら何かに取り組める機会があるのも何ならVRChatの良さです。何もかもVRChatのおかげです。
ちゅーたなさんと「シェーダーだけだと限界を感じていて、やっぱりメッシュを作れないと世界全体は作れないんじゃないか」みたいな話をしたことをよく覚えています。当時はレイマーチングを用いたシェーダーでVketのブースを作ったりしていて、私は負荷の面での限界を特に感じていました。シェーダー単体でやっていくには数学的な素養も必要だと思いますし。

私はまだまだ初学者で、本当はHoudiniやシェーダー全体がどうこうと語れるようなレベルには無いわけですが、今後もVR×シェーダー×Houdiniで出来ること・面白いことを模索しながら学んでいきたいです。
またVRC Houdini勉強会は隔週日曜21時から開催されていますので、興味のある方はぜひご一緒しましょう。聞くだけでもOKの形式ですので気軽に覗いてみてください。

制作の感想

初めての企業案件、初めてのチーム制作だったので、今思い返すと最初はけっこう緊張していました。一方でチームのメンバーはほとんどいつも一緒に遊んだりワールドを見せ合ったりしている仲だったので、ちょうどいい温度感で楽しく制作できました。

Unityを触るメンバーとは主にクレマテリア(2021年に運営されていたワールド制作者の交流会)で、それ以外のメンバーとはワールド探索部での縁でした。VRChatで自然に出会ったメンバーとこういった制作ができるのは幸運なことだと思います。どちらも自分のVRChat生活には欠くことのできない縁や交流の機会を与えてくれたコミュニティで、両コミュニティを運営していた(している)チワさんとタカオミさんには大変感謝しています。

beyond_a_bitのメンバーは終盤にjoinしたsabakichiさんを除いてライブ演出の作成経験が無かったのですが、どのメンバーからのフィードバックも大変ありがたいものでした。(経験のあるsabakichiさんからのフィードバックは流石の的確さでした)
制作上は私と直接の関わりが少ないはずのwakaさん、wata23さんからもたくさん意見や感想を貰えたのが印象に残っています。Discord上で素早くフィードバックが貰えることで、細かく進捗を共有したいと思わされ良いモチベーションになっていました。
制作終盤にはYuki Hataさんから多くのフィードバックをもらって細かい修正を重ねていきました。音楽に長く携わっているだけあって、私には見えないような部分が多く見えていたように思います。
そもそも、演出案が先にありきで楽曲を作成するなんてこと可能なんですねという驚きもありました。ブラッシュアップを通じてどんどん良くなる楽曲を聞くのも制作中の楽しみの一つでした。

Discordには個人チャンネルを設けるスタイルにしていて、そこで進捗共有を行った。
投稿先を迷わないし、つぶやきという体裁を取ることでハードルが下がり細かく投稿を行えた。
昔使わせてもらっていた作業部屋サーバーのスタイルを踏襲したもの。

個人として書いたコードや作った演出には反省が多いです。
もっと良い構成でシェーダーを書けたはずとか、もっとエディタ拡張に必要な機能があったはずとか、もっと合う演出があったかもとか。
TORIENAさんやキヌさんの演出を見たあとは余計に、自分はシェーダー周りに集中させてもらった割に成果が小さいなと感じました。技術面もまだまだですが、アイデアやデザイン的な部分が特に弱いと感じながらの制作で、他のメンバーにたくさん助けてもらいました。
まあ突然できるようになるわけもなく、普通に実力・経験不足なので今後地道に頑張っていくしか無いですね。
「自分なりの色」とか「世界観」を持ちたいです。この辺りは今年の課題です。

一方で、当初の計画内で作るべきものを期間内に一通り作り切れたことはまずまず良かったように思います。また、個人制作では手が届かないようなクオリティや規模に届くというのが純粋に楽しいと思いました。

なお全体としてうまくいったのはEstyさんの力が大きかったと思います。
(Estyさんはメンバー集めから字コンテ作成、タスク管理やGugenkaさんとのやり取り、メンバーの成果物確認、GitやTimeline周りのトラブルシューティングを行いつつ主要なオブジェクトのモデリングとアニメーション作成とエディタ拡張開発をやっていました)(本来のお仕事も忙しそうでした)(本当にありがとうございました)

今後もこういった機会があればお声がけ頂きたいと思っています。
お仕事を募集しています。どなたでもご連絡ください。
TwitterのDM等で大丈夫です)

別にそういうのは無いけどVRChatで遊びましょうというお誘いもお待ちしております。ワールド巡りとか。
シェーダーやHoudini、ワールド作成やアバター作成の話が好きです。

Team:beyond_a_bit 2022/12/19

この記事が気に入ったらサポートをしてみませんか?