第4.4章:卡通着色器实现

卡通着色器(Toon Shader)是非真实感渲染(NPR)的重要技术,通过特殊的光照模型和着色技术实现动画风格的渲染效果。本章将详细讲解如何在Cocos Creator中实现卡通风格的着色器。

🎯 学习目标

通过本章学习,你将掌握:

  • 卡通渲染的基本原理和特性
  • 卡通风格光照模型的实现
  • 分层阴影和色带技术
  • 描边效果的实现方法
  • 卡通材质参数的调节技巧

🎨 卡通渲染基本原理

卡通渲染特性

卡通渲染与真实感渲染的主要区别:

  1. 离散化光照: 光照被分成几个明确的层次
  2. 硬边缘阴影: 阴影边界清晰,没有渐变
  3. 简化高光: 高光通常是纯白色的圆形或椭圆
  4. 描边效果: 物体边缘有明显的轮廓
  5. 色彩饱和: 颜色鲜艳,对比度高

核心技术要点

1
2
3
4
5
6
7
8
9
10
11
12
// 卡通渲染的核心函数
float toonLighting(float NdotL, int levels) {
// 将连续的光照值离散化
float lightLevel = floor(NdotL * float(levels)) / float(levels);
return lightLevel;
}

float toonSpecular(float NdotH, float shininess) {
// 硬边缘高光
float spec = pow(max(NdotH, 0.0), shininess);
return step(0.5, spec);
}

🌟 基础卡通着色器实现

简单卡通着色器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
CCEffect %{
techniques:
- name: toon-basic
passes:
- vert: vs:vert
frag: fs:frag
properties:
mainTexture: { value: white }
mainColor: { value: [1, 1, 1, 1], editor: { type: color } }
shadowColor: { value: [0.5, 0.5, 0.8, 1], editor: { type: color } }
lightLevels: { value: 3, editor: { range: [2, 8], step: 1 } }
outlineWidth: { value: 0.1, editor: { range: [0, 0.5] } }
outlineColor: { value: [0, 0, 0, 1], editor: { type: color } }
}%

CCProgram vs %{
precision highp float;
#include <builtin/uniforms/cc-global>
#include <builtin/uniforms/cc-local>

in vec3 a_position;
in vec3 a_normal;
in vec2 a_texCoord;

out vec2 v_uv;
out vec3 v_worldNormal;
out vec3 v_worldPos;

vec4 vert () {
vec4 pos = vec4(a_position, 1);
vec4 worldPos = cc_matWorld * pos;

v_worldPos = worldPos.xyz;
v_worldNormal = normalize((cc_matWorldIT * vec4(a_normal, 0)).xyz);
v_uv = a_texCoord;

return cc_matViewProj * worldPos;
}
}%

CCProgram fs %{
precision mediump float;
#include <builtin/uniforms/cc-global>

in vec2 v_uv;
in vec3 v_worldNormal;
in vec3 v_worldPos;

uniform sampler2D mainTexture;
uniform ToonParams {
vec4 mainColor;
vec4 shadowColor;
float lightLevels;
float outlineWidth;
vec4 outlineColor;
};

vec4 frag () {
vec4 texColor = texture(mainTexture, v_uv);

// 计算光照方向
vec3 lightDir = normalize(cc_mainLitDir.xyz);
vec3 normal = normalize(v_worldNormal);

// 计算Lambert光照
float NdotL = dot(normal, lightDir);

// 卡通化光照
float toonFactor = floor((NdotL * 0.5 + 0.5) * lightLevels) / lightLevels;

// 颜色混合
vec4 lightColor = mix(shadowColor, mainColor, toonFactor);

return texColor * lightColor;
}
}%

多级光照卡通着色器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// 更复杂的多级光照实现
CCProgram toon-advanced-fs %{
precision mediump float;
#include <builtin/uniforms/cc-global>

in vec2 v_uv;
in vec3 v_worldNormal;
in vec3 v_worldPos;

uniform sampler2D mainTexture;
uniform sampler2D rampTexture; // 光照渐变纹理
uniform AdvancedToonParams {
vec4 mainColor;
vec4 shadowColor;
vec4 highlightColor;
float lightLevels;
float shadowThreshold;
float highlightThreshold;
float softness;
};

vec4 frag () {
vec4 texColor = texture(mainTexture, v_uv);
vec3 normal = normalize(v_worldNormal);
vec3 lightDir = normalize(cc_mainLitDir.xyz);
vec3 viewDir = normalize(cc_cameraPos.xyz - v_worldPos);

// 基础光照计算
float NdotL = dot(normal, lightDir) * 0.5 + 0.5;

// 使用渐变纹理进行光照查找
vec4 rampColor = texture(rampTexture, vec2(NdotL, 0.5));

// 分层光照
vec4 finalColor;
if (NdotL < shadowThreshold) {
finalColor = shadowColor;
} else if (NdotL > highlightThreshold) {
finalColor = highlightColor;
} else {
float t = (NdotL - shadowThreshold) / (highlightThreshold - shadowThreshold);
finalColor = mix(shadowColor, mainColor, smoothstep(0.0, 1.0, t));
}

// 应用软化
finalColor = mix(rampColor, finalColor, softness);

return texColor * finalColor;
}
}%

💡 分层阴影和色带技术

离散化光照函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 多种离散化方法
float discretizeLighting(float lightValue, float levels, int method) {
if (method == 0) {
// 硬分级
return floor(lightValue * levels) / levels;
} else if (method == 1) {
// 软分级
float step = 1.0 / levels;
float level = floor(lightValue / step);
float t = (lightValue - level * step) / step;
return (level + smoothstep(0.4, 0.6, t)) / levels;
} else {
// 基于阈值的分割
float thresholds[4] = float[](0.0, 0.3, 0.6, 1.0);
for (int i = 0; i < 3; i++) {
if (lightValue <= thresholds[i + 1]) {
return thresholds[i];
}
}
return 1.0;
}
}

渐变纹理实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 使用1D渐变纹理控制光照
CCProgram ramp-lighting-fs %{
precision mediump float;

in vec2 v_uv;
in vec3 v_worldNormal;

uniform sampler2D mainTexture;
uniform sampler2D lightRamp; // 1D渐变纹理
uniform sampler2D shadowRamp; // 阴影渐变纹理
uniform RampParams {
vec4 mainColor;
float rampOffset;
float rampScale;
float shadowIntensity;
};

vec4 frag () {
vec4 texColor = texture(mainTexture, v_uv);
vec3 normal = normalize(v_worldNormal);
vec3 lightDir = normalize(cc_mainLitDir.xyz);

// 计算光照强度
float NdotL = dot(normal, lightDir) * 0.5 + 0.5;

// 应用偏移和缩放
float rampU = NdotL * rampScale + rampOffset;
rampU = clamp(rampU, 0.0, 1.0);

// 从渐变纹理采样
vec4 lightColor = texture(lightRamp, vec2(rampU, 0.5));
vec4 shadowColor = texture(shadowRamp, vec2(rampU, 0.5));

// 混合光照和阴影
vec4 finalColor = mix(shadowColor, lightColor, lightColor.a);
finalColor.rgb *= mainColor.rgb;

return texColor * finalColor;
}
}%

多层次阴影

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// 三层阴影系统
CCProgram multi-shadow-fs %{
precision mediump float;
#include <builtin/uniforms/cc-global>

in vec2 v_uv;
in vec3 v_worldNormal;
in vec3 v_worldPos;

uniform sampler2D mainTexture;
uniform MultiShadowParams {
vec4 lightColor;
vec4 midtoneColor;
vec4 shadowColor;
vec4 darkShadowColor;
float lightThreshold;
float shadowThreshold;
float darkThreshold;
float feather;
};

vec4 frag () {
vec4 texColor = texture(mainTexture, v_uv);
vec3 normal = normalize(v_worldNormal);
vec3 lightDir = normalize(cc_mainLitDir.xyz);

float NdotL = dot(normal, lightDir) * 0.5 + 0.5;

vec4 finalColor;

// 三层阴影判断
if (NdotL >= lightThreshold) {
// 亮部
float t = smoothstep(lightThreshold - feather, lightThreshold + feather, NdotL);
finalColor = mix(midtoneColor, lightColor, t);
} else if (NdotL >= shadowThreshold) {
// 中间调
float t = smoothstep(shadowThreshold - feather, shadowThreshold + feather, NdotL);
finalColor = mix(shadowColor, midtoneColor, t);
} else if (NdotL >= darkThreshold) {
// 阴影
float t = smoothstep(darkThreshold - feather, darkThreshold + feather, NdotL);
finalColor = mix(darkShadowColor, shadowColor, t);
} else {
// 深阴影
finalColor = darkShadowColor;
}

return texColor * finalColor;
}
}%