-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;
            float4 _GrabTexture_TexelSize;

            #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 + 1) - _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