[Unity] WorldSpace 2D Grid Shader With Layered Pop-Up Tint

99 阅读4分钟

WorldSpace 2D Grid Shader With Layered Pop-Up Tint

世界坐标系下的,带有分层弹出 Tint 的 2D 网格着色器

基础 Tint

Shader "Unlit/UnlitGrid"
{
    Properties
    {
        [PerRendererData] _MainTex ("Texture", 2D) = "white" {}
        _GridSize ("Grid Size", float) = 1
        [FloatRange] _GridLineSize ("Grid Line Size", Range(0, 1)) = 0.02
        _LineColor ("Line Color", Color) = (0.7, 0.7, 0.7, 1)
        _ReachableTint ("Reachable Tint", Color) = (0.9, 0.9, 0.9, 1)
        _UnreachableTint ("Unreachable Tint", Color) = (0.6, 0.6, 0.6, 1)   
        _TintCenter("Tint Center", Vector) = (0, 0, 0, 0)
        _TintRadius("Tint Radius", float) = 1
        _TintPower("Tint Power", float) = 0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
                float4 worldSpacePos : TEXCOORD1;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _GridSize;
            float _GridLineSize;
            float4 _LineColor;
            
            float4 _ReachableTint;
            float4 _UnreachableTint;
            
            vector _TintCenter;
            float _TintRadius;
            float _TintPower;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);

                o.worldSpacePos = mul(unity_ObjectToWorld, v.vertex);

                return o;
            }

            fixed4 add_tint(fixed4 col, float2 ipos, float2 fpos)
            {
                // tint by ipos
                
                float2 ipos_to_tint_center = ipos - floor(_TintCenter/_GridSize);
                
                float dist_ipos_to_tint_center = abs(ipos_to_tint_center.x) + abs(ipos_to_tint_center.y);
                
                float reachable = step(dist_ipos_to_tint_center, _TintRadius + 0.001);
                
                fixed4 tint = col * _UnreachableTint * (1.0 - reachable) + col * _ReachableTint * reachable;

                col = lerp(col, tint, _TintPower);

                return col;
            }
            
            fixed4 add_grid(fixed4 col, float2 fpos)
            {
                float scaled_line_size = _GridLineSize/_GridSize;
                
                float2 bl = step(float2(scaled_line_size, scaled_line_size), fpos);       // bottom-left
                float2 tr = step(float2(scaled_line_size, scaled_line_size), float2(1.0, 1.0) - fpos);   // top-right
                float grid_brightness = 1.0 - bl.x * bl.y * tr.x * tr.y;

                col = col * (1.0 - grid_brightness) + _LineColor * grid_brightness;

                return col;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                float2 st = i.worldSpacePos.xy / _GridSize;

                float2 ipos = floor(st);  // integer
                float2 fpos = frac(st);  // fraction

                fixed4 col = tex2D(_MainTex, i.uv);

                col = add_tint(col, ipos, fpos);
                col = add_grid(col, fpos);
                
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
}

这个效果是点一下就有一个范围出现,但是没有动画过程

1.png

基础移动 Tint

假设我需要一个从中心往外移动的过程

Shader "Unlit/UnlitGrid"
{
    Properties
    {
        [PerRendererData] _MainTex ("Texture", 2D) = "white" {}
        _GridSize ("Grid Size", float) = 1
        [FloatRange] _GridLineSize ("Grid Line Size", Range(0, 1)) = 0.02
        _LineColor ("Line Color", Color) = (0.7, 0.7, 0.7, 1)
        _ReachableTint ("Reachable Tint", Color) = (0.9, 0.9, 0.9, 1)
        _UnreachableTint ("Unreachable Tint", Color) = (0.6, 0.6, 0.6, 1)   
        _TintCenter("Tint Center", Vector) = (0, 0, 0, 0)
        _TintRadius("Tint Radius", float) = 1
        _TintPower("Tint Power", float) = 0
        [FloatRange] _TintGridRadius("Tint Grid Radius", Range(0, 0.5)) = 0
        [FloatRange] _TintGridCenterOffset("Tint Grid Center Offset", Range(0, 0.5)) = 0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
                float4 worldSpacePos : TEXCOORD1;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _GridSize;
            float _GridLineSize;
            float4 _LineColor;
            
            float4 _ReachableTint;
            float4 _UnreachableTint;
            
            vector _TintCenter;
            float _TintRadius;
            float _TintPower;

            float _TintGridRadius;
            float _TintGridCenterOffset;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);

                o.worldSpacePos = mul(unity_ObjectToWorld, v.vertex);

                return o;
            }

            fixed4 add_tint(fixed4 col, float2 ipos, float2 fpos)
            {
                // tint by ipos
                
                float2 ipos_to_tint_center = ipos - floor(_TintCenter/_GridSize);
                
                float dist_ipos_to_tint_center = abs(ipos_to_tint_center.x) + abs(ipos_to_tint_center.y);
                
                float reachable = step(dist_ipos_to_tint_center, _TintRadius + 0.001);
                
                fixed4 tint = col * _UnreachableTint * (1.0 - reachable) + col * _ReachableTint * reachable;

                // tint by fpos

                // move dir

                // add 0.1f to avoid 0/0 = inf
                
                float2 move_dir = ipos_to_tint_center/(length(ipos_to_tint_center) + 0.1f);

                float2 tint_grid_center = float2(0.5, 0.5) - move_dir * (0.5f - _TintGridCenterOffset);
                
                float2 fpos_to_grid_center = fpos - tint_grid_center;

                float dist_fpos_to_grid_center = max(abs(fpos_to_grid_center.x), abs(fpos_to_grid_center.y));

                float tintable = step(dist_fpos_to_grid_center, _TintGridRadius) * reachable;

                col = lerp(col, lerp(col, tint, _TintPower), tintable);

                return col;
            }
            
            fixed4 add_grid(fixed4 col, float2 fpos)
            {
                float scaled_line_size = _GridLineSize/_GridSize;
                
                float2 bl = step(float2(scaled_line_size, scaled_line_size), fpos);       // bottom-left
                float2 tr = step(float2(scaled_line_size, scaled_line_size), float2(1.0, 1.0) - fpos);   // top-right
                float grid_brightness = 1.0 - bl.x * bl.y * tr.x * tr.y;

                col = col * (1.0 - grid_brightness) + _LineColor * grid_brightness;

                return col;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                float2 st = i.worldSpacePos.xy / _GridSize;

                float2 ipos = floor(st);  // integer
                float2 fpos = frac(st);  // fraction

                fixed4 col = tex2D(_MainTex, i.uv);

                col = add_tint(col, ipos, fpos);
                col = add_grid(col, fpos);
                
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
}

用法:在棋盘的脚本中,设置初始值和 Tween

render.material.SetVector("_TintCenter", new Vector4(transform.position.x, transform.position.y, 0f, 0f));
render.material.SetFloat("_TintRadius", reachableSteps);
render.material.SetFloat("_TintPower", 0f);
render.material.SetFloat("_TintGridRadius", 0f);
render.material.SetFloat("_TintGridCenterOffset", 0f);

render.material.DOFloat(1f, "_TintPower", 0.3f);
render.material.DOFloat(0.5f, "_TintGridRadius", 0.3f);
render.material.DOFloat(0.5f, "_TintGridCenterOffset", 0.3f);

效果如图,确实有一点移动了

2.gif

分层移动 Tint

但是其实这个移动是所有方块都同时移动,看上去没有层次

所以应该使用一个递增的变量来控制先后移动

由 st 的整数部分和小数部分的计算启发,不如使用一个时间的整数部分作为第几层,时间的小数部分作为每一层内的移动

Shader "Unlit/UnlitGrid"
{
    Properties
    {
        [PerRendererData] _MainTex ("Texture", 2D) = "white" {}
        _GridSize ("Grid Size", float) = 1
        [FloatRange] _GridLineSize ("Grid Line Size", Range(0, 1)) = 0.02
        _LineColor ("Line Color", Color) = (0.7, 0.7, 0.7, 1)
        _ReachableTint ("Reachable Tint", Color) = (0.9, 0.9, 0.9, 1)
        _UnreachableTint ("Unreachable Tint", Color) = (0.6, 0.6, 0.6, 1)   
        _TintCenter("Tint Center", Vector) = (0, 0, 0, 0)
        _TintRadius("Tint Radius", float) = 1
        _TintPower("Tint Power", float) = 0
        _TintSpreadTime("Tint Spread Time", float) = 0
        _TintSpreadSpeed("Tint Spread Speed", float) = 5
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
                float4 worldSpacePos : TEXCOORD1;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _GridSize;
            float _GridLineSize;
            float4 _LineColor;
            
            float4 _ReachableTint;
            float4 _UnreachableTint;
            
            vector _TintCenter;
            float _TintRadius;
            float _TintPower;

            float _TintSpreadTime;
            float _TintSpreadSpeed;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);

                o.worldSpacePos = mul(unity_ObjectToWorld, v.vertex);

                return o;
            }

            fixed4 add_tint(fixed4 col, float2 ipos, float2 fpos)
            {
                float itime = floor(_TintSpreadTime * _TintSpreadSpeed);  // integer time
                float ftime = frac(_TintSpreadTime * _TintSpreadSpeed);  // fraction time
                
                float2 tint_center_to_ipos = ipos - floor(_TintCenter/_GridSize);
                
                float dist_tint_center_to_ipos = abs(tint_center_to_ipos.x) + abs(tint_center_to_ipos.y);
                
                float stationary_reachable = step(dist_tint_center_to_ipos, itime - 1.0 + 0.001);
                
                float spreading_reachable = step(dist_tint_center_to_ipos, itime + 0.001) - step(dist_tint_center_to_ipos, itime - 1.0 + 0.001);
                
                // move
                
                float2 move_dir = tint_center_to_ipos/(length(tint_center_to_ipos) + 0.1f);

                float2 tint_grid_center = float2(0.5, 0.5) - move_dir * (0.5f - ftime/2.0);

                // scale
                
                float2 fpos_to_grid_center = fpos - tint_grid_center;

                float dist_fpos_to_grid_center = max(abs(fpos_to_grid_center.x), abs(fpos_to_grid_center.y));
                
                spreading_reachable *= step(dist_fpos_to_grid_center, ftime/2.0);

                // add tint

                float radius_reachable = step(dist_tint_center_to_ipos, _TintRadius + 0.001);
                
                float reachable = (stationary_reachable + spreading_reachable) * radius_reachable;
                
                fixed4 tint = col * _UnreachableTint * (1.0 - reachable) + col * _ReachableTint * reachable;
                
                return lerp(col, tint, _TintPower);
                
            }
            
            fixed4 add_grid(fixed4 col, float2 fpos)
            {
                float scaled_line_size = _GridLineSize/_GridSize;
                
                float2 bl = step(float2(scaled_line_size, scaled_line_size), fpos);       // bottom-left
                float2 tr = step(float2(scaled_line_size, scaled_line_size), float2(1.0, 1.0) - fpos);   // top-right
                float grid_brightness = 1.0 - bl.x * bl.y * tr.x * tr.y;

                col = col * (1.0 - grid_brightness) + _LineColor * grid_brightness;

                return col;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                float2 st = i.worldSpacePos.xy / _GridSize;

                float2 ipos = floor(st);  // integer
                float2 fpos = frac(st);  // fraction

                fixed4 col = tex2D(_MainTex, i.uv);
                
                col = add_tint(col, ipos, fpos);
                col = add_grid(col, fpos);
                
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
}

用法:

private void OnMouseDown()
{
    isDraging = true;
    spreadTime = 0f;
    Chessboard.Singleton.render.material.SetVector("_TintCenter", new Vector4(transform.position.x, transform.position.y, 0f, 0f));
    Chessboard.Singleton.render.material.SetFloat("_TintRadius", reachableSteps);
    Chessboard.Singleton.render.material.SetFloat("_TintPower", 0f);
    Chessboard.Singleton.render.material.SetFloat("_TintSpreadTime", spreadTime);

    Chessboard.Singleton.render.material.DOFloat(1f, "_TintPower", 0.3f);
}

protected void Update()
{
    if (!isDraging)
        return;

    spreadTime += Time.deltaTime;
    Chessboard.Singleton.render.material.SetFloat("_TintSpreadTime", spreadTime);
}

private void OnMouseUp()
{
    isDraging = false;
}

效果

3.gif

现在分层是有了,但是移动是线性变化的,看上去一般

Ease 曲线分层移动 Tint

所以还可以使用一些 ease 曲线

Shader "Unlit/UnlitGrid"
{
    Properties
    {
        [PerRendererData] _MainTex ("Texture", 2D) = "white" {}
        _GridSize ("Grid Size", float) = 1
        [FloatRange] _GridLineSize ("Grid Line Size", Range(0, 1)) = 0.02
        _LineColor ("Line Color", Color) = (0.7, 0.7, 0.7, 1)
        _ReachableTint ("Reachable Tint", Color) = (0.9, 0.9, 0.9, 1)
        _UnreachableTint ("Unreachable Tint", Color) = (0.6, 0.6, 0.6, 1)   
        _TintCenter("Tint Center", Vector) = (0, 0, 0, 0)
        _TintRadius("Tint Radius", float) = 1
        _TintPower("Tint Power", float) = 0
        _TintSpreadTime("Tint Spread Time", float) = 0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
                float4 worldSpacePos : TEXCOORD1;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _GridSize;
            float _GridLineSize;
            float4 _LineColor;
            
            float4 _ReachableTint;
            float4 _UnreachableTint;
            
            vector _TintCenter;
            float _TintRadius;
            float _TintPower;

            float _TintSpreadTime;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);

                o.worldSpacePos = mul(unity_ObjectToWorld, v.vertex);

                return o;
            }

            fixed4 add_tint(fixed4 col, float2 ipos, float2 fpos)
            {
                float itime = floor(_TintSpreadTime);  // integer time
                float ftime = frac(_TintSpreadTime);  // fraction time
                
                float2 tint_center_to_ipos = ipos - floor(_TintCenter/_GridSize);
                
                float dist_tint_center_to_ipos = abs(tint_center_to_ipos.x) + abs(tint_center_to_ipos.y);
                
                float stationary_reachable = step(dist_tint_center_to_ipos, itime + 0.001);
                
                float spreading_reachable = step(dist_tint_center_to_ipos, itime + 1.001) - step(dist_tint_center_to_ipos, itime + 0.001);
                
                // move
                
                float2 move_dir = tint_center_to_ipos/(length(tint_center_to_ipos) + 0.1f);

                float2 tint_grid_center = float2(0.5, 0.5) - move_dir * (0.5f - ftime/2.0);

                // scale
                
                float2 fpos_to_grid_center = fpos - tint_grid_center;

                float dist_fpos_to_grid_center = max(abs(fpos_to_grid_center.x), abs(fpos_to_grid_center.y));
                
                spreading_reachable *= step(dist_fpos_to_grid_center, ftime/2.0);

                // add tint

                float radius_reachable = step(dist_tint_center_to_ipos, _TintRadius + 0.001);
                
                float reachable = (stationary_reachable + spreading_reachable) * radius_reachable;
                
                fixed4 tint = col * _UnreachableTint * (1.0 - reachable) + col * _ReachableTint * reachable;
                
                return lerp(col, tint, _TintPower);
                
            }
            
            fixed4 add_grid(fixed4 col, float2 fpos)
            {
                float scaled_line_size = _GridLineSize/_GridSize;
                
                float2 bl = step(float2(scaled_line_size, scaled_line_size), fpos);       // bottom-left
                float2 tr = step(float2(scaled_line_size, scaled_line_size), float2(1.0, 1.0) - fpos);   // top-right
                float grid_brightness = 1.0 - bl.x * bl.y * tr.x * tr.y;

                col = col * (1.0 - grid_brightness) + _LineColor * grid_brightness;

                return col;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                float2 st = i.worldSpacePos.xy / _GridSize;

                float2 ipos = floor(st);  // integer
                float2 fpos = frac(st);  // fraction

                fixed4 col = tex2D(_MainTex, i.uv);
                
                col = add_tint(col, ipos, fpos);
                col = add_grid(col, fpos);
                
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
}

用法:

private void OnMouseDown()
{
    tintSeq.Pause();
    tintSeq = DOTween.Sequence();
    tintSeq.Insert(0f, Chessboard.Singleton.render.material.DOFloat(1f, "_TintPower", 0.3f));
    tintSeq.Insert(0f, Chessboard.Singleton.render.material
        .DOFloat(reachableSteps, "_TintSpreadTime", 1f)
        .SetEase(Ease.OutQuint));
    
    Chessboard.Singleton.render.material.SetVector("_TintCenter", new Vector4(transform.position.x, transform.position.y, 0f, 0f));
    Chessboard.Singleton.render.material.SetFloat("_TintRadius", reachableSteps);
    Chessboard.Singleton.render.material.SetFloat("_TintPower", 0f);
    Chessboard.Singleton.render.material.SetFloat("_TintSpreadTime", 0f);

    tintSeq.Play();
}

效果:

4.gif