Unity下的日式卡通渲染实现-阴影篇(二)

这边文章讲述的是项目中用到的一些卡通渲染阴影相关技术。

一、SDF面部阴影

SDF这个概念具体是什么意思了?可以去观看闫令琪在B站上的101课程,有一节专门讲述了SDF的定义和混合SDF能够产生什么效果。其实SDF面部阴影基本的思想就是混合面部的SDF得到一张阴影阈值图,然后利用这张阴影阈值图实现二维的阴影渲染。
比较详细的解释可以参考这篇文章,卡通渲染之基于SDF生成面部阴影贴图的效果实现(URP)。

1.1 如何计算SDF

根据SDF的定义(到边界的最短有符号距离,形状内部为负,外部为正),计算二维的SDF其实很容易,可以使用C++写程序离线处理图片得到对应的SDF图。不过,我们现在需要的是更进一步的阴影阈值图。

1.2 如何计算阴影阈值图

特定角度的SDF阴影图
阴影阈值图实际上是根据上述的多个角度的阴影图,首先计算每个阴影图的SDF图;然后将所有的SDF图递归混合起来,最终的输出就是阴影阈值图。
如何混合SDF图了?根据前后2个SDF图的在对应像素点的差异值来插值前后2个SDF图。
不过这些阴影图是有一定的要求的:图片必须连续且后一张必须可以覆盖前一张(可以是暗部覆盖也可以是亮部覆盖,但只能是一种)。
这个离线计算阴影阈值图的程序网上已经有人给出来了:如何快速生成混合卡通光照图。

1.3 如何渲染SDF面部阴影

文章卡通渲染之基于SDF生成面部阴影贴图的效果实现(URP)给出的Shader代码基本上问题不大,不过有2个比较明显的问题。
朝向问题:美术出的资源坐标系以及顶点位置不一定是朝着Unity局部坐标系的正Y轴,最好使用脚本传入正Y轴,否则有些模型的阴影就会反了。
左右判断问题:经过验证应该用right去判断还不是left,同样最好是用脚本传入,否则阴影过渡可能出现问题。
最后的if判断可以省略:直接用smoothstep计算bias即可达到效果。
最终的效果如下:

二、自阴影

自阴影实际上是一种ShadowMap的变形,默认的ShadowMap会应用到所有开启了阴影投射的问题上。如果角色还使用这个ShadowMap可能会造成分辨率不够,比如阴影不够清晰、阴影锯齿严重等。因此,对所有的角色重新投影到一个新的ShadowMap上,然后利用这个新的ShadowMap计算自阴影。

2.1 收集所有激活的角色

给每个角色增加一个MonoBehavior脚本,该脚本激活的时候收集角色的包围盒,角色删除时候取消
对应的包围盒。对所有的角色包围盒计算一个并集,将该并集作为正交相机的渲染范围去渲染下一步的阴影Pass。

2.2 自定义ShadowCastPass

增加Urp的自定义Pass,该Pass的渲染模板是额外的ShadowMap对应的RT。在该Pass执行的时候去收集上述的角色包围盒并集,同时将并集作为正交相机的渲染范围,然后去渲染自定义ShadowTag的Pass。
Shader内的阴影Pass实现跟Urp默认的阴影Pass基本一致。

2.3 渲染自阴影

自阴影的渲染基本与传统的ShadowMap一致。不过,需要注意的是阴影投影矩阵已经变化了,需要从脚本中传入Shader。同时,采样阴影贴图的z值需要增加偏移参数去调整,以获得好的效果。美术可能还需要去控制特定区域的阴影强弱,比如可以使用顶点色来控制阴影强度。
效果如下图:
Unity下的日式卡通渲染实现-阴影篇(二)_第1张图片

三、深度边缘阴影

深度边缘阴影和上一篇讲的深度边缘光的原理类似,都必须利用深度贴图来判断当前像素处于边缘
的程度。同样,角色Shader中需要增加DepthOnly来输出深度到深度贴图了。
效果如下图:
Unity下的日式卡通渲染实现-阴影篇(二)_第2张图片
对比自阴影的效果图,可以看到在细节的遮挡区域阴影效果得到了进一步的增强。

四、默认的Urp阴影

默认的Urp阴影决定的是主灯的阴影效果,比如角色走入主方向光形成的阴影区域内,那么角色的亮度是否需要做一定的调整了?
在urp的shader源码中,这个阴影体现为mainlight的shadowAttenuation大小,该值会去缩放主灯的颜色亮度。如果直接使用该值去缩放灯光,那么会出现一定的自遮挡的阴影,而且这个阴影很丑。因此,可以使用这个shadowAttenuation去缩放卡通着色后的结果,从而体现出角色在不同光照区域有一定的表现差异。
不过,如果采用非bake indirect的光照烘焙方式,烘焙后场景的静态物体就不会投影阴影了。因此,这个时候通过shadowmap得到的实时阴影就是不正确的,表现就会看起来很怪异。
默认的Urp阴影决定的是主灯的阴影效果,比如角色走入主方向光形成的阴影区域内,那么角色的亮度是否需要做一定的调整了?
在urp的shader源码中,这个阴影体现为mainlight的shadowAttenuation大小,该值会去缩放主灯的颜色亮度。如果直接使用该值去缩放灯光,那么会出现一定的自遮挡的阴影,而且这个阴影很丑。因此,可以使用这个shadowAttenuation去缩放卡通着色后的结果,从而体现出角色在不同光照区域有一定的表现差异。
不过,如果采用shadowmask的光照烘焙方式,烘焙后场景的静态物体就不会投影阴影了。因此,这个时候通过shadowmap得到的实时阴影就是不正确的,表现就会看起来很怪异。
有什么解决办法了?
方法一:一种简单的方式是让场景美术摆放一些大的不烘焙的隐藏面片来投影实时阴影。
方法二:另一种方式采用遮挡探针(和Unity的光照探针是一起的,实质上就是布置光照探针),类似光照探针一样,可以让动态的物体采用烘焙的阴影信息,不过这个时候主角就不采样shadowmap了。
方法三:改成DistanceShadowMask方式,这样子烘焙后的静态物体也会投影实时阴影,可以避免布置光照探针。不过性能损失会增大不少。

五、阴影如何跟着色结合?

对于每种阴影可以指定一个阴影颜色,上述的各种算法只是计算对应的阴影强度。最终,使用阴影强度插值卡通着色结果和阴影颜色,就可以得到应用阴影后的效果。

六、参考资料

卡通渲染之基于SDF生成面部阴影贴图的效果实现(URP)
如何快速生成混合卡通光照图

你可能感兴趣的:(Unity,渲染,unity,游戏引擎,图形渲染)