31 · 风吹旗帜或植被

返回系列目录

风动画常需要根部固定、越往上摆动越大。本章用底边固定的竖直布片演示:所有顶点都执行同一函数,但权重由局部高度决定。它是便宜的程序化形变,不是保持长度、质量和碰撞约束的布料物理模拟。

完整可运行场景:wind-deformation.js,共享函数在 deformation.js。示例使用 XY 平面,顶端沿 Z 摆动,主材质、depth、distance 同步。

1. 明确支撑边与局部轴

const geometry = new THREE.PlaneGeometry(3, 3, 64, 64);
geometry.translate(0, 1.5, 0);

初始高度从 0 到 3,底边 y=0。法线原本指向 +Z。局部高度不需要再从最小值减起,便于定义固定根部权重。

z′=z+Ay2sin⁡(kx+ωt),y≥0 z'=z+A y^2\sin(kx+\omega t),\qquad y\ge0

y=0 时位移为 0,且关于 y 的一阶导数也为 0,根部姿态比线性权重更平缓。顶部最大位移为 ∣A∣H2|A|H^2。示例 H=3、A=0.12,因此最大位移 1.08。

这里 A 的量纲不同于水面幅度:因为乘了 y²,A 应理解为控制弯曲程度的参数。若希望参数直接表示顶部位移,可用 (y/H)2(y/H)^2 作为无量纲权重。

2. 位移与解析法线

对曲面 p(x,y)=(x,y,f(x,y))p(x,y)=(x,y,f(x,y)),两条切线叉积给出:

n=normalize⁡(−fx,−fy,1) n=\operatorname{normalize}(-f_x,-f_y,1)

其中:

fx=Ay2kcos⁡(kx+ωt),fy=2Aysin⁡(kx+ωt) f_x=Ay^2k\cos(kx+\omega t),\quad f_y=2Ay\sin(kx+\omega t)

共享补丁在 beginnormal_vertex 后计算这些导数并设置 objectNormal;begin_vertex 后再变形 transformed。两者使用相同的局部 position 和时间,避免相位不同步。

float demoHeight = max(position.y, 0.0);
float demoPhase = uDemoFrequency * position.x + uDemoSpeed * uDemoTime;
float demoDx = uDemoAmplitude * demoHeight * demoHeight * uDemoFrequency * cos(demoPhase);
float demoDy = 2.0 * uDemoAmplitude * demoHeight * sin(demoPhase);
objectNormal = normalize(vec3(-demoDx, -demoDy, 1.0));

该法线公式只针对 XY 参数平面。直接用于树干、任意叶片或蒙皮角色会错,因为它们不是这张参数曲面。任意网格可以用局部切线上的差分或变形雅可比,但必须重新建立适用条件。

3. 让不同部位有不同权重

真实资产常在顶点色某个通道或自定义 aDemoWeight 中存“柔软度”。顶点阶段读取后乘到位移上,便可固定树干而摆动叶片。然而空间变化权重也进入导数,不能只给位移乘权重、法线公式完全不变。

若权重是离散美术数据,可接受近似法线,或者在邻域计算变形后切线;质量要求低的远景植被还可以保持原法线,但应把它标注为性能近似,并从高光闪烁等视觉代价评估。

4. 多对象相位与共享状态

所有物体共享同一个时间 uniform 是正常且高效的,问题在于怎样给每个对象不同相位。少量物体可以各自创建材质状态并增加 uDemoPhase;大量 InstancedMesh 更适合每实例 attribute,例如 aDemoPhase,并让阴影 pass 读取同一个属性。

只在 JavaScript 循环中给共享材质的 uDemoPhase 连续赋值,最后渲染时通常只留下最后一个状态,不会自动给每个对象保存一份。使用逐对象渲染回调又涉及 uniform 上传与材质复用时机,不应当作无条件替代独立状态的捷径。

不同相位是数据变化,不需要生成不同源码或为每个实例增加程序缓存键。风模式是不同函数结构时,才应编码在 cache key 中。

5. 阴影、剔除与性能

示例按最大 Z 位移扩展 boundingBox 和 boundingSphere;交互增大幅度后要更新上界。customDepthMaterial 与 customDistanceMaterial 使用同一 demoDeform,避免布已经弯曲而影子仍竖直。

DoubleSide 便于观察布背面,渲染器按内置流程处理正反面法线。添加 alpha 叶片贴图时还要同步覆盖条件;自定义溶解或裁切可参考上一章。大量植被时,顶点分段、阴影 pass 和 overdraw 通常比几个乘法更值得测量。

验证与练习

固定时间并检查底边所有顶点仍在 z=0;设幅度为 0 应恢复竖直布片;从前后两面观察光照;将布移到画面边缘,确认最大摆幅下不突然消失。检查影子时先固定时间,排除动态采样造成的错觉。

  1. 把底边固定改成左边固定,重新推导位移权重与法线。
  2. 将振幅改为“顶部最大位移”参数,使改变布高时摆幅保持一致。
  3. 为三块布添加不同相位,确保阴影与各自布面同步。
  4. 给风场增加第二个频率,比较视觉复杂度、法线计算及边界扩展的变化。