第4.4章:卡通着色器实现
卡通着色器(Toon Shader)是非真实感渲染(NPR)的重要技术,通过特殊的光照模型和着色技术实现动画风格的渲染效果。本章将详细讲解如何在Cocos Creator中实现卡通风格的着色器。
🎯 学习目标
通过本章学习,你将掌握:
- 卡通渲染的基本原理和特性
- 卡通风格光照模型的实现
- 分层阴影和色带技术
- 描边效果的实现方法
- 卡通材质参数的调节技巧
🎨 卡通渲染基本原理
卡通渲染特性
卡通渲染与真实感渲染的主要区别:
- 离散化光照: 光照被分成几个明确的层次
- 硬边缘阴影: 阴影边界清晰,没有渐变
- 简化高光: 高光通常是纯白色的圆形或椭圆
- 描边效果: 物体边缘有明显的轮廓
- 色彩饱和: 颜色鲜艳,对比度高
核心技术要点
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; 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; 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
| CCProgram ramp-lighting-fs %{ precision mediump float; in vec2 v_uv; in vec3 v_worldNormal; uniform sampler2D mainTexture; uniform sampler2D lightRamp; 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; } }%
|