33 KiB
title, date, excerpt, tags, rating
title | date | excerpt | tags | rating |
---|---|---|---|---|
知乎FlashYiYi的卡通渲染分享 | 2022-10-12 14:43:12 | 卡通渲染 | ⭐⭐ |
目录
- #一些较少人提过的二次元渲染方法:https://zhuanlan.zhihu.com/p/539950545
- #低成本皮肤渲染 Pre-integrated Skin:https://zhuanlan.zhihu.com/p/35628106
- #卡通风格场景的定制化技术部分:https://zhuanlan.zhihu.com/p/338334377
- #PBR光照体系下的卡通渲染光照模型:https://zhuanlan.zhihu.com/p/166147653
低成本皮肤渲染 Pre-integrated Skin
GPU Gem3的预积分文章:https://developer.nvidia.com/gpugems/gpugems3/part-iii-rendering/chapter-14-advanced-techniques-realistic-real-time-skin
曲率烘焙代码(Unity):
v2f vert(appdata_full v)
{
v2f o;
o.uv = fmod(v.texcoord.xy, 1);
o.pos = float4(o.uv * 2 - 1, 0, 1);
o.pos.y = -o.pos.y;
o.vertex = v.vertex.xyz;
o.normal = v.normal;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
float3 worldPos = i.vertex;
float3 worldBump = normalize(i.normal);
float cuv = length(fwidth(worldBump)) / length(fwidth(worldPos)) / 10000 * _CurveFactor;
return fixed4(cuv, cuv, cuv,1);
}
PBR光照体系下的卡通渲染光照模型
处理偏色
所有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的值……所以暂时没动。
真要认真做,可能那才是最完美的方案。
卡通风格场景的定制化技术部分
ToneMapping
在使用标准ACES的时候,如果亮度调的较低,暗部细节就会严重丢失(图2),而如果调高亮度至能看清暗部细节,亮部细节就会丢失(图3)。
其中一个方案是:可以考虑在无偏色的原始颜色和偏色后的颜色根据亮度做一个插值,保证小于1的区域是无偏色的,然后渐渐过渡到需要偏色的区域。然而我试了下,好像我之前的反转校色方案依然可用(图3)
做法是在图2的基础上对人物纹理的颜色输出做以下处理(处理强度可以通过和原颜色lerp来控制)
color = 3.4475 * color ^ 3 - 2.7866 * color ^ 2 + 1.2281 * color - 0.0056
其实这个公式相当于预处理了一次纹理,将纹理的高光和暗部区域拉长了,这样就不怕明暗细节被ACES破坏了。 本来想换个正常点的解决方案,结果……至少这也是个可行的解决方案。
半影色调(Penumbra Tint)
就是上图这样的东西。Unity在HDRP里竟然提供了这个功能,生造了个词叫Penumbra Tint,也是满符合Unity自己的人设的。
我是当初研究新海诚动画的画面特征的时候发现的这个东西。这个元素,也是大部分模仿新海诚风格渲染作品所缺失的最后一步。
我起初也觉得这个元素是个见缝插针加饱和度的“艺术设计”,直到我在实景中看到了这个现象(拍的不好,肉眼看更明显)
我也不知道这玩意儿是怎么出来的,泊油马路也不是次表面材质啊?
总之,从结果上来看,人眼似乎是可以接受这种“在明暗交界处饱和度提升甚至换个颜色”的表现的。所以作为风格化作品,强化这个表现也就一点问题都没有。
这个元素比较类似次表面材质的效果,所以也可以用类似(差不多是完全一样)的方式来实现。分为两部分:
- 投影部分在软阴影部分叠个额外颜色就好了,次表面就是这么做的,区别就是UE的实现里,亮部也同样需要叠加颜色。
- 而非投影部分的次表面特性有两个实现。一个是预积分LUT,相比普通Ramp还需要用曲率修正LUT UV的V坐标,否则半影部分在缓慢曲率上会过宽。另一个就是基于屏幕空间卷积的SSS,可以很完美的实现这个效果。
所以这个玩意儿大概真的就是个次表面材质特性的艺术延申?万物皆次表面?
或者也可以是上图这样的情景:虽然整体光照都是橙色,但是亮面一侧由于亮度过高导致了泛白,半影部分恰好卡在了不会泛白的亮度区域,保留了原色,最终形成了这个视觉效果。
总之,这两个视觉效果如果用物理打光实现,出现条件较为苛刻。所以在技术实现上,要提供“脱离亮度限制,硬把这个效果挖出来”的功能。其颜色和强度应该由光源和材质双方决定。
如果不希望增加延迟光照下GBuffer的数量,也可以完全由光照决定半影颜色和强度,仅通过材质蒙版来过滤不希望出现这个效果的物体。这个效果也可以仅通过增加半影处饱和度来实现,这样不依赖GBuffer也可以让不同物体的半影颜色互不相同。
原神人物上的Ramp其实也是基于这个现象的产物。对于缺乏细节的二值化光照,这是一个很好的补充细节。
GI
GI的比重,是区分风格化渲染和真实感渲染的一个很重要的标志。(这里的GI主要指漫反射GI)
GI是表达真实感很重要的一个要素,只要把GI做对,画面就会显得真实。换言之,只要GI是对的,不管怎么做看着都会像照片——而这是风格化渲染需要重点避免的地方。
一个方案是干脆去掉GI,手动补光,退化回PBR之前的模式,典型的例子就是原神。缺点自然是光照平,且单调。但是正因为单调,控制起来也容易,每个地方的颜色都可以单独处理。
另一个方案是反过来加强GI,这同样也能达到“不真实”的目的,色彩区域的融合也可以让多个物件浑然一体。缺点就是控制起来比较困难,只能做成啥样就啥样。
日式卡通风格是从纸上作画转移过来的,自然平涂色块会比较多(毕竟人手绘制GI难度较高),所以风格比较接近无GI的效果。如果想符合日系风格,GI就是一个需要压低的元素。
比如下图的情况,如果放任GI的染色效果,这么大片的绿色草地必然会影响到房子墙壁的颜色,无法保持这么高的色彩对比差异。
Bloom
Bloom从物理角度应该是一种高亮度产生的视觉现象。
但因为NPR体系下的颜色经过了各种特殊指定,并不和实际现实亮度直接对应,很容易出现纯白区域不希望Bloom,而高饱和低亮度区域反而希望Bloom的情况。 其次,Bloom同时也是一种画面上的低频信息,为了营造一种柔和的画面“氛围”,我们也希望增加这种低频信息的比例。而强行拉高整体Bloom强度又会导致高亮度区域过曝。
所以我们需要一个自定义的Bloom缩放系数。因为从崩三开始的游戏都实装了这个特性,是否要实现它一般并不会有疑问。
具体实现的时候,最佳方案其实是准备一个单独的Half通道以储存一个HDR范围的亮度值,并在frag中将原始颜色的亮度值计算出并存入。只有这样,才能保证Alpha Blend, Additive,以及各种特殊混合模式下的Bloom值的正确性。
通常,我们并不愿意花费一个16bit的通道来实现这个效果,只愿意使用一个8bit的通道,所以只会储存一个Bloom的缩放值。这样在透明混合的时候就会出现各种麻烦。
目前我比较倾向的方案是: 放弃透明物体的Bloom值自定义功能,透明物体阶段并不写入Bloom值,这样也节约了带宽。而为了最终计算Bloom结果正确,必须将透明物体和不透明物体分别渲染,然后在Bloom阶段重新组合。(为什么必须这样解释起来很麻烦,信我就行了)
这个分离透明物体渲染的功能UE本来就提供了,只需要修改Bloom部分的RT组合,实现还是很简单的。而且通过配置r.SeparateTranslucencyScreenPercentage还可以获得半透物体降分辨率功能,还可以根据帧率自动进行,基本上不会不开,分离渲染导致的性能代价也就无所谓了。
事实上,这就是原神的方案,所以估计也没啥人会反对。 (原神的透明物体分辨率过低的问题确实严重。我觉得可以将部分需要精度的透明材质转为Mask材质来解决问题,Mask通过TAA抖动模拟单层半透的效果还是不错的)
虽然Flare,Bloom,Volume Light从物理角度是不同的光学现象,但它们都是画面的低频信息提供方,也可以近似处理。 风格化渲染实际上非常需要这类低频信息增强画面的柔和感,以及缓解纯色块的单调感。除了Bloom,Flare和体积光同样也是实现这一点的很好用的工具。
严格来讲,最理想的方案其实是执行两次Bloom,一次无视物理亮度给画面整体做一次模糊叠加,第二次根据亮度做正常Bloom模拟实际的辉光物理现象。但因为Bloom的性能成本较高,当然希望一次做完。
配置Bloom参数的时候要考虑倒这一点。两个Bloom的功能其实是不同的。
投影
投影的风格化依然遵守分频规则。
- 投影或者保持边缘清晰,或者保持一个大范围的模糊过渡,而不要在两种模式之间暧昧不清。
- 同时,需要表达一个简单清晰的形状。清晰边缘不要有尖锐的锯齿,模糊边缘的透明过渡不要有坑坑洼洼的锯齿转角。
- 同时,投影的强度依然不要暧昧不清。或者是清晰可见的,或者就直接没有,这样才能避免脏污感。
这些条件加在一起给投影制造了很多写实渲染没有的麻烦。
人物通常都需要清晰的投影边线,避免和二值化的画面元素冲突。而人物的装饰尖角又是常见元素,投影面多为弧面,这导致投影在不同方向下非常容易出现下面的凌乱边缘。
注意看小腿和后脑勺上的投影(确实许多人会看不到问题在哪吧……)
利用距离场简化投影形状是比较直接的解决方案,但性能较差。目前比较靠谱的是手动隐藏部分难看的投影,或者使用代理投影物体。
AO
上面说过了,现在我们要的不光是AO,它同时也包含了Local光投影的部分。
具体的要求则是:
- 需要明显的AO,但是只能出现在墙角,柜子后面这种地方,范围要比较大,且边缘要尽量柔软。
- 而小的结构周围则不能出现AO,人物身上尤其不能有。
笔触化
总有人想靠“水彩”“油画”“蜡笔”创造特殊的风格化效果。玩家想要“和画一样”的体验,那就将画面做得和画一样。
虽然是有实时解决方案,但实际效果却不尽人意。
日式动漫的摄影技术
https://zhuanlan.zhihu.com/p/20202161
还可以参考
有人在Unity尝试实现了对应的方法 https://zhuanlan.zhihu.com/p/363790714
个人认为添加了这个Highlight之后才跟《动画基础知识大百科》那案例差不多……
影パラ将角色稍微压暗一些
具体的实现方法由于相对复杂一些,将在下文详细阐述
总之,将光感+ハイライト叠加上去,将影パラ乘上去,就获得我们的最终结果了
一些较少人提过的二次元渲染方法
逆向Tonemaping
常态的做法是利用调色图层和切换不同画笔颜色来尝试一个比较好的结果,但这其实是在用人工做机器应该做的事情,费时费工而且控制精度还不高。 既然目标就是让呈现效果和原画中一致,那就用算法算出一致的效果就好了。 【UE4】FilmicTonemapperを打ち消してみた - てんちょーの技術日誌 UE4本来就提供了一个现成的逆向ACES函数可供调用,效率并不低,也可以牺牲一些精确度将其更换成单矩阵版本的。
return FilmToneMapInverse(col);
(需要在Custom Node节点中include "Engine/Private/TonemapCommon.ush") 上面的文章则自己实现了一个可以对应更改过Tonemaping曲线情况的更复杂的逆向ACES算法,性能比原版低了很多。但可以沿着它的思路再进行优化。
加权生成描边法线
之前讲过一次,用法线所处的三角形夹角作为权重进行加权混合。 其实这个算法也是许多DCC软件自动法线平滑的算法,我们一开始不用纯粹是自己菜,直接平均的结果怎么可能是对的。
SDF内描边
这个方案最初就是我提的,实际应用效果还不错,也就是侧面观察的时候会有些瑕疵。 除了可以确保描线不出现马赛克外,还可以随视角距离变更宽度,和Backface描边的缩放效果保持统一(几乎无法相互分辨)。
这个方法生成的描线在靠近观察的时候很容易产生扭曲,但这种扭曲的质感反而会形成一股笔触的感觉,也不全是坏事(接受不了老老实实画本村线去)
原图的描线并没有擦除,凑合看看 现在我使用的SDF生成工具是下面这个: https://github.com/Chlumsky/msdfgen
还可以选择生成多通道SDF来保留尖角。但实际情况下需要锐利尖角的情况很少,我用的依然是单色SDF的版本。
烘焙顶点曲率
Ramp的窄过渡区域在部分情景下实际上是希望保持等宽的,否则在平直平面上会散开。
烘焙曲率基本可以解决这个问题。
不要烘焙到纹理,顶点曲率基本够用,还省去了模糊的步骤。在计算平滑法线的同时顺便计算一下就好了。
除了Ramp外,也可以用来控制边缘光照的宽度。
半视角方向自阴影
现在一般都会用一个逐物体的Shadowmap来绘制角色的自阴影,以便获得较高的精度。 但锐利的自阴影依然容易在光照方向和切线接近平行的时候,出现杂边。
改变逐物体阴影的光照方向,将原始的光照方向和视角方向相加归一化作为阴影方向,基本可以避免混乱边缘的出现,一般人也看不出区别(代价是即使光照不变,视角变化的时候阴影位置也会有轻微变动,但意外的并不会有违和感)
反转阴影投射屏蔽背面Shadowmap
把ShadowCast时的背面剔除改为正面剔除。
这样一般凸多边形物体的投影就不会固定生成在自己的背光处了,但依然会生成到其他物体上。如此一来,我们就可以通过改变NoL的光照范围,让人物背后也可以获得更大范围的光照,这也是强制调整光照仰角所必须的。
但人物并不是完全的凸多边形,所以自阴影其实还是会出现在物体背面,只不过不是“全都会出现”而已。实际情况只要调参不要太过分就不会露馅。
(当然,如果没自阴影,一开始也没这个问题。但自阴影还是要有的。)
RAMP下的多光照体系
二次元渲染用的二分法光照有一些固有的问题:
- 多个二分法光照同时出现,很容易导致光照边线的交错,变得难看。一般只会有一个二分法的光照。
- Ramp本身就是一个单光照体系下的产物,Ramp颜色本身就包含了环境光的结果。那么环境光应该如何和直接光混合呢?如果环境光是额外叠加上去的,最终呈现的效果就不是Ramp图所期望的。
我选择的办法是,Ramp结果独立计算,计算完直接乘到其他混合完毕的光照结果上。这样能完整保留Ramp本身的特征。
Ramp外的光照如果直接根据NoL计算,始终会有塑胶感。现在常见的方法是忽略法线,或者取视角方向作为法线。我选择的是后者。环境光也同样处理。 如此一来,整个人物会使用一个统一的,平直的光照结果。这样就能保持二次元渲染的平面特征,同时和周围物体的亮度保持一致。
更关键的是,它解决了“面光处看到的阴影不能暗(通透)”和“背光和阴影下的人物依然要变暗,保持和周围物体亮度一致”之间的矛盾。因为面光处的整个人是整体变亮的,而背光处是整体变暗的,通过视角变化以不易察觉的方式在两个亮度之间过渡。
法线无关的光照渐变区域
我将用于代替光照方向的视角方向,改为了一个基于视角的筒状模型:
这样就可以让角色获得一些和法线无关的光照渐变区域,不至于任何时候都只有统一的光照颜色。
看视频才能懂是怎么做的,根据灯光在屏幕空间的位置为圆心,根据半径绘制一个Alpha来控制光照强度。
- 表现不出光照的方向性
用和周围光照相关的高亮度边缘光来体现光照方向,身体上出现的高光也使用同样的规则。
此外,这个方案本质修改的是法线,所以可以很大程度兼容基于SH的烘焙光照。烘焙和实时在默认配置下只有少量的区别。
而这个方案改完后会很大程度忽略物体法线,但细节法线又是表现材质的重要环节。因此,我后来又将细节法线重新应用于修改后的法线上,尽可能保留了原本的材质感(对于PBR的部分,这点是很重要的)
这个平面光也可以和原本的正常光照混合使用(以多一点塑料感为代价增加一些光照变化,通常也可以接受)
最终方案 将渐变的起点轨迹调整为圆弧
既然方块不行,那干脆用圆得了,让渐变的起始点都在圆上,那它的渐变旋转想必是非常平滑的,
那么这次的代码就非常简洁了:
float2 newUV_SS = (scrPos - centerPosSS) / float2(lightRampWidth, lightRampHeight);
float2 intersectPoint = normalize(lightDirVS.xy) * _RampCircleSize.x;
float lightRamp = max(0, 1 + dot(normalize(lightDirVS.xy), newUV_SS - IntersectPoint));
经过简单的调整,这个方案果然让渐变的旋转“自然”了许多。
而影パラ就是用光感的值进行一些调整后获得,就不再多说了。
那么接下来讲讲关于Highlight的部分,
其实Highlight很明显就是边缘光,那先按边缘光的做法做着,
关于边缘光,其实大家也非常熟悉了,个人采用的算法是
pow(saturate(1 - dot(viewDirectionWS, normal)), _RimLightSmoothness)
非常易懂的算法,笔者也尝试过使用次表面散射来模拟,但发现跟普通边缘光的效果相差无几,还是采用了上述简单易懂的边缘光算法。
接下来结合之前算的渐变值来进行边缘光的范围限制就行了
效果正如各位之前所看到的那样
影颜色叠加
二次元渲染的阴影颜色是不能直接乘在原始颜色上的,因为会越来越暗,饱和度也会变低。
一个常见的做法是,亮部一张图,暗部一张图,不采用乘法而使用Lerp作为两者的插值,这样就能确保暗处的颜色可以自由配置。即使在使用Ramp的情况下,也可以将Ramp的A通道指定明暗贴图的插值依据。
但这种做法多多少少有些笨重,两张图画起来也麻烦。
而原神为了省掉ShadowColor这张图,采用了多个Ramp选择+Index标记这种方法,最终也会遇到同样的问题。
其实这个问题用GGXX的方案会比较好。它混合阴影的方式是先加一个白色然后再乘阴影颜色,这样阴影就能突破原颜色的限制,最终结果很大概率是符合要求的。加的这个白色的亮度会决定阴影的亮度,这样二级阴影的颜色也可以被同时确定。
Ramp方案下,依然可以用A通道来表示这个附加的白色的强弱,最终更好地通过Ramp来规定暗部颜色。
从烘焙光照中获取光照方向
在没有直线光存在的情况下,Ramp光照将会消失,效果会很不好。
通常方案是寻找场景里最近的一个点光源来作为Ramp光照的依据,可以用Volume来实现。原神也是有这个效果的。
然而实际上,烘焙的SH光照数据里,SH1就是此处光源的大致方向。将它拿出来归一化一下就可以用了。
用UV2来偏移高光
目前卡通高光一般都是画死的,视角只会影响亮度。
画死当然不好。所以一个折中方案是让高光区域可以随视角进行一定的偏移(也有缩放的做法,总之要让它有一定变化)
我没有选择把高光画在主纹理上,而是画在另一个地方,并使用UV2。这张图在UV2上的角度和缩放将会决定它在视角变化时的偏移方向和幅度,于是也可以用来模拟肩膀处的光点以及大腿上的竖条高光。
而且这样一来,高光图将会使用独立的贴图精度和范围。不会出现整个人高光就这么几处,却要占据覆盖人体的整张通道图的情况。而且它还可以和细节图案纹理共用UV2。
并不是什么特别的做法,但我看基本没人提,网络上的全是FLowmap一类的玩意儿。图案整体偏移,用第二套UV控制才是最简单的做法。
透射
一个比较简单的效果,让薄衣服可以违反NoL的规则始终被照亮。手动标记区域。
可以很容易做出所谓“通透感”。
基于这个原理,我还在人物边缘根据法线加了一些次表面散射的效果,但说实在话效果并不明显。
估计很难看出两者的区别,而且还需要画区域屏蔽鼻子处的漏光
头部FOV修正
将脑袋压扁来修复FOV造成的畸变是离线常用的方法,但我还没见谁真的在常态的游戏流程中使用。主要原因在俩:
- 如果你一直使用50左右的低FOV,畸变问题并不显著。动作游戏多使用低FOV,只有射击游戏才会用90的高FOV。所以这不是一个急需解决的问题。剧情过场中也可以(本来也应该)降低FOV。
- 压扁物体会导致各种各样的问题(如排序问题和穿模),深度值可不是随便能改的东西。
对于前者,我必须得说,本来人家离线动画里用的FOV也不高。人家选择压扁脑袋,是为了获得“极致”的表现。畸变不明显又不代表没有畸变。
对于后者,这就是个技术问题。而它是可以解决的。
我选择的方案不是压扁模型(A移动到B),而是使用结果等价的“平移”(B')来取代压扁,保持深度的正确。
(实际使用的时候是既压扁也平移,选择其中顶点偏移距离最短的结果,即B'')
篇幅有限我就不解释了,就是个线性代数问题。
柔光光照
卡通光照大幅忽略了物体法线,在使用颜色光照的时候,很容易导致整个物体的颜色都变得非常单一。
参考离线2D动画的打光技巧,我选择让光照结果通过柔光的方法应用到BaseColor上。
但最后,我分析柔光的混合公式,总结了一个近似的,能够更直接地实现我的目的的公式。
A * lerp(B,Lum(B),A)
即,原图中的色彩通道亮度越高,光照的饱和度就越低。这样图象中较亮的部分就不会受到光照颜色的影响,但暗处则会正常受到影响,效果如下:
不过,实际使用的时候,这个效果也会导致人物很难被颜色光改变颜色,需要和正常的光照结果再Lerp一下。
Diffusion
GGXX和蓝色协议都在分享里提到了Diffusion,解释说是另一个Bloom,但却没有后续的细节。
我参考动画流程,采用了小卷积高斯模糊后和原图像素取Max的做法(变亮混合模式)
因为是取Max,并不会提高亮处的亮度,只会拉高暗部的亮度,符合我们的需要。而且这样的效果是普通Bloom无法实现的(普通Bloom亮部怎么调都肯定会更亮)
不过为了做出蓝色协议原图的效果,我还额外再用了一次叠加混合(相乘)。亮度会下降一些,且颜色变深。具体怎么用看实际情况吧。
由于只是一个小卷积模糊和简单计算,额外性能成本其实不高。
SNN预处理纹理
就是蓝色协议介绍的场景纹理处理方法,效果是让远处的纹理呈现色块化质感,看上去更像画。
我采用的是在图片预处理阶段对各级Mip分别应用SNN滤镜的方法,低Mip和高Mip的卷积核一样。但由于图片大小不同,低Mip变化小,高Mip变化大。
最后就能自然地形成上图的效果,且渲染成本为零。
染色雾
重点在于,NPR场景会使用一个写实绝对不可能用的手段:
对渲染结果直接用叠加混合(乘法混合)。
因为这是物理世界中不存在的现象,但是却是动画后期非常常见的做法。
所谓染色雾指的是:计算雾效的时候不用Additive混合,也不用Alpha Blend混合,而是用Multiply混合(当然全用Multiply也不行,这只是一个额外的效果)
这使得你可以很轻易地控制场景各部分的颜色,而不导致原有信息大幅丢失。
实现方面用一个Multiply混合的读深度的透明物体就可以,也可以在原本的雾效计算中加入此元素。
(之前也有人搞过Multiply光照,本质上和Multiply雾是一回事)
当然美术能不能用好这个工具就是另一个问题了。
(我并不是在嘲讽,而是这类体系外的工具,对于已经形成体系的美术来讲确实很难融入他的体系内。而缺失这部分工具正是场景始终不“卡通”的其中一个重要原因)
蓝色协议的间接光按距离染色也是这个机制的一个应用。