转载:https://zhuanlan.zhihu.com/p/86726394
卡通渲染是一种非真实感的渲染技术(NPR),可以使生成的图像呈现出手绘般的效果。广义的卡通渲染技术有很多,本文介绍的主要是日系卡通的渲染技术。
1 轮廓线
参考《Real-Time Rendering》,卡通渲染中的轮廓线有以下几种:
(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.1 点乘法(Surface Angle Silhouette)
基本思想是利用viewpoint的方向和surface normal的点乘结果得到轮廓线信息。这个值越接近0,说明离轮廓线越近。计算方法如下:
half edge = saturate(dot (normal, viewDir));
edge = edge < threshold ? edge/4 : 1;
由于这种方法渲染的轮廓线宽度不均,实际应用不多。
参考资料:
【Unity Shader实战】卡通风格的Shader(一) http://blog.csdn.net/candycat1992/article/details/37882425
在Unity进行水墨风3D渲染的尝试 https://zhuanlan.zhihu.com/p/25346977
1.2 背面扩张法(Procedural Geometry Silhouetting)
这种技术的核心是用两个pass渲染。第一个pass中正常渲染frontfaces,第二个pass中在渲染backfaces,并使用某些技术来让它的轮廓可见。
常用的方法是,在顶点着色器把backface的顶点沿着顶点法向方向向外扩张,使其包裹原有的模型。
这种方法的优点是,不需要任何关于相邻顶点/边等信息,所有的处理都是独立的,渲染速度很快,而且线条宽度可控,有一定的健壮性。
缺点是:
一,像cube这样的模型,它的同一个顶点在不同面上具有不同的顶点法向,所以向外扩张后会形成一个gap,如下图所示。
一种解决方法是强迫同一个位置的顶点具有相同的法线朝向。另一种解决方法是在这些轮廓处创建额外的网格结构。由于gap大多在模型放大的情况出现,根据到Camera的距离控制轮廓线的宽度,一定程度上可以减少gap的出现。
二,这种方法渲染的backfaces有可能与原有的模型在深度上发生冲突,并且遮挡模型,如下图所示:
一种解决方法是给backfaces设置Z-offset,使轮廓线埋没到临近的面里。另一种解决方法是修改backfaces扩张的法线,使轮廓线扁平化。
参考资料:
【Unity Shader实战】卡通风格的Shader(二) http://blog.csdn.net/candycat1992/article/details/41120019
Silhouette-Outlined Diffuse http://wiki.unity3d.com/index.php/Silhouette-Outlined_Diffuse
总结:
上述方法都有一个共同的缺点,那就是对轮廓线的外观可控性很少,而且如果如果场景上的物体都需要绘制轮廓线,每个物体都会增加一个draw call,性能上的开销会比较大。
此外,背面法只能渲染Silhouette edge,不能渲染Crease Edge和Material Edge。
1.3 屏幕后处理法(Silhouetting by Image Processing)
这种技术利用了G-buffer,在每个buffer中使用了图像处理的技术来检测轮廓信息。
基本思想是,利用图像处理中的一些算法,在Z-buffer中找到不连续地方,由此计算出大致的轮廓线。还可以在surface normal中找到不连续点,来获取更完整的轮廓线。最后还可以在ambient colors中,进一步完备前两步找到的轮廓线信息。
因此,基本步骤是:
1. 使用vertex shader把world space normals和z-depths渲染到纹理中。
2. 使用一些滤波算法来找到边缘信息。一种常见的滤波算子是Sobel边缘检测算子。
3. 找到边缘后,我们还可以使用一些图像处理操作,例如腐蚀和膨胀,来扩展或者缩小轮廓线宽度。
这种方法的优点在于:
(1)适用于任何种类的模型,而且不需要CPU参与创建和传递一些边的信息。
(2)可以渲染Crease Edge,而且可以设置阈值,控制Crease Edge的采样。
但也有它的缺点:
(1)这种对z-depth比较来检测边界的方法,会受z变化范围的影响,一些z变化很小的轮廓,比如桌子上的纸张,就无法检测出来。
(2)轮廓线的宽度不能根据物体单独控制,而且不能区分Silhouette edge和Crease Edge。
1.4 实际案例
对于角色,大部分游戏中的轮廓线都是用背面沿法线向外扩张的方法实现的。而且很多游戏还在此基础上做了轮廓线的风格化处理。例如,《罪恶装备》大量使用了顶点色控制轮廓线的风格:
R通道:控制光照的明暗变化
G通道:控制顶点根据视距膨胀的强度
B通道:控制z-bias,值越大轮廓线越靠后
A通道:控制轮廓线的粗细
对于一些特殊的分镜,可以使用更复杂的轮廓线制作方法。在《火影忍者疾风传》中,使用了判断轮廓线的RenderTexture,一次进行两种方法的绘制。还加入了可以给角色分别指定颜色的【ID Buffer】,用4X4的16像素Texture保存颜色信息,再取得颜色来绘制角色的轮廓线。另外也可以追加Glare的信息,制作出角色轮廓的自发光。
参考: http://gad.qq.com/article/detail/26584
2 着色
卡通渲染的着色主要包括阴影、高光、边缘光和内轮廓线(主要是角色身上细碎的线条,区别于上文的外轮廓线,类似于Material edge)等要素。
2.1 原理
卡通渲染的阴影不同于一般意义上的阴影,而是拆分成了两个更精细的定义:
阴:很难被光照射到的暗面,即背对光源的区域
影:由其他物体遮挡生成的暗面
(1)阴
物理上的漫反射是用dot(N,L)模拟。卡通渲染的漫反射是一个阶梯函数(step function),直接将光照分为明暗两部分,用一个阈值(Threshold)映射dot(N, L)。
实际使用时,可以用smoothstep代替step,并增加一个参数控制暗面与亮面的过渡平滑程度,一个参数控制亮面与暗面的过渡位置。
代码:
half NdotL = dot(N, L) * 0.5 + 0.5;
half diff = min(NdotL, attenuation); // attenuation为shadowmap计算的阴影
half diffuseMin = saturate(_DiffuseCutLocation - _DiffuseCutSmoothness);
half diffuseMax = saturate(_DiffuseCutLocation + _DiffuseCutSmoothness);
half diffuseColor = smoothstep(diffuseMin, diffuseMax, diff) * col;
不同smoothness的效果:
(左)偏于2D动画的风格,(右)偏于3D写实的风格
函数可以是两段也可以是多段。
也可以用Ramp Map代替函数。多组ramp可以组合在一起,供美术自由切换。
因为光照的计算用了step function,明暗界线对于法线非常敏感,尤其是脸部,很容易产生不平整的界线,影响模型的美观。
而且,法线对轮廓线描边的影响也很大,所以卡通渲染的模型必须人工调整法线。
修改前的效果:
修改后的效果:
可以使用的模型法线修改工具:
(1)UserNormalTranslator (SoftImage XSI)
(2)NormalPainter (Unity)
(3)NormalThief (Max)
因为修改过顶点法线的模型不能直接使用法线贴图,所以如果需要使用法线贴图,最好把修改过的顶点法线存在顶点色或者UV上,这样不影响其他效果的使用。
2)影
实际制作中,会希望阴影有一定的倾向,即有些地方容易产生阴影,有些地方则很难产生阴影。
比如,凹陷的地方和受周围遮挡的地方应该被认为是影,需要增加阴影倾向,改变光照表现。
这时候可以用一张贴图或者顶点色控制阴影的倾向,对上面的映射函数进行偏移。
没有阴影倾向的效果:
有阴影倾向的效果:
但是,阴影倾向信息不能反映外部遮挡物投射的阴影,最好结合shadowmap一起使用。
(3)高光
高光、边缘光的处理方法和漫反射的映射类似,也是用step function控制明暗。
如果是金属或者其他特殊材质,需要保留更多的高光细节,可以和Blinn-Phong或者GGX光照模型混用。
不同材质的边缘光最好用mask精确区分:
盔甲上没有边缘光:
盔甲上有边缘光:
(4)内轮廓线
材质上的轮廓线只能在贴图上绘制。但是,由于精度问题,贴图上的线放大会出现锯齿。
《罪恶装备》使用了一种叫本村线的方法,即扭曲UV,只用垂直线和水平线构成贴图。
但是,这种方法有个缺点,就是会扭曲贴图上的文字和花纹。所以,《罪恶装备》的角色都是用的纯色贴图,一些带有文字的区域都做成贴花,用新的mesh附在模型上。
无内轮廓线:
有内轮廓线:
2.2 制作流程
以罪恶装备为例,shader主要用到3张贴图:main tex, ilm map, sss map
(1)main tex:固有色
(2)ilm map:用于调整阴影和高光区域的形状
R通道:
底色为128灰色,可以理解成材质的光泽度,控制高光的强弱。
材质越粗糙,灰度越小;材质越光滑,灰度越大。
G通道:
底色为128灰色,表示阴影的倾向,控制阴影区域的大小。
容易产生阴影的区域(可以理解成有自投影的区域)灰度较小,如脖子、腋下、鼻子下方、衣服的褶皱、头发遮住脸部的部分。
B通道:
底色为黑色,控制高光区域的大小。
A通道:
底色为白色,表示内轮廓线,贴图上的线条需要拉成横线和竖线,否则会有像素锯齿。
(3)sss map:用于调整阴影的颜色
亮部颜色 = mainTex
暗部颜色 = mainTex * sssMap
3 参考资料
[1] GuiltyGearXrd's Art Style : The X Factor Between 2D and 3D