描边效果
希望游戏的整体风格偏向卡通风格,因此需要对模型进行描边处理。
一开始采用的是双Pass的顶点扩张的方法进行描边,但效果并不理想,因为有些模型的法线并不光滑,导致出现描边断层,比如立方体的描边,由于每个顶点被三个三角面共享,且法线方向并不相同,导致顶点分离:
而且利用双Pass进行渲染,会导致无法批处理优化,该游戏的场景由瓦片元素生成,虽然数量较多,但模型种类并不多,大多数模型和纹理都是一致的,所以易与批处理。因此描边方案仍旧基于后处理,利用深度差值来判断轮廓。比如一个放置于空场景的模型,模型本身的深度差异并不大,但与无限远的空场景的深度有着明显差异,所以利用像素的深度差值可以判断是否存在轮廓。
由于深度纹理采用物体的阴影投射Pass获得,所以对于那些不希望进行描边的物体,可以删除其阴影投射Pass,比如下图中的狼牙小球。
步骤
- 设置开启获得深度纹理
cam.depthTextureMode |= DepthTextureMode.DepthNormals;
//开启后会将纹理数据传给shader 的sampler2D _CameraDepthTexture变量
- 定义参数变量
public Color edgeColor = Color.black;
public float sampleDistance = 1.0f;
public float sensitivityDepth = 1.0f;
- shader中获取存储纹素大小的变量(需要获取渲染纹理的单个像素在UV坐标的距离)
Half4 _MainTex_TexelSize
- shader中获取纹理仿射系数
Float4 _MainTex_ST
- 采样算子
采用Roberts算子,即计算对角差值
- 实现OnRenderImage
void OnRenderImage(RenderTexture source, RenderTexture destination)
{
if(material != null)
{
material.SetColor("_EdgeColor", edgeColor);
material.SetFloat("_SampleDistance", sampleDistance);
material.SetVector("_Sensitivity", new Vector4(sensitivityNormals, sensitivityDepth, 0.0f, 0.0f));
Graphics.Blit (source, destination, material);
}
else
{
Graphics.Blit(source,destination);
}
}
- 实现shader
Shader "Unlit/edgeDetectByDepth"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_EdgeColor ("Edge Color", Color) = (0, 0, 0, 1)
_SampleDistance ("Sample Distance", Float) = 1.0
_Sensitivity ("Sensitivity", Float) = 1//检测灵敏度
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
CGINCLUDE
#include "UnityCG.cginc"
sampler2D _MainTex;
half4 _MainTex_TexelSize;//x=1/width y=1.0/height z=width w =height
float4 _MainTex_ST;
float _SampleDistance;
fixed4 _EdgeColor;
half _Sensitivity;
sampler2D _CameraDepthTexture;
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
half2 uv[5]: TEXCOORD0;
float4 vertex : SV_POSITION;
};
//利用差值大小判断是否存在边缘
half isBeEdge(half sample1Depth, half sample2Depth)
{
//计算差值
float diffDepth = abs(sample1Depth - sample2Depth) * _Sensitivity;
//判断差值,根据距离缩放设置阈值
int isBeEdgeByDepth = diffDepth < 0.1;//(0.1 * sample1Depth);
return isBeEdgeByDepth ? 1.0 : 0.0;
}
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
half2 uv = TRANSFORM_TEX(v.uv, _MainTex);
o.uv[0] = uv;
//判断纹理坐标空间,如果原点在上面需要变换
#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0)
uv.y = 1 - uv.y;
#endif
//利用roberts算子 获得对角坐标
o.uv[1] = uv + _MainTex_TexelSize.xy * half2(-1,1) * _SampleDistance;//左上
o.uv[2] = uv + _MainTex_TexelSize.xy * half2(1,-1) * _SampleDistance;//右下
o.uv[3] = uv + _MainTex_TexelSize.xy * half2(1,1) * _SampleDistance;//右上
o.uv[4] = uv + _MainTex_TexelSize.xy * half2(-1,-1) * _SampleDistance;//左下
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//采样深度和法线纹理
half sample1 = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv[1]);
half sample2 = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv[2]);
half sample3 = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv[3]);
half sample4 = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv[4]);
half isEdge = 1.0;
isEdge *= isBeEdge(sample1, sample2);
isEdge *= isBeEdge(sample3, sample4);
return lerp(_EdgeColor, tex2D(_MainTex, i.uv[0]), isEdge);
}
ENDCG
ZTest Always Cull Off ZWrite Off
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
}