原理很简单,即根据目标是否被遮挡返回不同的颜色即可。目标被障碍物遮住的部分其深度值必然要大于障碍物,因此我们可以用一个pass处理当深度值大于障碍物的时也就是目标被障碍物遮住的部分的颜色——例如我们返回红色。
Pass { ZTest Greater ... fixed4 frag (v2f i) : SV_Target { fixed4 col = fixed4(1, 0, 0, 1); return col; } }再用另一个pass处理目标未被遮挡住的部分,也就是深度值小于障碍物时返回目标的正常颜色。
Pass { ZTest Less ... fixed4 frag (v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.uv); return col } }不过墙后的敌人如果只是显示一个红色是否有点太单调了呢?还有很多游戏,它的透视效果是下面这样的:目标身上多了一些描边。
这个效果的实现其实也很简单。我们可以根据观察方向和目标多边形的法线方向的夹角来判断目标的边缘——毕竟目标面向我们的面的法线和我们观察方向的夹角相对较小,而边缘部分的面的法线和我们的观察方向的夹角显然更大——这里的边缘判断用到了观察方向,下文我们还会聊聊跟观察方向无关的边缘检测。
所以,给墙后的目标描边这件事就又变得十分简单了。我们只需要在处理被遮挡部分的那个pass中返回的红色变为与法线和观察方向的夹角相关的一个值就好了。
为了实现这个目标,我们首先要获取法线和观察方向的信息。
之后再计算法线和观察方向的夹角信息:
float NdotV = 1 - dot(i.normal, i.viewDir) ;最后,只需要把这个值当作影响最后颜色输出的因素就好了。
return _EdgeColor * NdotV;完整的项目可以到这里到这里下载:UnitySpecialEffectWithDepth 0x04 护盾/能量场效果
很多科幻游戏也有这种能量场或者护盾的效果。例如暴雪的守望先锋中的猩猩温斯顿的屏障发射器、光环系列的圣堂防卫者的能量护盾甚至一些手游中也有类似的效果,比如网易的光明大陆。
这个效果的实现和原理其实也并不复杂。简单的说可以分为以下这几个部分:
首先我们要开启透明混合并指定渲染队列为透明。
SubShader { ZWrite Off Cull Off Blend SrcAlpha OneMinusSrcAlpha Tags { "RenderType" = "Transparent" "Queue" = "Transparent" } ... }之后像上一个例子那样,根据观察方向绘制能量场的边缘。
//vert o.normal = UnityObjectToWorldNormal(v.normal); o.viewDir = normalize(UnityWorldSpaceViewDir(mul(unity_ObjectToWorld, v.vertex))); //frag float rim = 1 - abs(dot(i.normal, normalize(i.viewDir)));这样,我们就得到了一个半透且带有描边效果球体,能量场已经初具雏形了。
接下来,我们就要实现相交高亮的效果了。所谓的相交高亮指的是能量场和别的物体相交时,在相交处绘制出高亮效果。这时我们就要用到深度信息了。当能量场和某个物体相交时,二者的深度信息应该一致,基于这个对比深度信息,我们可以用来估计一个像素的“相交程度”。
需要注意的是,能量场的shader在执行时_CameraDepthTexture中只保存了场景中不透明物体的深度信息,因此这个时候无法从CameraDepthTexture中获取能量场的深度信息,所以要在vert中计算顶点的深度,这里我利用了COMPUTE_EYEDEPTH这个内置的宏。在之后的frag内就可以很方便的获取场景和能量场当前片元的深度了。
//vert o.screenPos = ComputeScreenPos(o.vertex); COMPUTE_EYEDEPTH(o.screenPos.z); //frag float sceneZ = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.screenPos))); float partZ = i.screenPos.z;两者相减就是深度的差异diff,再用1 - diff就得到了一个“相交程度”。
float diff = sceneZ - partZ; float intersect = (1 - diff) * _IntersectPower;最后我们还需要实现一个能量场的扭曲效果。扭曲效果是游戏里面经常有的一个效果,其实也很简单,我们只需要一张渲染能量场之前的场景的渲染图,之后随时间调整uv的偏移就可以模拟扭曲的效果了。
GrabPass { "_GrabTempTex" } ... //frag float4 offset = tex2D(_NoiseTex, i.uv - _Time.xy) * _DistortTimeFactor; i.grabPos.xy -= offset.xy * _DistortStrength; fixed4 color = tex2Dproj(_GrabTempTex, i.grabPos); ...