原文链接https://blog.uwa4d.com/archives/usparkle_cartoonshading.html
这是侑虎科技第246篇原创文章,感谢作者洛城供稿,欢迎转发分享,未经作者授权请勿转载。当然,如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群:465082844)
作者知乎:https://www.zhihu.com/people/luo-cheng-11-75/answers
同时,作者也是U Sparkle活动参与者哦,UWA欢迎更多开发朋友加入 U Sparkle开发者计划,这个舞台有你更精彩!
前言:卡通渲染是图形学中一个有趣的话题,属于非真实感计算机图形学(NPR)的范畴,在NPR领域中也最多地被应用到实际游戏中,近年来流行的《守望先锋》,《英雄联盟》,《DOTA2》,《崩坏3》等游戏中都或多或少地出现过卡通渲染的身影,恰好最近对这个领域的内容作了一些了解和探索,所以就对其中涉及的一些经典技术做一个概述。
壹 · 卡通渲染的分类
在具体讨论技术手段之前,先就卡通渲染做一个分类。卡通渲染最关键的特征包括不同于真实感渲染的艺术化光影效果和描边。以这两个关键的特征为卡通渲染分类的话,可以将近年来游戏中常用的卡通渲染分为美式卡通风格和日式卡通风格。美式卡通风格在色彩上比较连续,有渐变色,着色风格很大程度上依赖于艺术家定义的色调(tone),而在阴影和高光方面常常采取夸张和变形的做法,比较典型的是《军团要塞2》;日式卡通风格往往角色造型更写实,但在着色方面,则趋向于大片大片纯色色块,并有的明暗交界,例如《崩坏3》。虽然这样的分类并没有清晰界限,但易于描述,接下来我们就按照美式卡通和日式卡通的分类,从光影和描边两个维度上分别列举各类技术实现。
军团要塞2的卡通渲染,人物造型夸张,但着色连续,接近真实感光照
崩坏3游戏截图,着色以单色色块为主,有明显的明暗交界
贰· 描边
描边是一个比较常用的技术,在《Real Time Rendering》中有相当篇幅的综述,大致来说包含了三类:
(1)基于视角的勾边,这部分的计算依赖于我们的一个直觉观察:当我们的视线和某个表面相切时,这个表面上的像素点往往就是模型的边缘,基于这个观察,我们可以用dot(viewDir, normal)^{k} 来估计一个像素的“边缘程度”,当然,这个值也可以用来作为纹理坐标去采样一张预定义的“轮廓纹理”。
基于视角的描边,最大的缺点是线宽粗细差别较大,不易控制
(2)基于几何生成方法的描边,这类方法的特点是描边本身是一个单独的几何体,通过特殊的方法绘制出来,比较常见的做法是shell method,原理和实现都比较简单:首先在绘制结束正常的模型后,将需要描边的物体改用正面剔除再绘制一遍,在VS中将顶点沿着法线方向膨胀一定距离,然后在FS中将模型用纯色输出。另外一种叫做z-bias的方法,也是绘制背面,但不膨胀,而是把背面顶点的Z值稍微向前偏移一点点,使得背面的些许部分显示出来形成描边效果。
基于shell method的绘制方法,实现简单,线宽较为均匀
(3)基于图像处理的描边,这类方法的实现可以说更接近于“边缘”这一概念的本质定义,什么是“边缘”呢?边缘就是在深度或者法线上不连续的位置。因此为了获取边缘,我们只需要在图片上找到深度或者法线不连续的位置即可,因此,我们需要将深度信息和法线信息以贴图的形式传入,运用边缘检测算法去寻找这些像素。这类方法的优点是描边的线宽一致,缺点是需要额外的法线和深度信息,当然,由于近年来流行的延迟渲染框架,法线和深度本来就是G-Buffer的一部分,因此往往不需要额外绘制法线和深度的信息。
基于边缘检测的描边方法,分别用深度信息和法线信息进行单独的边缘检测,而后合并起来成为最终的描边
美式卡通中的做法
美式卡通中往往倾向于使用基于图像处理的描边方法来生成均匀一致的描边效果。在《英雄联盟》中小兵和英雄的勾边效果就是用Sobel算子对深度信息进行边缘检测来获得的。由于游戏中只需要针对小兵和英雄勾边而不需要对场景地图进行勾边,因此在LOL中,勾边的计算并非全屏后处理,而是逐物体进行的,这样的好处是可以随意控制哪些物体描边,每个物体可以单独指定描边颜色,缺点是当物体较多时(尤其是skinned mesh较多时)计算量会增大。一个折衷的方案是,在进行正常绘制的阶段用stencil buffer标记出需要描边的物体,然后用一个全屏的后处理,对stencil buffer标记的像素进行边缘检测,当然这样的话,就很难给每个物体单独指定描边颜色了。
实际上,在LOL中有两种类型的描边,一种是小兵和英雄的固定描边,另一种是防御塔发出攻击警报或者某个单位被点选时才产生的红色描边,这两种描边在处理上略有差别,前者直接使用边缘检测的结果作为最终描边,而后者则是对边缘检测结果再进行一次模糊,借此来扩大和柔化描边效果。
LOL中两种类型的描边,可以看出第二类描边的线宽更宽,并且有明显的过度效果
日式卡通中的做法
日式卡通中往往倾向于使用基于几何体生成的方法去描边,这类描边方法相较于另两类方法的好处在于线宽更容易为美术所控制,而在日式卡通中,往往需要粗细有变化的描边去体现角色不同部位的特征,例如在《GUILTY GEAR Xrd》中,角色的描边就是通过几何体生成的方法,结合了shell method和z-bias method,并引入了逐物体的顶点色来控制描边细节,同时也是为了保证描边粗细不会随着摄像机视距发生变化,具体来说,顶点色存储的信息包括:
- R通道:控制toon shading的阈值,和描边无关,和着色有关,这个我们后面描述
- G通道:控制顶点根据视距膨胀的强度(这个部分具体操作我也没有完全弄清楚,希望了解的朋友来补充)
- B通道:控制描边的z-bias,越大则描边越不可见
- A通道:控制描边的粗细
上述做法中比较直观的理解是,通过引入逐顶点的线宽系数,使得整个描边的细节更易为美术控制,但是从我的理解来看,线宽控制只需要一个值即可,视距无关的粗细可以通过给偏移值offset.xy乘以当前顶点的z值来实现,似乎并不需要三个值来控制。
没有vertex color,轮廓线宽没有粗细变化
有vertex color, 轮廓线可以按照美术的需要去设定逐顶点粗细变化
叁 · 着色
Cel Shading和Tone Based Shading
先来描述两种经典的NPR着色方法,分别是Cel Shading和Tone Based Shading5。
Cel Shading的基本思想是把色彩从多色阶降到低色阶,减少色阶的丰富程度,从而实现类似手工着色的效果,具体来说,可以用如下计算方法:
其中,Kd表示模型自身的贴图颜色,celCoord表示法线和光照方向的点积,用作一维色彩表的查找坐标,而paletteTex则是由美术绘制的一维色阶表,一般来说是由几个纯色色块组成的,如下图:
上述做法可以用于模拟卡通渲染的漫反射分量,却并没有考虑到视角相关的光照分量的模拟,因此很难实现类似菲涅尔效果的卡通渲染。实际上,也可以用类似的查找表的思路来视角相关光照分量的色阶离散化6,只需要将一维查找表扩展到二维即可:
相应地,查找坐标也扩展到了二维。
不同于Cel Shading,Tone Based Shading的风格化是基于美术指定的色调插值,并且插值得到的色阶是连续的。首先需要由美术指定冷色调和暖色调,而最终模型的着色将根据法线和光照方向的夹角,在这两个色调的基础上进行插值,具体算法如下:
,
其中,Kd仍是模型自身色彩贴图,Kblue,Kyellow和alpha,beta则均是自定义的参数。
基于tone based shading绘制的球体
日式卡通中的着色
前面已经描述过,日式卡通在着色方面比较典型的特点是以大量纯色为主,进一步说,往往只有“明暗”或者“冷暖”两个色阶,因此光照计算往往最后也要映射到离散的色彩表上。 仍然以《GUILTY GEAR Xrd》为例,它也一定程度上包含了Cel Shading和Tone Based Shading的部分思想,将色阶离散成为“明暗”两个色调,并由美术指定冷暖色调的颜色:
上述公式表示了这个卡通渲染的漫反射部分,其中threshold表示明暗交界的阈值,在游戏中通过顶点色的R通道来实现逐顶点的控制。Kcool和Kwarm由美术逐物体地指定,Ksss是对模型次表面散射效果的模拟,对皮肤而言一般呈粉红色,通过美术绘制的SSS贴图来实现逐像素控制,并且只有暗部的像素才会受SSS贴图的影响。Kd是模型自身的颜色贴图。darkness表示了某个像素的明暗程度,用于确定色调的冷暖。除了正常的dot(normal, lightDir)项,游戏中还加入了由美术绘制的AO贴图,来实现一些边角缝隙的暗部效果。我在实现时又引入了动态的阴影部分,最终darkness的计算公式为:
其中shadow是由shadowMap的算法计算得来的。
高光的计算更简单一些:
其中,spec表示高光的强度,threshold可以由美术逐物体或逐顶点指定,specMask和specPower由美术绘制的贴图来逐像素控制,类似于phong着色中的specular和glossiness的作用。specColor可以由美术逐物体地指定,也可以把AO,shadow和明暗色调作为影响因素添加进去。最终的着色结果将漫反射和高光叠加即可。
在实际游戏中使用时,上述方法往往还需要配合美术针对具体模型进行法线修正。 根据模型顶点位置和拓扑关系计算出的法线往往细节过度,表现在上述卡通渲染的结果上就是往往会出现许多不需要的暗部细节,修正的方法是使用模型法线转印,给精细的模型一个近似的低精度proxy(比如用一个球形代表模型的头部,用一个圆柱形代表模型的胳膊或者腿),然后用proxy上附近顶点的法线作为模型的法线来使用。此外,还需要考虑到明暗交界处反走样的问题,这里不做展开。
根据我的观察和研究,《崩坏3》应该是沿用了《GUILTY GEAR Xrd》中的卡通着色方法和美术工艺,因此在效果上和后者非常相似。
基于不同的冷暖色调设定值得到的卡通渲染结果
美式卡通中的着色
Valve在其游戏《军团要塞2》7中描述了他们的卡通渲染方案,这个卡通渲染算法也在后来影响了《DOTA2》的卡通渲染的实现。他们将卡通渲染着色分为了view dependent term和view independent term。两部分的计算分别如下:
这部分的实现在其他知乎专栏文章中有详细的描述和实现,这里不再做详细的解释。直观地来说,在视角无关的照明部分,《军团要塞2》中除了考虑了一般的漫反射部分外,还加入了基于模型法线方向的环境光分量,此外,通常的漫反射分量改为了wrapped diffuse;而在视角相关的照明部分,《军团要塞2》除了考虑了一般的镜面反射外,还基于菲涅尔现象实现了类似边缘光的效果。实际上,类似ambient cube和warpped diffuse的做法也被Valve应用在《Half Life》等其他游戏中9,在早期3D游戏中用以模拟全局照明。虽然这些方法都是一些纯粹的trick,但是能够以很小的开销实现不错的效果。
《军团要塞2》的最终着色结果,可以看出明暗交界处有明显的泛红(warpped diffuse的效果),模型边缘可以看到边缘光
经典的half-lambert方法也算是warpped diffuse的一种变体
肆 · 风格化高光和阴影
在7的Future Work里,还提到了可变形状的高光10和风格化阴影11,这两个风格化渲染算法的思路都比较有趣,这里简单就其实现原理进行一个概述。
可变形状的高光
我们在日式卡通渲染的着色部分描述了一个相对较为简单的高光计算方式,从计算方法可以看出,该方法和经典的Blin-Phong模型有很多相似之处,尤其是对高光强度的计算上,都采用了这个计算项:
这个halfVec也就是我们常说的半角向量,计算方法是:
其中,L和V分别是光源方向和视线方向。
从我们上面描述的卡通渲染高光算法可以看出来,改变卡通渲染高光形状的关键就在于改变这个半角向量。因此文章中就针对半角向量定义了一系列的修改操作,这些修改操作可以叠加使用,也可以单独使用,每个操作对高光形状的影响均不同,具体有以下几个操作:
(1)平移,改变高光的位置:
这里,du和dv表示的是切线空间中的x轴和y轴,也就是切线和副法线,alpha和beta是自定义平移参数,最终偏移后的向量需要进行归一化处理。
(2)有方向的缩放,沿着切线空间的某个轴缩放高光形状:
sigma是自定义参数,范围是(0, 1],上式将使高光沿着切线空间的X轴缩放。
(3)分割,将一块连续的高光切分成两块:
其中,sgn是符号函数,负数返回-1,否则返回1,gamma1和gamma2分别是自定义参数,若其中一个为0,则只沿着另一个方向将高光切为两部分,若两个参数均不为0,则高光被切成四块。
(4)方块化,将趋于圆形的高光变成方形:
其中,n是自定义整数,n越大高光形状越方,sigma则定义了方形高光区域的大小,范围是[0, 1]。
上述四个操作的具体实现可参见这篇文章12。
四个基本的操作符
风格化阴影
类似于风格化的高光,风格化的阴影也是在标准的阴影计算流程之后,定义了一系列针对标准阴影的操作,通过这些操作,配合用户自定义的参数,便可以达到风格化阴影的效果,总的来说,共有四类操作:
(1)膨胀/腐蚀(Inflation):扩大或者缩小阴影范围,用参数i来控制
(2)亮度(Brightness):阴影区域的亮度,可以用于模拟半影区的效果,用参数b控制
(3)柔度(Softness):阴影边界处的柔和程度,用参数s控制
(4)抽象度(Abstraction):阴影形状的抽象程度,用参数alpha控制
几种操作和相应的效果
整个风格化阴影的生成是基于图像空间的,从一个已经生成的精确阴影图开始。可以分成五个阶段:
(1)精确阴影的生成,由于是基于图像空间的,因此对精确阴影图的生成方法没有特别要求,可以是shadow volume,shadow map,ray tracing或者其他阴影生成技术,但必须要注意的是这里的阴影值一定是二值化的。
(2)有向距离场的生成,基于图像空间的精确阴影,计算每个像素距离最近阴影边界的有向距离,这是文中算法的核心,也是后面风格化的基础,在文中给出了一种有向距离场的计算方法,当然也可以采用其他方案。
(3)有向距离场的高斯模糊,这一步是抽象阴影生成的关键。
(4)过滤,通过一个转移函数,将模糊后的有向距离场重新映射为阴影图。
(5)使用过滤后的阴影进行光照计算。
整个算法的流程,图3,4中红色部分表示阴影内部,蓝色表示阴影外部
可以清楚地看出整个算法流程的核心是(2)(3)(4),其中(2)是在整个图像空间计算有向距离场,文中给出的有向距离场公式13如下:
文中p的取值为8,按照文中的说法,这个距离计算方法相较于欧几里得距离,在精确性(accuracy)和平滑度(smoothness)上有一个比较好的折衷(trade off)。这里C表示的是所有阴影的边界像素的合集(边界就是黑白发生变化的位置),分母上的积分项表示的是整个边界的长度,是一个归一化参数,离散化来看,就是屏幕上所有边界像素的个数。
从上面这个计算公式就可以知道,如果要精确计算每个像素的有向距离,则需要针对每个像素遍历整个图像空间中的其他像素,找到所有是边界的像素,并代入上述积分中进行求和运算,这个计算是比较低效的,因此文中采用的方法是在当前像素周围随机取一些像素参与到有向距离的计算,然后用计算结果去估计精确的有向距离的值,也就是所谓的蒙特卡洛方法。此外 | x - y | 在文中使用的是一个三维的欧几里得距离,因此实际上计算这个有向距离时还需要一张深度图。
计算出有向距离场后,接着要做的就是对这张图进行模糊,可以想见,如果直接针对visibility图进行模糊的话,得到的实际上是柔化的软阴影,而不是我们想要的抽象阴影,所谓抽象阴影就是把精确阴影中的一些细节给略去,恰好就对应了模糊有向距离的值。这一步是用一个标准的高斯模糊去完成的,参数alpha表示高斯模糊的方差,这个值越大,则模糊程度越高,细节丢失越多,抽象程度越高。在文中的也使用了蒙特卡洛方法来减少高斯模糊的采样次数。
在得到了经过模糊的有向距离场后,接下来就是如何把模糊后的有向距离场重新映射回阴影值,这里用了一个很巧妙的转移函数,一次性完成了边界膨胀/腐蚀,亮度和柔和度的操作:
其中,b是亮度,表示阴影区域的亮度值(非阴影区域亮度值是1),D是经过模糊处理的有向距离值,s表示柔和度,换句话说表示了亮度从b到1过渡的区域宽度,也就是软阴影的宽度,i表示了膨胀或者腐蚀的强度,正值表示膨胀,负值表示腐蚀,0表示不膨胀也不腐蚀。
上图是这个转移函数的图像,结合有向距离场的定义再来直观地看这个转移函数其实很好理解,i可以理解为等高线的值,我们认为有向距离值小于i的都是阴影区域,b作为最低亮度很好理解,smoothstep的功能是让阴影边界不再是跃变(如果是step就变成了跃变)而是有一定过渡,过渡的区间中点由i决定,区间长度则由s来决定。
上述算法中计算量最大的部分是有向距离场的生成,因为最终效果和采样数量关系密切,因此很难做到完全实时,这大概也是《军团要塞2》最终没有集成这个算法的原因。
伍 · 总结
本来是想简单的就卡通渲染做一个概述,写着写着就扯了一堆相干和不相干的技术。总的来说这篇文章偏综述性质,没有涉及到太多具体的实现,实现部分可以参考我在引用中列出的一些文章。卡通渲染这个领域从我的理解来看,是一个经验大于理论,美术大于算法的领域,因此应该主动地接受更多地trick,不要过度地思考“为什么”,毕竟效果好看,直观上好理解即可。关于卡通渲染的在本文中的分类,可能不是一个准确的描述,但是也提供了一种思考角度:有那么多的NPR相关技术,在一个具体的项目中我究竟要使用其中的哪些技术?这时候具体的画风要求就变得格外重要,因为画风决定了具体技术的选择和创新。文中的许多内容属于我自己对一些资料的解读,可能有许多理解上的错误,欢迎指正,另外今后我也会尽量避免写这么长的内容。
陆 · 引用
[1] A Trip Down The LOL Graphics Pipeline
[2] 西川善司[GUILTY GEAR Xrd -SIGN-]的[纯卡通动画的实时3D图形]前篇
[3] 西川善司[GUILTY GEAR Xrd -SIGN-]的[纯卡通动画的实时3D图形]后篇
4 Cel Shading - Wikipedia
https://en.wikipedia.org/wiki/Cel_shading
5 Amy Gooch, Bruce Gooch, Peter Shirley, Elaine Cohen. A non-photorealistic lighting model for automatic technical illustration
6 Pascal Barla, Joëlle Thollot, Lee Markosian. X-toon: an extended toon shader
7 Jason Mitchell, Moby Francke, Dhabih Eng. Illustrative Rendering in Team Fortress 2
9 Jason Mitchell, Gary McTaggart, Chris Green. Shading in Valve’s Source Engine
10 Ken-ichi Anjyo, Katsuaki Hiramitsu. Stylized Highlights for Cartoon Rendering and Animation
11 Christopher DeCoro, Forrester Cole, Adam Finkelstein, Szymon Rusinkiewicz. Stylized Shadows
13 Jianbo Peng, Daniel Kristjansson, Denis Zorin. Interactive Modeling of Topologically Complex Geometric Detail
14 Drew Card, Jason L. Mitchell. Non-Photorealistic Rendering with Pixel and Vertex Shaders
文末,再次感谢洛城的分享,如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群:465082844)。
也欢迎大家来积极参与U Sparkle开发者计划,简称"US",代表你和我,代表UWA和开发者在一起!