Shader学习:初步了解卡通渲染Cel Shading

卡通渲染(英文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>)。

这样就能够使用计算出来的值通过查表来乘相应的系数。

到此,超简单版本的卡通渲染就完成了,结果大概是这个样子。当然如果你是把它作用在整个场景上的话,可能会丢失光照。场景的背景就是黑色的,下图是只用在了人物的模型上,就很动画。

参考资料:

  1. Anime-Look Cel Shading in UE4(貌似是一个日本学生写的,模型就是用他推荐的,强推)
  2. 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算子)。所以,待续中。

(以上首发于知乎)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容