PHASE 4 · 更广阔的世界

4.1游戏中的 Shader

从卡通渲染到溶解特效

游戏是 Shader 的主场:你在任何一款游戏里看到的水面、光影、爆炸、受击闪白,背后都是着色器。这一章不教你做游戏,而是回答一个更实际的问题:你这本书学的东西,在游戏引擎里对应什么、还能直接用吗?答案会让你惊喜——几乎全部通用,连代码都长得差不多。

1游戏里 Shader 都在干什么

游戏引擎里的 Shader 按岗位分四类,前三类你都已经见过它们的「App 版」:

岗位干什么本书对应
材质(Surface)决定物体表面长什么样:颜色、光照、金属感、卡通描边Phase 1 的图形数学 + 本章光照
后处理(Post)整个画面渲染完后再加工:泛光、色调映射、景深、暗角Phase 2 全部(输入纹理=游戏画面)
特效(VFX)溶解、受击闪白、护盾、传送门,常配合粒子系统造型函数 + 噪声 + 本章溶解
顶点动画草地摆动、旗帜飘扬、水面起伏——动的是几何体本身唯一的新东西,第 4 节

换句话说:游戏后处理 = 你的 Phase 2,输入从照片换成了每帧的游戏画面。CRT、色差、玻璃、马赛克,在游戏里全都有对应的名场面(恐怖游戏的信号故障、水下的折射、像素风滤镜)。

2光照一分钟 + 卡通渲染

2.2 章乐高凸点里偷偷用过的 dot(N, L),是整个实时光照的地基,正式介绍一下:表面朝向光源的程度 = 法线 N 与光方向 L 的点积。正对着光是 1(最亮),侧面是 0,背面为负(黑)。这叫兰伯特(Lambert)漫反射,一行代码撑起了三十年游戏画面。

而「卡通渲染(Toon/Cel Shading)」的秘密更简单:把连续的 N·L 量子化成两三档(1.1 章的 floor / step!),明暗交界立刻变成动画片般的硬边。下面在 2D 里伪造一个 3D 球演示——球的法线可以从圆内坐标直接推出来,这是 Shadertoy 社区的经典白嫖:

就这四个零件——兰伯特、量子化、边缘光、描边——《塞尔达:旷野之息》《原神》风格化渲染的地基就齐了(它们当然还叠了几十层细节,但骨架是这个)。真 3D 里的差别只是:法线不用白嫖,顶点数据自带。

3溶解:游戏特效第一课

怪物死亡化灰、装备升级重构、传送门吞噬——游戏里一切「物体逐渐消失」都是同一个配方,而且你已经有全部原料:噪声(1.5)+ 阈值(step)+ 边缘发光(1.2):

同门师兄弟一并点名,全是几行的变体:受击闪白 = mix(color, vec3(1.0), flash),flash 由受击时刻起指数衰减;护盾 = 半透明球 + rim 发光 + 噪声流动;幽灵化 = 溶解不加火边、alive 换成半透明。游戏特效的门,推开就是这么薄。

4顶点着色器的岗位

本书的 Vertex Shader 一直在摸鱼(原样输出四个顶点)。游戏里它有真正的岗位:移动顶点本身。草会弯、旗会飘、水面有浪,不是美术做了动画,而是 Vertex Shader 每帧根据时间和位置推挤顶点:

GLSL · 顶点动画的思路(以旗帜为例)
// Vertex Shader 里(伪代码,引擎里变量名各异):
vec3 pos = aPosition;

// 离旗杆越远摆得越大;时间 + 位置 = 波浪(全是 1.4 章的思路)
float sway = sin(pos.x * 3.0 + uTime * 4.0) * 0.15 * pos.x;
pos.z += sway;

gl_Position = uProjection * uView * uModel * vec4(pos, 1.0);

看出来了吗?顶点动画就是「造型函数作用在顶点上」——sin 波、噪声、缓动,全套 Phase 1 知识换个作用对象。草地是每根草按世界坐标错开相位(1.4 章的 stagger),水面是 fbm 顶点位移 + 法线重算。规模大了还有 GPU 粒子、蒙皮骨骼,但那是引擎的事,思想不变。

5引擎对照:你的知识怎么迁移

引擎Shader 语言与本书 GLSL 的关系上手路径
UnityHLSL(ShaderLab 包装)类型改名:vec3→float3、fract→frac、mix→lerp,其余九成同先玩 Shader Graph(节点版),再写 HLSL
Godotgdshader几乎就是 GLSL,入口换成 fragment() + COLOR直接写,官方文档一晚上读完
Unreal材质节点图(底层 HLSL)节点名 = 本书函数名(Lerp、Fresnel、Noise)连节点;Custom 节点里可手写 HLSL
Web(Three.js)GLSL原生同款,onBeforeCompile / ShaderMaterial 直接贴零迁移

看一眼 Godot 版的溶解,感受「知识原样迁移」不是空话:

gdshader · Godot 版溶解(对照第 3 节)
shader_type canvas_item;

uniform float progress : hint_range(0.0, 1.0);
uniform sampler2D noise_tex;    // Godot 内置噪声纹理,连 fbm 都不用写

void fragment() {
    float n = texture(noise_tex, UV).r;
    float alive = step(progress * 1.2, n);
    float edge = smoothstep(progress * 1.2, progress * 1.2 - 0.08, n) * alive;
    vec3 fire = mix(vec3(1.0, 0.3, 0.05), vec3(1.0, 0.9, 0.3), edge);

    vec4 tex = texture(TEXTURE, UV);
    COLOR = vec4(tex.rgb * alive + fire * edge * 1.6, tex.a * max(alive, edge));
}
想深入游戏 Shader,沿这条线走

Freya Holmér 的「Shaders for Game Devs」系列(YouTube,免费,业界公认第一课)→ Catlike Coding 的 Unity 教程(文字,极扎实)→ The Book of Shaders 补数学美感。如果你只是想在 Android 游戏里加特效,Godot + gdshader 是离你现有知识最近的落点。

本章小结

  • 游戏 Shader 四岗位:材质、后处理、特效、顶点动画;前三个你已经会了 App 版。
  • 光照地基 = dot(N, L);卡通渲染 = 光照量子化 + 边缘光 + 描边,四个零件全是旧知识。
  • 溶解 = 噪声耐烧度 + 进度阈值 + 燃烧边缘;受击闪白、护盾都是它的几行变体。
  • 顶点动画 = 造型函数作用在顶点上:旗帜是 sin,草地是相位错开,水面是 fbm。
  • 迁移成本:Godot≈0、Unity=改函数名、Unreal=连节点;思维模型全程不变。

动手练习

  1. 给卡通球加「第二光源」:一个固定的冷色补光(蓝紫),强度是主光的 30%(提示:两个 N·L 各自量子化再相加)。
  2. 做「受击闪白」:溶解 Demo 里加 uniform float uHit,让画面在 uHit=1 时整体泛白、随后 0.3 秒指数衰减(用编辑器手改数值模拟)。
  3. 下载 Godot(免费,200MB),建一个 Sprite2D,把本节 gdshader 溶解贴上去跑通——亲手验证一次「知识零成本迁移」。