卡通渲染(英文Cel Shading或者Toon shading),是一种能够使3D的图像产生动漫效果的渲染手段。它具体的定义我们先看一段来自维基的解释:
Cel shading or toon shading is a type of non-photorealistic rendering designed to make 3-D computer graphics appear to be flat by using less shading color instead of a shade gradient or tints and shades. Cel-shading is often used to mimic the style of a comic bookor cartoon and/or give it a characteristic paper-like texture.
(卡通渲染(英语:Cel-shading或者Toon Shading)是一种去真实感的渲染方法,旨在使电脑生成的图像呈现出手绘般的效果。为了使图像可以与漫画或者卡通达到形似的效果,专业人员通常使用卡通渲染着色器进行处理)
总结来说,卡通渲染是一种NPR(非真实感绘制),可以使用在一些3D的日系二次元风格的手游或者端游中,使得画面的质感更加接近动漫中的感觉。
比较的有名的使用卡通渲染的游戏,这里我必须提一下崩坏三(崩坏学园3)。每次看到崩崩崩的视频,总是会被它的画风给惊叹到。米忽悠真不愧是被游戏耽误的动画公司。崩坏三大量使用了卡通渲染技术,这一点在米哈游的技术总监贺甲对于崩坏3的技术演讲中可以了解到。B站有关于这个演讲的视频(不过是日语的,因为是在Unite Tokyo 2018上的演讲,貌似被官方翻译了)。文字版本的可以百度。
这种风格对于喜欢动漫的人来说简直是完美,恨不得把自己的所有游戏都加上一个卡通渲染。那么,卡通渲染具体是怎么实现的呢?原理又是什么?
简单来说,在正常的渲染着色过程中,由于使用了真实的光照模型,在模型上的颜色是随着光照的角度渐变的,而卡通渲染为了模拟动画绘制时候的风格,根据每个像素的法线和光照的方向的关系,来使得这片区域的像素投影到其中一个明暗区域上面,达到多段离散的明暗区域的效果。
接下来我们将分别在Unreal Engine 4(UE4)中和WebGL中来实现简单的Cel Shading,可以选择自己感兴趣的阅读,其中WebGL涉及shader代码。
Unreal Engine 4
在UE4当中,我们甚至不需要写shader的代码,只需要通过材质的蓝图编辑器就能够实现卡通渲染的效果。首先先新建一个Material。然后在Material Domain中选择PostProcessing,这样这个材质就会被作为后处理的材质,能够作用在计算了光照和颜色的模型上面。
选择Material domain
接下来就是在蓝图中实现卡通渲染的细节了。在UE4中,我们无法直接获得光照的信息,这里需要使用一些手段来获得光照LightMap。
UE4中可以通过SceneTexture来获得一些有用的信息,在SceneTextureID中可以选择其类型
这里我们需要一个PostProcessingInput0来获得场景中应用了光照和后处理之后的颜色,再添加一个SceneTexture节点并且选择DiffuseColor来获得场景中初始(无光照和后处理)的颜色,用一个divide节点来从PostProcessingInput0中除去DiffuseColor获取LIghtMap的灰阶值。
当然,可以先先将两个输出连接DeSaturation的节点来更彻底地将颜色去除。
我们将获得的结果映射到0-1的范围内
通过这个结果,我们可以大概地了解到对于每个像素点的光照信息。这样我们就可以根据光照信息来计算最后的颜色值。
这里我采用了最简单的方法,只有两种明暗差别,如果之前获得结果大于0.5,则最后的颜色信息直接为DIffuseColor,否则要将DiffuseColor乘一个系数。最后的整个蓝图如下图显示
当然你可以将结果更加细分(WebGL的部分中就更加细分),这样结果就会有更多段的明亮度。之后在场景中添加一个PostProcessingVolume并且把整个后处理材质添加到PostProcessingVolume中就可以作用到场景上。
设置PostProcessingVolume中的Infinite Extend为Infinite,整个场景就会被PostProcessingVolume给覆盖,添加的后处理材质就能够作用到整个场景上
这里你可能会发现结果有点和想象中的不一样。记住,在卡通渲染的蓝图选项中把Blendable location选为Before tonemapping
关于Blendable location的解释,我们可以在官网中看到这个介绍
简单来说,Before tonemapping能够作为在HDR的颜色上,而After tonemapping只剩下LDR的颜色,会失去很多细节。至于Translucency(透明度)相关的选项,Before translucency作用在Before tonemapping之前。
假如你想要把卡通渲染只作用在一些模型上而不是整个场景。这时就要使用custom depth buffer了。把需要使用卡通渲染的模型中的render custom depth pass勾选上
然后修改刚才的蓝图为如下
可以看到,我们主要添加了对于depth buffer的判断,根据custom depth buffer和scene depth的关系来进行最后明亮度的取舍。
到目前为止,我们都是将颜色乘一个自定义的系数来表明明亮度。更好的方法是使用一个材质查询表(Texture LUT<look-up table>)。
这样就能够使用计算出来的值通过查表来乘相应的系数。
到此,超简单版本的卡通渲染就完成了,结果大概是这个样子。当然如果你是把它作用在整个场景上的话,可能会丢失光照。场景的背景就是黑色的,下图是只用在了人物的模型上,就很动画。
参考资料:
- Anime-Look Cel Shading in UE4(貌似是一个日本学生写的,模型就是用他推荐的,强推)
- Unreal Engine 4 Cel Shading Tutorial(这个人的很多Unreal教程都不错)
WebGL
Webgl上面的实现就很算法了,没用什么好的模型,webgl的一个好处在于不需要像opengl什么的要去配置一下环境,只要一个支持webgl的浏览器就能跑了。而且这个教程还给了一个在线的编辑器(cyos),直接在上面能够像在IDE里面一样写shader的代码,能直接运行展示结果,并且把结果下载下来,对于新手特别友好。
流程和UE4中的基本没什么两样,只是用代码实现了。下面直接上shader代码:
Vertex shader(顶点着色器)
precision highp float;
// Attributes
attribute vec3 position;
attribute vec3 normal;
attribute vec2 uv;
// Uniforms
uniform mat4 world;
uniform mat4 worldViewProjection;
// Varying
varying vec3 vPos;
varying vec3 vNormal;
varying vec2 vUV;
void main(void) {
gl_Position = worldViewProjection * vec4(position, 1.0);
vUV = uv;
vPos = vec3(world * vec4(position, 1.0));
vNormal = normalize(vec3(world * vec4(normal, 1.0)));
}
在vertex shader中,我们把UV,世界坐标系下的顶点坐标和正规化后的法线计算出来传给pixel shader,以便后面的光照计算。
Pixel (fragment)shader(片元着色器)
precision highp float;
// Lights
varying vec3 vPos;
varying vec3 vNormal;
varying vec2 vUV;
// Refs
uniform sampler2D textureSampler;
void main(void) {
float celShadingThreshold[4];
celShadingThreshold[0] = 0.02;
celShadingThreshold[1] = 0.1;
celShadingThreshold[2] = 0.6;
celShadingThreshold[3] = 0.9;
float celShadingVal[5];
celShadingVal[0] = 0.1;
celShadingVal[1] = 0.5;
celShadingVal[2] = 0.87;
celShadingVal[3] = 0.95;
celShadingVal[4] = 1.0;
vec3 lightPos = vec3(0, 5, 20);
vec3 lightDir = normalize(lightPos - vPos);
float angle = dot(lightDir, vNormal);
vec3 color = texture(textureSampler, vUV).rgb;
if (angle < celShadingThreshold[0])
{
color = color * celShadingVal[0];
}
else if (angle < celShadingThreshold[1])
{
color = color * celShadingVal[1];
}
else if (angle < celShadingThreshold[2])
{
color = color * celShadingVal[2];
}
else if (angle < celShadingThreshold[3])
{
color = color * celShadingVal[3];
}
else
{
color = color * celShadingVal[4];
}
gl_FragColor = vec4(color, 1.0);
}
片元着色器中,通过计算法线和光线的点积(光源的点是自定义的),我们得到一个浮点数结果,使用这个结果去查询一个系数表,我们可以知道最后显示的颜色是材质贴图中采样得到的颜色乘一个多大的系数。这样通过顶点的法线和光线的夹角的不同,就能使得其明亮度落入不同的区域,造成手绘的质感。
结果大概是这个样子
结论
以上,我们已经完成了初步的简单卡通渲染,当然想要达到崩崩崩的那种效果还任重而道远,除了要模型上面的配合外,至少还要加一个描边。描边又是一个大话题,我现在也还在学习怎么给模型描边(sobel,laplacian算子)。所以,待续中。
(以上首发于知乎)
网友评论