-100p

-10p

+10p

+100p

Unity魚眼レンズシェーダー

Unity uGUI用、魚眼レンズシェーダー。樽型歪曲収差の実装。
(Barrel Distortion Shader for Unity uGUI)

関連ページ 参考URL
前回 逆魚眼レンズシェーダー を作ったので、次は魚眼シェーダーを作ろうと思った。
逆魚眼を応用すればすぐ作れるかと思ったらそんな事なくて、結構苦労した。

とりあえず動くまでには漕ぎつけたが、厳密な魚眼レンズとは見た目が違う。
現状であんまり満足行ってないので、いつかコードを改修する予定。

魚眼レンズシェーダー概要

魚眼レンズの視覚効果は、正式には樽型歪曲収差という。
画面の中心に近づくほど極端に手前に迫ってくるような歪みを与える。

下の動画は今回のシェーダーのサンプル。


逆魚眼同様に、歪みの掛け方を3タイプ用意した。x軸+y軸、y軸のみ、x軸のみのパターンが選べる。

魚眼レンズシェーダー、コード全文

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

        [KeywordEnum(All, Vertical, Horizontal)] _Direction("Direction", Int) = 0
        _DistortionRange ("DistortionRange", Range(0.0, 10.0)) = 10
        _EasePow ("EasePow", Range(0, 10)) = 9
    }

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

        GrabPass{}
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            sampler2D _GrabTexture;

            #pragma multi_compile _DIRECTION_ALL _DIRECTION_VERTICAL _DIRECTION_HORIZONTAL

            fixed _DistortionRange;
            fixed _EasePow;

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

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

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

            fixed2 getIsUnderHalf(v2f i)
            {
                
//x軸が0.5より小さければ1を代入

                fixed isUnderHalfX = step(i.uv.x, 0.5);
                
//y軸が0.5より小さければ1を代入

                fixed isUnderHalfY = step(i.uv.y, 0.5);

                return fixed2(isUnderHalfX, isUnderHalfY);
            }
            
            fixed2 getFarFromSafe(v2f i)
            {
                fixed fixRange = _DistortionRange * 0.1;
                fixed safeRange = 1 - fixRange;

                fixed farFromSafeX = abs(0.5 - i.uv.x) - (safeRange / 2);
                fixed farFromSafeY = abs(0.5 - i.uv.y) - (safeRange / 2);
                return fixed2(farFromSafeX, farFromSafeY);
            }

            fixed2 getFarFromCenter(v2f i)
            {
                fixed farFromCenterX = abs(0.5 - i.uv.x);
                fixed farFromCenterY = abs(0.5 - i.uv.y);
                return fixed2(farFromCenterX, farFromCenterY);
            }

            fixed getFixEase()
            {
                fixed easePowMax = 10;
                return (easePowMax + 0.5) - _EasePow;
            }

            fixed2 getMainDist(fixed2 farFromSafe)
            {
                
//InQuadのEaseでfarFromSafeが大きい程指数関数的に座標がずれるようにする

                fixed quadX = farFromSafe.x * (farFromSafe.x - 2);
                fixed quadY = farFromSafe.y * (farFromSafe.y - 2);

                
//指数関数の伸びが強すぎるのでfixEaseで抑える

                
//_EasePowのrangeが0~10なのでfixEaseの振れ幅は10.1~0.1

                fixed fixEase = getFixEase();
                fixed mainDistX = pow(quadX * quadX, 2) / fixEase;
                fixed mainDistY = pow(quadY * quadY, 2) / fixEase;

                return fixed2(mainDistX, mainDistY);
            }

            fixed2 getSubDist(fixed2 farFromCenter, fixed2 mainDist)
            {
                fixed subDistX = farFromCenter.x * mainDist.y;
                fixed subDistY = farFromCenter.y * mainDist.x;
                return fixed2(subDistX, subDistY);
            }

            fixed2 getMainReduce(fixed2 isUnderHalf, fixed2 mainDist)
            {
                fixed mainReduceX = (isUnderHalf.x * -1 * mainDist.x) + ((1 - isUnderHalf.x) * (mainDist.x));
                fixed mainReduceY = (isUnderHalf.y * -1 * mainDist.y) + ((1 - isUnderHalf.y) * (mainDist.y));
                return fixed2(mainReduceX, mainReduceY);
            }

            fixed2 getSubReduce(fixed2 isUnderHalf, fixed2 subDist)
            {
                fixed subReduceX = (isUnderHalf.x * -1 * subDist.x) + ((1 - isUnderHalf.x) * (subDist.x));
                fixed subReduceY = (isUnderHalf.y * -1 * subDist.y) + ((1 - isUnderHalf.y) * (subDist.y));
                return fixed2(subReduceX, subReduceY);
            }

            fixed2 getUseDist(fixed2 farFromSafe)
            {
                
//x軸が歪み対象エリア内かどうか

                fixed useDistX = step(0, farFromSafe.x);
                
//y軸が歪み対象エリア内かどうか

                fixed useDistY = step(0, farFromSafe.y);
                return fixed2(useDistX, useDistY);
            }

            fixed4 frag(v2f i) : SV_Target
            {
                
//対象のy軸とx軸座標が中心点を超えてるかどうかを習得

                fixed2 isUnderHalf = getIsUnderHalf(i);

                
//非歪みエリアから外側にx軸、y軸がどれだけ離れてるかを取得

                fixed2 farFromSafe = getFarFromSafe(i);
                
//中心からx軸、y軸がどれだけ離れてるかを取得

                fixed2 farFromCenter = getFarFromCenter(i);

                
//メインの歪みの基本値を取得

                fixed2 mainDist = getMainDist(farFromSafe);
                
//サブの歪みの基本値を取得

                fixed2 subDist = getSubDist(farFromCenter, mainDist);
                
//メインの座標の減退値を取得

                fixed2 mainReduce = getMainReduce(isUnderHalf, mainDist);
                
//サブの座標の減退値を取得

                fixed2 subReduce = getSubReduce(isUnderHalf, subDist);

                
//対象のx軸、y軸が歪み対象エリア内かどうか

                fixed2 useDist = getUseDist(farFromSafe);

#ifdef _DIRECTION_ALL
                i.uv.x = i.uv.x + useDist.x * mainReduce.x + useDist.y * subReduce.x;
                i.uv.y = i.uv.y + useDist.y * mainReduce.y + useDist.x * subReduce.y;
#elif _DIRECTION_VERTICAL
                i.uv.x = i.uv.x + useDist.y * subReduce.x * 2;
                i.uv.y = i.uv.y + useDist.y * mainReduce.y;
#else
                i.uv.x = i.uv.x + useDist.x * mainReduce.x;
                i.uv.y = i.uv.y + useDist.x * subReduce.y * 2;
#endif
                fixed fixEase = getFixEase();
                fixed isUnderOne = step(fixEase, 1);
                
                fixed magni = 1 + (1 / fixEase);
                fixed shift = (1 - 1 / magni) / 2;
                fixed4 col = tex2D(_GrabTexture, i.uv * (1 / magni) + shift);
                return col;
            }
            ENDCG
        }
    }
}
0
0

-100p

-10p

+10p

+100p