Unity集中線シェーダーと集中線の解説

Unity集中線シェーダー。漫画のような集中線の作り方。
(Radical Line Shader for Unity uGUI)

関連ページ 参考URL
サークル画面遷移シェーダー の経験を経て、本丸の集中線シェーダーの作成に取り掛かった。
前回学習した通り、集中線はサークル状のグラデーションとノイズを組み合わせると比較的簡単に作れる。

当初の予定では、Unity Shader Graphの Simple Noise を丸パクリして実装するつもりだった。
しかしSimple Noiseを解析するうちに、今回はここまで複雑なモノを使う必要がないと思い、結果的に自前でコードを用意することになった。
またこのシェーダーの知識を応用した FTLドライブシェーダー も作ったのでそちらもどうぞ。

集中線シェーダーの概要

画面端から中心に向けて、いくつかの線がランダムに伸びるシェーダー。漫画でよく見る表現。
プロパティの設定次第で、この線はUnity再生中に自動的にアニメーションする。
下がそのサンプル動画。


基本的には、UI Canvasの一番下に画面全体を覆うようにImageを用意し、そこにマテリアルをアタッチして使う。
集中線シェーダーのHierarchy

集中線シェーダー、コード全文

以下コード全文。uGUI Imageへの反映方法は こちらの記事 参照。
Shader "UI/RadialLine"
{
    Properties
    {
        [HideInInspector] _MainTex("-",2D)="white"{}
        [KeywordEnum(Rough, Sharp)] _Line("Line", Int) = 0
        _Color("Color", Color) = (0,0,0,1)

        [Space(10)]
        _NoiseScale("NoiseScale", Range(100, 1000)) = 500
        _PatternSeed("PatternSeed", Range(10, 100)) = 1  
        
        [Space(10)]
        _Edge1("Edge1", Range(0, 1)) = 0.5
        _Edge2("Edge2", Range(0, 1)) = 1

        [Space(10)]
        [Toggle] _IsAutoAnim("IsAutoAnim", float) = 0
        _AutoAnimSpeed("AutoAnimSpeed", Range(1, 20)) = 10
    }

    SubShader
    {
        Tags { "Queue" = "Transparent" }
        LOD 100
        Cull Off
        ZWrite Off
        Blend SrcAlpha OneMinusSrcAlpha

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile _LINE_ROUGH _LINE_SHARP 

            struct appdata
            {
                fixed2 uv : TEXCOORD0;
                fixed4 vertex : POSITION;
            };

            struct v2f
            {
                fixed2 uv : TEXCOORD0;
                fixed4 vertex : POSITION;
            };

            sampler2D _MainTex;
            fixed4 _Color;

            fixed _NoiseScale;
            fixed _PatternSeed;

            fixed _Edge1;
            fixed _Edge2;

            fixed _IsAutoAnim;
            fixed _AutoAnimSpeed;

            
//radianの最大値、degreeで言うと360度のこと

            static const float PI2 = 3.14159 * 2;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }
        
            
//あらゆる範囲の2次元変数を0~1の範囲の1次元変数に変換する

            fixed random(fixed2 uv)
            {
                return frac(sin(dot(uv, float2(12.9898, 78.233)))*43758.5453);
            }

            
//引数に渡された対象座標の角度を0~1の範囲に圧縮して返す。

            
//反時計周りに徐々に値が大きくなる。

            fixed2 getUvAngle(fixed2 uv)
            {
                
//uv座標を、画面中央を原点として(0,0)>(1,1)から(-1,-1)>(1,1)の範囲に修正する

                fixed2 fixUv = uv * 2 - 1;
                
//修正したuv座標の角度をradian値で取得

                fixed angle = atan2(fixUv.y, fixUv.x);
                
//0~PI*2の値を0~1の範囲に圧縮

                return angle / PI2;
            }

            
//半時計周りにグラデーションする0~1の値を、バラバラの、かつある程度はまとまった集中線に変換する

            fixed getRadialLine(fixed angle)
            {
                
//_NoiseScaleを大きくするほど、集中線が細かくなる。

                
//これは_NoiseScaleで値を大きくすると、floorで切り捨てられる要素が少なくなり

                
//angleの細かい値まで漏れなく後のrandom関数に反映されるため。

                
//random関数は、どんな巨大な値も0~1の範囲に収めるため、与える値の大きさは問題ではなく

                
//どこからどこまでのangle値を同値として扱うかが重要になる

                fixed2 lineSeed = floor(fixed2(angle, angle) * _NoiseScale);
                fixed lineVal = random(lineSeed); ;

                
//sin関数はあらゆる値を規則的な-1~1の範囲の波形に変換する。

                
//ここでは集中線の出現パターンを変える効果に使っている。

                
//_IsAutoAnimがtrueの時は、Unity再生時に自動でアニメーションを実行し、

                
//_IsAutoAnimがfalseの時は、_PatternSeedを操作することで集中線の見た目が変化する

                lineVal = (_IsAutoAnim * sin(lineVal * _Time.w * _AutoAnimSpeed)) + 
                            ((1 - _IsAutoAnim) * sin(lineVal * _PatternSeed));
                return lineVal;
            }

            
//引数のuv座標を参照し、中心から外側に向けて0から1に遷移していく値を返す

            fixed getCenterCircle(fixed2 uv)
            {
                
//uv座標を、画面中央を原点として(0,0)>(1,1)から(-1,-1)>(1,1)の範囲に修正する

                fixed2 fixUv = uv * 2 - 1;
#ifdef _LINE_ROUGH
                
//ランダム関数を使ってちょっと線をザラザラにする

                fixed2 i = random(uv) * 0.5 + 0.7;
                return length(fixUv) * i;
#else
                return length(fixUv);
#endif
            }

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

                
//以下の処理は全てalpha値を算出するための処理

                fixed angle = getUvAngle(i.uv);
                fixed lineVal = getRadialLine(angle);
                fixed circle = getCenterCircle(i.uv);
                
//中心まで届く集中線と、中心に近いほど0に近づく円を掛け算して、

                
//中心付近は空になる集中線を作る

                fixed resultLine = saturate(lineVal * circle);
                
//smoothstepで濃い所はより濃く、薄い所はより薄くする

                fixed smoothAlpha = smoothstep(_Edge1, _Edge2, resultLine);

                col = _Color;
                col.a = smoothAlpha;
                return col;
            }
            ENDCG
        }
    }
}

プロパティの解説

今回用意してあるプロパティは8つで、結構多い。
集中線シェーダーのプロパティ
 Line 
: Variant 。RoughとSharpが用意してあり、Roughはザラザラな線、Sharpはツルツルな線を表現する。
 Color 
: 集中線の色。
 NoiseScale 
: 集中線の細かさ。大きいほど細かくなる。
 PatternSeed 
: 集中線の出現パターン。マニュアルでアニメーションさせる時に使う。
 Edge1 
: 集中線をどこまで中央に伸ばすかの値。小さいほど中央まで伸びる。
 Edge2 
: 集中線の濃さ。Edge1の値に近づけるほど線がくっきり色濃く表示される。
 IsAutoAnim 
: 自動アニメーションのトグル。Onにすると、Unity再生中に自動アニメーションを実行する。
 AutoAnimSpeed 
: 自動アニメーションのスピード。大きいほど切り替わりが早くなる。

下はRoughとSharpの比較画像。作った時はテンション上がったけどこうしてみると微妙な差かもしれない。
LineをRoughにした時の集中線
LineをSharpにした時の集中線
下はColor、NoiseScale、Edge1/Edge2を弄った際のサンプル動画。



コードを少し解説

今回のシェーダー処理の肝は、サークル状グラデーションをランダムノイズを使って集中線にしている部分。
例えば下のような、半時計周りにalpha値が0~1に遷移する画像があったとする。
集中線の説明1

このalpha値を、ランダムノイズ関数に渡す。
ここで言うランダムノイズ関数の処理は、この世のあらゆる数値を0~1の範囲に収めてくれる処理で、同じ入力に対しては必ず同じ出力を出す。
つまりある入力に対して毎回ランダムな出力をするのではなく、入力に対しランダムに分布する値を返すということ。
言い換えると、入力と出力の間に一見すると関連性が見られない値を返す。
            
//あらゆる範囲の2次元変数を0~1の範囲の1次元変数に変換する

            fixed random(fixed2 uv)
            {
                return frac(sin(dot(uv, float2(12.9898, 78.233)))*43758.5453);
            }

ランダムノイズ関数にalpha値を渡す際には、floorを使って小数点以下を切り捨て、ある程度値を大雑把にしておく。
この処理をしないとただのモザイクになって集中線にならない。
以前は集中線を作るにはグラデーションと シンプルノイズ が必要だと思ってたが、今回必要なのはシンプルノイズ中のこのfloor処理だけだった。
                fixed2 lineSeed = floor(fixed2(angle, angle) * _NoiseScale);
                fixed lineVal = random(lineSeed); ;
結果は次の通りで、グラデーションだったalphaの値が集中線に変化している。
集中線の説明2
0
0