从下边两张图来看,这种漫画风格的线条出现在贴图存在线条的地方,同时物体的边缘也有这种描边效果。前者可以用边缘检测,后者可以用深度图。
边缘检测
边缘检测可以用卷积进行计算,具体可以参考这篇文章
https://gameinstitute.qq.com/community/detail/121016gameinstitute.qq.com fixed luminance(fixed4 color){
return color.r * 0.33 + color.g * 0.33 + color.b * 0.33;
}
half edge(half2 uv){
const half Gx[9] = {-1, 0, 1,
-2, 0, 2,
-1, 0, 1};
const half Gy[9] = {-1, -2, -1,
0, 0, 0,
1, 2, 1};
half dx = 0, dy = 0;
half center = luminance(tex2D(_MainTex, uv));
for (int i = -1;i < 2;i++){
for (int j = -1;j < 2;j++){
half x = _MainTex_TexelSize.x * i;
half y = _MainTex_TexelSize.y * j;
half color = luminance(tex2D(_MainTex, uv + half2(x, y)));
dx += Gx[(i + 1) * 3 + j + 1] * color;
dy += Gy[(i + 1) * 3 + j + 1] * color;
}
}
return (abs(dx) + abs(dy)) * 10;
}
fixed4 frag (v2f i) : SV_Target{
// -------------------
float rate = 1;
if (edge(i.uv) > _Edge)
rate = 0;
col = col * diff * _Diffuse + spec * _Specular;
return col * rate;
}
放个对比图
从上图来看,像胸部这些纹理较为单一的贴图部位描边的效果极好。就是脸部比较惨不忍睹。当然,可以通过调节阈值来控制效果。
深度图检测物体边缘
深度图的利用是在相机渲染时调用的,相当于一个后处理特效。
[ExecuteInEditMode]
public class PostEffect : MonoBehaviour
{
public Material mat;
private void OnRenderImage(RenderTexture source, RenderTexture destination) {
Graphics.Blit(source, destination, mat);
}
}
将这个脚本挂载到主摄像机上,并添加一个材质。在预览时便可以查看效果
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Edge ("Edge", Range(0, 0.2)) = 0.1
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
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;
float2 dxy : TEXCOORD1;
};
sampler2D _MainTex;
sampler2D _CameraDepthTexture;
float2 _MainTex_TexelSize;
float _Edge;
fixed luminance(fixed4 color){
return color.r * 0.33 + color.g * 0.33 + color.b * 0.33;
}
half edge(half2 uv, float2 xy){
half offset = 0;
half center = Linear01Depth(UNITY_SAMPLE_DEPTH(tex2D(_CameraDepthTexture, uv)));
for (int i = -1;i < 2;i++){
for (int j = -1;j < 2;j++){
offset += center -
Linear01Depth(UNITY_SAMPLE_DEPTH(tex2D(_CameraDepthTexture, uv +
half2(i * xy.x, j * xy.y))));
//offset += abs(center - luminance(tex2D(_MainTex, uv + half2(i, j))));
}
}
return abs(offset) * 10000;
}
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
o.dxy = _MainTex_TexelSize.xy;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 color = tex2D(_MainTex, i.uv);//fixed4(depth, depth, depth, 1);
float rate = saturate(edge(i.uv, i.dxy));
rate = rate > _Edge ? 0 : 1;
return color * rate;// fixed4(rate, rate, rate, 1);
}
ENDCG
}
}
此处有几个关键点,一是深度值的获取,分别是深度图的声明、采样、非线性转换;二是像素点对应 UV 坐标的获取,这里使用了_MainTex_TexelSize
,对应的是单位像素的长宽,需要先声明。深度图是自动生成的,使用前需要先声明,名字固定为_CameraDepthTexture
,采样之后再使用UNITY_SAMPLE_DEPTH
采样,最后用Linear01Depth
将深度值转换到 0 ~ 1 区间。检测方法也很简单将周围的像素深度值分别去减当前像素深度值,如果当前像素周围很平滑,那么结果必然趋近于零,反之则变大,尤其是物体最外侧的像素点。
效果如下图
有意思的是,在将阈值调低之后,可以看见模型的三角形