UnityShader——屏幕空间的能量罩(模拟守望先锋温斯顿的能量罩)

来源:互联网 发布:易语言播放音乐源码 编辑:程序博客网 时间:2024/05/04 02:09

前言:

因为是第一次写技术博客可能有些不到位请谅解,废话不多说就先上最终效果图了(PS.可以看出效果图和游戏中还是有差距的,因为Shader刚入门所以有什么不足望指出)。主要的原理是:屏幕空间深度图,纹理动画,遮罩纹理,Blinn-Phong光照模型。(Ps,代码思路主要参考腾讯大神Mya喵神写过的一个的Shader)



所需数学知识:

模型空间到裁剪空间的变换,详情请见冯乐乐的Shader入门到精通4.6.7章节,因为冯乐乐在自己的github上已经放出了第四章的pdf所以这里直接粘一个链接了。


主要的原理:

首先我们要先了解UnityShaderLab中的深度图是如何渲染出来的,在Unity中会直接获取深度缓存或是着色器替换技术,深度图的渲染是需要通过ShadowCaster的Pass来渲染的,也就是说如果没有这个Pass的物体是不会被渲染到深度图中的,而通常透明的物体是没有ShadowCaster这个Pass的(也可以让透明物体有,但是透明物体也会投射阴影),所以只要设置好RenderType(也就是"RenderType"="Transparent"),就可以让我们的能量罩不出现在深度图中,我们可以从下面的图中看出在LinearEyeDepth中的深度图如图所示,深度图中没有出现能量罩(看着是一片黑仔细看,还不行的话再仔细→ →)。


然而只是这样是不够的,我们还可以通过Unity内置的函数o.scrPos = ComputeScreenPos ( o.pos )来得到Clip Space的深度值了,但是需要注意的是Clip Space空间的z值的大小是从-Near至Far的(详情见下图),而深度图中LinearEyeDepth后的范围是Near至Far的,所以我们要再次借助便利的Unity内置函数COMPUTE_EYEDEPTH(o.scrPos.z),来将o.scrPos.z的值转化到Near至Far。

机智的小伙伴应该已经看出来了,在深度图中没有显示出来的能量罩的深度值,却可以通过获取Clip Space的z值来获取,所以我们只要比较这两个值就可以判断出能量罩与别的物体的交汇位置,也就是当深度图中的深度值等于Clip Space的z值的时候是交汇的,于是我们就可以通过这个来做出相交高光的效果了(也就是能量罩与别的物体相交的时候边缘会有蓝白色)。

因为这个文章主要是讲深度图部分,所以纹理动画,Mask和Blinn-Phong就不提了(其实是懒得打字了orz)。


源码:

首先我们需要一个C#脚本来设置相机的深度图模式。

using System.Collections;using System.Collections.Generic;using UnityEngine;public class NewBehaviourScript : MonoBehaviour {        private Camera myCamera;    public Camera camera    {        get        {            if (myCamera == null)            {                myCamera = GetComponent<Camera>();            }            return myCamera;        }    }    void OnEnable()    {        camera.depthTextureMode |= DepthTextureMode.Depth;    }    }
其次到了我们的主角Shader部分。

Shader "MyShader/XiangJiaoGaoGuang" {Properties {_MainColor ("Color" , Color) = (1,1,1,1)_HighlightColor("HighlightColor" ,Color) = (0,0,1,1)_EdgePow("Threshold" , Range(0 , 5)) = 0.5_RimNum("Rim" , Range(0 , 5)) = 1_MainTex("Main Tex" , 2D) = "white"{}_MaskTex("Mask Tex" ,  2D) = "white" {}_speed("Speed" ,Range(0 , 2)) = 1.0}SubShader {Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "DisableBatching"="True"}Pass{Tags { "LightMode"="ForwardBase" }Blend One OneZWrite OffCull OffCGPROGRAM#include "UnityCG.cginc"#pragma vertex vert#pragma fragment frag#define UNITY_PASS_FORWARDBASE        #pragma multi_compile_fwdbasefloat4 _MainColor;float4 _HighlightColor;sampler2D _CameraDepthTexture;float _EdgePow;sampler2D _MainTex;float4 _MainTex_ST;sampler2D _MaskTex;float _speed;float _RimNum;struct a2v{float4 vertex:POSITION;float3 normal:NORMAL;float2 tex:TEXCOORD0;};struct v2f{float4 pos:POSITION;float4 scrPos:TEXCOORD0;half3 worldNormal:TEXCOORD1;half3 worldViewDir:TEXCOORD2;float2 uv:TEXCOORD3;};v2f vert (a2v v ){v2f o;o.pos = UnityObjectToClipPos ( v.vertex );o.scrPos = ComputeScreenPos ( o.pos );float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; o.worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos));o.worldNormal = UnityObjectToWorldNormal(v.normal); o.uv = TRANSFORM_TEX(v.tex , _MainTex);COMPUTE_EYEDEPTH(o.scrPos.z);return o;}fixed4 frag ( v2f i ) : SV_TARGET{//纹理动画和Mask部分,主要作用是实现扫描效果还有六边形图案fixed mainTex = 1 - tex2D(_MainTex , i.uv).a;fixed mask = tex2D(_MaskTex , i.uv + float2(0 , (_Time.y)*_speed)).r;fixed4 finalColor = lerp(_MainColor , _HighlightColor , mainTex);finalColor=lerp(fixed4(0,0,0,1),finalColor,mask);//获取深度图和clip space的深度值float sceneZ = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.scrPos)));float partZ = i.scrPos.z;//diff为比较两个深度值,rim为Phong:在边缘位置加上一层_HighlightColor的颜色 float diff = 1-saturate((sceneZ-i.scrPos.z)*4 - _EdgePow);half rim = pow(1 - abs(dot(normalize(i.worldNormal),normalize(i.worldViewDir))) , _RimNum);//最后通过插值混合颜色finalColor = lerp(finalColor, _HighlightColor, diff);finalColor = lerp(finalColor, _HighlightColor, rim);return finalColor;}ENDCG}}}

源文件:

https://github.com/Porco24/UnityShaderEnergyShield