GPU Gems 3 · Chapter 16. Vegetation Procedural Animation and Shading in Crysis
从每实例风力合成出发,解释植被主弯曲、叶片细节波、顶点色控制、廉价透光和 alpha 边缘平滑如何共同支撑密集场景。
学习目标
- 能解释方向风和局部风如何合成为每个植被实例的世界空间向量,并区分整株主弯曲与叶片细节弯曲
- 能修改 Vegetation Animation & Shading Lab 的风源、风力、波频、着色位置和边缘路径,比较形变、透光与 fill-rate
- 能回答:为什么密集草地要把一部分着色移到顶点阶段,以及为什么 alpha test 的硬边需要深度证据而不是普通 blur
先问:为什么静态树海一有风就像同一张贴纸
想象一片很大的草地:远处树冠慢慢偏向风,近处叶片却各自抖动,爆炸或直升机经过时,附近植被还会先被推开再回弹。如果所有树只执行同一个正弦摆动,画面会像一块同步移动的绿色幕布。
本章解决的是:怎样让成千上万的植被既能响应全局和局部风源,又不会因为每片叶子都做昂贵的片元计算而拖垮帧率。没有这条分层路径,动画会失去空间层次,或者只能在真实感和密度之间二选一。
1. 每个实例先得到自己的风
↡沿风向推动整株植被的低频形变;通常用世界空间风向量和归一化高度决定位移量。 ↡只改变叶缘和单叶姿态的高频形变;它使用风力、叶片刚度和每片叶子的相位制造局部差异。植被动画先分成两层:main bending 让树冠、灌木或草丛整体朝风向偏移;detail bending 让叶缘和单叶上下摆动。方向风给出统一的风向,局部风源则按到实例的距离衰减,所有影响最后在实例级合成为一个世界空间向量。
每个实例还带有 stiffness。风源停止影响后,强度不能瞬间归零,而应随时间 dampen;否则物体会像被开关切断一样突然冻结。主弯曲使用风向量的 xy 分量,并用植被高度放大顶部、限制底部;细节弯曲主要使用风力和相位,不应把整株树再次整体平移。
2. 顶点色是给美术师的运动控制图
↡把每个顶点的 RGBA 通道当成可绘制的控制数据,而不是只当作颜色;不同通道分别编码叶缘刚度、相位、整体刚度和预计算遮蔽。细节弯曲不应该让每片叶子都由同一个参数驱动。vertex color encoding 把控制权交给资产:R 通道可以表示叶缘 stiffness,G 通道提供每片叶子的 phase variation,B 通道表示整体 leaf stiffness,A 通道保存预计算的 ambient occlusion。
这样同一套 shader 可以服务不同树种:艺术家只需改变顶点色分布,就能让一棵树的边缘更柔、另一棵树的叶片更硬,而不用为每种植物复制一套代码。顶点色越接近“可解释的控制图”,运行时参数就越少,资产审查也越容易。
3. 用三角波近似重复运动
↡用 frac 和 abs 生成的周期波形;它比直接调用正弦更适合批量向量计算,再用 cubic 插值抹平尖角。传统的周期动画常写成 sine,但叶片细节需要多组频率、多个相位和向量化计算。triangle wave 可以用周期取小数部分和绝对值生成,再经过 x * x * (3 - 2 * x) 形式的 cubic smoothing,得到足够连续的运动。
本章的关键不是“用便宜波形替代所有正弦”,而是把四组向量化波分配给不同角色:两组控制叶缘,两组控制叶片或枝条。频率不要都相同,否则整片植被会出现机械同步;G 通道提供的 per-leaf phase 让相邻叶片错开峰值。
等价的 GLSL 风格写法如下。先生成 triangle wave,再平滑;不要在每个分支里单独计算不同周期,以免失去向量化的好处。
vec4 smoothCurve(vec4 x) {
return x * x * (3.0 - 2.0 * x);
}
vec4 triangleWave(vec4 x) {
return abs(fract(x + 0.5) * 2.0 - 1.0);
}
vec4 smoothTriangleWave(vec4 phase) {
return smoothCurve(triangleWave(phase));
}
vec2 waves = time + vec2(vertexPhase, branchPhase);
vec4 waveSet = waves.xxyy * vec4(1.975, 0.793, 0.375, 0.193);
vec4 motion = smoothTriangleWave(waveSet);4. 按 fill-rate 选择植被着色策略
↡画在低分辨率纹理上的厚度/透光控制图;它把叶脉、枝条和不同厚度编码进颜色,用来近似背光时的柔和透光。树干可以使用标准 Lambert 和 Phong;叶片需要双面渲染、alpha test 和廉价的背光近似;草因为覆盖面积大、fill-rate 压力高,通常把更多着色移到顶点阶段。不是所有物体都值得同样的 per-pixel 质量。
叶片透光不必立刻做完整的物理次表面积分。用 subsurface texture 记录内部厚度,再把 -N · L、E · L、光照可见性和该纹理相乘,就能得到方向相关的 back-light。这个近似仍然保留“薄叶朝光时更亮”的线索,而且可以由美术师直接调整。
5. 用深度把 alpha test 的硬边磨平
↡先把不透明几何写入深度纹理,再根据深度不连续检测轮廓,并用旋转样本和双线性查找重建边缘覆盖。alpha test 的轮廓本质上是硬阈值:像素要么保留,要么丢弃。当硬件还没有 alpha-to-coverage 时,可以先做 z-pass,把深度写入浮点纹理,再在后处理中比较邻域深度,找到真实几何轮廓,最后用旋转三角形样本做依赖纹理查找。
这条路径只适用于写入深度的 opaque geometry。非透明叶片没有同样的 z-target 证据,不能假设同一套 edge smoothing 会自动正确。工程上要把“硬边质量”和“是否可写深度”一起作为材质契约,而不是只给所有 alpha 贴图加一个模糊开关。
6. 三步组装成可扩展的植被 shader
先在实例级合成风和形变边界
汇总方向风和局部风的方向、距离衰减与强度;乘上实例 stiffness,并在风源停止后做阻尼。主弯曲使用归一化高度和中心距离限制位移,避免树冠被拉成长条。
Vegetation Animation & Shading Lab
让一片森林同时有风、有层次、可负担
调整风源、主弯曲强度、细节波频、着色位置和 alpha 边缘路径,观察形变、叶片细节、透光、边缘质量与 fill-rate 的取舍。
main bending
67%
leaf detail motion
61%
fill-rate cost
58 units
edge quality
90%
深度边缘证据可压低 alpha test 的硬边。这些读数表达稳定的相对趋势,不代表特定 GPU 的 benchmark。
本章小结
- 实例级合成风向量,让大量风源不直接放大顶点成本
- main bending 控制整株姿态,detail bending 控制叶片局部差异
- vertex color encoding 把 stiffness、phase、AO 变成可绘制资产数据
- triangle wave 加 cubic smoothing 以低成本产生多频率叶片运动
- fill-rate 决定 per-pixel、per-vertex、subsurface 和边缘平滑的分工
练习
问题 1|修改 Demo 代码。 当前主弯曲直接把 wind.xy * height 加到顶点位置。请补上中心距离限制,并说明为什么要限制位移长度。
float bend = vPos.z * bendScale;
vec3 newPos = vPos;
newPos.xy += wind.xy * bend;
return newPos;问题 2|诊断同步叶片。 一片树林的叶子都在同一时间达到峰值,画面像一张同步摆动的布。你会检查哪两个输入?
问题 3|场景选型。 近景大树、满屏草地和透明树叶卡片分别怎样选择着色阶段与边缘路径?
名词解释
本章出现的专业名词,用大白话再讲一遍。
- main bending
- detail bending
- vertex color encoding
- triangle wave
- subsurface texture
- edge smoothing