《GPU Zen 4: Advanced Rendering Techniques》第 3 章中文译稿。原文作者:Maxime Beaudoin、Jade Marcoux-Ouellet。
译者导读
《刺客信条:影》用一套相互配合的物理模型驱动森林:玩家周围运行多层嵌套的 3D Fluid Simulation,树干、枝条与叶簇则被组织成 Angular Spring Hierarchy。本文从 Wind Simulation 讲起,接着说明树木的 Spring Parameterization、Animation Shader 与 Skinning,最后给出 PlayStation 5 上的 CPU、GPU 和显存数据。
3.1 引言
《刺客信条:影》包含大量自然环境,其中分布着茂密森林。为了让这个世界动起来,风和树木都采用 Physically Based 方法模拟。两套 Simulation 相互配合,让树木对风的响应更真实。本文先介绍 Wind Simulation,再重点讨论 Tree Animation Model。
一种简单的树木动画方法,是不考虑树木层级,直接移动 Vertex。这种方案容易实现,过去许多游戏都用过,《刺客信条》系列在《影》之前也不例外。但缺少树木结构会严重限制运动形式,枝条无法自然弯曲。
[Zioma 07] 给出了一种更好的做法:把 Rig Information 存进 Vertex Attribute。Vertex Shader 因而可以沿着 Vertex Hierarchy 遍历枝条,并让每根枝条绕自己的 Anchor Point 旋转。即使运动并不严格符合物理规律,枝条也能自然弯曲,动画效果会好得多。近年的许多游戏都在使用这种方法的变体。
《刺客信条:影》在 [Zioma 07] 的基础上更进一步,把枝条模拟成由风驱动的 Angular Spring Hierarchy。为了避免手工调整大量 Spring 的复杂工作,系统会利用树木结构自动完成 Parameterization。这种 Physics-Based 方法让运动更真实,也更准确地表现了风与树木的相互作用。
3.2 Wind Simulation
风由一套以玩家为中心的 3D Fluid Simulation 生成。Simulation Domain 被离散成 的 Grid,每个 Cell 在三个方向上的尺寸均为 0.5 m。Grid 之外的风,则定义为模拟区域内的平均风速。
系统基于 [Stam 99],使用迭代 20 次的 Pressure Projection Solver 与 Semi-Lagrangian Advection 来保证稳定性;同时加入 [Fedkiw et al. 01] 提出的 Vorticity Confinement,以增强气流中的湍流细节。

为了让风与大型静态障碍物发生碰撞,系统按照 [Harlow and Welch 65] 的方法,为每个 Grid Cell 分配一个 Marker。Terrain 与建筑 Asset 会经过 Voxelization,再转换为 Collision Marker。Fluid Solver 参考 [Bridson 08] 处理这些固体交互,并针对 Cell-Centered Grid 做了调整。
玩家也可以通过一套简单的 Effector System 动态影响 Simulation。例如武器挥动时,会沿运动方向生成 Linear Force Effector,从而制造局部阵风。
为了把全局风注入 Simulation,Grid 外围增加了一圈不参与模拟的 Border。Border 中填入 Prevailing Wind,作为 Simulation 的 Boundary Condition。这些 Velocity 还会受到 Perlin Noise 调制,为气流补充小尺度细节。图 3.1 展示了简化后的 Grid 配置。
3.2.1 Nested Grids
前面的 Simulation Domain 只有 。为了在不明显增加性能成本的情况下扩大范围,系统使用一组相互嵌套的 Cascaded Grids。所有 Grid 的 Resolution 相同,但 Cell Size 每一级都会翻倍。图 3.2 用二维示意图展示了相邻两级 Grid 的关系。
《刺客信条:影》使用三级 Nested Grid,Cell Size 分别为 0.5 m、1 m 和 2 m,最终把模拟体积扩大到 。
全局 Prevailing Wind 会按前述方法注入最粗一级 Grid 的 Border;内层 Grid 的 Border 则由外层较粗 Grid 的 Velocity 填充。这样可以保持各级 Grid 交界处的连续性,并让 Prevailing Wind 沿整个 Grid Cascade 传播。
为了保留小尺度细节,粗 Cell 的 Velocity 还会被与它重叠的细 Cell 平均值覆盖,使粗粒度 Velocity Field 能忠实反映更细一级的结果。

每次更新会按以下顺序执行;完成所有 Grid 的当前步骤后,才进入下一步:
- 向最粗一级 Grid 的 Border 注入 Prevailing Wind。
- 让所有 Grid 重新以玩家为中心。
- Advect Velocity。
- 应用 Vorticity Confinement。
- 应用 Effector。
- 填充内层 Grid 的 Border。
- 用细粒度 Velocity 覆盖粗粒度 Velocity。
- 求解 Pressure。
求解得到的 Velocity Field 会提供给 Particle、Cloth、Sound Effect 和 Tree System。
3.3 Tree Animation
为了准确响应风场,树木运动由一套 Physics-Based Angular Spring Hierarchy 驱动。这种方法很适合前述 Wind Simulation,可以表现 Fluid Motion 中复杂的局部变化;即使换成更简单的 Wind Implementation,这个 Tree Model 也同样适用。
树干、枝条和叶簇都会建模为 Angular Spring。独立的 Compute Shader Pass 负责模拟这些 Spring,并以局部风场作为驱动力。随后,Vertex Shader 沿 Branch Hierarchy 采样各级 Rotation,把它们组合成最终的 Vertex Transformation。枝条弯曲只影响视觉表现:Vertex 越靠近枝条 Pivot Point,应用的 Rotation Angle 越小。
有些树包含数十万片叶子,逐片模拟的成本无法接受。因此,叶片会被分组为由纹理平面组成的 Bundle,文中称为 Foliage Branch。这样一棵树通常包含一根树干、数十根 Internal Branch,以及最多一千根 Foliage Branch。为简化表述,下文把它们统一称为 Branch。
Spring 的 Angular Acceleration 定义为:
其中, 是 Wind Drag Coefficient,
是当前 Wind Force,
是 Stiffness Coefficient,
是相对 Rest Pose 的当前 Rotation,
是 Damping Coefficient,
是 Angular Velocity,
是 Mass。
可以把 Angular Acceleration 理解为三部分共同作用的结果:Wind Torque、Spring Torque 与 Spring Damping,分别对应公式(3.1)分子中的三项。Animation Shader 会为所有可见树木的全部 Branch 计算这条公式。
3.3.1 Spring Parameterization
公式(3.1)中的 、
、
与
,直接决定最终运动是否自然。这些常量会在离线阶段通过分析 Visual Mesh 的简化模型,为每根 Branch 预计算。树干与 Internal Branch 用体积为
、长度为
的 Cylinder 表示;Foliage Branch 则近似成一个面积为
、能够包围其全部 Visual Plane 的 Rectangle。
这些 Parameterization 主要来自 Heuristic,用户可以继续调整参数,得到所需的运动效果。参数按 Tree Species 和 Branch Level 指定,也就是分别控制树干、Internal Branch 与 Foliage。同一 Species 的所有实例共享同一套参数,不受实例 Scale 影响。
Mass
Branch 的总质量 ,由它自身的质量
与 Hierarchy 中所有 Child Branch 的总质量相加得到。因此,树干的质量就是整棵树的质量。
单根 Branch 的质量 按下式计算:
对于 Non-Foliage Branch,为简化计算,系统使用固定的木材密度。Foliage Branch 只采用近似模型,其密度又受多种难以准确估算的因素影响,因此系统把 Foliage Density 作为用户参数开放出来。
Stiffness
Branch 的 Stiffness Coefficient 按下式计算:
其中 是用户参数。对于 Non-Foliage Branch,Stiffness Coefficient 与 Cylinder 截面积和长度之比成正比,因此粗短枝条更硬,细长枝条更柔软。
Foliage 也希望得到类似结果,让较小的 Foliage Branch 比较大的更硬。由于公式(3.1)中的 Stiffness Coefficient 还会除以 Mass,因此 应与 Foliage Branch 的近似长度
成正比。
Damping
为了让 Damping Parameterization 不受 Stiffness 与 Mass 影响,系统使用 Damping Ratio :
它表示 Spring 实际 Damping 与 Critical Damping 的比值。当 时,Spring 会缓慢回到 Rest Position,不发生振荡;当
时,则会无限振荡。
直接把 暴露为用户参数后,不同尺寸的 Branch 也能得到相近的 Damping Behavior。Coefficient
因而按下式计算:
《刺客信条:影》中,大多数 Damping Ratio 位于 0.05–0.15 之间。
Wind Drag
Foliage 是树木受风运动的重要来源。例如落叶树在夏季会比冬季摇摆得更明显。原因在于叶片大幅增加了迎风面积,也增强了树与风的交互。作用在叶片上的力会沿 Branch 向树干传递,因此一根 Branch 的 Wind Drag Coefficient 必须计入所有 Child Branch 的贡献。
系统先根据迎风面积计算每根 Branch 自身的 Direct Wind Drag :
其中 是用户参数。Non-Foliage Branch 的迎风面积,用对应 Cylinder 的纵向截面积估算;Foliage Branch 则直接使用简化模型给出的面积
。
随后,系统沿 Hierarchy 把每根 Branch 的 Direct Drag 传播给 Parent,得到最终的 Drag Coefficient
,具体实现见清单 3.1。第 19 行会计算 Branch Position 在 Parent 上的 Scalar Projection,并以它缩放 Child Contribution。这样一来,靠近枝条末端的 Child 会比靠近 Root 的 Child 产生更大影响,如图 3.3 所示。
void PropagateWindDrag(
float* d, // Final propagated wind drag
const float* D, // Direct wind drag from Equation (3.2)
const Vector3* position, // Attachment point to the parent branch
const Vector3* direction,
const float* length,
const int* parent, // Parent branch index (-1 if none)
int branchCount)
{
memcpy(d, D, branchCount * sizeof(float));
for (int i = 0; i < branchCount; ++i)
{
Vector3 anchorPos = position[i];
float propagatedDrag = D[i];
for (int j = parent[i]; j >= 0; j = parent[j])
{
propagatedDrag *= GetWindPropagation(j);
float projection = dot(anchorPos - position[j], direction[j]);
d[j] += propagatedDrag * saturate(projection / length[j]);
anchorPos = position[j];
}
}
}
清单 3.1|Wind Drag 传播。每根 Branch 都会把自己的 Direct Drag 传播给 Hierarchy 中的所有 Parent。第 18 行的
GetWindPropagation 返回一个用户参数,用于控制 Branch 受 Child Motion 影响的程度。

3.3.2 Animation Shader
Animation Compute Shader 按 Tree Specimen 执行 Dispatch,并更新相应可见实例的 Spring;每个线程处理同一棵树中的 64 根 Branch。计算公式(3.1)之前,Shader 会先处理 Inertia,再计算要施加到 Branch 上的 Wind Torque 。
Inertia
Angular Spring Hierarchy 会带来一个直接结果:Parent Branch 一旦旋转,所有 Child 都会随之移动,Inertia 则被忽略。为补偿这一点,每根 Branch 都会反向旋转,使其 Center of Mass 尽量保持在上一帧的位置。图 3.4 展示了这项修正。

系统把 Branch 在 Rest Pose 下的 Center of Mass 近似为位于其长度 75% 处的一个点。当前帧与上一帧的 Center of Mass,都会通过组合 Hierarchy 中全部 Spring Rotation 求出。随后构造一个 Quaternion,表示从当前 Center of Mass 指向上一帧位置的 Rotation,并用它修正 Branch。
Inertia Correction 因而需要两份 Rotation Buffer,分别保存上一帧和当前帧。不过,由于所有 Branch 都是并行计算的,当前帧中其他 Spring 的状态此时还不可用。解决方法是增加第三份 Rotation Buffer,把 Inertia 延迟一帧处理。也就是说,在第 帧,系统会使用第
帧到第
帧的 Center of Mass 位移来修正 Inertia。虽然多了一帧延迟,最终运动依然可信。
Wind Torque
Wind Torque 取决于 Branch Center of Mass 处的 Wind Velocity。为了让运动更有活力,并补足 Physical Model 没有覆盖的现实扰动,系统会给 Wind Velocity 加入 Noise。实现方法有很多;这里选择让 Velocity 在水平方向旋转,因为视觉效果更自然。
Rotation Angle 由 Perlin Noise Texture 驱动,其 Animation Speed 会匹配 Spring 的 Natural Frequency :
这样会让 Spring 与 Noise 产生 Resonance,使振荡更协调。随后,带 Noise 的 Wind Velocity 会被转换为 Torque 。

这股 Angular Force 的 Rotation Axis 与 Branch Direction、Wind Velocity 都正交,如图 3.5 所示;当风向与 Branch 垂直时,Amplitude 最大。只要计算 Branch Direction 与 Wind Velocity 的 Cross Product,就能得到满足这些条件的 Rotation Vector。
Spring Simulation
计算公式(3.1)之前,系统会根据 Timestep 限制 Stiffness 和 Damping Coefficient
、
,从而约束单帧应用的 Rotation,避免 Spring Overshoot,让 Simulation 保持稳定:
接着读取上一轮模拟得到的 与
,计算 Angular Acceleration
。把这三个量都写成 Rotation Vector 后,Angular Spring Simulation 的计算方式就与 Linear Spring Simulation 基本相同。
更新后的 Angular Velocity ,以及这一帧应用到 Branch 上的 Rotation
为:
应用 Rotation 时, 与
会被转换为 Quaternion,再连接起来,得到新的 Branch Rotation
。
3.3.3 Skinning
每个 Vertex 都会关联一根 Branch,这项 Mapping 以 16-bit Integer 编码在 Vertex Attribute 中。因此,每一级 LOD 都可以建立自己到同一套 Simulation Model 的 Mapping,LOD 切换也能保持连续。
Vertex Shader 会以 Quaternion 形式读取关联 Branch 的 Rotation。为了保证 Branch 与 Parent 始终连在一起,Rotation 会根据 Vertex 到 Branch Root 的距离逐渐衰减,再用于变换 Position 与 Normal。随后沿 Hierarchy 对每一级 Branch 重复同样的处理,直到抵达树干。
3.4 性能
Wind Simulation 实际运行在 CPU 上。它的实现可以比 GPU 版本更简单,而且 CPU System 采样风场时,不会承受 GPU-to-CPU Readback 无法避免的延迟。
代码经过手工 Vectorization,以固定 30 Hz()的时间步运行在低优先级 Background Thread 上。因此 Wind Simulation 不会进入 Critical Path,对 Frame Time 的影响可以忽略。在 PlayStation 5 上,总更新时间为 3.5–5.5 ms。时间变化主要取决于 Simulation Domain 中 Collision Marker 的数量,因为这些 Cell 不会被处理。
Tree Simulation 方面,在 PlayStation 5 上处理一百万根 Branch 需要 480 μs。实际更新时间通常为 150–500 μs,主要由可见树木数量决定。Simulation 使用 Async Compute 执行,测得的耗时平均会增加约 50%。
Tree Simulation Data 会在实例进入 Simulation Range 时立即分配,不考虑当前是否可见。为了减少 Memory Footprint,Rotation 被表示为两个 Spherical Angle,每个角度压缩成 16-bit Fixed-Point。最终 Angular Error 小于 0.006 度,在当前场景中无法察觉。代价是移除了 Branch Torsion,不过它对整体运动的贡献很小。
Angular Velocity 使用 Half-Float 保存,最终每根 Branch 的 Simulation Data 占 20 Bytes。每棵树包含 250–1500 根 Branch,主要取决于树木尺寸。96 MB 的 Memory Budget 最多可保存五百万根 Branch;实际平均显存占用通常为 25–50 MB。
3.5 结论
与系列前作相比,《刺客信条:影》的植被动画真实感有了明显提升。Tree Parameterization 也适合 Production:用户参数由同一 Species 的全部 Tree Specimen 共享,有经验的用户只需数小时,就能校准数百个 Tree Model,并让它们在各种风况下都呈现自然结果。
这套 Parameterization 的学习曲线较陡,不熟悉 Physics-Based Animation 的用户尤其需要时间适应。例如树干摆动幅度过大时,经验不足的用户可能会先提高 Stiffness。这样虽然能减小 Spring Amplitude,也会同时提高 Oscillation Frequency,导致不自然的运动;更合适的做法其实是降低 Wind Drag。要形成判断这类问题的直觉,仍需花几小时实际调参。
后续可以把树木本身纳入 Wind Simulation。树木理应遮挡一部分风,加入这项交互后可以进一步提高真实感。另一方面,更精确地建模 Foliage,也能打散大型 Bundle 因包含叶片过多而产生的粗粒度运动。下一步可以尝试把这些 Bundle 拆成更小的部分,同时尽量少增加新的 Spring。
参考文献
- [Bridson 08] Robert Bridson. Fluid Simulation for Computer Graphics. A K Peters, Ltd., 2008.
- [Fedkiw et al. 01] Ronald Fedkiw, Jos Stam, and Henrik Wann Jensen. “Visual Simulation of Smoke.” In Proceedings of the 28th Annual Conference on Computer Graphics and Interactive Techniques, SIGGRAPH ’01, p. 15–22. Association for Computing Machinery, 2001. https://doi.org/10.1145/383259.383260.
- [Harlow and Welch 65] Francis H. Harlow and J. Eddie Welch. “Numerical Calculation of Time-Dependent Viscous Incompressible Flow of Fluid with Free Surface.” The Physics of Fluids 8:12 (1965), 2182–2189. https://doi.org/10.1063/1.1761178.
- [Stam 99] Jos Stam. “Stable Fluids.” In Proceedings of the 26th Annual Conference on Computer Graphics and Interactive Techniques, SIGGRAPH ’99, p. 121–128. ACM Press/Addison-Wesley Publishing Co., 1999. https://doi.org/10.1145/311535.311548.
- [Zioma 07] Renaldas Zioma. “GPU-Generated Procedural Wind Animations for Trees.” In GPU Gems 3, edited by Hubert Nguyen, Chapter 6. Addison-Wesley, 2007.