🌈

そろそろShaderをやるパート43 セルラーノイズでトゥーン調の波を作ってみる

2021/10/03に公開

そろそろShaderをやります

そろそろShaderをやります。そろそろShaderをやりたいからです。
パート100までダラダラ頑張ります。10年かかってもいいのでやります。
100記事分くらい学べば私レベルの初心者でもまあまあ理解できるかなと思っています。

という感じでやってます。

※初心者がメモレベルで記録するので
 技術記事としてはお力になれないかもしれません。

下準備

下記参考
そろそろShaderをやるパート1 Unite 2017の動画を見る(基礎知識~フラグメントシェーダーで色を変える)

デモ

良い感じに波の白い感じが出ました。

絵面として参考にしたのは下記動画内の水面Shaderです。
【参考リンク】:エフェクト作成のために知っておきたいShader Graphの話 - Unity道場2019 8月

Shaderサンプル

Shader "Custom/CellularNoise"
{
   Properties
    {
        _SquareNum ("SquareNum", int) = 5
        [HDR]_WaterColor("WaterColor", Color) = (0.09, 0.89, 1, 1)
        _WaveSpeed("WaveSpeed", Range(1,10)) = 1
        _FoamPower("FoamPower", Range(0,1)) = 0.6
        _FoamColor("FoamColor", Color) = (1, 1, 1, 1)
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue" = "Transparent" }
        Blend SrcAlpha OneMinusSrcAlpha

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;          
            };

            float2 random2(float2 st)
            {
                st = float2(dot(st, float2(127.1, 311.7)),
                            dot(st, float2(269.5, 183.3)));
                return -1.0 + 2.0 * frac(sin(st) * 43758.5453123);
            }

            uniform sampler2D _CameraDepthTexture;
            int _SquareNum;
            fixed4 _WaterColor;
            fixed4 _FoamColor; 
            float _WaveSpeed;
            float _FoamPower;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float2 st = i.uv;
                st *= _SquareNum; //格子状のマス目作成 UVにかけた数分だけ同じUVが繰り返し展開される

                float2 ist = floor(st); //各マス目の起点
                float2 fst = frac(st); //各マス目の起点からの描画したい位置

                float4 waveColor = 0;
                float m_dist = 100;

                //自身含む周囲のマスを探索
                for (int y = -1; y <= 1; y++)
                {
                    for (int x = -1; x <= 1; x++)
                    {
                        //周辺1×1のエリア
                        float2 neighbor = float2(x, y);

                        //点のxy座標
                        float2 p =  0.5 + 0.5 * sin(random2(ist+neighbor) +_Time.x *_WaveSpeed);

                        //点と処理対象のピクセルとの距離ベクトル
                        float2 diff = neighbor + p - fst;

                        m_dist = min(m_dist, length(diff));

                        waveColor =  lerp(_WaterColor,_FoamColor,smoothstep(1-_FoamPower,1,m_dist));
                    }
                }
                
                return waveColor;
            }
            ENDCG
        }
    }
}

セルラーノイズ

あるピクセルから、ランダムに与えられた複数の点の中から一番近い点までの距離を出力するノイズです。
【引用元】:シェーダでノイズ3(セルノイズ)

とのことです。

セルラーノイズにもいろいろと実装手法があるようですが、
今回は空間をタイルに分割する手法を用いました。
空間をタイル分割する手法を用いて、過去記事ではSkyboxに星をちりばめて遊んでいました。

【参考リンク】:そろそろShaderをやるパート25 Skyboxで星空をちりばめる

セルラーノイズのロジックの概要は下記引用の説明の通りです。

それぞれのピクセルについて、そのピクセルを含むタイルとその周囲の8つのタイルの中にある点までの距離を計算し、最も近い点までの距離を記録します。その結果、ディスタンスフィールドが得られます。
【引用元】:セルラーノイズ

今回は、上記説明の中のディスタンスフィールド(直訳すると"距離の領域")を色の塗分けに利用して、波の白い部分を描画しています。

この手法のメリットとして、計算精度が向上することが挙げられます。
各マス目の原点を計算に利用することで、ワールド空間のような大きな範囲での座標として扱う必要が無くなります。マス目の原点をローカル座標として使用できるようなイメージです。

思いついた人は天才ですね。

参考リンク

【Unity】shaderでセルラーノイズ・ボロノイ図を描いてみる【ソース・解説あり】

Discussion