实现原理
1.先用一个Pass将原来的物体沿着该物体的顶点法线外移,使它变得比之前大
2.使用Outline的颜色填充整个物体,并且剔除正面的渲染,得到一个放大的背面
3.用第二个Pass正常渲染渲染,第二次正常渲染会挡住第一个渲染的背面,但是第一个Pass变大导致部分挡不住,则挡不住的那部分就变成了该物体的外描边
代码图如:
//法线外扩实现法
Shader "Custom/Outline"
{
Properties
{
_MainTex("MainTex", 2D) = "white" {}
_Outline("Outline",float) = 0.1
_OutlineColor("OutlineColor",Color) = (0,0,0,1)
}
SubShader
{
Tags { "RenderType" = "Transparent" } //Transparent可以在opaque之后画,否则会被天空盒遮住
LOD 100
Pass
{
Name "Outline_Fst"//第一个Pass,法线外扩+正面剔除
Cull Front
ZWrite Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;
};
float _Outline;
float4 _OutlineColor;
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
//把法线转换到视图(相机)空间,UNITY_MATRIX_IT_MV为模型视图矩阵的逆转置矩阵
float3 vnormal = mul((float3x3)UNITY_MATRIX_IT_MV,v.normal);
//把法线转换到投影(剪裁)空间
float2 clip_normal_xy = mul((float2x2)UNITY_MATRIX_P,vnormal.xy);
//向法线方向延伸
o.vertex.xy = o.vertex.xy + clip_normal_xy * _Outline;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
return _OutlineColor;
}
ENDCG
}
Pass
{
Name "Outline_Sec" //第二个Pass,正常渲染
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
struct appdata
{
float4 vertex : POSITION;
float2 uv :TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
float2 uv:TEXCOORD0;
};
sampler2D _MainTex;
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
return tex2D(_MainTex,i.uv);
}
ENDCG
}
}
}
核心点:
1.渲染类型改为Transparent目的是在opaque之后画,否则会被天空盒(opaque)遮住
2.法线从模型空间转到视图(相机)空间,使用的是UNITY_MATRIX_IT_MV而不是UNITY_MATRIX_I_MV,原因是非等比缩放情况下,使用和顶点变换一样的UNITY_MATRIX_I_MV会导致法线异常,我在非等比缩放模型法线转换一文中有写过详细推导,此处只给结论:法线的变换矩阵为模型变换矩阵的逆转置矩阵
效果图:
优点:不需要写深度缓冲
缺点:棱角分明的物体(顶点非连续)的物体会穿帮
效果图:
总结,项目中的模型一般来说不会出现棱角分明的情况,所以此方法是项目中最常使用的方法,真正的问题在于此方法是硬描边,无法达到类似光(带模糊效果)的描边效果
文章评论