Origin
zhuanlan.zhihu.com
Tags
渲染
风格化
流程规范
技术详解
项目
收藏夹
创建时间
收藏类型
Cubox 深度链接
更新时间
原链接
描述

前言

最近在研究卡渲,就想着把看过的文章整合一下,持续更新。。。

日式卡通

着色

Cel Shading

Cel Shading 的基本思想是把色彩从多色阶降到低色阶,减少色阶的丰富程度,从而实现类似手工着色的效果,具体来说,可以用如下计算方法:
notion image
其中,Kd 表示模型自身的贴图颜色,celCoord 表示法线和光照方向的点积,用作一维色彩表的查找坐标,而 paletteTex 则是由美术绘制的一维色阶表,一般来说是由几个纯色色块组成的,如下图:
notion image
上述做法可以用于模拟卡通渲染的漫反射分量,却并没有考虑到视角相关的光照分量的模拟,因此很难实现类似菲涅尔效果的卡通渲染。实际上,也可以用类似的查找表的思路来视角相关光照分量的色阶离散化 [6],只需要将一维查找表扩展到二维即可:
notion image
相应地,查找坐标也扩展到了二维。
notion image

Tone Based Shading

Tone Based Shading 的风格化是基于美术指定的色调插值,并且插值得到的色阶是连续的。首先需要由美术指定冷色调和暖色调,而最终模型的着色将根据法线和光照方向的夹角,在这两个色调的基础上进行插值,具体算法如下:
notion image
其中,Kd 仍是模型自身色彩贴图,Kblue,Kyellow 和 alpha,beta 则均是自定义的参数。
notion image

日式卡通的着色

前面已经描述过,日式卡通在着色方面比较典型的特点是以大量纯色为主,进一步说,往往只有 “明暗” 或者 “冷暖” 两个色阶,因此光照计算往往最后也要映射到离散的色彩表上。 仍然以《GUILTY GEAR Xrd》为例,它也一定程度上包含了 Cel Shading 和 Tone Based Shading 的部分思想,将色阶离散成为 “明暗” 两个色调,并由美术指定冷暖色调的颜色:
notion image
上述公式表示了这个卡通渲染的漫反射部分,其中 threshold 表示明暗交界的阈值,在游戏中通过顶点色的 R 通道来实现逐顶点的控制。Kcool 和 Kwarm 由美术逐物体地指定Ksss 是对模型次表面散射效果的模拟,对皮肤而言一般呈粉红色,通过美术绘制的 SSS 贴图来实现逐像素控制,并且只有暗部的像素才会受 SSS 贴图的影响。Kd 是模型自身的颜色贴图。darkness 表示了某个像素的明暗程度,用于确定色调的冷暖。除了正常的 dot(normal, lightDir) 项,游戏中还加入了由美术绘制的 AO 贴图,来实现一些边角缝隙的暗部效果。我在实现时又引入了动态的阴影部分,最终 darkness 的计算公式为:
notion image
其中 shadow 是由 shadowMap 的算法计算得来的。
高光的计算更简单一些:
notion image
其中,spec 表示高光的强度,threshold 可以由美术逐物体或逐顶点指定,specMask 和 specPower 由美术绘制的贴图来逐像素控制,类似于 phong 着色中的 specular 和 glossiness 的作用。specColor 可以由美术逐物体地指定,也可以把 AO,shadow 和明暗色调作为影响因素添加进去。最终的着色结果将漫反射和高光叠加即可。

法线

在实际游戏中使用时,上述方法往往还需要配合美术针对具体模型进行法线修正。 根据模型顶点位置和拓扑关系计算出的法线往往细节过度,表现在上述卡通渲染的结果上就是往往会出现许多不需要的暗部细节,修正的方法是使用模型法线转印,给精细的模型一个近似的低精度 proxy(比如用一个球形代表模型的头部,用一个圆柱形代表模型的胳膊或者腿),然后用 proxy 上附近顶点的法线作为模型的法线来使用。此外,还需要考虑到明暗交界处反走样的问题,这里不做展开。
修改前的效果:
notion image
修改后的效果:
notion image
可以使用的模型法线修改工具:
(1)UserNormalTranslator (SoftImage XSI)
(2)NormalPainter (Unity)
(3)NormalThief (Max)
因为修改过顶点法线的模型不能直接使用法线贴图,所以如果需要使用法线贴图,最好把修改过的顶点法线存在顶点色或者 UV 上,这样不影响其他效果的使用。
一种方案是手动调整法线:推特上一位作者提到他最常用的几种方式,(下面三张图)简单来说即通过复制粘贴让某个区域内法线对齐一致,来实现像 lowpoly 一样的粗略阴影,常见于脸部、手部的法线调整。Yoolies 的脸部法线调整教程
notion image
notion image
notion image
另一种是使用类似 maya 这类 dcc 软件中的 data transfer 功能,将圆柱或胶囊体的法线映射到模型上,可以得到平滑的光照效果,常用于衣服、头发上,是一个简单高效的小技巧。
notion image
根据我的观察和研究,《崩坏 3》应该是沿用了《GUILTY GEAR Xrd》中的卡通着色方法和美术工艺,因此在效果上和后者非常相似。
notion image
基于不同的冷暖色调设定值得到的卡通渲染结果
notion image
中间那个想做出眼下三角的感觉,但那个效果不理想,大致感觉是试出来了,这个模型的布线有点差。
除了法线外,还存在一种更加简单粗暴的调整手段:mask 遮罩,例如用一个顶点色的通道存储 mask,来实现出 “鼻子、嘴巴处没阴影” 的效果。
notion image
这种可以理解为就是前面所说的阈值调整,这种方式比起法线调整要直观得多,门槛也低。原神、战双都是用的这种。
然而这种做法也有些限制。一个是网格本身,在眼部和嘴边应该得有这种一圈一圈向外扩大的布线;另一个是法线,毕竟是基于 HalfLambert 值来进行的调整,而 HalfLambert 本身就是 NdotL,法线如果本身就很平滑的话调整起来会方便很多。
notion image

光照

uts 中将光与影分为三种颜色:亮色、一阶暗色和二阶暗色。一个像素的颜色是基于法线 n 和光照方向 l 的点积 dotNL 来控制的。dotNL 大于阈值时为亮色,小于阈值时为暗色,根据 dotNL 值的大小,暗色中还可以再细分为一阶暗色和二阶暗色。
notion image
也有许多游戏使用 Ramp 贴图作查找表取色,这两种做法没有本质区别,只是用 Ramp 图的好处在于颜色间的过渡更好控制,更适合于表现细腻柔和的插画风格。
notion image
现在许多游戏中角色材质都有单独的光照方向,这算是一个普遍的做法了,ggxx 甚至在过场动画中为了达到最好的效果,会逐帧改变角色的光照方向。
充分了贯彻日式动漫中 “相机下的正确才是正义” 的理念。
notion image
手游上一般都会有人物专门的展示场景。为了保持最好的光影让角色始终看起来很可爱,这个场景的其实有一些讲究。拿战双举例
notion image
人物身上的影子包含两部分,一个是基于 nl 点积的光影(例如腿部两侧),另一个是来自方向光的投影(图中两个红色箭头)。
先说光影,就是指根据法线和光照方向来计算的阴影,光影方向随着角色旋转会发生变化,就我自己的观察结论,这是一个分段函数:
  1. 在 0-90 和 270-360 之间,始终是从 x 角度打过来
  1. 在 90-180 时,lerp(90, 0, (x-90)/90)
  1. 在 180-270 时,lerp(0, -90, (x-180)/90)
x 指的是角色旋转的角度,x=0 时光在角色的正前方。这样既能让玩家感受到光影存在方向的变化,又能让光始终照在玩家能看到的地方。
另一个是投影。这明显分了两个方向,能看到地上投影的光是从右边打过来,裙子、刀和额发投影是从上方打下来。针对这种有一个兼顾性能与表现的做法,就是裙子、刀这种自投影使用 ShadowMap,地上的投影使用平面阴影,毕竟地上大多数情况都是平坦表面,不需要考虑穿模,完美适合高性能的平面阴影,战双是不是这么用的我不清楚,没拆过包。(最开始想到的是用两个 ShadowMap,不过这样会多耗一张 RT,放手机上其实性能有点勉强)

风格化高光

高光、边缘光的处理方法和漫反射的映射类似,也是用 step function 控制明暗。
如果是金属或者其他特殊材质,需要保留更多的高光细节,可以和 Blinn-Phong 或者 GGX 光照模型混用。
不同材质的边缘光最好用 mask 精确区分:
盔甲上没有边缘光:
notion image
盔甲上有边缘光:
notion image
高光的模型大多是 blinn-phone。
spec = pow(dot(n, h) * 0.5 + 0.5, exp2(power));
但头发的高光算法一般是 kajiya-kai 模型,俗称 kk,一层高频,一层低频,再加上一个切线扰动贴图达到各向异性效果。但要想使得高光沿着每根发束移动,还需要美术在头发模型的垂直方向进行 uv 展开。
notion image
其它不那么硬核的方案,例如 uts 里使用视线空间法线采样 matcap 贴图做出假高光,法线贴图做出各向异性,加上遮罩图调整出某些位置的渐隐,也能达到不错的效果。
notion image
这种相似的做法在新樱花大战中也有使用,不过他们的遮罩图是通过 uv 采样,这样可以精准控制到每根发丝。
notion image
notion image
一般除了高光外,大家还会选择叠加一个基于法线 n 和视线 v 的菲涅尔边缘光,也就是
notion image
用来模拟出反光的感觉,这种光最早有在军团要塞 2 中提到,他们用来当做在黑暗时的补光手段和用来区分敌我阵营。
然而… 动漫里的边缘光和这种边缘光有点不同,动漫中所需要的一般是单向的硬边缘光,而不是后者中这种全方向的菲涅尔边缘光,当然我们可以通过一些手段来让后者变成硬过渡和单向的,UTS 里就有做到,不过这有点杀鸡用牛刀的感觉。这种边缘光大家一般还是用来表现特效的多一点。
notion image
要达到动漫里的边缘光,一种取巧的方式是用 NDotL 来计算,这里的 L 不是场景中的光方向,而是一个自定义的方向光,拿熊熊这张图举例的话,光方向就是在左上方射向右下方的样子,相当于把边缘光当作一般的直接光漫反射计算,做出来效果还挺还原的,更重要的是可控性更好。

可变形状的高光

我们在日式卡通渲染的着色部分描述了一个相对较为简单的高光计算方式,从计算方法可以看出,该方法和经典的 Blin-Phong 模型有很多相似之处,尤其是对高光强度的计算上,都采用了这个计算项:
notion image
这个 halfVec 也就是我们常说的半角向量,计算方法是:
notion image
其中,L 和 V 分别是光源方向和视线方向。
half3 halfVec = normalize(lightDir + ViewDir);
从我们上面描述的卡通渲染高光算法可以看出来,改变卡通渲染高光形状的关键就在于改变这个半角向量。因此文章中就针对半角向量定义了一系列的修改操作,这些修改操作可以叠加使用,也可以单独使用,每个操作对高光形状的影响均不同,具体有以下几个操作:
(1)平移,改变高光的位置:
notion image
这里,du 和 dv 表示的是切线空间中的 x 轴和 y 轴,也就是切线和副法线,alpha 和 beta 是自定义平移参数,最终偏移后的向量需要进行归一化处理。
(2)有方向的缩放,沿着切线空间的某个轴缩放高光形状:
notion image
sigma 是自定义参数,范围是 (0, 1],上式将使高光沿着切线空间的 X 轴缩放。
(3)分割,将一块连续的高光切分成两块:
notion image
其中,sgn 是符号函数,负数返回 - 1,否则返回 1,gamma1 和 gamma2 分别是自定义参数,若其中一个为 0,则只沿着另一个方向将高光切为两部分,若两个参数均不为 0,则高光被切成四块。
(4)方块化,将趋于圆形的高光变成方形:
notion image
其中,n 是自定义整数,n 越大高光形状越方,sigma 则定义了方形高光区域的大小,范围是 [0, 1]。
上述四个操作的具体实现可参见这篇文章 [12]。
notion image

风格化阴影

类似于风格化的高光,风格化的阴影也是在标准的阴影计算流程之后,定义了一系列针对标准阴影的操作,通过这些操作,配合用户自定义的参数,便可以达到风格化阴影的效果,总的来说,共有四类操作: (1)膨胀 / 腐蚀(Inflation):扩大或者缩小阴影范围,用参数 i 来控制
(2)亮度(Brightness):阴影区域的亮度,可以用于模拟半影区的效果,用参数 b 控制
(3)柔度(Softness):阴影边界处的柔和程度,用参数 s 控制
(4)抽象度(Abstraction):阴影形状的抽象程度,用参数 alpha 控制
几种操作和相应的效果
整个风格化阴影的生成是基于图像空间的从一个已经生成的精确阴影图开始。可以分成五个阶段:
(1)精确阴影的生成,由于是基于图像空间的,因此对精确阴影图的生成方法没有特别要求,可以是 shadow volume,shadow map,ray tracing 或者其他阴影生成技术,但必须要注意的是这里的阴影值一定是二值化的
(2)有向距离场的生成,基于图像空间的精确阴影,计算每个像素距离最近阴影边界的有向距离,这是文中算法的核心,也是后面风格化的基础,在文中给出了一种有向距离场的计算方法,当然也可以采用其他方案
(3)有向距离场的高斯模糊,这一步是抽象阴影生成的关键
(4)过滤,通过一个转移函数,将模糊后的有向距离场重新映射为阴影图
(5)使用过滤后的阴影进行光照计算
notion image
整个算法的流程,图 3,4 中红色部分表示阴影内部,蓝色表示阴影外部
可以清楚地看出整个算法流程的核心是(2)(3)(4),其中(2)是在整个图像空间计算有向距离场,文中给出的有向距离场公式 [13] 如下:
notion image
文中 p 的取值为 8,按照文中的说法,这个距离计算方法相较于欧几里得距离,在精确性(accuracy)和平滑度(smoothness)上有一个比较好的折衷(trade off)。这里 C 表示的是所有阴影的边界像素的合集(边界就是黑白发生变化的位置),分母上的积分项表示的是整个边界的长度,是一个归一化参数,离散化来看,就是屏幕上所有边界像素的个数
从上面这个计算公式就可以知道,如果要精确计算每个像素的有向距离,则需要针对每个像素遍历整个图像空间中的其他像素,找到所有是边界的像素,并代入上述积分中进行求和运算,这个计算是比较低效的,因此文中采用的方法是在当前像素周围随机取一些像素参与到有向距离的计算,然后用计算结果去估计精确的有向距离的值,也就是所谓的蒙特卡洛方法。此外 |�−�| 在文中使用的是一个三维的欧几里得距离,因此实际上计算这个有向距离时还需要一张深度图。
计算出有向距离场后,接着要做的就是对这张图进行模糊,可以想见,如果直接针对 visibility 图进行模糊的话,得到的实际上是柔化的软阴影,而不是我们想要的抽象阴影,所谓抽象阴影就是把精确阴影中的一些细节给略去,恰好就对应了模糊有向距离的值。这一步是用一个标准的高斯模糊去完成的,参数 alpha 表示高斯模糊的方差,这个值越大,则模糊程度越高,细节丢失越多,抽象程度越高。在文中的也使用了蒙特卡洛方法来减少高斯模糊的采样次数。
在得到了经过模糊的有向距离场后,接下来就是如何把模糊后的有向距离场重新映射回阴影值,这里用了一个很巧妙的转移函数,一次性完成了边界膨胀 / 腐蚀,亮度和柔和度的操作:
notion image
其中,b 是亮度,表示阴影区域的亮度值(非阴影区域亮度值是 1),D 是经过模糊处理的有向距离值,s 表示柔和度,换句话说表示了亮度从 b 到 1 过渡的区域宽度,也就是软阴影的宽度,i 表示了膨胀或者腐蚀的强度,正值表示膨胀,负值表示腐蚀,0 表示不膨胀也不腐蚀。
notion image
上图是这个转移函数的图像,结合有向距离场的定义再来直观地看这个转移函数其实很好理解,i 可以理解为等高线的值,我们认为有向距离值小于 i 的都是阴影区域,b 作为最低亮度很好理解,smoothstep 的功能是让阴影边界不再是跃变(如果是 step 就变成了跃变)而是有一定过渡,过渡的区间中点由 i 决定,区间长度则由 s 来决定。
上述算法中计算量最大的部分是有向距离场的生成,因为最终效果和采样数量关系密切,因此很难做到完全实时,这大概也是《军团要塞 2》最终没有集成这个算法的原因。
实际制作中,会希望阴影有一定的倾向,即有些地方容易产生阴影,有些地方则很难产生阴影。比如,凹陷的地方和受周围遮挡的地方应该被认为是影,需要增加阴影倾向,改变光照表现。这时候可以用一张贴图或者顶点色控制阴影的倾向,对上面的 Step Function 进行偏移。
notion image
没有阴影倾向的效果:
notion image
有阴影倾向的效果:
notion image
但是,阴影倾向信息不能反映外部遮挡物投射的阴影,最好结合 shadowmap 一起使用。

描边

参考《Real-Time Rendering》,卡通渲染中的轮廓线有以下几种:
notion image
(1)Boundary or border edge:仅为 1 个多边形所拥有的边界。封闭模型一般不具有这样的边界;
(2)Crease or hard edge:为 2 个多边形共有的边界,而且 2 个多边形的夹角(dihedral angle)大于一个阈值。这个阈值的参考值为 60 度;
(3)Material edge:不同材质的 2 个多边形的公共边界,或者是人为期望显示的边界;
(4)Silhouette edge:相对于当前观察方向而言,面向不同朝向的 2 个多边形的公共边界。

1. 点乘法(Surface Angle Silhouette)

基本思想是利用 viewpoint 的方向和 surface normal 的点乘结果得到轮廓线信息。这个值越接近 0,说明离轮廓线越近。计算方法如下:
half edge = saturate(dot (normal, viewDir)); edge = edge < threshold ? edge/4 : 1;
由于这种方法渲染的轮廓线宽度不均,实际应用不多。

2. 背面扩张法(Procedural Geometry Silhouetting)

这种技术的核心是用两个 pass 渲染。第一个 pass 中正常渲染 frontfaces,第二个 pass 中在渲染 backfaces,并使用某些技术来让它的轮廓可见。
常用的方法是,在顶点着色器把 backface 的顶点沿着顶点法向方向向外扩张,使其包裹原有的模型。
这种方法的优点是,不需要任何关于相邻顶点 / 边等信息,所有的处理都是独立的,渲染速度很快,而且线条宽度可控,有一定的健壮性。
缺点是:
一,像 cube 这样的模型,它的同一个顶点在不同面上具有不同的顶点法向,所以向外扩张后会形成一个 gap,如下图所示。
notion image
一种解决方法是强迫同一个位置的顶点具有相同的法线朝向也就是平滑法线。另一种解决方法是在这些轮廓处创建额外的网格结构。由于 gap 大多在模型放大的情况出现,根据到 Camera 的距离控制轮廓线的宽度,一定程度上可以减少 gap 的出现。
需要注意的是 backface 的方式需要顶点法线连续,否则在硬边缘的模型上表现会比较差,法线连续的方式一般是将顶点的多个法线取均值,即使用点法线而不是面法线。
Unity 法线默认导入方式是面法线,对此的解决方案一种是在 ddc 类软件中导出点法线贴图,或者是直接做平滑组平滑法线。另一种在导入 Unity 时使用脚本计算一次点法线并存入模型的顶点属性通道中,后者相对来说更灵活一些,因为可以描边用平滑法线而光源计算依旧用法线贴图。
这中间有一个问题是平滑法线后如何存入 fbx 文件中?
因为 Unity 一般只能导出为. asset 文件。所以这里可以选择直接在 Unity 外部用 FBX SDK 写一个平滑法线的工具,或者是选择平滑法线后用 Unity 官方的 FBX Export 插件,这个插件目前还是 preview 版本,不过够用了。讲道理,我觉得工具放在项目内美术做起来会更舒服一点,而且我们还能自己控制脚本写点批处理 UI,所以我选后者。
二,这种方法渲染的 backfaces 有可能与原有的模型在深度上发生冲突,并且遮挡模型,如下图所示:
notion image
一种解决方法是给 backfaces 设置 Z-offset,使轮廓线埋没到临近的面里。另一种解决方法是修改 backfaces 扩张的法线,使轮廓线扁平化。
总结:
上述方法都有一个共同的缺点,那就是对描边的外观可控性很少,而且如果如果场景上的物体都需要绘制轮廓,每个物体都会增加一个 draw call,性能上的开销会比较大。
此外,背面法只能渲染 Silhouette edge,不能渲染 Crease Edge 和 Material Edge。

3. 屏幕后处理法(Silhouetting by Image Processing)

这种技术利用了 G-buffer,在每个 buffer 中使用了图像处理的技术来检测轮廓信息。
基本思想是,利用图像处理中的一些算法,在 Z-buffer 中找到不连续地方,由此计算出大致的轮廓线。还可以在 surface normal 中找到不连续点,来获取更完整的轮廓线。最后还可以在 ambient colors 中,进一步完备前两步找到的轮廓线信息。
因此,基本步骤是:
  1. 使用 vertex shader 把 world space normals 和 z-depths 渲染到纹理中。
  1. 使用一些滤波算法来找到边缘信息。一种常见的滤波算子是 Sobel 边缘检测算子,还有 Canny 算子、Laplace 算子、Robert 算子和 Prewitt 算子。
  1. 找到边缘后,我们还可以使用一些图像处理操作,例如腐蚀和膨胀,来扩展或者缩小轮廓线宽度。
这种方法的优点在于:
(1)适用于任何种类的模型,而且不需要 CPU 参与创建和传递一些边的信息。
(2)可以渲染 Crease Edge,而且可以设置阈值,控制 Crease Edge 的采样。
但也有它的缺点:
(1)这种对 z-depth 比较来检测边界的方法,会受 z 变化范围的影响,一些 z 变化很小的轮廓,比如桌子上的纸张,就无法检测出来。
(2)轮廓线的宽度不能根据物体单独控制,而且不能区分 Silhouette edge 和 Crease Edge。

4.RT 高亮描边

其实… 还有一种,就是高亮描边,比方说求生里的这种。它的原理是用一张 RT 单独绘制纯色网格,然后对 RT 做 Blur,最后再与原本的颜色图混合。
不过一般用它来做选中的效果,而不会用来做卡渲的描边,虽然它效果不错,也不会把内描边绘制出来,但是它的问题依旧是不好单独控制描边宽度和颜色,而且它是真的那种纯粹的外描边,一点内描边都没有的,太过于适合用来表现轮廓了。
notion image

5. 内描边

一般我们想要动漫里的效果,应该是网格叠在一块时也会有描边的。
notion image

1. 本村线

材质上的描边只能在贴图上绘制。但是,由于精度问题,贴图上的线放大会出现锯齿。
notion image
《罪恶装备》使用了一种叫本村线的方法,即扭曲 UV,只用垂直线和水平线构成贴图。
靠近本村式线的地方(Wire Frame 显示)
说起来,Texture Mapping 的锯齿,某个纹素(构成 Texture 的 Texel)对应多边形面用单独 Texel 来描绘就会变得显眼化。相对的,如果有邻接的 Texel,和单独 Texel 的时候不同,因为四角 Texel 形状的轮廓实际上消失了,很难会呈现锯齿感。
不过,是因为邻接也有斜上或斜下的,事实上这个和 Texel 是单一的状态是一样的,也会有锯齿感出现。总之,水平线形状和垂直线形状的 Texel 如果是排列好的的 Texel 集合的,附加一些模糊化,就可以回避锯齿感出现的状况
notion image
本村式线的 UV 展开的例子
要试着理解是理所当然的事情,注意到这点的本村氏,[轮廓线是,只用垂直线和水平线予的实线构成的 Texture] 来制作出来。基于这个,在 3D 模型上使用时,在想要斜线或曲线的地方,把 Mapping 也一样用扭曲和弯曲的形状的来设计 UV(3D 模型上的每个多边形,来表示和 Texture Map 对应的数据),
好吧,这个方法是试着进行 Texture Mapping,不可思议的。即使是那种分辨率不高的 Texture,也可以获得美丽光滑的画线。
本村式线用的 Texture 的例子。这里是垂直线的绘制。
notion image
notion image
notion image
没使用这个技术的参考,使用 Line Texture(在贴图上直接画线) 的例子。放大后的可以看到图形锯齿明显。Mesh 分割也是,进行的是和绘制的线无关的拓扑。
但是,这种方法有个缺点,就是会扭曲贴图上的文字和花纹。所以,《罪恶装备》的角色都是用的纯色贴图,一些带有文字的区域都做成贴花,用新的 mesh 附在模型上。
本村式线的例子,只用直行的线段,看起来就像城市规划图那样不可思议的 Texture,例如四角线沿着边缘的 Texutre, 适用于肌肉凸起部分的轮廓线。肌肉的隆起,变成椭圆形的半球形状,使用本村式线就变成「把四角形装的边缘线对椭圆形状来 Mapping」。
四角变成椭圆形状,四角内部的领域有了很大的拉伸和歪曲。所以,如果这个四角型内部有文字或花纹,当然也会扭曲,因为最终这个 Texture 是用来附加轮廓线的东西,那样的文字或花纹没加进来。因为是外观显现的,最终只有线,这个手法生成的扭曲,作为外观问题上并不会显露出来。
当然,给予歪曲和扭曲的是基于 UV 的 Texture Mapping,角色或视点移动,Texture 也会收到放大缩小旋转的影响。所以说,绘制的线段也会受到这种影响,变成曲线或斜线,因为这种情况 Texture Mapping 可以使用 Bilinear Filtering,就会给曲线或斜线适当的模糊感。于是,这个正好成为了很好 Anti-Aliasing 的效果
notion image
notion image
不设定 Bilinear Filtering(上) 和设定 (下)
本村式线描绘的线段的粗细的强弱是可以看到的。但是,本村式线用的 Texture 的自身没有这样微妙的线段强弱,线的粗细是大体上单一的。既然 Texture 的线的粗细是平均的,如何处理画线的强弱的?
本村式线进行画线的粗细的强弱,是用 UV Map 的设计来加上的。想要出现粗的线,在 Texutre 上的线表现用的 Texel,所对应的多边形面分配的大一些,在 UV Map 上下功夫就可以了。
这个的本村式线,和制作流程,预感到也会在 Anime 笔触以外的游戏图形里广泛使用。
notion image
notion image
notion image
设定画线粗细强弱的例子。上边是没有设定,中央是 UV 和线重叠状态来设置强弱,下面是线自然的切断的表现。
无内轮廓线:
notion image
有内轮廓线:
notion image

2. 基于 SDF 的内描边抗锯齿

他的做法是将内描边使用 sdf 预计算后单独存放一个通道,也就是通过一种核运算来预计算出贴图上每个像素 “距离最近描边” 的程度,而根据 sdf 的性质用这种方式甚至可以控制 “较远” 的像素的处理方式。

3. 基于边缘程度的后处理

二之国中的做法选择在一个顶点属性通道中记录边缘程度,然后在后处理中来进行绘制内描边。
notion image

6. 特效

除了 matcap 外,七大罪也为特效添加专用的光源,让人物与特效有一致的表现。随着手机性能发展,多跑一个光源也慢慢能被主流机型接受了,在战双中能看见不少特效都有点光源。
notion image

7. 其它

1. 眉毛

动漫里一般眉毛是是能够显示在头发上的(反物理 + 1)。
uts 的一种做法是用模板测试,不过这样会多用一张模板贴图,在手机上对带宽不友好。
另一种做法,使用 RenderQueue 按头发 - 脸 - 内眉毛 - 外眉毛的顺序绘制,眉毛绘制两遍,第一个用 ZTest LEqual 绘制没被头发挡住的眉毛,第二个用 ZTest GEqual 绘制盖在头发上的眉毛。眉毛的计算量并不多,这种做法会更省一点。
notion image

2. 表情

表情的制作要么就是改模型,要么就是改贴图。
如果对精度没有较高要求、数量也很有限的话,其实直接用贴图就是一种非常实惠的做法,例如 >.< 这些表情换张图就能很好搞定。贴图的局限性在于动态得通过帧动画的形式来做,数量较多的话还不如改模型。
改模型的做法又可以分为 blend shape 和改骨骼。
blendshape 在 CG 制作动漫中非常常见,本质是修改网格顶点进行形变预先制作好一套表情组合,像是眨眼之间的表情切换可以通过顶点动画插值来实现。
notion image
修改骨骼的变换信息,通过蒙皮来修改脸部模型,就性能上来说这种会好一点。
notion image
如果需要表现出一些特殊的漫画中表情,就得考虑为眼睛和眉毛单独建模。
notion image
还特殊一点的:颜艺,光影计算已经救不了了,需要另外用贴图来制作,像是褶皱和嘴巴三角阴影,将一些常用小物件(汗、#)放在贴图中,在动画每帧中间插进去,国内基本不会做到这么细致。
notion image

3. 眼睛

眼睛能表达的东西很多,根据需求不同,实现方案也有很多种,最简单省事的就是贴图,最多一层 baseMap 和一层高光足够了,但这放在高品质游戏里难免有点敷衍。
新樱花大战中的做法比较复杂。眼睛的建模实际是两层,外层是向外凸的半透明层,用来绘制高光和反射,表示人眼的角膜;内层微微内凹,表示人眼的虹膜与中心的瞳孔。
notion image
C:Albedo Map,基本贴图,表现出人眼的虹膜效果。
D:对基本贴图的加算图
E:高光图,也是通过加法计算,会通过 UV 动画进行移动
F:环境反射图

8. 实际案例

对于角色,大部分游戏中的轮廓线都是用背面沿法线向外扩张的方法实现的。而且很多游戏还在此基础上做了轮廓线的风格化处理。例如,《罪恶装备》大量使用了顶点色控制轮廓线的风格:
R 通道:控制光照的明暗变化
G 通道:控制顶点根据视距膨胀的强度
B 通道:控制 z-bias,值越大轮廓线越靠后
A 通道:控制轮廓线的粗细
notion image
罪恶装备为什么使用顶点属性而不是贴图来存储数据? 虽然常见的是使用贴图,但这也是一个优化的思路,是为了排除像素精度带来的影响。因为采样极度依赖于分辨率,除非是超高分辨率,否则在特写镜头下很容易出现锯齿,而使用顶点属性则会根据屏幕分辨率来插值,达到更清晰的表现。
对于一些特殊的分镜,可以使用更复杂的轮廓线制作方法。在《火影忍者疾风传》中,使用了判断轮廓线的 RenderTexture,一次进行两种方法的绘制。还加入了可以给角色分别指定颜色的【ID Buffer】,用 4X4 的 16 像素 Texture 保存颜色信息,再取得颜色来绘制角色的轮廓线。另外也可以追加 Glare 的信息,制作出角色轮廓的自发光。
notion image
notion image

制作流程

罪恶装备

对于角色,大部分游戏中的轮廓线都是用背面沿法线向外扩张的方法实现的。而且很多游戏还在此基础上做了轮廓线的风格化处理。例如,《罪恶装备》大量使用了顶点色控制轮廓线的风格:
R 通道:控制光照的明暗变化,判断阴影的阈值对应的 Offfset。1 是标准,越倾向变成影子的部分也会越暗 (接近 0),0 的话一定是影子。
G 通道:根据相机距离控制描边宽度的系数,对应到 Camera 的距离,轮廓线的在哪个范围膨胀的系数
B 通道:控制 z-bias,值越大轮廓线越靠后,描边的 z 偏移值,一般很大,可以使得一些不想要的描边消失
A 通道:描边粗细程度,0.5 是标准,1 是 max,0 是无
notion image
以罪恶装备为例,shader 主要用到 3 张贴图:main tex, ilm map, sss map
(1)main tex:固有色
notion image
(2)ilm map:用于调整阴影和高光区域的形状
notion image
R 通道:
notion image
底色为 128 灰色,可以理解成材质的光泽度,控制高光的强弱。
材质越粗糙,灰度越小;材质越光滑,灰度越大。
G 通道:
notion image
底色为 128 灰色,表示阴影的倾向,控制阴影区域的大小。
容易产生阴影的区域(可以理解成有自投影的区域)灰度较小,如脖子、腋下、鼻子下方、衣服的褶皱、头发遮住脸部的部分。
B 通道:
notion image
底色为黑色,控制高光区域的大小。
A 通道:
notion image
(3)sss map:用于调整阴影的颜色
亮部颜色 = mainTex
暗部颜色 = mainTex * sssMap

变形的几何体,替换几何体

首先,不经意的看下 GUILTY GEAR Xrd -SIGN - 的战斗场景。各种各样的建筑物和背景对象排列着,这些不是 [布景的一张绘画],而是通过 3D 模型来表现的, 从用户视点也很容易辨别。
让对打的两个角色向左右移动的时候,近景会向左右很大的移动,远景会慢慢的向左右移动。是 2D 格斗游戏图形里常见的背景表现,实际上这里,隐藏着意想不到的秘密。
notion image
notion image
伊利里亚城下町的背景里的,建筑物的 Mesh 的单体表示,挪动摄像机。就看到了非常有冲击性的图形。
notion image
那样,场景中的近景,建筑物等背景 3D 模型有相对强烈的远近透视,是因为有意图把模型的向深处扭曲。
另一方面,一定距离以外的远景的 3D 模型群,虽然没有附加透视,但通过实际尺寸 Scale,也被大幅的小型化了。
这样配置模型是有理由的。
没有附加透视的正确尺寸缩放的模型,变得无法表现出场景的深度感。
远景对象缩小也是相同的理由,特意的缩小后放置在并不是那么远的距离上,战斗场景打斗时,与角色向左右移动一起,让远景物体一样向左右移动。
真实的 Scale 的情况,如果要把建筑物就那么小的来放置位置,必须配置在相当远的地方。想到在现实世界也有经验的,远方的景色自己左右移动数米也几乎没有移动。变成这样的 2D 格斗游戏的背景也变得没有变化,为了更好的运动,把小的 3D 模型配置在场景近处。
K.O 的场景时,摄相机会旋转,通常摄像机也会朝向内侧的方向。因为这种情况,内侧的,Camera 看到的范围的背景对象也要好好的设定。
还有,剧情场景的情况,使用了一部分 2D 的渲染布景来做背景。随着照摄像机工作,渲染布景的 2D 背景动态的移动,2D 背景自身用电视 Anime 的理论来处理的地方也是有的。
notion image
notion image
对话场景(上),从别的角度看的截图
notion image
在伊利里亚城内的拖拽摄像机 。关于战斗场景的视点内侧的模型也可以明白。
背景关联有 1 个补充,背景的集群角色 (路人角色),接近战斗舞台的是做成 3D 模型,远景的东西是 “纸片人”, 变成动画的 billboard。
那么,在战斗中登场的主要角色是约 4 万面多边形的模型,像前篇介绍的那样,和一般的基于 3D 图形的游戏角色有稍微不同的动作方式。
notion image
Faust 的开襟(?)追加部件的实现。
关于一般的 3D 图形,角色是用模型内部配置的骨骼来运动的,这里追随成为外皮的 3D 模型的结构来获取姿势或变形,并实现动画。
GUILTY GEAR Xrd -SIGN - 是情况是,基本的动作上,虽然也是根据这种方法的,但例如像毛发,刀剑的形状变形那样的攻击动作就无法实现了,那种特殊情况,要把 3D 模型的一部分部件替换来处理。
notion image
notion image
notion image
notion image
notion image
头发变形的 Milia 的情况,准备了追加部件,来进行替换的样子
每个角色的运动,要像 Cel Anime 的动态表现那样,进行独特的控制。和一般的 3D 游戏图形不同,GUILTY GEAR Xrd -SIGN - 的角色们,脸上的眼睛和鼻子,以及嘴的 “大移动”,有现实的人无法做到的那种部位的伸缩或肥大化,反过来缩小忽略的事情也是有的。
玩家可以看到游戏画面上的漂亮的事。对于 GUILTY GEAR Xrd -SIGN - 的图形来说就是正义的。
notion image
notion image
notion image
关于一击必杀的剧情场景 BEDMAN 的使用例子。一开始,制作的是从哪个方向看都说得过去的外表一样的表情 (上),初期印象的设定图(中央) 不同表现的地方很多。因此,驱动 Rig,调整目鼻口的位置和角度(下)。因为追求[从摄像机里看的印象是要一样的] 的事情,实现了角色有效果的演出。
GUILTY GEAR Xrd -SIGN - 上登场的角色,和一般 3D 游戏角色加入了同样的骨骼,这些骨骼的运动方法和控制方法,给予了高度的自由度。
例如眼球,可以在脸内非常自由的移动,嘴是可以像脸部体积变化那样变形和开闭。手足也可以自由的伸缩。这就是为了实现我们常见的 [Anime 的积极的表情表现] 而组装进去的。
滑稽的 [圆圆的眼球],表示愤怒的标志等漫画的记号表现,果然是这个系统无法对应的。为此,这种漫画表现必须要进行部位部件的替换,以及部分部件的附加,还有就是用 Texture 表现的方法来对应。
像突出手足的一部分的姿势,只是调整视角 (Angle of view),并没有动态的来表现。要很大的显示突出的手,为了强调远近感要扩大视角,深度方向的脸就变得很小了。
这种手被很大的显示的情况,拉伸 3D 模型方面的手腕,让手靠近摄像机。结果,GUILTY GEAR Xrd -SIGN - 的摄像机视角没有变化,可以说是在角色模型上附加了视角(笑)。
notion image
notion image
最终截图 (上),为了实现这种而进行“体格変更” 的揭秘截图(下)
标准状态的 UE3,是三个轴同时应用 1 个参数来对应骨头放大缩小的表现的。这么用的话,可以表现出变形的像二头身一样的角色。
notion image
notion image
通常头身和而头身角色(参考)。还有,这里展示的二头身角色,并不是本文谈到的 [3 个轴同时扩大缩小来实现的 UE3 的基本机能],而是通过 Arc System Works 对 UE3 进行[3 个轴分别扩大缩小的机能扩张] 来实现的。
notion image
notion image
部位扩大的实际使用例子,上边是产品版使用的最终截图,下面是没有使用部位扩大的状态
家弓氏对 UE3 的改造,对应了骨头的三轴分别扩大缩小处理。而且,移植到实际的制作阶段的时候,使用这个结构让手足等简便的扩大缩小,伸缩自如的调整的定制接口也制作了。这样一来,在肢体操作上,使用这个定制接口,就可以进行手足的扩大缩小,伸缩的调整的动作制作了。
notion image
notion image
动作的设定工作中,预览画面里进行手足的扩大缩小的例子。在「SoftImage」上,把小型的窗口内的数字上下移动来变更任意轴的 Scale,让 Rig 进行 Setup。

做成 3D 模型的特效群

发出的必杀技等附带的特效类的东西,除了一部分例外的大多的都是 3D 模型。
具体的,烟,火焰,各种闪光,防御特效等等。
石渡氏:
Finish(必杀技) 的时候等,有一部分摄像机的 3D 运动的演出,特效用 billboard 的话哗啦哗啦的感觉样子很不好,就下决心把特效群也用 3D 模型来做了(笑)。
这个是 [说起来容易做起来难],实际问题,还是花相当多的力气活来实现的。
例如,下面展示的烟的特效,看起来有一点像流体动画,实际上,是美术师通过手动 1 帧 1 帧的建模做出来的。飞舞着膨胀,破裂的爆烟效果,为了能像看到的那样,也是美术师 1 帧 1 帧做出来的。
notion image
notion image
烟的特效(上),模型是 3D 数据的缘故,移动摄像机,变得好看。
notion image
跳跃时产生的烟的 Wire Frame 显示
notion image
Zato 的影子融化的特效模型。附加动作前的素体。
操纵影子的 Zato,使用技能可以液态和溶解到镜子里活动,中途还可以变形为 Zato 的角色模型,中途要另外准备 [影子溶解] 特效模型来进行替换。
notion image
notion image
这里是附加了动作完成版的影子溶解模型 (上),下面是,3D 模型的证明,尝试着改变了角度。
顺便说一下,目前介绍的特效群的里面,烟等纯然的特效类没有进行光照 (lighting),阴影等是直接烘培到 Texture 来做成的。也没有使用出现轮廓线的处理。火焰或闪光等发光系的特效,是只能看到光,并不实际的照亮周围,这个是 2D 格斗的情况,根据条件特效外观变化的话,担心游戏感觉会出现故障。
另一方面,Zato 的影溶等,对于附加在角色上的部件类型的特效,要进行光照。3D 特效的采用,今后也要进行慎重讨论。

Post Effect

近年来照片真实系的 3D 游戏图形,倾向实现各种各样的 Post Effect。对于这个,把像 Cel Anime 作品风格为目标的 GUILTY GEAR Xrd -SIGN - 的场合,可以说是相对次要的东西。虽说如此,但 Post Effect 的 Pass 还是有的。
像前篇传达的一样,GUILTY GEAR Xrd -SIGN - 的 Anti-Aliasing 的处理采用了 FAXX,FXAA 是用 Post Effect 实现的。还有 GUILTY GEAR Xrd -SIGN - 的 RenderTarget 采用的是 aRGB 的 16 位浮点数类型的 64 位 Buffer,因为实现了对应 HDR(High Dynamic Range)Rendering Pipeline,高亮度部分溢出的 Light Bloom 效果的 Post Effect 也被使用了。
notion image
notion image
战斗场景,让 FXAA 有效化的最终截图(上),和无效化状态的截图(下)
notion image
notion image
这个是同样的战斗场景,最终截图(上)和所有 Post Effect 都无效化的截图(下)的比较
石渡氏:
最近的 Anime 也是,因为这种 Light Bloom 的效果也被普通的使用了,所以考虑不会有违和感。除此之外,Diffusion 的效果,也用 Post Effect 给予了。
本村氏:
对于有明暗精确的二值划分感觉的 Anime 的画面,Diffusion 在增加柔和的风格上是非常有用的。和 Light Bloom 在亮度值 1.0 以上的地方才处理相比,Diffusion 在较少的亮度值的地方也能表现效果。
Light Bloom 和 Diffusion,都是给予把亮部模糊溢出的效果的东西,分别作为系统的 Filter 处理来实现的。具体的是, 使用 Light Bloom Filter 后,进一步的使用 Diffusion Filter。Diffusion 虽然在散乱的处理上比较有限,但是有在某种程度的亮度地方都可以使用的印象。石渡氏是,[主要的格斗游戏部分,太过模糊的话游戏也会变得难玩,这种 Post Effect 加强的地方以剧情场景 (Cut Scene) 为中心]这样的叙述着。
通过 Material,可以设定 Texture 给予 1.0 以上的亮度来作为发光体。
notion image
notion image
Light Bloom 有效(上)和无效(下)
notion image
notion image
Diffusion 有效(上)和无效(下)
notion image
notion image
使用发光体 Texture 的 Ramlethal(上)和 Potemkin(下)

其他的一些实例

七大罪中使用 basemap 和 matcap 来控制人物颜色,缺点是得到的是一个比较固定的光照表现(相机不旋转的话),但好处是可以通过更换 matcap 贴图来轻松实现不同 buffer 下的人物特效。
notion image
由于是手游,牺牲一些光照效果来使用 matcap 确实是比较实惠的选择,前提是美术能接受的话。
此外,在一些需要将衣服颜色提供给玩家染色的游戏中,就不能再在 BaseMap 中存入颜色,而应该在着色器中计算,BaseMap 仅存放衣服的褶皱之类阴影。
notion image

PBR 与卡渲

卡渲与 PBR,这类做法在霓虹国那边业界已经很熟悉了,至少异度之刃 2、噬魂者 3 就已经有了这种做法,然后传承到噬血代码中,米哈游也一直在探索这个方向,这在以后说不准会成为主流。虽说有 PBR,但实际上皮肤还是保持卡通渲染(顶多加个次表面散射),而服装和装备则通过 PBR 元素来表现质感,不完全是 PBR,而是通过一个权重值来与卡渲融合。
下图是噬血代码的渲染流程,他们使用的是延迟渲染,除了 GBuffer 外还单独扩展一张专门存放数据用的贴图。
notion image
A:未经后处理的 PBR 表现
B:BaseMap
C:后处理 Bloom 图
D:GBuffer 扩展图的 R 通道:表示 PBR 程度的遮罩,越白则 PBR 程度越强
E:后处理中计算的光影图,在 A 图中就可以看到已经算进去了
F:后处理中计算的投影和 AO,顺便一提 GBuffer 扩展图的 A 通道:投影的落下距离(偏移值),此外 G 通道是投影的优先级,B 通道是用于区分角色、背景等的 ID
G:E 与 F 的最小值合成的阴影图
H:PBR 高光和后处理高光的合成
I:最终效果,B 图中的 BaseMap 和 G 中的阴影做乘算,根据参数与 H 图和 C 图相加,再根据 D 图的权重来与 A 图的 PBR 进行混合。
可以从 D 图看到皮肤光影处没有任何 PBR 权重,就是纯卡渲,并且颜色有渐变的过渡;投影是硬投影;眼睛处有些许 pbr 权重。
而服装和装备是卡渲和 PBR 的权重混合,噬血代码和异界锁链 PBR 都是使用三个通道来控制:金属度、AO 和粗糙度 / 光滑度。间接光部分没有说明,但提到在地图中布有许多 probe,猜应该是 lightProbe 和 reflectProbe。
这种算是比较复杂了,其实要表现出 PBR 感觉,最显著的就是 PBR 高光:直接高光和间接高光。直接高光选择常见的 GGX,间接高光用采样 reflectprobe 来表现,其实 Unity 的默认材质那一套就很好。至于间接漫反射光可以看情况选择采样 lightprobe,不过漫反射相对而言不明显,根据实际情况决定要不要。

后处理

卡渲里对画面提升最大的 Bloom 和 ColorGrading。

1.Bloom 效果

基本算是必备之一的后处理效果,有没有 Bloom 那种朦胧感真的不一样。
虽然放手机上带宽消耗挺大的,但是效果着实不错,Bloom 实现的相关文章很多就不多说了,去 Unity 的 PostProcess(PP)包看看源码都可以。
notion image

2.ColorGrading

调色绝对算得上是最神奇的后处理效果了。它一般有两种使用方式。

1. 用查找表 LUT 来进行颜色转换

LUT 表可以由美术在 PS 里生成并导出,可以做出昼夜黄昏效果,什么新海诚风格、日式风格都可以。它原本就是摄影领域的产物,各种大片感都能做,难点在于调色也是一门学问(电影还有调色师呢)。它的原理是通过 rgb 通道值作为采样坐标从查找表中查找并返回一个新的颜色。不同的 LUT 可以不同的画面整体颜色。(需要注意大概就是提供给美术的测试图的颜色空间需要和计算时一致,在 Unity PP 中为了导出的测试图具有更大的色域,计算前会先转到 ALEXA LogC 空间)
例如昼夜黄昏。
notion image

2. 在引擎内实时调整反馈

这种其实和在 PS 里原理是一样,不过 PS 能导出一整张 LUT,这种则封装在引擎内可以实时调整。一般颜色调整分为大致两个步骤:
Color Grading:画面整体调色
ToneMapper:将 HDR 颜色映射到 LDR 范围
Color Grading 又分了很多小项:Post Exposure、Contrast 等,建议看看 catlike 这篇文章
原理讲得很清楚,一看就懂(误)。不过就算明白了原理,但是要做出还不错的效果还是挺不容易的。

3. 色散效果

这在动作游戏见到比较多,增强击中感,常和径向模糊配套出现,不算常用,仅仅记录一下。
原理上挺简单,就是对不同颜色通道值使用不同的 uv 偏移值来采样,这里是 rg 通道,并且根据像素离屏幕中心的距离远近会有不同的偏移值。
notion image
后处理的其它类别太多了,就不细说,类似的文章也很多。

有意思的技巧

Fake Interior

用 CubeMap 和一个正方体实现假的 “室内” 效果,我觉得挺有趣的,于是做了一个实现。

Imposter Animation

对大量的 npc 使用面片播放帧动画代替实际人物模型,而这些图片是由多个相机对 3D 模型预计算生成图集所得,实际游戏中根据视线角度和关键帧来显示子图。
又是一个能省下来计算量的做法,况且这些面片还可以合批。
不过用它做动画的问题在于每一帧就需要一张图,要保证精度,一张图至少得要 512 分辨率,一个帧动画少说都有 5、6 帧,这么多贴图放 shader 里难免对带宽有点吃力,这种还是只做静物就行,大批量的房屋比较合适。
这个效果我也在 Unity 做了一个初版,基本和视频中一致。
notion image
notion image

顶点运动模糊

通过移动顶点位置来实现的运动模糊,简单高效,但要调整出好看的效果估计得加个遮罩。这个也有人实现过一遍。
notion image

AnimMap

本质上是一种顶点动画,通过贴图记录下每个关键帧的顶点位置,比方说 6*1024 的贴图,6 表示 6 个关键帧,1024 记录 1024 个顶点的位置,结合 GPUInstance,达到高性能且不错的表现,非常适合大批量路人、观众这种,可以弥补 imposter 的不足。

参考: