shader描边的方式很多,我们这里重点考虑其中一种。
一个pass沿着顶点法线挤出,剔除正面
一个pass正常渲染,剔除背面
第一种
下面贴出只是单纯的沿着将顶点沿着法线挤出的代码和效果(只贴出顶点挤出pass)
Tags { "RenderType" = "Opaque" }
LOD 100
pass
{
Tags { "LightMode" = "Always" }
Cull Front
ZWrite On
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
float _Outline;
float _Factor;
struct v2f
{
float4 pos: SV_POSITION;
};
v2f vert(appdata_full v)
{
v2f o;
float4 pos = v.vertex; //转换到视图空间
float3 normal = v.normal;//将法线转换到视图空间
pos = pos + float4(normalize(normal), 0) * _Outline;//将顶点沿着法线方向延伸或者收缩
o.pos = UnityObjectToClipPos(pos);
return o;
}
float4 frag(v2f i): COLOR
{
return float4(1, 0, 0, 0);
}
ENDCG
}
就像下图看到的那样,cube的描边会有很严重的割裂现象,这是因为只是单纯的沿着法线挤出,效果就好像将他的面往顶点的Z方向拉出去,这种效果显然不是很不好。
第二种
既然会出现这种事情,那么我们就会想到想发现往回缩一点是不是就能让他们铆合起来呢。
pass
{
Tags { "LightMode" = "Always" }
Cull Front
ZWrite On
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
float _Outline;
float _SubNormal;
struct v2f
{
float4 pos: SV_POSITION;
};
v2f vert(appdata_full v)
{
v2f o;
float4 pos = mul(UNITY_MATRIX_MV, v.vertex); //转换到视图空间
float3 normal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);//将法线转换到视图空间
normal.z = -_SubNormal;//将Z轴进行收缩
pos = pos + float4(normalize(normal), 0) * _Outline;//将顶点沿着法线方向延伸或者收缩
o.pos = mul(UNITY_MATRIX_P, pos);
return o;
}
float4 frag(v2f i): COLOR
{
return float4(1, 0, 0, 0);
}
ENDCG
}
效果也不是很好,描边部分隔裂感依旧很重。
第三种
既然我们一开始描边是顺着法线方向挤出,那如果朝着顶点的方向会不会好一点,于是就想到了让挤出方向顺着顶点的方向和法线的方向做过度。
pass
{
Tags { "LightMode" = "Always" }
Cull Front
ZWrite On
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
float _Outline;
float _Factor;
struct v2f
{
float4 pos: SV_POSITION;
};
v2f vert(appdata_full v)
{
v2f o;
float3 dir = normalize(v.vertex.xyz);
float3 dir2 = v.normal;
float D = dot(dir, dir2);
dir = dir * sign(D);
dir = dir * _Factor + dir2 * (1 - _Factor);
v.vertex.xyz += dir * _Outline;
o.pos = UnityObjectToClipPos(v.vertex);
return o;
}
float4 frag(v2f i): COLOR
{
return float4(1, 0, 0, 0);
}
ENDCG
}
效果来看差不多满足我们的需求了。