【Unity Shader】Sprite实现Image的环形填充

游戏中NPC脚下的地面上通常会显示其攻击范围。如果NPC具有视野属性,那么这个攻击范围就可能是任意角度。就需要Sprite如同Image组件一样可以360度填充。

不过Image的角度填充是在C#层实现的,无法套用在Sprite上。

 参照Unity内置shader,Sprites-Default.shader :

        Pass
        {
        CGPROGRAM
            #pragma vertex SpriteVert
            #pragma fragment SpriteFrag
            #pragma target 2.0
            #pragma multi_compile_instancing
            #pragma multi_compile_local _ PIXELSNAP_ON
            #pragma multi_compile _ ETC1_EXTERNAL_ALPHA
            #include "UnitySprites.cginc"
        ENDCG
        }

Sprite的shader相当简短,因为它的顶点/片元着色器全都是在UnitySprites.cginc里实现。参考默认片元着色器函数SpriteFrag的实现,自定义片元着色器:

        Pass
        {
        CGPROGRAM
            #pragma vertex SpriteVert
            #pragma fragment MySpriteFrag
            #pragma target 2.0
            #pragma multi_compile_instancing
            #pragma multi_compile_local _ PIXELSNAP_ON
            #pragma multi_compile _ ETC1_EXTERNAL_ALPHA
            #include "UnitySprites.cginc"

            fixed4 MySpriteFrag(v2f IN) : SV_Target
            {
                fixed4 c = SampleSpriteTexture (IN.texcoord) * IN.color;
                c.rgb *= c.a;
                return c;
            }
        ENDCG
        }

 根据角度填充圆形Sprite,首先根据UV坐标判断角度,角度大于限定值的区域不显示。夹角的计算很简单,还有一个更简单的方法就是Unity有现成的函数Vector3.Angle(Vector3 dirA, Vector3 dirB),把函数实现移植到shader中:

            fixed GetAngle(fixed2 from, fixed2 to){
                float denominator = sqrt((from.x*from.x + from.y*from.y) * (to.x*to.x + to.y * to.y));
                if (denominator < 0.000001)
                    return 0;

                float dotNum = clamp(dot(from, to) / denominator, -1.0, 1.0);
                return degrees(acos(dotNum));
            }

判断当前UV坐标方向相对坐标系Y轴正方向的夹角,并把限定角度外的像素设置为透明:

            fixed4 MySpriteFrag(v2f IN) : SV_Target
            {
                fixed4 c = SampleSpriteTexture (IN.texcoord) * IN.color;
                fixed2 uvCenter = fixed2(0.5, 0.5);
                fixed absAngle = GetAngle(fixed2(0,1), IN.texcoord - uvCenter);
                c.a *= absAngle <= _Angle;
                c.rgb *= c.a;
                return c;
            }

 中心的角度比较尖锐,为了美观通常还会对近点进行裁剪,将圆心掏空。实现原理就是把距离圆心一定距离内的像素改为全透明:

            fixed4 MySpriteFrag(v2f IN) : SV_Target
            {
                fixed4 c = SampleSpriteTexture (IN.texcoord) * IN.color;
                fixed2 uvCenter = fixed2(0.5, 0.5);
                fixed absAngle = GetAngle(fixed2(0,1), IN.texcoord - uvCenter);
                c.a *= (absAngle <= _Angle) * distance(IN.texcoord, uvCenter)*2 > _NearClip;
                c.rgb *= c.a;
                return c;
            }

完整shader:

Shader "Custom/SpriteFillCircle"
{
    Properties
    {
        [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
        _Color ("Tint", Color) = (1,1,1,1)
        _Angle ("Angle", Range(0,180)) = 30
        _NearClip ("Near Clip", Range(0,1)) = 0.1
        [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0
        [HideInInspector] _RendererColor ("RendererColor", Color) = (1,1,1,1)
        [HideInInspector] _Flip ("Flip", Vector) = (1,1,1,1)
        [PerRendererData] _AlphaTex ("External Alpha", 2D) = "white" {}
        [PerRendererData] _EnableExternalAlpha ("Enable External Alpha", Float) = 0
    }

    SubShader
    {
        Tags
        {
            "Queue"="Transparent"
            "IgnoreProjector"="True"
            "RenderType"="Transparent"
            "PreviewType"="Plane"
            "CanUseSpriteAtlas"="True"
        }

        Cull Off
        Lighting Off
        ZWrite Off
        Blend One OneMinusSrcAlpha

        Pass
        {
        CGPROGRAM
            #pragma vertex SpriteVert
            #pragma fragment MySpriteFrag
            #pragma target 2.0
            #pragma multi_compile_instancing
            #pragma multi_compile_local _ PIXELSNAP_ON
            #pragma multi_compile _ ETC1_EXTERNAL_ALPHA
            #include "UnitySprites.cginc"
            float _Angle;
            float _NearClip;
            fixed GetAngle(fixed2 from, fixed2 to){
                float denominator = sqrt((from.x*from.x + from.y*from.y) * (to.x*to.x + to.y * to.y));
                if (denominator < 0.000001)
                    return 0;

                float dotNum = clamp(dot(from, to) / denominator, -1.0, 1.0);
                return degrees(acos(dotNum));
            }
            fixed4 MySpriteFrag(v2f IN) : SV_Target
            {
                fixed4 c = SampleSpriteTexture (IN.texcoord) * IN.color;
                fixed2 uvCenter = fixed2(0.5, 0.5);
                fixed absAngle = GetAngle(fixed2(0,1), IN.texcoord - uvCenter);
                c.a *= (absAngle <= _Angle) * distance(IN.texcoord, uvCenter)*2 > _NearClip;
                c.rgb *= c.a;
                return c;
            }
        ENDCG
        }
    }
}

猜你喜欢

转载自blog.csdn.net/final5788/article/details/118397491