分享

PBR光照体系下的卡通渲染光照模型

 秋刀录 2022-08-16 发布于北京

最近在做一个UE4端游的卡通渲染,场景偏写实,所以会遇到一些一般手游不会遇到的问题。当然一开始我就是用的自己之前的那个光照模型,基本没遇到啥问题,这可以算是个实证。

多光照模型

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

其中二值化系数=smoothstep(MidPoint - Blur,MidPoint + Blur,Dot(L,N)),由两个参数来决定光照过渡的软硬。

如果是采样Ramp图,则将二值化系数替换成Ramp的采样结果。

需要除PI是个经验结论,如果不除亮部会显得过亮。这是因为正常物体能够产生全额亮度的部分只是少量面积,而二值化光照整个正面都是最大亮度,观感上就会显得过亮。

额外高光部分指的是边缘光,GGX高光和部分强制指定的高光,一般会乘LightColor来确定基本亮度。这部分面积小,对整体亮度影响不大,在和非高光部分对比的时候感觉合理就行了。只要单层叠加是合理的,多层叠加的结果也是合理的。

多光源的情况下,直接将所有光照结果相加。

其中环境光和烘培光会处理成一个和主要光源相同方向的单独光源,获取光照探针的R0(具体做法是在获取光照探针数据的时候将传入的法线设为(0,0,0)),然后乘PI * PI,作为上面公式的LightColor,使用天光方向或者一个指定的方向作为L,然后完整进行一次计算(包括高光等等)

实际环境下,光照一般只有两种:一种是白天的室外,物体受到的光照是天光和环境光各占一半,一种是室内和夜晚,物体基本只受环境光影响。

但是在这两种环境下,卡通渲染的结果应当是相似的,所以物体必须在环境光为主的环境下也具有正常强度的明暗变化,因此只能将环境光处理成一个普通直线光。

乘两次PI也是经验结论。最终可以让物体在大部分实际环境下都具有合理的亮度,不会过亮或者过暗。

但是显然,因为上面的公式也会让光源照亮物体的背面(仅仅是乘以暗部颜色,但暗部颜色并不会很暗),这种做法在多个高亮度的动态光照下依然还是会产生过曝的现象,比如人物前后各放置一个点光源。因此需要在光源上添加参数,让这些额外光源计算卡通光照时忽略暗部,压低整体亮度。

但通常情况,动态光照并不会有很多,大多是临时出现的特效光,因此即使不处理也没什么。

但不管是UE还是Unity给光源添加参数都不算太困难。而只要光源上有参数,就容易处理不同环境下材质的兼容问题。许多参数,比如二值化的模糊程度都可以放在光源上,用于区别不同功能的光,主光源可以边缘清晰,辅助点光源就可以是模糊的。

加上光源参数还是很有意义的,但只是处理不同环境下的基本亮度问题,参数一般不需要调整。

注意上面的计算都是针对物理光照的辐射度的,如果你的游戏是LDR的,这样做并不一定正确。

内外阴影分离

每个光源都存在阴影遮蔽。如果物体处于ShadowMap的阴影下,整个光照必须被完全去掉,否则在室内却受到外部光源的影响是完全不合理的。但是人物本身的自投影(比如胳膊头发对身体的投影)又需要和人物本来的暗部融合避免产生杂乱的线条,直接去掉光照也容易显得过暗,所以必须将自阴影和外部阴影用完全不同的方式分别处理。

自阴影需要创建一个以图集作为基础的Pre-Object ShadowMap,顺便解决了精度问题,外部使用普通CSM。因为两者功能完全不同,分别计算并叠加就可以。

但CSM依然会生效到物体本身的自阴影上,产生两次阴影计算,必须屏蔽掉CSM的自阴影部分。调整bias怎么都会存在某些条件下穿帮的问题,所以我直接在Shadowmap里绘入Mask,然后让人物根据Mask过滤阴影。

Mask其实是可以直接借用ShadowMap的Stencil来标记的,这样就没有额外成本。但Resolve Stencil需要一定的API版本,最终还是要用普通的颜色通道作为fallback手段。

这个问题并不简单,现在大部分游戏都选择了回避这个问题。要不无法正常让外部的CSM投射到人物上,要不导致外部CSM影响人物无法达到正确的亮度,无法做到室内外无缝切换。

或者……仅使用普通CSM并且正常去掉光照计算,导致人物自投影一片死黑。

里面最靠谱的其实还是完全屏蔽自阴影。

但何必呢,又不是不能解决。

我这也实现了单独PASS绘制物体深度/模板到RT,然后通过偏移采样做的假阴影,或者叫Mesh Shadow,因为有了标记物体的手段,利用它也可以方便地做眼部半透。

它可以更好的处理ShadowMap无法生成的不符合物理的阴影。而它的算法其实是个简化的材质版接触阴影,因此也可以作为部分位置精度的补充。

SSAO需要屏蔽掉,只保留外部的。UE直接判断ShadeModeId就行了。

内部能不能利用SSAO来辅助处理明暗分界线?……说起来是应该可以,但普通画在贴图上的AO也能处理大部分情况(还有Mesh Shadow可以用),加入这个动态的部分又增加了不稳定因素……

可能有用把,但我反正屏蔽了。

光照方向修正

事实上,这个问题的源头是“背面死暗”和“阴阳脸”,而这两个东西其实在PBR也一直存在。

光照只有在正面才有亮度变化,转到背面当然只有固有色的“平面”。现在因为有了全局光照,背面也有了粗略的光照效果,但依然缺少一些特性(比如说自阴影,SSS),依然会导致背面不如正面好看。所以即使是2020年的现在,在过场剧情和人物的聚焦画面时,依然会给人物打补光。

如果没有全局光照就更不用说了,只能长期打着补光才能让人物有一个稍微好看的结果。

而卡通渲染的二值化光照下是不能随便打补光的,并且二值化还会放大光照的不美观之处。

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

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

其次,给予光照一个水平偏转角度,扩展它处于视觉正面的角度比例。即使光照处于正背面,依然可以看到足够比例被照亮的部分,并在某个角度快速转向到另一侧。

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

上面说的都是决定人物基本光照的主光源方向。比较强调方向性的辅光源在仰角的限制可能就需要去掉(出现在头顶的光总不成还是水平的),但水平的调整依然可以保留。总之这一切都需要通过设置光源的参数来进行区分。

另外,上面也说了,玩家不能接受的并非“正反面光照一直相同”,而是“视角和光照变化时人物光影没有变化”,所以像战双那样,始终保证光照从摄像机射出,但是变化时给予一定延迟,让玩家看到一个光照变化的过程,虽然完全“物理错误”,却也是可以接受的设计。但我暂时还没用这个方案,现方案实在不行的时候再说吧,毕竟乱晃镜头的时候破绽太明显了。要加也是加个脚本的事。

另一个常用的方案是边缘光。这个边缘光并不是什么菲尼尔效应,而是一个自动的背部补光。通常来讲,跟随光照变化的单侧边缘光的会更好看。如果你想要双侧,可以打另一个光来产生。(我的边缘光是和光源绑定的,生成方向和光照方向有关,这样可以两侧打出不同的边缘光颜色)

边缘光需要相对固定的覆盖面积。我试过100%固定角度的边缘光,但实际使用时违和感很强,还是覆盖面随视角有一定变化的边缘光看上去更加自然,虽然在某些角度下覆盖面积变小了。

因为实际上是背光,所以合理的边缘光应该投射阴影来避免漏光现象……但一般不会愿意付出这个成本(其实也能跑,毕竟不是点光源投影,只是另一个低精度Pre-Object投影),而且一定程度的漏光还可能更好看,物理正确没人在意的。

边缘光有个新技术,是利用深度对比来在边缘生成固定宽度的边缘光,这样可以打出非常细而且持续存在的边缘光来,而且即使是方块物体也能产生。但这种边缘光假如整个模型都没有粗细变化依然不太好,会失去背光的感觉,但可以像描边粗细那样用贴图绘制出来。

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

自阴影也需要控制方向。既然上面使用的是Pre-Object阴影这点也就容易实现,生成时临时修改光照方向就好了。

处理偏色

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

通常只能在贴图上反向增加饱和度和对比度,但非常不直观,而且精度损失严重。饱和度可以后处理加回去,对比度可没辙。

我用了个自己的骚方法:拟合ACES公式的逆运算。

color * (2.51 * color + 0.03)) / (color * (2.43 * color + 0.59) + 0.14

这是ACES的一个简化公式,只保证了基本亮度。我用Excel拟合了它的逆运算,结果如下:

3.4475 * color * color * color - 2.7866 * color * color + 1.2281 * color - 0.0056

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

它的缺点是同样参数在暗处会显得对比度有些过高,想做的完美还需要根据光照强度来调整lerp的比例(注意是光照强度而不是最终辐射度,因为我们要保证是的纹理内容的对比度,乘过纹理后就没法得知这个原始的对比度了)

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

而且暗部对比过高的现象也证明了,仅仅靠改贴图颜色无法解决这个问题。

而且在ACES下好看的材质纹理,对比度需要非常高,也非常难看,一般美术很难直接画或者选出符合要求的颜色,大家都是在用色板的经验数值取色,实际操作根本不可能做到符合ACES的偏色要求。

所以这可能是本文最有价值的东西。

当然,更聪明的做法是根本不用ACES,甚至不用ToneMaping。一个固定亮度,而且没有室内外切换的卡通渲染游戏其实根本不需要ToneMaping,一切都可以原样画出来,毕竟物理光照规则对它来讲并没有意义。

严格来讲,甚至不需要线性空间。在卡通渲染下,实际上伽马空间的光照结果更加美观。否则PS也不会用伽马作为图层的混合方式了。

但是如果你还打算用PBR来节约画师的工作量,还希望借用PBR的那些“好东西”,就需要保留ToneMaping和正确的物理光照模型。现在的修正偏色方案,就是个折中的方法。

自定义Bloom

这基本是个卡通渲染游戏都会做的东西,但这次我分别处理了物体的面光和背光Bloom,可以让亮部单独产生Bloom的效果。

从“物理正确”角度,其实也是需要这样做的。因为Bloom的强弱其实代表了受光的强弱,但是卡通渲染的光照强度并不会完全通过颜色表达出来。为了保持画面的饱和度,亮部的实际亮度并不会太高,而暗部的亮度常常也不能过暗。

因此,将Bloom和颜色剥离开来,反而更加“物理正确”。

卡通渲染还特别喜欢让头发和皮肤具有更高的Bloom强度,使得它们看上去更加具有光泽。

而为了保持发光物体的饱和度,实际颜色值不能过高,从而也需要用高Bloom值来补齐原本想要的泛光。

我也想过是否可以把实际亮度和颜色在光照体系下就直接拆分开,把亮度当作RGB之外的第4个颜色分量,然后用“亮度”值直接算Bloom,这样处理Additive物体时也更加方便(现在我直接屏蔽了Additive物体的Bloom值写入)。

但这样改的东西满多的,所有涉及光照的部分都要改,而且这需要让Bloom值是个HDR的值……所以暂时没动。

真要认真做,可能那才是最完美的方案。

结语

要说的话还有很多可说的,但许多都有人说过,或者我自己已经说过了。

本文主要还是和PBR光照有关的。

所以就这样吧。

卡通渲染是个完全不一样的东西,如果还用PBR的思路考虑问题基本是死路一条,尤其是那些只知背书而不理解PBR为何那样做的理由的人,而这类人是绝大多数。所以你们多半会看到很多奇奇怪怪的游戏诞生。

基本上,卡渲,就是在画一幅动态的画。原画能力是第一位的。技术要做的,是让原画结果在3D世界中自然呈现。可以认为,一切“物理”都是无意义的,尤其是和颜色相关的物理。

但实际操作的时候,又不可避免需要使用PBR的手段减少成本……我只能说,一定要搞清楚哪个是第一性的。

卡渲的第一性,永远都是原画脑中所想的画面。即使原画想要的东西我们永远都做不出来,但那并不妨碍朝那个方向前进。事实上,也可以把PBR的渲染部分当作是向NPR结果的一种低端拟合。目标暧昧不清,且使用PBR的“规则”做事,做出来的东西永远都只是四不像,两边都不讨好。

而这是目前很难避免的现况。

(不要觉得我骇人听闻,我可是亲耳听到过“原画也是想画得和写实一样只是做不到才画成了现在这样”这样的说法。不要高估现在的人。)

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多