管线定制

在这里,我增加了两个单独的pass来实现我们的卡通渲染,这是由于我们既想要延迟的管线的好处,但是延迟又有其本身的局限,并不能很好的支持卡通渲染。这主要是因为延迟渲染的光照计算的延后,每个物体的必要信息都存储在Gbuffer里面,在计算光照的时候我们读取Gbuffer里面的图片进行光照计算。这对卡通渲染来说,由于需要的参数过多,所以如果完全按照延迟的去做,Gbuffer的带宽压力会非常大。所以我们是在延迟渲染中嵌套了一个前向的管线,只对人物进行处理

这也是因为项目的局限性,我们的人物是卡渲,而我们的场景走的是正常的PBR流程。卡通渲染和普通的物体的分开。人物走我们这里加的Cartoonpass 。

由于UE4 的渲染管线的限制,并不能像Unity一样非常轻松的去添加一个自定义的pass。当然,这里的设计是最开始的设计,不透明物体的Cartoonpass在后期版本的光照计算是集成在写Gbuffer的时候计算的。

20210408171402

image.png image.png

着色

我们将拓展材质编辑器,并定义我们需要的变量。我们的参数为:

单光源:Result = Lerp(暗部颜色,亮部颜色,二值化系数) * LightColor / PI + 额外高光部分

二值化系数 = Smoothstep(MidPoint - Blur,MidPoint + Blur,Dot(L,N))

额外高光部分:固定高光+边缘光+GGX高光 image.png image.png

处理偏色

和PBR不同,卡通渲染是个极度依赖原画能力的课题,你没有固定的材质库可用,也没有什么预定参数,决定结果美观的就是贴图的质量,出现偏色是致命的。

然后用它来处理传入的固有色贴图,通过和原本的贴图颜色lerp选择一个合适的混合比例,就可以在最终显示上还原出和原贴图几乎一样的结果。

如果可以回避偏色问题,那么绘制贴图的时候就可以直接采用原画的结果。

ACES ToneMapping 逆运算:3.4475 * color * color * color - 2.7866 * color * color + 1.2281 * color - 0.0056

image.png 所有HDR的卡通渲染游戏都会遇到这个问题:ToneMaping以后饱和度变低了。更糟糕的是纹理的对比度也降低了,导致图片丢失了大量细节,甚至连嘴都看不到了。

着色-二值光照

image.png

中点决定明暗分界线的位置,为0的时候正好对半分割。可以调整成负数,但这样生成的影就会和自投影重合,只有屏蔽掉自投影的时候才有意义。 模糊决定明暗分割的过渡区域。

着色-普通高光

image.png

Lighting.Specular = NoL * SpecularGGX( GBuffer.Roughness, SpecBaseColor, Context, NoL, AreaLight );

目的:这里走的是传统的GGX的高光,只是自这里添加了一个SpecColor.

着色-额外高光

image.png 一般可以使用贴图来指定写实渲染的头发渲染一般用的是各向异性扰动贴图。头发的高光算法一般是kajiya-kai模型,俗称kk,一层高频,一层低频,再加上一个切线扰动贴图达到各向异性效果。但要想使得高光沿着每根发束移动,还需要美术在头发模型的垂直方向进行uv展开。但写实渲染的各向异性其实接近于纯随机,并不需要手绘。所以头发也可以选择不拉直UV,而采用flowmap的方式实现。 而卡通渲染,这个高光的形状既不物理也不重复,无法使用flowmap。如果要做的话,必须把UV拉直然后小心地手绘高光偏移和粗糙度来凑出这个高光形状。(不拉直UV会出现贴图精度不够导致的锯齿)

写实渲染的各向异性其实接近于纯随机,并不需要手绘。我们采用的方式就是放弃光照角度的正确性,其中最简单的做法,就是使用固定贴图。GGXX的头发高光就基本是固定贴图。但完全固定的贴图的贴图感太明显了,为了回避这点可以在视角变化时加一点变化。 image.png

image.png

1.R通道是一个软边的控制图,根据视角和法线的差异来决定高光区的显隐,而高光强度指的是最终结果的总体透明度。 2.如果不希望高光随视角有宽窄的变化,则应该让R通道保持1,利用G通道标记高光的位置。反之则是用R通道表示高光的位置,G通道保持1。 3.高光偏移是一个随视角变化的贴图整体偏移,会沿着UV的V方向移动。颜色越亮偏移幅度越高。

着色-额外高光-视角控制

因为我们的高光是画上去的,但是我们需要根据一些角度来修正我们的效果从而产生一些变化。这里我是改变的限制角度,来控制高光的显示范围。

image.png image.png

着色-边缘光

image.png 跟随光照变化的单侧边缘光的会更好看。如果你想要双侧,可以打另一个光来产生。(我的边缘光是和光源绑定的,生成方向和光照方向有关,这样可以两侧打出不同的边缘光颜色)

边缘光本身还是一个背光的模拟,而不仅仅是一种勾勒边缘的手段。调得过亮过粗,周围却完全没有产生这个背光的光照是不行的。比如你人物处在一个黑乎乎的场景里,整个人却被一个大白边包起来这绝对不行。而如果是白天场景,加上这个就没什么不妥,毕竟你也看不出来它背后到底有光还是没光。但假如白天环境基本是白色的,你却给人包裹一个紫色的背光依然不行。所以边缘光应该是一个光源的属性,而非材质的。材质需要做的只是处理在同一光源下不同部位的差异。

image.png image.png 生成的边缘光会面朝光源方向,并且保持相对物体较为固定的宽度。 阈值设置越大,模型内部的边缘光出现越困难。

着色-MATCAP

image.png

  1. 光照MatCap是基于物体空间的,相当于一个固定在人物上的CubeMap,可以用来给人物一个固定的顶光。

  2. 高光MatCap是基于摄像机空间的,和人物位置无关,只和看的角度有关,用于实现和具体环境无关的环境反射效果。

着色-光照分量混合

image.png

在改动后,金属度现在不会对漫反射和高光的亮度产生影响,它们的亮度完全由这个选项控制。高光和环境反射依然会受到粗糙度的影响。 Tex_P按次序对应以上通道,可以用来绘制蒙版。

float DiffuseMuit = ToonData.LightMuitParams.y;
float3 color = Diffuse_Lambert(ToonColor) * ToonDiffuseMuit;

着色-光源属性

我们将在光源上定制特殊属性 image.png Toon Front Color会再次影响传统光照颜色,可以调整这个参数让卡通物体和写实物体接受不同亮度和颜色的光照。 通常不会受到光照的背面,卡通物体可以通过设置Toon Back Color让它也能被照亮。由于卡通物体的背面本来都有通过DarkMap进行过变暗的处理,如果背面不受光相当于重复变暗,所以通常应该保持较高的值。 Toon Spec Color会单独对光照的高光成分提亮。设暗可以屏蔽掉高光,设亮则是额外提亮高光,主要用于影响边缘高光。 Cell Shade Blur会对二值光照的模糊程度再次产生影响,设高后可以消除单独光源的二值化分界。 SoftBlend用于非主光源外的颜色光照,可以让物体在受到光照影响颜色的同时明度不会增加太多,用于物体通过光照染色的情景。

其次,三个颜色都有自己的Alpha通道。Toon Front Color,Toon Spec Color的Alpha通道设为0后,表示光照将不会乘以物体的BaseColor,会显著变亮。用于表现高强度光照,同时又希望保留饱和度的情况。直接拉高光照的亮度会让颜色光变成白光。

着色-光照方向修正

图片1.png 图片2.png

“背面死暗”和“阴阳脸”,两个东西其实在PBR也一直存在。 光照只有在正面才有亮度变化,转到背面当然只有固有色的“平面”。现在因为有了全局光照,背面也有了粗略的光照效果,但依然缺少一些特性(比如说自阴影,SSS),依然会导致背面不如正面好看。所以即使是2020年的现在,在过场剧情和人物的聚焦画面时,依然会给人物打补光。有的方案是一个逆光时亮部区域增大为70%来解决背部缺少光照的问题。

为了人物好看,只能限制光照角度,尽量使用正面光。但同样是正面光,仰角也有很大的区别,一般情况是45度比较好看,但45度就会在脸上打出上下的阴阳脸。并且,强制使用正面光,也会在镜头旋转时让玩家发现人物缺乏光照变化,好好的动态光照变成了死光照,正反面相同反而是小事。

我的方案,首先是可以定制每个部分的光照仰角。一般用户并不会太在意光照的仰角是否合理地,是否和日照方向同步。 image.png image.png image.png

首先是可以定制每个部分的光照仰角。一般用户并不会太在意光照的仰角是否合理地,是否和日照方向同步。其次,给予光照一个水平偏转角度,扩展它处于视觉正面的角度比例。即使光照处于正背面,依然可以看到足够比例被照亮的部分,并在某个角度快速转向到另一侧。

还有个常用方案是让光照偏向头顶,这样至少头上那块是一直照亮的。但是这样做很容易导致一些有角度的物体(比如说裙子)一直都是亮的,所以需要手动调整。但既然你固定了仰角,那旋转一周就能看到所有可能的光照结果,还算比较容易处理。

上面说的都是决定人物基本光照的主光源方向。比较强调方向性的辅光源在仰角的限制可能就需要去掉(出现在头顶的光总不成还是水平的),但水平的调整依然可以保留。总之这一切都需要通过设置光源的参数来进行区分。 另外,上面也说了,玩家不能接受的并非“正反面光照一直相同”,而是“视角和光照变化时人物光影没有变化”,所以像战双那样,始终保证光照从摄像机射出,但是变化时给予一定延迟,让玩家看到一个光照变化的过程,虽然完全“物理错误”,却也是可以接受的设计。但我暂时还没用这个方案,现方案实在不行的时候再说吧,毕竟乱晃镜头的时候破绽太明显了。要加也是加个脚本的事。

阴影

阴影-额发投影

image.png 固定成正面光照同时还把令人恼火的额发投影解决了。大家应该发现很多游戏明明角色各种地方都有投影,偏偏隐藏了额发投影。但在一些侧面的角度,其实依然不美。 过场中可以根据情况设置光源避免出现这个情况,但在实际游戏中,总会在一些时候被丑到。人眼对光照方向的敏感度很低,只要别搞太多的旋转镜头,一般人甚至永远都无法发现这个问题。

阴影-混用阴影

image.png image.png 我们需要混用两种阴影。引擎是是不支持即使用级联又使用自阴影的。CSM依然会生效到物体本身的自阴影上,产生两次阴影计算,必须屏蔽掉CSM的自阴影部分。调整bias怎么都会存在某些条件下穿帮的问题,所以我直接在Shadowmap里绘入Mask,然后让人物根据Mask过滤阴影。Mask其实是可以直接借用ShadowMap的Stencil来标记的,这样就没有额外成本。但Resolve Stencil需要一定的API版本。SSAO需要屏蔽掉,只保留外部的。UE直接判断ShadeModeId就行了。

描边

目前比较流行的描边方法有两种,一个是法线延括的方式,通过两次绘制,一次绘制角色,一次绘制描边。还有一种是基于后处理的描边。基于后处理的描边相对不容易定制,比较适用于对复杂场景进行描边。本村线是一种画在贴图上的描边方案本村线这个技术本身是有局限性并且费时费力的。这里讲述通过2次绘制来绘制描边的方法。

这个方案主要是因为我们想要每个部位的描边精确的进行控制。并且我们希望角色内部也要有轮廓线,比如头发周围的描边。 20210408171540 描边材质需要放在Mesh面板的属性页里,不需要显示描边则保持这个位置为空。

20210408171507

破面问题

image.png 模型扩边用了单独储存共点平均法线的方式来修复分叉,是常见做法。这个描边法线想在蒙皮网格上正常使用需要转到切线空间,或者直接储存在切线上。

但是它本身还是会有局限性,由于这个游戏只对角色使用,所以没有严重的性能影响,如果整个场景都要进行描边的话,那么性能会有影响。

参考资料: https://zhuanlan.zhihu.com/p/107664564