見出し画像

簡単なシェーダーをかいてclusterで試してみよう

clusterにおいて唯一プログラム的なものが動かせるのがシェーダー(Sheader)になります。
すでにいろいろなSheaderが出ているので、自ら作ることはないと思いますが、そこまで知識なくとも作れるSheaderはあるので、
もし興味ある方のきっかけになればと思って書いてみようと思います。
※例は、Windows環境での作成方法になります。
 (macの方ごめんなさい。)

これはCluster Creator Advent Calendar 2021(https://adventar.org/calendars/6317)12月11日の記事です。

セットアップ

UnityHub、Unity2019.4.22(2021/12/11現在)、Cluster Creator Kit v1.14.1(ClusterCreatorKitSample)をインストールします。
下記を参照してインストールしてください。(まるなげ)

ただし、「Unity 2019.4.22」をインストールするとき、
Microsoft VisualStudio 2019の何らかのバージョンをインストールしていない場合は、「Microsoft VisualStudio Community 2019」にチェックを必ず入れてインストールするようにしてください。
※シェーダーを作るとき必須になります。
また、「Microsoft VisualStudio Community 2019」をインストールした場合、インストールが終わった後、PCを再起動しておくといいです。
(そうしないとUnityの連携がうまく動作しなかったです。(一敗))

実際シェーダーを書いてみる

Unityを開いて、ClusterCreatorKitSampleにある「Assets/ClusterVR/Scenes/MinimalSample.unity」を使って進めていきたいと思います。
シェーダーのベースを作る必要があるので、今回は、Unlintシェーダーをベースとして、作っていきたいと思います。
またそのシェーダーを使うマテリアル、表示するためのオブジェクトを作成します。

[シェーダのベースを作成]

MinimalSampleを開いたら、下にある「Projects」内のツリーの「Assets」を選択します。
そうすると右に、「Assets」の内容が表示されるので、その領域までマウスを持っていきマウスを右クリックします。
メニューが出てくるので、

「Create」-「Shader」-「Unlint Shader」を選択します。

そうすると、「NewUnlintShader」というファイルができあがるので、名前を任意の名前に変えます。
今回は「ExUnlintShader」とします。
ファイルとしても「ExUnlintShader.shader」として保存されます。

[マテリアルの作成]

次に、シェーダーを使うマテリアルを作成していきます。
同じように、何もないところに右クリックしてメニューを出し、
「Create」-「Material」を選択すると「New Material」というのができるので、「ExUnlintShaderMat」という名前に書き換えます。

作成できたら、
一度「ExUnlintShaderMat」を選択して「Inspector」にその内容が表示されたら下にある球体部分が出てくると思います。
そのような状態になったら、今度は「ExUnlintShader」をその球体に向けて
ドラッグします。

そうすると、球体が真っ白になり、「Sheadr」と書かれた部分に「ExUnlintShader」という表記に代わると思います。

[オブジェクトの作成]

オブジェクトも作成していきます。
こんどは、「Hierarchy」に目を向けていきます。
「MinimalSample」と書かれた部分選択して、右クリックをします。
そうするとメニューが出てくるので、
「GameObject」-「3D Object」-「Quad」
を選択します。
そうすると、基準原点にQuad Objectができると思います。

ただ、一見最初裏面が見えるため、何も表示されていないように見えます。

Quadを選択すれば、ちゃんと追加されていることがわかります。

これだとわかりずらいので
PositionをYだけ+0.5
表が見えるように、RotationのYを-90など環境に合わせて、
入力しておきます。

この状態にしたら、先ほど作成した「ExUnlintShaderMat」をこのQuadオブジェクトに反映させます。(Quadオブジェクトを選択した状態でExUnlintMatをドラッグしたら反映できます。裏面のままだとうまく反映できません。)


シェーダをいじっていこう!

ここまで出来たら、「ExUnlintShader」をダブルクリックします。
そうすると、VisualStudio 2019が立ち上がりこのファイルの中身が表示されると思います。
※下記のようなメニューが出てきたら
 「Microsoft VisualStudio Version Selector」を選択して「OK」を押すと
 立ち上がります。


これが、実際のシェーダーの中身です。
とはいえこれをすべて理解するとちょっと大変なので、今回はあえて省きます。
まずは、
「こんな風に変えればできる。。。」
ということに重点を置いて書いていきます。

1.カリングを設定できるようにする

まずは、カリング(表・裏)の描画を切り替えられるようにしたいと思います。
書き換えるところは2つ。。。
・「[KeywordEnum(None, Front, Back] _Cull("Culling", Int) = 0 」を追加
・「Cull [_Cull]」を追加

では、順に追加していきたいと思います。

-「[KeywordEnum(None, Front, Back] _Cull("Culling", Int) = 0 」-

Propertiesの部分を書き換えます。

Properties
{
    _MainTex ("Texture", 2D) = "white" {}
}

となっているのを

Properties
{
    _MainTex ("Texture", 2D) = "white" {}
    [KeywordEnum(None, Front, Back)] _Cull("Culling", Int) = 0 // 追加
}

と、
「[KeywordEnum(None, Front, Back] _Cull("Culling", Int) = 0 」を
追加します。

-「Cull [_Cull]」-

SubSheaderというところにある、「LOD 100」と書かれているあたりの下に追加するといいです。

    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {

という部分を

SubShader
{
    Tags { "RenderType"="Opaque" }
    LOD 100
    Cull [_Cull] // 追加

のようにします。
変更が終わったら、保存します。
保存が終わったら、「Unity」の画面に戻るって、QuadObjectを選択した状態にしてみます。
そうすると、「Inspector」の下にあるマテリアルの設定に「Culling」という項目ができています。

この「None」と書かれているところは選択式になっていて、下記の項目が選べます。

この項目は、
「[KeywordEnum(None, Front, Back)] _Cull("Culling", Int) = 0 // 追加」
を追加したことで表示されています。
ちなみに、
--
None:両面カリングしない(両面描画)(=0)
Front:表面をカリングする(表面を表示しない)(=1)
Back:裏面をカリングする(裏面を描画しない)(=2)
--
というのを表します。
実際切り替えると変わります。
実は、上記の分は、「選択項目(None、Front、Back)を表示してデフォルト(None(=0))を選択するという意味です。
※その設定値を「_Cull」という器に入れるという意味も含めています。

上記の文章は設定値を器に入れるだけでした。
それを、実際のシェーダーとしてマテリアルに反映する部分が必要です。
それが、「Cull [_Cull]」という部分になります。

これは、すごいざっくりいうと、、、
Cullというコマンドをつかって「_Cull」の値をカリング値として反映しろ
という意味になります。
もっと詳しく知りたい場合は、
下記の「Cull」の項目を見るといいと思います。

以上で、カリングの設定ができるシェーダーが完成しました!
Noneにすると、さっきまで見えていなかった裏面もちゃんと描画できていると思います。
では実際その設定どおり、clusterのワールドとして反映できているか確認してみましょう。
ShaderSampleとか名前を付けてアップしてみると、設定どおり、描画されていると思います。

ただ、
このまま真っ白のままでは味気ないので、
下記の画像を反映させたいと思います。

反映した結果が下記のとおりです。

テクスチャは反映してみると、両面は描画されていますが、想定通りの形になりません。
これは、透明部分があるテクスチャのためこのように見えます。
次は、この画像がちゃんと見えるようになるようにしてみましょう。
(カットオフ)

2.カットオフをできるようにする

透明部分を表示しないようにするカットオフという機能を追加します。
また、VisualStudioに戻って、下記の部分を変更します。

・「_CutoffParam("Alpha cutoff", Range(0,1)) = 0.0」を追加
・「fixed _CutoffParam;」を追加
・「clip(col.a - _CutoffParam);」を追加

-「_CutoutParam("Alpha cutoff", Range(0,1)) = 0.0」-

Propertiesを下記のように書き換えます。

Properties
{
    _MainTex ("Texture", 2D) = "white" {}
    [KeywordEnum(None, Front, Back)] _Cull("Culling", Int) = 0
    _CutoffParam("Alpha cutoff", Range(0,1)) = 0.0 // 追加
}

「fixed _CutoffParam;」

「SubShader」の中に、「float4 _MainTex_ST;」と書かれいている部分があるので、その下に「fixed _CutoffParam;」を追加します。

「clip(col.a - _CutoutParam);」

「SubShader」の中に、「fixed4 col = tex2D(_MainTex, i.uv);」と書かれいている部分があるので、その下に「clip(col.a - _CutoutParam);」を追加します。

シェーダー全体を書くと下記の通りになります。

Shader "Unlit/ExtUnlintShader"
{
    Properties
    {
        _MainTex("Texture", 2D) = "white" {}
        [KeywordEnum(None, Front, Back)] _Cull("Culling", Int) = 0
        _CutoffParam("Alpha cutoff", Range(0,1)) = 0.0 // 追加
    }
    SubShader
    {
        Tags {"RenderType" = "Opaque"}
        LOD 100
        Cull[_Cull]

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed _CutoffParam; // 追加

            v2f vert(appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);

                clip(col.a - _CutoffParam); // 追加(Cutoff)

                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
}

そうすると、QuadオブジェクトのInsupectorのシェーダー部分にスライドバーが追加され、0.0~1.0の値が設定できるようになります。

このスライドバーは、先ほどPropertiesに追加した
「_CutoffParam("Alpha cutoff", Range(0,1)) = 0.0」
を記載したことで、作らています。
Propertiesには、このUI拡張機能を使うことで設定をUIからいじれるようにするものです。
0のままだと見た目が変わらないので、これをスライドバーを動かしてみてください。
そうすると、テクスチャの表示が同じになると思います。

Cutoffなし
Cutoffあり

今回は、「0かそれ以上か」で動作が変わりましたが、
カットオフは、画像が持っている透明度を表す値の数値がそれ以上なら表示、それ以下なら表示しないとする機能になるので、グラデーションなどあるようなものはその設定値以下でいきなり表示が消えるという形になります。
グラデーションを表現する場合は、半透明の対応をする必要が出てきます。
ただ、それを説明するとそれだけでおなか一杯になるので、ここでは触れません。(気が向いたとき書くかもしれませんが。。。)

では、実際カットオフの値を設定してワールドに上げてみましょう。
そうすると、ちゃんと透明部分が非表示になったテクスチャが見えると思います。

このカットオフを行っているのは、
「clip(col.a - _CutoutParam);」の部分になります。
元のテクスチャの透過値(col.a)からスライダーでしていた値が格納されている「_CutoutParam」の値を引いたとき、0以下の場合は表示しないという、clipというシェーダーで定義されたの機能を使って実現しています。

ちなみにコライダーは四角のままのため、実際表示は抜けていても、透明部分のコライダーは残ったままですので注意してください。

3.UVスクロールをできるようにする。(おまけで回転)

UVスクロールはちょっと難しくなります。
追加するコードも増えます。
ただ追加する内容は、いたってシンプルです。

・UV(横方向、縦方向)に対して変化させる値を設定できるようにする。
 (移動量・スピード)
・回転する回転量を設定できるようにする

では、進めていきます。

Propertiesを下記のように書き換えます。

Properties
{
    _MainTex("Texture", 2D) = "white" {}
    [KeywordEnum(None, Front, Back)] _Cull("Culling", Int) = 0
    _CutoffParam("Alpha cutoff", Range(0,1)) = 0.0

    // 追加
    [Space(10)]
    [Header(UV Scroll)]
    [Space(3)]
    //X方向のシフトとスピードに関するパラメータを追加
    _XShift("Xuv Shift", Range(-1.0, 1.0)) = 0
    _XSpeed("X Scroll Speed", Range(1.0, 100.0)) = 0.0

    //Y方向のシフトとスピードに関するパラメータを追加
    _YShift("Yuv Shift", Range(-1.0, 1.0)) = 0
    _YSpeed("Y Scroll Speed", Range(1.0, 100.0)) = 0.0

    _RotateSpeed("Rotate Speed", float) = 0.0
    // ここまで
}

また、この設定を保持するための器も作成します。
「fixed _CutoffParam;」の下のあたりに追加します。

            //追加
            float _XShift;
            float _YShift;
            float _XSpeed;
            float _YSpeed;
            float _RotateSpeed;
            // ここまで

こうすることで、設定の個所が下記のように変わります。

これで、縦横の移動量を設定できるようになりました。
では、この値を実際反映できるように書き換えていきます。
VisualStudioの画面に戻り、先ほどの状態から、
「追加」~「ここまで」の部分を追加します。

        #define PI 3.141592 // 追加

        fixed4 frag(v2f i) : SV_Target
        {
            //追加
            // UV Scroll
            //Speed
            _XShift = _XShift * _XSpeed;
            _YShift = _YShift * _YSpeed;
            // Timeを入力として現在の回転角度を作る
            half angle = frac(_Time.x) * PI * 2;
            // 回転行列を作る
            half angleCos = cos(angle * _RotateSpeed);
            half angleSin = sin(angle * _RotateSpeed);
            half2x2 rotateMatrix = half2x2(angleCos, -angleSin, angleSin, angleCos);

            //add Shift
            i.uv.x = i.uv.x + _XShift * _Time;
            i.uv.y = i.uv.y + _YShift * _Time;

            // 中心を起点にUVを回転させる
            i.uv = mul(i.uv - 0.5, rotateMatrix) + 0.5;
            // ここまで

            // 以下 カットオフから変更なし
            // sample the texture
            fixed4 col = tex2D(_MainTex, i.uv);

            clip(col.a - _CutoffParam);

            // apply fog
            UNITY_APPLY_FOG(i.fogCoord, col);
            return col;
        }

-縦横移動についてー
[_XShift、_YShift]
実際の移動量(スピードも加味した結果の移動量)になります。

上記の計算した移動量を
下記で、テクスチャ座標に変換して反映しています。
※TimeというUnityが管理する時間情報を使って移動量を制御しています。
i.uv.x = i.uv.x + _XShift * _Time;
i.uv.y = i.uv.y + _YShift * _Time;

-回転について-
[rotateMatrix]
パラメータから求めた結果の回転量になります。

この回転量をもとに、下記の計算を使ってテクスチャ座標に反映しています。
i.uv = mul(i.uv - 0.5, rotateMatrix) + 0.5;
※UVは左上が(0, 0)右下が(1,1)のため、
 回転軸を中央(0.5, 0.5)にしてから回転させるためにこうなっています。
 mulはベクトル積を求めるシェーダーが用意しているものです。

[補足]四則演算
「+」:足し算、「-」:引き算、「*」:掛け算、「/」:割り算

これでUVスクロールもできるようになりました。
0の数値だと動かないので、値を変更していろいろ実際試してみましょう。

[一応動くよというサンプル動画]

横方向
Shift:-1/Speed:5
縦方向
Shift:1/Speed:5

回転スピード:1のみ


今回作成したシェーダーの全体です。

Shader "Unlit/ExtUnlintShader"
{
    Properties
    {
        _MainTex("Texture", 2D) = "white" {}
        [KeywordEnum(None, Front, Back)] _Cull("Culling", Int) = 0
        _CutoffParam("Alpha cutoff", Range(0,1)) = 0.0

        // 追加
        [Space(10)]
        [Header(UV Scroll)]
        [Space(3)]
        //X方向のシフトとスピードに関するパラメータを追加
        _XShift("Xuv Shift", Range(-1.0, 1.0)) = 0
        _XSpeed("X Scroll Speed", Range(1.0, 100.0)) = 0.0

        //Y方向のシフトとスピードに関するパラメータを追加
        _YShift("Yuv Shift", Range(-1.0, 1.0)) = 0
        _YSpeed("Y Scroll Speed", Range(1.0, 100.0)) = 0.0

        _RotateSpeed("Rotate Speed", float) = 1.0
        // ここまで
    }
    SubShader
    {
        Tags {"RenderType" = "Opaque"}
        LOD 100
        Cull[_Cull]

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed _CutoffParam;

            //追加
            float _XShift;
            float _YShift;
            float _XSpeed;
            float _YSpeed;
            float _RotateSpeed;
            // ここまで

            v2f vert(appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }

            #define PI 3.141592 // 追加
            fixed4 frag(v2f i) : SV_Target
            {
                //追加
                // UV Scroll
                //Speed
                _XShift = _XShift * _XSpeed;
                _YShift = _YShift * _YSpeed;
                // Timeを入力として現在の回転角度を作る
                half angle = frac(_Time.x) * PI * 2;
                // 回転行列を作る
                half angleCos = cos(angle * _RotateSpeed);
                half angleSin = sin(angle * _RotateSpeed);
                half2x2 rotateMatrix = half2x2(angleCos, -angleSin, angleSin, angleCos);

                //add Shift
                i.uv.x = i.uv.x + _XShift * _Time;
                i.uv.y = i.uv.y + _YShift * _Time;

                // 中心を起点にUVを回転させる
                i.uv = mul(i.uv - 0.5, rotateMatrix) + 0.5;


                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);

                clip(col.a - _CutoffParam);

                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
}


おわりに

今回は以上で、おわります。
clusterでは、シェーダーを使えると色々と表現ができるようになります。
自分でこれをきっかけに何か挑戦してもらえるとか思ってくれたら幸いです

まぁ、ここまで書いていてアレですが、UnityのAssetStorにある「Amplify Shader Editor」とか使うとNodeベースで作れたりして、有償ではありますが、その価値があるものでもっと簡単に試せたりします(^^;

また、ツヨツヨなかたは、自分で作ったりしてるので、ホントすごいなぁと思います。。。
(自分ももっとかけるようになりたいとおもいます。)

明日12日は、
ほびわんさんの

「丈夫なスタッフコライダー、会場設計について(ぽえ...」

とのことです。
昨今スタッフコライダーはイベントではかなり重要になっているので、とても参考になるのではないかと思います!

自分もチェックしなきゃ!!

でわでわ!!!