Programming Serendipity

プログラミングを中心に種々雑多に書き留めます

シェーダーでスムーズなトランジションを行う

f:id:q7z:20170311003757g:plain こういうのをシェーダーを使って実装します。続きからどうぞ。

まず思いつくのはclipで描画をカットする方法です。上下左右の4方向に対応する場合、例えば以下のようにできます。

// 切れ目あり
if (_X > 0)
{
    clip(-_X + IN.texcoord.x);
}
else
{
    clip(_X - IN.texcoord.x + 1);
}

if (_Y > 0)
{
    clip(-_Y + IN.texcoord.y);
}
else
{
    clip(_Y - IN.texcoord.y + 1);
}

結果は次のようになります。 f:id:q7z:20170311003941g:plain

これはこれでナシではないですが、最初の画像のように境目がなめらかだとより見栄えが良くなりそうです。
というわけで、改善したのが下のコードです。

// なめらか
if (_X > 0)
{
    tex.a *= 1 - saturate(_X * (_Sharpness + 1) - IN.texcoord.x * _Sharpness);
}
else
{
    tex.a *= saturate((1 + _X) * (_Sharpness + 1) - (IN.texcoord.x) * _Sharpness);
}
if (_Y > 0)
{
    tex.a *= 1 - saturate(_Y * (_Sharpness + 1) - IN.texcoord.y * _Sharpness);
}
else
{
    tex.a *= saturate((1 + _Y) * (_Sharpness + 1) - (IN.texcoord.y) * _Sharpness);
}

なぜこれでうまくいくのかはわかりません。ガチャガチャいじってたら上手くいきました。(誰か理屈を教えてください)

これで完成です。シェーダー全体のコードを以下に載せます。Particle/Alpha Blend.shaderをベースにしています。
(Categoryなんていうキーワードがいつのまにか追加されてたんですね。5.5からでしょうか。)

Shader "Custom/Transition" 
{
    Properties 
    {
        _TintColor ("Tint Color", Color) = (0.5,0.5,0.5,0.5)
        _MainTex ("Particle Texture", 2D) = "white" {}
        _X("X", Range(-1, 1)) = 0
        _Y("Y", Range(-1, 1)) = 0
        _Sharpness("Sharpness", Range(0, 16)) = 2
        [Toggle] _Smoothed("Smoothed Edge", Float) = 1
    }

    Category 
    {
        Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" }
        Blend SrcAlpha OneMinusSrcAlpha
        ColorMask RGB
        Cull Off Lighting Off ZWrite Off

        SubShader 
        {
            Pass 
            {
        
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                #pragma target 2.0
                #pragma multi_compile_particles
                #pragma multi_compile_fog

                #pragma multi_compile SMOOTHED_EDGE CUT_EDGE
            
                #include "UnityCG.cginc"

                sampler2D _MainTex;
                float4 _MainTex_ST;
                fixed4 _TintColor;
                float _X;
                float _Y;
                float _Sharpness;
                float _Smoothed;
            

                struct appdata_t 
                {
                    float4 vertex : POSITION;
                    fixed4 color : COLOR;
                    float2 texcoord : TEXCOORD0;
                    UNITY_VERTEX_INPUT_INSTANCE_ID
                };

                struct v2f 
                {
                    float4 vertex : SV_POSITION;
                    fixed4 color : COLOR;
                    float2 texcoord : TEXCOORD0;
                    UNITY_FOG_COORDS(1)
                    UNITY_VERTEX_OUTPUT_STEREO
                };
            
                v2f vert (appdata_t v)
                {
                    v2f o;
                    UNITY_SETUP_INSTANCE_ID(v);
                    UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
                    o.vertex = UnityObjectToClipPos(v.vertex);
                    o.color = v.color * _TintColor;
                    o.texcoord = TRANSFORM_TEX(v.texcoord,_MainTex);
                    UNITY_TRANSFER_FOG(o,o.vertex);
                    return o;
                }

                fixed4 frag (v2f IN) : SV_Target
                {
                    fixed4 tex = tex2D(_MainTex, IN.texcoord) * IN.color;

                    if (_Smoothed > 0)
                    {
                        // なめらか
                        if (_X > 0)
                        {
                            tex.a *= 1 - saturate(_X * (_Sharpness + 1) - IN.texcoord.x * _Sharpness);
                        }
                        else
                        {
                            tex.a *= saturate((1 + _X) * (_Sharpness + 1) - (IN.texcoord.x) * _Sharpness);
                        }
                        if (_Y > 0)
                        {
                            tex.a *= 1 - saturate(_Y * (_Sharpness + 1) - IN.texcoord.y * _Sharpness);
                        }
                        else
                        {
                            tex.a *= saturate((1 + _Y) * (_Sharpness + 1) - (IN.texcoord.y) * _Sharpness);
                        }
                    }
                    else
                    {
                        // 切れ目あり
                        if (_X > 0)
                        {
                            clip(-_X + IN.texcoord.x);
                        }
                        else
                        {
                            clip(_X - IN.texcoord.x + 1);
                        }

                        if (_Y > 0)
                        {
                            clip(-_Y + IN.texcoord.y);
                        }
                        else
                        {
                            clip(_Y - IN.texcoord.y + 1);
                        }
                    }

                    return tex;
                }
                ENDCG 
            }
        }   
    }
}

このシェーダをアタッチしたマテリアルのインスペクタのSmoothed Edgeのチェックをオンオフすることで切り替えられます。
確認用にif文盛り盛りなので、使う場合は適宜削ってください。