こんにちは、クラスター社のスタジオチームのn_mattun(まっつん)です。
普段はclusterを用いたイベントの快適な体験環境とイベント進行環境を作るためであれば
人力/ハード/ソフトと技術領域は問わず、お助けになれるようなことは色々と何でもやってるマンです。
この記事はクラスター「Advent Calendar 2025」 17日目の記事で、昨日の記事「連載:ClusterMUSICJAMの照明演出はどうやっていたのか(基本設計編)」の続きにあたります。
ClusterMUSICJAMの照明演出の仕組みの実装のお話
まずは前回の記事の最後に紹介したデータ経路図を再掲です。
この中で実際にガッツリを手を加えた仕組みは大きく分けて2つあり、ひとつは「照明制御アプリから来る信号を色テーブルに変換する変換アプリ※」で、もうひとつは「照明シェーダー内でその色テーブルを解釈するための処理」です。それぞれ説明していきましょう。
※本編とはあまり関係ないですが、この色テーブルを生成してくれるアプリはなるべくアプリ自体の目的を明確に伝えたくて「ステージ演出できるくん」というそのまんまの名称を付けていました。
色テーブル生成アプリ側
開発環境
使いなれてるUnity C#環境で作りました。
ですが、やってることは任意の場所に色生成をするだけといえばそれだけなので、同じものはVJソフトの定番であるResolumeArenaやTouchDesignerなどでもおそらく作れると思います。
Artnet信号を扱う
信号の読み込み処理部分は先人の方々がUnityで使えるArtnetを読めるライブラリをいくつも公開しているのでそちらを使っています。
Artnetのデータ形式は512個のパラメータを1セットとして取り扱うようなフォーマットになっており、512個のパラメータには0~255まで整数の値が入ってきます。
OSC信号などでは/区切りでデータに文字列で変数名やグループ名に相当する名前を付けて管理することができますが、Artnetではそのような定義部分がないため、後述するパラメータの役割数に応じて自分なりにindex要素を分割管理することが求められます。
各パラメータに役割を振る
というわけで、512個のパラメータひとつひとつに対して「それぞれどの照明のどんな演出を担う変数なのか」の役割付けをしていってみます。
超ざっくりとした意味分けだと「照明の色」「照明の明るさ」「照明の向いている方向」みたいな感じかと思いますが、この部分は実装上もっと細かく分けられています。パラメータが配列のindexで管理されてると仮定して、データ構造をイメージしてみましょう。
[0] 1コ目の照明の色(R成分)
[1] 1コ目の照明の色(G成分)
[2] 1コ目の照明の色(B成分)
[3] 1コ目の照明の明るさ(舞台照明用語ではDimmerやIntensityと呼ばれています)
[4] 1コ目の照明のPan方向の角度(約1.4度単位)
[5] 1コ目の照明のTilt方向の角度(約1.4度単位)
まずはこんな感じの分解イメージです。
色成分は0,1,2でそれぞれRGBに分解、3で明るさ、4と5で照明の首部分の向いている方向(左右首振りと上下の首振り)を調整している感じです。ですが、これではまだ制御情報が足りないので追加していきましょう。
[6] 1コ目の照明の色(W成分)
[7] 1コ目の照明が目的のPan方向に到達するまでの首振りの移動速度
[8] 1コ目の照明が目的のTilt方向に到達するまでの首振りの移動速度
[9] 1コ目の照明のPan方向の超細かい角度(約0.0055度単位)
[10] 1コ目の照明のTilt方向の超細かい角度(約0.0055度単位)
W成分はRGBで表現された色に対して、さらに白色を混ぜるためのパラメータです。
7,8の速度は首振りの速度です。バラードのようなゆっくりとしたテンポの曲では首振りの移動速度をゆるめたり、逆にデスメタルのようなめっちゃ激しい曲では首振りをめっちゃ早くしたりするので、速度を柔軟にいじれる状態は欲しいですね。
9,10の「超細かい角度」というのは、4,5で指定した角度情報では約1.4度単位※でしか角度調整ができないために、その間を補完するための粒度の細かい角度情報です。この細かい角度情報がないと、照明を動かした際の停止位置がデジタル時計の秒針のごとく、ざっくりとした場所でしか止まれなくなってしまいます。照明の光線は長くなればなるほど照らした先での誤差が出てしまうので、この超細かい角度指定は大事だったりします。
※そもそも何で1.4度単位やねんっていう話なんですが、Artnetの単一のパラメータは0~255の範囲の整数値しか表現できないので、これでは小数点以下はおろか360度を1度単位で表現するにも足りないためです。
MUJICJAMで実装されている実際の役割分けはもう少し多いですが、本物っぽい照明演出としての最低限必要なものとしてはこれくらいです。そしてそれぞれの要素に「1コ目の~」と記載している通り、ステージ上には何十個も照明が配置されてるので、次のindex要素からは2コ目の照明の役割を割り当てていきます。
[11] 2コ目の照明の色(R成分)
[12] 2コ目の照明の色(G成分)
:
:
[21] 2コ目の照明のTilt方向の超細かい角度(約0.0055度単位)
こんな感じですね。
これを延々と繰り返して、舞台上に置きたい照明の数と、その照明の種類に応じた制御要素を512個のパラメータに対して割り当てを行っていきます。
ちなみにこの一連のパラメータに役割を振る作業は、実物の舞台照明の取り扱う際にもまったく同じような割り当て作業を行います。気になる人はAmazonでDMX対応している照明を実際に調べたり購入したりして説明書を見てみよう!
各役割をもったパラメータを色として出力する
各役割を持ったパラメータをアプリ内の任意の座標に色として出力します(画像は例)
この四角形の色テーブルの中のタイルがそれぞれ「n個目の照明の色(R成分)」だったり
「xコ目の照明のPan方向の超細かい角度(約0.0055度単位)」だったりするわけで、これを制御要素の数ぶん(1セットの場合は最大512個)ズラっと並ぶイメージになるわけですね。
※実際のタイルの数は送出パラメータの数に対して1:1ではなくデータ種類によって増減があります。そのへんは次回の「検証・運用編」でもっと詳しく説明していきます。
シェーダー側
次は照明に使用しているシェーダー側の実装についてです。
今回の実装ではシェーダーはゼロから作ったものではなく、ある程度パラメータがいじれる社内既存アセットの照明シェーダーがあったため、それに対して画面共有経由の色データを読み取れるような改修を入れています。
テクスチャ内の任意の座標の色を読み取る
シェーダーコードには「参照対象テクスチャの任意の座標の色を取得する」という命令があるのでそれを使って色を読み取ります。照明の配置場所や参照したいパラメータによって指定するべき座標が変わるので、面倒ですがパラメータに対して1コ1コ座標を明確に指定できる状態を作っておくとよいでしょう。
また、指定座標内で表現されている色はシェーダー内で「R要素」「G要素」「B要素」で3つに分けで扱うことができるので、単一の色タイルから3つのパラメータを取得することができます。
以下の画像が当該シェーダーのコードの一部のスクショと実際のインスペクタ画面です。
コードの赤枠部分が「参照対象テクスチャの任意の座標の色を取得する」の処理ですね。
インスペクタ上では照明の演出役割ごとにそれぞれ
「ClipPoint(色を取得する座標)」
「RectSize(色タイルの大きさ)」
「RectIndex(色テーブルの行番号と列番号と、RGBのうちどの色要素を参照するか)」
を指定することで、テクスチャ上の狙った座標を指定できるようにしています。
シェーダーはTransformで回せない
照明の首振り制御にあたるパラメータも画面共有経由で色テーブルで渡されてきているのですが、このパラメータを受けとって処理しているのはClusterScriptではなくシェーダーコード(HLSL)になるので、このままではTransformを使って照明を回すような処理ができません。
なので、ClusterScriptからC#のGetMaterial相当の命令を使って回転値を取得して、その値を元にTransformで回していきましょう・・・みたいな説明をしたい所ではあるのですが、そんな命令は残念ながらありません(2025年12月現在)
ですが、シェーダーにはTransformで定義されている座標から異なる位置や角度で描画をするような処理が用意されていたりするので、今回はシェーダー内で回転を表現するような実装を無理やり加えています。HLSLは筆者は専門外のため、当時のChatGTPにガッツリ助けてもらいました。
(なお、無理やり回転処理を実装した結果、元々の照明シェーダーが持っていた効果的な表現の一部が期待通りの描画にならなかったりしたため、やむなくその部分は表現自体をオミットしています)
もちろん胴体も回せない
回転処理ができないのは照明の光源に加えて照明の胴体も同様です。そのため胴体側のシェーダーにも同じ回転処理を加えていて、回転情報を示す色タイルの座標を参照するようにしています。
そして検証へ・・
そんなこんなでデータ経路に沿って照明がいい感じに動くはずの状態になりました。
ですが実際に試していくと「あれっ、何でそうなるんだ..?」みたいな部分が当然ながら色々とあったりなかったりするわけですので、そういう話を明日の「検証・運用編」にてさらに詳しく書いていきます。
次回もお楽しみに!



