游戏中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
}
}
}