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

どーも、ぐるたか@guru_takaです。

先日、セルラーノイズとボロノイ図を学び、UnityのShaderでお絵かきしてみました。成果物はこちら!

セルラーノイズ

ボロノイ図

備忘録として、セルラーノイズとボロノイ図の原理をまとめます。Shaderのソースも公開しているので、ぜひ参考にしてみてください!

セルラーノイズとは?

セルラーノイズとは簡単にまとめると、あるピクセルにおいて複数の点(ランダム)から最も近い距離を出力するノイズです。距離は色で表現しています。

画像はセルラーノイズです。白い点が複数のランダムの点を表しています。自身のピクセルに対し、最も近い白点から近ければ近いほどゼロに寄るので、白点付近は黒っぽく映し出されます。

そして、ある2つの白点との中点は最も距離が離れるため、白で区切られたような絵になります。

セルラーノイズのshader

Shaderのソースはこちらです。コメントも記載しています!

Shader "Custom/CellNoise"
{
    Properties
    {
        _SquareNum ("SquareNum", int) = 10
        _Brightness ("Brightness", Range(0.0, 1.0)) = 0.5
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            int _SquareNum;
            float _Brightness;

            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);
            }

            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; //格子状の1辺のマス目数

                float2 ist = floor(st);//整数
                float2 fst = frac(st);//小数点以下

                float distance = 5;

                //自身含む周囲のマスを探索
                for (int y = -1; y <= 1; y++)
                for (int x = -1; x <= 1; x++)
                {
                    //マスの起点(0,0)
                    float2 neighbor = float2(x, y);

                    //マスの起点を基準にした白点のxy座標
                    float2 p = 0.5 + 0.5 * sin(_Time.y  + 6.2831 * random2(ist + neighbor));

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

                    //白点との距離が短くなれば更新
                    distance = min(distance, length(diff));
                }

                //白点から最も短い距離を色に反映
                return distance * _Brightness;
            }
            ENDCG
        }
    }
}

以下のサイトを参考にしました!
参考 セルラーノイズthe book of shaders 参考 Unity-ShaderSketches | setchiGitHub

簡単な解説

処理の流れはこんな感じ!

STEP.1
格子状のマス目を作成
STEP.2
forループで自身のピクセル周辺のマス目ごとに処理
STEP.3
処理対象のマス目内にある白点と自身のピクセルとの距離を計算
STEP.4
前に算出した距離よりも短ければ更新
STEP.5
距離の色を出力

格子状のマス目について


引用:楽しい!Unityシェーダー お絵描き入門!

格子状のマス目の原理は、引用したスライドページの通りです。興味ある方はぜひチェックしてみて下さい!

float2 st = i.uv;
st *= _SquareNum; //格子状の1辺のマス目数

float2 ist = floor(st);//整数
float2 fst = frac(st);//小数点以下

マス目のイメージはこんな感じ!

周囲のマス目のforループについて

周囲のマス目にある白点との距離をforループで算出します。

 

//自身含む周囲のマスを探索
//自身のマスは(0,0)
for (int y = -1; y <= 1; y++)
for (int x = -1; x <= 1; x++)
{
    //マスの起点
    float2 neighbor = float2(x, y);

    //マスの起点を基準にした白点のxy座標
    float2 p = 0.5 + 0.5 * sin(_Time.y  + 6.2831 * random2(ist + neighbor));

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

    //白点との距離が短くなれば更新
    distance = min(distance, length(diff));
}

distanceを算出するイメージは以下の通りです。

白点と自身のピクセルの距離ベクトルdiffを図にならってわかりやすく書くと、diff = (neighbor + p) - (0 + fst)になります。その最終結果が、diff = neighbor + p - fstとなるわけです!

各マス目内の白点の座標pist,neighborで表しているので、共有できています。

あとは距離を色として出力すれば完成!

ボロノイ図のshader

ボロノイ図とは、あるピクセルにおいて複数の点(ランダム)から最も近い点にならって領域をわけた図です。セルラーノイズのshaderを少し改変すれば簡単にできます。

ソースはこちら!

Shader "Custom/CellNoiseSketch"
{
    Properties
    {
        _SquareNum ("SquareNum", int) = 10

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

        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);
            }

            int _SquareNum;



            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;


                float2 ist = floor(st);//整数
                float2 fst = frac(st);//少数の右側

                float distance = 5;
                float2 p_min;

                for (int y = -1; y <= 1; y++)
                for (int x = -1; x <= 1; x++)
                {
                    float2 neighbor = float2(x, y);
                    float2 p = 0.5 + 0.5 * sin(_Time.y + 6.2831 * random2(ist + neighbor));

                    float2 diff = neighbor + p - fst;

                    if(distance > length(diff)){
                        distance = length(diff);
                        //最も近い白点も更新
                        p_min = p;
                    }
                }

                //最も近い白点の座標を基に色を出力
                //色を変化させるための式
                p_min.x += sin(_Time.y);
                p_min.y += cos(_Time.y);

                return fixed4(p_min.x ,p_min.y, p_min.x + p_min.y,1);
            }
            ENDCG
        }
    }
}

セルラーノイズとの違いはこちらです。

if(distance > length(diff)){
    distance = length(diff);
    //最も近い白点も更新
    p_min = p;
}

最も近い白点の座標に基づいて色を出力すればボロノイ図が完成です!
参考 シェーダでノイズ3(セルノイズ)空の缶詰

最後に:他にも色んな表現ができる!

セルラーノイズの原理を使うと、色んなお絵かきができます。例えばメタボールのような表現も可能です!

他にもテクスチャをボロノイ図で表現できたりして、遊んでみると面白いです。こちらはGitHubでソースを公開しています。

参考 gurutaka/VoronoiTextureShaderGitHub

ぜひ色々試してみてください!!
参考 セルラーノイズ「ボロノイを改善する」the book of shaders

コメントを残す