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 分量,并用植被高度放大顶部、限制底部;细节弯曲主要使用风力和相位,不应把整株树再次整体平移。

wind sources → per-instance vector → vertex deformationdirectional + omnidirectionaldistance attenuationper-instance winddirection · strengthbend with stiffnessdamp after source stopsworld-space vector lets one shader handle wind, explosions, helicopters, cloth, and hairmany forces → one bounded vertex cost
风源数量可以很多,但顶点阶段只消费每个实例已经合成好的风向量;额外成本主要落在每实例的 CPU 更新。
one wind input, two deformation scalesmain bendingheight scale · wind.xy · center limitdetail bendingred=edge · green=phase · blue=stiffnessvertex color RGB is a compact artist-authored control map; alpha can carry ambient occlusion
主弯曲负责大尺度姿态,细节弯曲负责叶片的局部生命感;两层共享风力强度但使用不同的几何信号。

2. 顶点色是给美术师的运动控制图

细节弯曲不应该让每片叶子都由同一个参数驱动。vertex color encoding 把控制权交给资产:R 通道可以表示叶缘 stiffness,G 通道提供每片叶子的 phase variation,B 通道表示整体 leaf stiffness,A 通道保存预计算的 ambient occlusion。

这样同一套 shader 可以服务不同树种:艺术家只需改变顶点色分布,就能让一棵树的边缘更柔、另一棵树的叶片更硬,而不用为每种植物复制一套代码。顶点色越接近“可解释的控制图”,运行时参数就越少,资产审查也越容易。

3. 用三角波近似重复运动

传统的周期动画常写成 sine,但叶片细节需要多组频率、多个相位和向量化计算。triangle wave 可以用周期取小数部分和绝对值生成,再经过 x * x * (3 - 2 * x) 形式的 cubic smoothing,得到足够连续的运动。

本章的关键不是“用便宜波形替代所有正弦”,而是把四组向量化波分配给不同角色:两组控制叶缘,两组控制叶片或枝条。频率不要都相同,否则整片植被会出现机械同步;G 通道提供的 per-leaf phase 让相邻叶片错开峰值。

cheap periodic motion: triangle wave → smooth curvesine referencesmooth but more worktriangle wavefrac + abs + vectorizedsmoothcubicinterpolationuse different frequencies and per-leaf phase so a field does not move as one rigid sheet
三角波容易用 frac 和 abs 生成,再用 cubic smooth curve 把尖角抹平;它用更低成本近似正弦运动。

等价的 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 · LE · L、光照可见性和该纹理相乘,就能得到方向相关的 back-light。这个近似仍然保留“薄叶朝光时更亮”的线索,而且可以由美术师直接调整。

geometry type → shading path → quality / efficiency trade-offtrunkLambert + Phongleavesdouble-sided + alpha testgrassalpha blend + vertexsubsurface texture × (−N·L) × (E·L) × visibility → cheap leaf back-lightworld-space shading reduces discontinuities for instanced vegetation
把每种植被的画面占比和 fill-rate 放进取舍:草的片元太多时,统一改用顶点着色换取效率。

5. 用深度把 alpha test 的硬边磨平

alpha test 的轮廓本质上是硬阈值:像素要么保留,要么丢弃。当硬件还没有 alpha-to-coverage 时,可以先做 z-pass,把深度写入浮点纹理,再在后处理中比较邻域深度,找到真实几何轮廓,最后用旋转三角形样本做依赖纹理查找。

alpha test edge → depth-aware post-process smoothing1. z-passfloating depth texture2. detect edgedepth discontinuity3. rotated tapsbilinear coverage只对写入深度的 opaque geometry 做这类 edge smoothing;transparent geometry 没有同样的 z 证据depth gives the post-process a contour, not just a color edge
alpha test 的硬边不是再加一次 blur:先用深度找到真实轮廓,再用旋转样本重建边缘覆盖。

这条路径只适用于写入深度的 opaque geometry。非透明叶片没有同样的 z-target 证据,不能假设同一套 edge smoothing 会自动正确。工程上要把“硬边质量”和“是否可写深度”一起作为材质契约,而不是只给所有 alpha 贴图加一个模糊开关。

6. 三步组装成可扩展的植被 shader

分步1 / 3

先在实例级合成风和形变边界

汇总方向风和局部风的方向、距离衰减与强度;乘上实例 stiffness,并在风源停止后做阻尼。主弯曲使用归一化高度和中心距离限制位移,避免树冠被拉成长条。

wind sources → per-instance vector → vertex deformationdirectional + omnidirectionaldistance attenuationper-instance winddirection · strengthbend with stiffnessdamp after source stopsworld-space vector lets one shader handle wind, explosions, helicopters, cloth, and hairmany forces → one bounded vertex cost
风源数量可以很多,但顶点阶段只消费每个实例已经合成好的风向量;额外成本主要落在每实例的 CPU 更新。
one wind input, two deformation scalesmain bendingheight scale · wind.xy · center limitdetail bendingred=edge · green=phase · blue=stiffnessvertex color RGB is a compact artist-authored control map; alpha can carry ambient occlusion
主弯曲负责大尺度姿态,细节弯曲负责叶片的局部生命感;两层共享风力强度但使用不同的几何信号。

Vegetation Animation & Shading Lab

让一片森林同时有风、有层次、可负担

调整风源、主弯曲强度、细节波频、着色位置和 alpha 边缘路径,观察形变、叶片细节、透光、边缘质量与 fill-rate 的取舍。

directional wind fieldmain bend 67% · detail 61% · back-light 64%

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

讨论

评论区加载中…