【Cel-Shading】基于SDF贴图的面部阴影实现

一般来说,卡渲都会对面部阴影进行单独的处理,使得面部阴影效果更贴近卡通绘画的习惯。

本文中采取使用SDF贴图的方式来形成面部阴影,个人觉得比修改法线更简单快速。

效果展示

先上个效果动图:

img

与直接用漫反射模型计算出来的阴影做比较:

img

在一些特殊角度的时候,SDF贴图生成的效果也比直接用漫反射计算出来的好,更符合卡通人脸的效果:

image-20210817004047448

SDF阴影贴图的结果

image-20210817004323091

直接用漫反射计算的结果

SDF算法介绍

首先我们需要搞清楚SDF算法生成的贴图是什么,为什么他不像写死在上AO的阴影,可以动态帮我们实现阴影效果?

SDF算法可以实现图像之间的平滑过渡,只需要对两张SDF进行lerp的插值,就可以得到视觉上的平滑过渡效果。(本质上是距离间的平滑过渡)

SDF面部阴影图的获得

知乎上有大佬写了一个脚本,可以直接生成这种面部光照图,要对图的效果进行修改也很容易,修改完example文件夹里的东西后再次运行脚本就可以啦~

在此就不重复造轮子,偷懒直接用大佬的脚本生成的光照图,感谢大佬。

在运行完脚本后,我们获得了这样一张混合光照图:

wow

接下来我们要做的就是在我们的项目中使用上这张混合光照图。

具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#if _Face 
//用一个toggle来控制是否进行SDF的光照控制
float4 left_FaceTex = tex2D(_FaceTex,i.uv);
float2 right_uv = float2(1-i.uv.x,i.uv.y);//光照左右方向各来一遍,还得是水平对称
float4 right_FaceTex = tex2D(_FaceTex,right_uv);

float2 Left = normalize(mul(unity_ObjectToWorld, float3(1, 0, 0)).xz); //世界空间角色正左侧方向向量
float2 Front = normalize(mul(unity_ObjectToWorld, float3(0, 0, 1)).xz); //世界空间角色正前方向向量
float2 light_Dir = normalize(worldLightDir.xz);
float angel = 1- clamp(0,1,dot(Front,light_Dir)*0.5 +0.5);
float tex_direct = dot(light_Dir,Left) > 0 ? right_FaceTex.r : left_FaceTex.r;

float isShadow = step(tex_direct,angel);
float bias = smoothstep(0,_LerpMax , abs(angel - tex_direct));

if(angel > 0.99 || isShadow ==1){
diffcol = lerp(diffcol, diffcol *_SpecularColor.rgb, bias);
}
#endif