先上一张效果图:
物体在被遮挡后,被遮挡的部分边缘高亮显示,实现这效果大致思路是:
使用2个pass块来渲染这个物体,没有被遮挡的部分正常显示,被遮挡的部分则使用边缘光显示。
这里需要引入一个知识点就是深度值和深度缓存(z-buffer):
在场景中的每一个物体都有自己的深度值,距离摄像机越近深度越小,越远越大;
而在屏幕上显示的每个像素点都有一个深度缓存(z-buffer),会对当前需要渲染像素的深度值进行排序,默认情况下是深度值小的像素覆盖深度值大的像素;
所以说,在渲染队列一样的情况下,近距离的像素也就是深度值小的像素会被渲染。
Unity中提供了ZWrite 和 ZTest对应深度写入和深度测试:
ZTest控制深度测试的模式,有如下参数,默认是LEqual:
ZWrite则用来控制,当测试通过时,是否需要写入到深度缓存(z-buffer)中,On为写入,off不写入,如果未通过测试的话也不会写入。
好了,有上述的解释后,接下来实现一shader的两个pass块,第一个pass用来显示被遮挡的模型(注意代码中的注释解释),第二个pass则正常显示;
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_EdgeColor("Edge Color", Color) = (1,1,1,1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
Pass
{
Name "Edge pass" //显示被遮挡时的pass
ZTest Greater //Greater表示像素点深度值大的通过,这样就可以显示被遮挡住的模型了。
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float4 _EdgeColor;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return _EdgeColor;
}
ENDCG
}
Pass
{
Name "Model pass"
ZTest Less
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
return col;
}
ENDCG
}
}
}
完成上述功能后,显示被遮挡模型的功能就实现啦:
然后在优化一下被遮挡的显示效果,单纯的显示这个颜色显然是不太好看的,这里我们使用到的是边缘光,同时和遮挡的物体做一个透明度混合。
首先是透明度混合,在第一个pass块中加入blend one one:
Pass
{
Name "Edge pass"
ZTest Greater
Blend One One
透明度混合常用的几种方式如下,可以自行测试一下:
Blend SrcAlpha OneMinusSrcAlpha // 传统透明度 Blend One OneMinusSrcAlpha // 预乘透明度 Blend One One // 叠加 Blend OneMinusDstColor One // 柔和叠加 Blend DstColor Zero // 相乘——正片叠底 Blend DstColor SrcColor // 两倍相乘
接下来就是边缘光的实现:
正常来说,物体法线与视线(从顶点至相机的方向)角度越一致,则表示越接近能被玩家看见的中间,物体的边缘一般与视线垂直,所以通过物体法线和视线的点乘即可计算轮廓光,即1-dot(normal,view).
dot(normal,view)的值越到中心越大,现在我们需要的是越到边缘越大,则使用1减去该值即可。
下面添加一些计算边缘光的代码参数:
加入一个控制边缘光范围的参数,
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_EdgeColor("Edge Color", Color) = (1,1,1,1)
_OutLine("OutTime",range(0.5,2)) = 1 //在属性面板中加入一个控制边缘光范围的参数
}
加入法线和视线方向,
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL; //取得模型的法线
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float3 normal : NORMAL; //法线的方向
float3 viewDir : TEXCOORD1;//视线的方向
};
在顶点函数中进行坐标系的转化以及计算视野方向,
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.normal = UnityObjectToWorldNormal(v.normal); //将法线坐标转化到世界坐标
o.viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz); //计算视野的方向
return o;
}
最后在片元函数中计算边缘光的颜色,
fixed4 frag (v2f i) : SV_Target
{
float NdotV = 1 - dot(i.normal, i.viewDir) * _OutLine; //计算边缘光,OutLine越大靠近边缘
return _EdgeColor * NdotV;
}
完成到这一步,遮挡透明的效果就和一开始的动图一样啦~~~
git仓库:https://github.com/Looooooong/ShadersRoom
最后附上完整的shader:
Shader "ShadersRoom/OutLine" {
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_EdgeColor("Edge Color", Color) = (1,1,1,1)
_OutLine("OutTime",range(0.5,2)) = 1
}
SubShader
{
Tags { "RenderType"="Opaque" }
Pass
{
Name "Edge pass"
ZTest Greater
Blend One One
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float3 normal : NORMAL;
float3 viewDir : TEXCOORD1;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float4 _EdgeColor;
float _OutLine;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.normal = UnityObjectToWorldNormal(v.normal); //将法线坐标转化到世界坐标
o.viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz); //计算视野的方向
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float NdotV = 1 - dot(i.normal, i.viewDir) * _OutLine; //计算边缘光,OutLine越大靠近边缘
return _EdgeColor * NdotV;
}
ENDCG
}
Pass
{
Name "Model pass"
ZTest Less
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
return col;
}
ENDCG
}
}
}