永远相信美好的事情即将发生 😊!

卡通渲染之基于SDF生成面部阴影贴图的效果实现(URP)

分享 Mavis 19℃ 0评论

看了很多大佬的教程:

https://zhuanlan.zhihu.com/p/279334552   这篇讲了图的制作流程(大概思路)

http://www.codersnotes.com/notes/signed-distance-fields/   这篇讲了SDF的算法

https://zhuanlan.zhihu.com/p/356185096 这是之前一个群里的大佬写的脚本,可以生成这种图

好了,接下来讲讲我经过学习后如何完成这部分内容的吧

背景:

哎,最近记性不好,明明记得自己下了大佬的代码也运行过,但当我想使用的时候我竟然完全记不得我下载到哪儿了,还记得那晚上浪费了一整晚的时候在找这个文件,又不敢问群里的大佬……我也不知道为啥自己怂的很

所以第二天就准备……自己写一个算了,花了两三天才终于搞定,然而今天我突然就发现了脚本……一看速度的对比我就知道,我的代码还有问题,虽然实现上没有问题,但是速度差那么多,说明我做了很多无用功。

我写的第一个版本主要就是按照这篇参考文献来做的,根据插值次数算出每一张插值图,然后加起来平均,,,然而开了多线程,算起来依旧很慢,插值5次差不多能达到大佬的效果,但是速度却要花上几分钟(大佬默认的图有12M)

看到大佬代码的速度,,我突然醒悟了,修改后的代码终于可以在二十几秒内解决战斗,虽然比起大佬的代码还是慢了点……

正文:

对于要生成这样一张阈值图:

要算的当然就是每个像素位置的阈值

输入为几个特定脚本的阴影图:

根据我醒悟后的观察和理解,我发现了下面几个东西:

首先,可以观察到所有图的同一个像素位置中,只会有一次值的突变(这个其实很好理解,脸上的一个部分,当灯光旋转过来的时候,只会有一次从暗部变成亮部,不管跟其他部分比是提前还是延后)

这就可以得到输入图片的要求:图片必须连续且后一张必须可以覆盖前一张(可以是暗部覆盖也可以是亮部覆盖,但只能是一种)

然后就可以按顺序操作了,先生成对应的sdf图(这里有个坑,那个算法的源代码中最后对dist进行了缩放,需要注意,这里如果弄得太大,会导致后面计算阈值的时候缺少渐变的效果)

至于怎么具体计算阈值,其实理解起来很简单,就是根据该像素点在前后两张图中采样的sdf值的差值去插值前后两张图的阈值,我尽量把自己的想法讲明白

我们要生成一张0-255的阈值图,在ps中阈值为1或255的时候看到的图片一定是第一张或者最后一张,那么自然而然中间这几张的阈值就可以计算出来(因为图片是均匀变化的),先用平均算出一个间隔为多少,然后得到index就可以得到每一张原始图对应的阈值了。

对于该阈值图的某个像素点来说,例如该点值为128,当ps中阈值调到128,就会发现该点在边界上,对于这个像素点来说,只会有一次处于边界,或者永远不处于边界(一直被照亮或者一直处于黑暗),需要找到在哪两张图中间时该像素点会在某一个阈值条件下处于边界,然后对于已知前后AB两张图对应阈值的情况下,该点的阈值计算就很简单了:前一张图的阈值+用该点离前一张图边界的距离/前后两张图的距离差

距离的相关信息从对应的sdf图中就可以获得了,这里要提的就是因为会计算距离差,如果之前那个缩放做的太猛了,距离差可能会在一定区域内成为定值,那样渐变的效果就不够强烈了,所以可以自行调整得到符合要求的缩放效果。

虽然不知道自己讲清楚了没有,但是我自己是明白了这个图的生成过程,至于SDF图的算法,大概懂了,但我还是比较难讲明白,就不班门弄斧了,至于生成这种图的代码,我也就不放了,还是大佬的香。

 

当然了,图是一个准备工作,我们需要做的还是要把图用到shader里面

先看一下效果(阴影图我自己画的,可能位置啥的不是很准确):

可以看到效果是没太大问题的了

shader代码也不难,https://zhuanlan.zhihu.com/p/279334552 这里面也讲了,我就再发一遍我的代码吧,嘻嘻

float isSahdow = 0;
//这张阈值图代表的是阴影在灯光从正前方移动到左后方的变化
half4 ilmTex = tex2D(_IlmTex, input.uv);
 //这张阈值图代表的是阴影在灯光从正前方移动到右后方的变化
half4 r_ilmTex = tex2D(_IlmTex, float2(1 - input.uv.x, input.uv.y));
float2 Left = normalize(TransformObjectToWorldDir(float3(1, 0, 0)).xz);	//世界空间角色正左侧方向向量
float2 Front = normalize(TransformObjectToWorldDir(float3(0, 0, 1)).xz);	//世界空间角色正前方向向量
float2 LightDir = normalize(light.direction.xz);
float ctrl = 1 - clamp(0, 1, dot(Front, LightDir) * 0.5 + 0.5);//计算前向与灯光的角度差(0-1),0代表重合
float ilm = dot(LightDir, Left) > 0 ? ilmTex.r : r_ilmTex.r;//确定采样的贴图
//ctrl值越大代表越远离灯光,所以阴影面积会更大,光亮的部分会减少-阈值要大一点,所以ctrl=阈值
//ctrl大于采样,说明是阴影点
isSahdow = step(ilm, ctrl);
bias = smoothstep(0, _LerpMax, abs(ctrl - ilm));//平滑边界,smoothstep的原理和用法可以参考我上一篇文章
if (ctrl > 0.99 || isSahdow == 1)
    diffuse = lerp(diffuse , diffuse * _ShadowColor.xyz ,bias);

注释我觉得都写的比较清楚了,如果有问题的,欢迎大家指正!


Mavis , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:卡通渲染之基于SDF生成面部阴影贴图的效果实现(URP)
喜欢 (0)
发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址