我们在玩游戏的时候,经常会看到这样的效果:当我们的角色走到墙壁后边的时候,角色被挡住的部分会半透明高亮显示,没有被挡住的部分会正常显示。
这样的效果是怎么实现的呢?主要分为以下两部分:
1.默认情况下,模型如果有一部分被其他物体遮挡,在进行深度测试的时候是不会被通过的,所以最终不会被渲染出来。如果想让被遮挡的部分渲染出来,那就只能让他通过深度测试,然后跟已存在的像素进行混合,从而产生半透明效果。
2.模型未被遮挡的部分走正常渲染流程即可。
开放变量
Shader "Custom/OcclusionShader"{
Properties
{
//part of being occlusion _Color("Occlusion Color", Color) = (0,1,1,1)
_Width("Occlusion Width", Range(0, 10)) = 1
_Intensity("Occlusion Intensity",Range(0, 10)) = 1
//ordinary part _Albedo("Albedo", 2D) = "white"{}
_Specular("Specular (RGB-A)", 2D) = "black"{}
_Normal("Nromal", 2D) = "bump"{}
_AO("AO", 2D) = "white"{}
}
上半部分的变量用于控制被遮挡部分。_Color用于控制颜色;_Width控制边缘高亮的宽度;_Intensity控制高亮的亮度。
下半部分是正常PBR Specular工作流的部分,为了节省资源,我把Shininess放在了_Specular的a通道里。
被遮挡部分
SubShader{
Tags{"Queue" = "Transparent"}
Pass
{
ZTest Greater
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert #pragma fragment frag #include "UnityCG.cginc"
struct v2f
{
float4 worldPos : SV_POSITION;
float3 viewDir : TEXCOORD0;
float3 worldNor : TEXCOORD1;
};
fixed4 _Color;
fixed _Width;
half _Intensity;
v2f vert(appdata_base v)
{
v2f o;
o.worldPos = UnityObjectToClipPos(v.vertex);
o.viewDir = normalize(WorldSpaceViewDir(v.vertex));
o.worldNor = UnityObjectToWorldNormal(v.normal);
return o;
}
float4 frag(v2f i) : SV_Target
{
half NDotV = saturate( dot(i.worldNor, i.viewDir));
NDotV = pow(1 - NDotV, _Width) * _Intensity;
fixed4 color;
color.rgb = _Color.rgb;
color.a = NDotV;
return color;
}
ENDCG
}
这一部分主要做了以下操作:
1.我们想要被遮挡的部分透明显示,所以把shader的渲染序列设为Transparent
2.我们想要被遮挡的部分仍然通过深度测试,所以把测试方式改为Greater,也就是说当前面有物体挡住它的时候,它就会显示出来
3.我们只需要使用普通的透明混合方式即可,所以使用Blend SrcAlpha OneMinusSrcAlpha
4.考虑到性能,我们在这一部分使用顶点-片段着色器。顶点着色器中使用内置的appdata_base结构体作为输入,然后输出包含“裁切空间顶点坐标”、“世界空间视线向量”和“世界空间法线向量”的v2f结构体。
5.在片段着色器中,我们把视线向量和法线向量进行点乘,然后使用saturate函数得到范围[0,1]内的数值,从而得到中间白边缘黑的效果。1减去这个结果,得到反相效果,也就是边缘白,中间黑的效果。使用pow函数,控制边缘的宽度,然后乘以一个变量控制边缘最终的亮度,最终输入给透明度,实现半透效果。
6.把_Color输出给颜色。
第二部分
CGPROGRAM
#pragma surface surf StandardSpecular #pragma target 3.0
struct Input
{
float2 uv_Albedo;
};
sampler2D _Albedo;
sampler2D _Specular;
sampler2D _Normal;
sampler2D _AO;
sampler2D _Emission;
void surf(Input IN, inout SurfaceOutputStandardSpecular o)
{
o.Albedo = tex2D(_Albedo, IN.uv_Albedo).rgb;
fixed4 specular = tex2D(_Specular, IN.uv_Albedo);
o.Specular = specular.rgb;
o.Smoothness = specular.a;
o.Normal = UnpackNormal(tex2D(_Normal, IN.uv_Albedo));
o.Occlusion = tex2D(_AO, IN.uv_Albedo).a;
}
ENDCG
}
Fallback "Diffuse"}
第二部分就是普通的Surface Shader了,没什么特殊的,这里就不累述了。