-100p

-10p

+10p

+100p

Unityレンズフレアシェーダー

Unity uGUI用、レンズフレアシェーダー。太陽の光を表現。
(Lens Flare Shader for Unity uGUI)

関連ページ
前から陽炎の演出をシェーダーで作成できないかと思っていて、そのためにはまず太陽の輝きを表現するレンズフレアの作成が不可欠だった。
大分苦労するかなと思いきや、あれこれ試行錯誤して意外にそれっぽい演出が作れた。

このシェーダーを作成した後、元々の目的であった 陽炎シェーダー も作ってるのでそちらもどうぞ。

レンズフレアシェーダー概要

レンズフレアとは、写真や動画を光源付近に向けて撮った時によく画面に映りこむ光線のこと。
この光は単純な白色ではなく、なんか繊細微妙な、多彩で複雑な色を含んでいる。
人間の目ではこういう現象は起きないが、太陽光の表現としてかなり一般的に周知されている。

今回作ったレンズフレアシェーダーのサンプル動画は次の通り。
C#からシェーダー変数にアクセス することで演出を実装している。

このシェーダーは9個の変数を用意していて、組み合わせ次第で割と複雑な演出も作成できると思う。

レンズフレアシェーダーの研究している人は結構いるので、まずそのコードを勉強しようと思ったものの、高度な数学を使っていて理解不能だった。
そのため本シェーダーでは早々にインプット作業を諦めて、過去に作った 虹色シェーダー を応用する方法に切り替えた。
光の繊細微妙な調子は、論理的には作れなかったで感覚でそれっぽく整えている。
厳密にはレンズフレアは円ではなく六角形を作る必要があるらしいけど、そこまでやる気は起きなかった。

レンズフレアシェーダー、コード全文

以下コード全文。uGUI Imageへの反映方法は こちらの記事 参照。
今回の場合GrabPassを使っているので、 こちら の知識も必要になる。
Shader "UI/LensFlare"
{
    Properties 
    {
        [HideInInspector]_MainTex("-",2D)="white"{} 

        _Degree("Degree", Range(0, 359)) = 90 
//光線の角度を指定

        _FlareLength("FlareLength", Range(0, 1)) = 1 
//光線の長さを指定   

        _FlareConverge("FlareConverge", Range(0, 1)) = 1 
//大きいほど光が端に寄り小さいほど中央に寄る


        [Space(10)]
        _FlareSize("FlareSize", Range(0.1, 2)) = 1 
//レンズフレアの大きさ

        _FlareLuminance("FlareLuminance", Range(0.1, 2)) = 1 
//レンズフレアの光の強さ

        _FlareHuePlus("FlareHuePlus", Range(-1, 1)) = 0 
//レンズフレアの虹色を変化させる


        [Space(10)]
        _Brightness("Brightness", Range(1, 2)) = 1 
//画面全体の輝き

        _OverrideCol("OverrideCol", Color) = (1.0, 1.0, 1.0, 1.0) 
//画面全体を単一色で上書きする際の色

        _OverrideRate("OverrideRate", Range(0, 1)) = 0 
//画面全体を単一色で上書きする配合率

    }

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

        CGINCLUDE

        #pragma vertex vert
        #pragma fragment frag
        #include "UnityCG.cginc"

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

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

        sampler2D _MainTex;
        sampler2D _GrabTexture;

        fixed _Degree;
        fixed _FlareLength;             
        fixed _FlareConverge;

        fixed _FlareSize;
        fixed _FlareLuminance;
        fixed _FlareHuePlus;

        fixed _Brightness;
        fixed4 _OverrideCol;
        fixed _OverrideRate;

        v2f vert(appdata v)
        {
            v2f o;
            o.vertex = UnityObjectToClipPos(v.vertex);
            o.uv = ComputeGrabScreenPos(o.vertex);
            return o;
        }

        
//光線の長さを修正

        fixed2 reflectLength(fixed baseX, fixed baseY)
        {
            fixed remainX = saturate(0.5 - baseX) * (1 - _FlareLength);
            fixed remainY = saturate(0.5 - baseY) * (1 - _FlareLength);
            return fixed2(baseX + remainX, baseY + remainY);
        }

        
//光線を適切に中央に寄せる

        fixed2 reflectConverge(fixed baseX, fixed baseY)
        {
            return fixed2(baseX * _FlareConverge, baseY * _FlareConverge);
        }

        
//_Degreeの入力値に合わせて座標を適切に変換

        
//baseX = 右上にレンズフレアがある時のx軸のベース座標

        
//baseY = 右上にレンズフレアがある時のy軸のベース座標

        fixed2 reflectDegree(fixed baseX, fixed baseY)
        {
            
//_Degree=0が右上になってるので、少し計算が特殊になってる

            
//画面を左45度傾けたとして、_Degree値が中心から右に寄ってるか。180で割るとバグるので179.9で代替え

            fixed isRight = step(_Degree / 179.9, 1);
            
//画面を左45度傾けたとして、_Degree値が中心から上に寄ってるか。90で割るとバグるので89.9で代替え

            fixed isUp = isRight * step(_Degree % 179.9 / 89.9, 1) + (1 - isRight) * (1 - step(_Degree % 179.9 / 89.9, 1));

            
//画面を4つの区間に分割

            fixed isRightUp = isRight * isUp;
            fixed isRightDown = isRight * (1 - isUp);
            fixed isLeftDown = (1 - isRight) * (1 - isUp);
            fixed isLeftUp = (1 - isRight) * isUp;

            
//0~1の値を返す。_Degreeが45, 135, 225, 315の時transは(ほぼ)ゼロになる

            fixed trans = (45 - _Degree % 89.9) / 45;

            
//x軸の座標を修正

            fixed fixX = isRightUp * baseX +
                         isRightDown * baseX * trans +
                         isLeftDown * -baseX +
                         isLeftUp * -baseX * trans;
            
//y軸の座標を修正

            fixed fixY = isRightUp * baseY * trans +
                         isRightDown * -baseY +
                         isLeftDown * -baseY * trans +
                         isLeftUp * baseY;

            return fixed2(fixX, fixY);
        }

        
//レンズフレアの座標修正

        fixed2 fixFlarePos(fixed x, fixed y)
        {
            fixed2 fixUv = reflectConverge(x, y);
            fixUv = reflectLength(fixUv.x, fixUv.y);
            fixUv = reflectDegree(fixUv.x, fixUv.y);
            return fixUv;
        }

        
//レンズフレアの大きさを修正

        fixed2 reflectSize(fixed size)
        {
            return size * _FlareSize;
        }

        
//floatをRGBに変換

        fixed3 HUEtoRGB(in float H)
        {
            float R = abs(H * 6 - 3) - 1;
            float G = 2 - abs(H * 6 - 2);
            float B = 2 - abs(H * 6 - 4);
            return saturate(float3(R, G, B));
        }
        
        
//レンズフレアの円を作成する

        
//shiftX = 画面中央からどれだけズレるか、-0.5~0.5の値を受け入れる

        
//shiftY = 画面中央からどれだけズレるか、-0.5~0.5の値を受け入れる

        fixed4 createFlare(v2f IN, fixed size, fixed shiftX, fixed shiftY, 
            fixed hueBlendRate, fixed overrideRate, fixed hueOffset,
            fixed inStartAlpha, fixed inRangeAlpha, fixed outStartAlpha, fixed outRangeAlpha)
        {
            size = reflectSize(size);

            fixed4 color = (tex2D(_MainTex, IN.uv));
            fixed offset = max(0, hueOffset + _FlareHuePlus);
            fixed3 hueColor;
        
            fixed outAlpha = 1;
            fixed inAlpha = 1;

            
//渡されたuv座標を作成する円の座標に改変。座標は画面中央を0とし、xもyも最小値が-1、最大値が1になる

            fixed fixX = abs(IN.uv.x - 0.5 - shiftX) * 2;
            fixed fixY = abs(IN.uv.y - 0.5 + shiftY) * 2;
            
//解像度の縦横比に関わらず円を正円にするための処理と、円の大きさの修正

            fixed minScreen = min(_ScreenParams.x, _ScreenParams.y);
            fixX = fixX * (minScreen / _ScreenParams.y) / size;
            fixY = fixY * (minScreen / _ScreenParams.x) / size;
            
//画面中央からの直線距離を取得

            fixed atan = sqrt(fixX * fixX + fixY * fixY);

            
//距離からHUE値を取得

            fixed fixHue = abs(atan + offset);
            
//HUEをRGBに変換しを虹色として円に反映

            hueColor= (HUEtoRGB((abs(fixHue)) % 1) / hueBlendRate);
            color.xyz *= 1 / hueColor;

            
//円の中心から外に向かうアルファ値を取得

            fixed fixInStartAlpha = inStartAlpha - hueBlendRate;
            inAlpha = saturate(atan - fixInStartAlpha);
            inAlpha = saturate(inAlpha / inRangeAlpha);
            
//円の輪郭付近のアルファ値を取得

            fixed fixOutStartAlpha = outStartAlpha - (hueBlendRate / 2) ;
            outAlpha = saturate(atan - fixOutStartAlpha);
            outAlpha = 1 - saturate(outAlpha / outRangeAlpha);

            
//変数を4つかけて最終的なアルファ値を決定

            color.a = outAlpha * inAlpha * overrideRate * _FlareLuminance;

            return color;
        }

        ENDCG

        
//虹リング大の描写

        Pass
        {
            CGPROGRAM
            fixed4 frag(v2f IN) : SV_Target
            {
                fixed2 fixPos = fixFlarePos(0.35, 0.35);
                fixed4 color = createFlare(IN, 0.4, fixPos.x, fixPos.y, 0.1, 0.25, 12.8, 0, 0, 0.95, 0.1);
                return color;
            }
            ENDCG
        } 

        
//虹リング小の描写

        Pass
        {
            CGPROGRAM
            fixed4 frag(v2f IN) : SV_Target
            {
                fixed2 fixPos = fixFlarePos(0.17, 0.17);
                fixed4 color = createFlare(IN, 0.23, fixPos.x, fixPos.y, 0.1, 0.2, 14.9, 0, 1, 0.8, 0.2);                
                return color;
            }
            ENDCG
        }

        
//虹リング中の描写

        Pass
        {
            CGPROGRAM
            fixed4 frag(v2f IN) : SV_Target
            {
                fixed2 fixPos = fixFlarePos(0.23, 0.23);
                fixed4 color = createFlare(IN, 0.38, fixPos.x, fixPos.y, 0.55, 0.35, 4.65, 0.2, 0.8, 0.9, 0.1);
                return color;
            }
            ENDCG
        }          
        
        
//白色発光大の描写

        Pass
        {
            CGPROGRAM
            fixed4 frag(v2f IN) : SV_Target
            {
                fixed2 fixPos = fixFlarePos(0.47, 0.47);
                fixed4 color = createFlare(IN, 0.6, fixPos.x, fixPos.y, 1, 0.9, 0.3, 0, 0, 0.8, 0.7);
                return color;
            }
            ENDCG
        }

        
//白色発光中の描写

        Pass
        {
            CGPROGRAM
            fixed4 frag(v2f IN) : SV_Target
            {
                fixed2 fixPos = fixFlarePos(0.26, 0.26);
                fixed4 color = createFlare(IN, 0.3, fixPos.x, fixPos.y, 1, 0.5, 1.5, 0, 0, 0.9, 0.15);
                return color;
            }
            ENDCG
        }

        
//白色発光小の描写   

        Pass
        {
            CGPROGRAM
            fixed4 frag(v2f IN) : SV_Target
            {
                fixed2 fixPos = fixFlarePos(0.12, 0.12);
                fixed4 color = createFlare(IN, 0.15, fixPos.x, fixPos.y, 0.9, 0.3, 2, 0, 0, 0.9, 0.1);
                return color;
            }
            ENDCG
        }           

        
//画面全体を発光させる処理 

        GrabPass{}     
        Pass
        {
            CGPROGRAM
            fixed4 frag(v2f IN) : SV_Target
            {
                fixed4 color = (tex2D(_GrabTexture, IN.uv));
                color = color * _Brightness;
                color = (1 - _OverrideRate) * color + (_OverrideRate) * _OverrideCol;
                return color * _Brightness;
            }
            ENDCG
        } 
    }
}

プロパティを解説

シェーダーで用意してる9つのパラメーターは次の通り。
レンズフレアシェーダーのプロパティ
        _Degree("Degree", Range(0, 359)) = 90 
//光線の角度を指定

        _FlareLength("FlareLength", Range(0, 1)) = 1 
//光線の長さを指定   

        _FlareConverge("FlareConverge", Range(0, 1)) = 1 
//大きいほど光が端に寄り小さいほど中央に寄る


        [Space(10)]
        _FlareSize("FlareSize", Range(0.1, 2)) = 1 
//レンズフレアの大きさ

        _FlareLuminance("FlareLuminance", Range(0.1, 2)) = 1 
//レンズフレアの光の強さ

        _FlareHuePlus("FlareHuePlus", Range(-1, 1)) = 0 
//レンズフレアの虹色を変化させる


        [Space(10)]
        _Brightness("Brightness", Range(1, 2)) = 1 
//画面全体の輝き

        _OverrideCol("OverrideCol", Color) = (1.0, 1.0, 1.0, 1.0) 
//画面全体を単一色で上書きする際の色

        _OverrideRate("OverrideRate", Range(0, 1)) = 0 
//画面全体を単一色で上書きする配合率
以下動画でそれぞれの機能を解説。

Degree

Degreeの値を弄ると光線の角度を変えることができる。

FlareLength

FlareLengthの値を弄ると光線の長さを変えることができる。

FlareConverge

FlareConvergeの値を弄るとレンズフレアを中央に寄せることができる。

FlareSize

FlareSizeの値を弄るとレンズフレアの大きさを変えることができる。

FlareLuminance

FlareLuminanceの値を弄るとレンズフレアの輝度を変えることができる。

FlareHuePlus

FlareHuePlusの値を弄るとレンズフレアの虹色を染め具合を変更することが出来る。

Brightness

Brightnessの値を弄ると画面全体の輝度を変えることができる。

OverrideColとOverrideRate

OverrideCol及びOverrideRateの値を弄ると、画面全体を単一色で塗りつぶすことが出来る。
0
0

-100p

-10p

+10p

+100p