游戏中经常会有如下图所示的表示攻击范围的圆环,圆环是由渐变色绘制而成。无论圆环的直径多大,圆环的宽度都是不变的。这一特点决定了无法用图片实现,只能通过程序绘制。
在Unity中,我们可以用Shader来绘制这个圆环。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
Shader "Custom/AttackRangeEffect" { Properties { _Color ("Color", Color) = (1,1,1,1) _Width("RoundWidth", int) = 100 } SubShader { Pass { ZTest Off ZWrite Off ColorMask 0 } Pass { Blend SrcAlpha OneMinusSrcAlpha CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct v2f { float4 pos : SV_POSITION; float4 oPos : TEXCOORD1; }; fixed4 _Color; int _Width; float4 _MainTex_ST; v2f vert (appdata_base v){ v2f o; o.pos = mul(UNITY_MATRIX_MVP, v.vertex); o.oPos = v.vertex; return o; } fixed4 frag(v2f i) : COLOR{ float dis = sqrt(i.oPos.x * i.oPos.x + i.oPos.y * i.oPos.y); float maxDistance = 0.5; if(dis > 0.5){ discard; }else{ float ringWorldRange = _Object2World[0][0]; float minDistance =(ringWorldRange * 0.5 - _Width)/ringWorldRange; if(dis < minDistance){ discard; } _Color.a = (dis - minDistance)/(0.5 - minDistance); } return _Color; } ENDCG } } FallBack "Diffuse" } |
我们用这段Shader来渲染一个1×1的正方形面片,可以通过_Object2World[0][0]来获取矩阵中的Scale,也就是面片的实际边长。然后在Fragment Shader中通过计算每个像素到面片中心的距离,来计算是否绘制以及像素的透明度。就可以实现图上的效果。
当然,在Unity中遇到了一个小坑,有一些炮塔是需要有两个攻击范围的。如下图:
但当我们加入两个面片时,发现都画不出来。而单独显示一个的时候就没问题。这是由于Unity中的Dynamic Batching导致的,Dynamic Batching的原理是将一些顶点少的,使用相同材质的面片,合成一个批次,送入到渲染管线中,从而减少DrawCall。而在合成批次的时候,就要提前用变换矩阵将所有的顶点展开。
在我们的例子中,如果同时渲染两个圆环,这两个圆环的面片顶点就会被换算成实际的坐标位置。也就是已经乘了Scale。这样我们在Shader中假设变换前的每一个像素坐标都<=0.5就错了。所以导致两个圆圈都画不出来。
所以就需要在绘制圆圈的时候关闭Unity中的Dynamic Batching。在文档中,提供了一个开启Dynamic Batching的条件列表,其中有一条是这样的:
- Multi-pass shaders will break batching. Almost all unity shaders supports several lights in forward rendering, effectively doing additional pass for them. The draw calls for “additional per-pixel lights” will not be batched.
多个Pass的Shader将不会被纳入到Dynamic Patching中,所以我们在代码中加入了这样一个没有任何卵用的Pass,来避开Dynamic Patching:
1 2 3 4 5 |
Pass { ZTest Off ZWrite Off ColorMask 0 } |
另外一个问题,就是在老型号手机上,不支持shader中的if 和 discard语句,因此,做一些修改,绕开这两种语句:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
fixed4 frag(v2f i) : COLOR{ float dis = sqrt(i.oPos.x * i.oPos.x + i.oPos.y * i.oPos.y); float maxDistance = 0.5; float ringWorldRange = _Object2World[0][0]; float minDistance =(ringWorldRange * 0.5 - _Width)/ringWorldRange; _Color.a = step(dis, 0.5); _Color.a = _Color.a * step(minDistance, dis); _Color.a = _Color.a * (dis - minDistance)/(0.5 - minDistance); return _Color; } |