Skip to content

四风归一

背景:风参数统一存储在 envState,但云/粒子/水面/布料各自独立读取、自行计算风力矢量——逻辑重复三次,水面和布料根本不响应风向。

过程:新增 wind-utils.ts 统一风力入口,补齐水面波向 uniform + 布料风场力,粒子改用统一函数。


外交官和 AI 站在观景台上,看着眼前的世界。

云正平稳地往东飘。细密的粒子从地面扬起,摇摇晃晃地往北飞。远处的湖面泛着波光,波浪排成固定的队列,从西往东推进。

布料呢?布料静止不动。

"你感觉到了吗?"外交官问。

"风,"AI 说,"感觉到了。"

"有几阵风?"

AI 看了看四周,犹豫了一下:

"一阵?"

"一阵,"外交官点头,"但你看看它们——云往东,粒子往北,水往西。一个风,三个方向。"

AI 又看了看,沉默了。

"那个布料呢?"

"布料没动,"外交官说,"它根本没感觉到风。"


四套模式

他们从观景台走下来,一个一个地检查。

云。 "云漂移有一套自己的风计算公式——windDirection × windSpeed × factor,"外交官翻开代码,"乘完直接用在云的坐标偏移上。"

粒子。 "粒子偏转也有一套——同样是 windDirection × windSpeed × factor,乘完作为粒子的加速度方向。"

水面。 "水面最离谱——"外交官停在一段代码前面,"波浪方向是硬编码的。"

AI 凑过去看:

glsl
const vec2 WAVE_DIR[4] = vec2[](
    vec2(1.0, 0.0),   // 东
    vec2(0.7, 0.7),   // 东北
    vec2(0.0, 1.0),   // 北
    vec2(-0.7, 0.7)   // 西北
);

"这不是风向,"AI 说,"这是……四个固定方向。"

"对。水温感调控的滑条连着 UI——想调成什么方向都行,滑条随便拖——但波浪根本就不读它。你拖了也白拖。"

"那布料呢?"

"布料没有风,"外交官说,"XPBD 求解器给每个粒子算的是位置约束和碰撞。风?不存在的。"

AI 看着这四个子系统:

"所以一阵风到了这个世界,变成了四种不同的力?"

"一阵风,四种解释。"


诊断

"为什么会这样?"AI 问。

外交官靠在一棵树上:

"因为风是环境里唯一一个'广播式'的状态——一个输入,四个独立的子系统都要响应:云要漂、粒子要偏、水面要起浪、布料要飘动。"

"但它们的代码是分开写的。写云的人想着云,写粒子的人想着粒子。没有人想过'这四个东西应该被同一阵风吹'。"

"那架构上呢?怎么治?"

"架构上有一个蠢方案和一个不蠢的方案。"


否决的方案

"蠢方案是什么?"

"加一个全局事件总线——'风变了,通知所有人'——专门做风的中转。"

typescript
globalPhysics.on('wind:change', (dir) => {
    clouds.updateWind(dir);
    particles.updateWind(dir);
    water.updateWaveDir(dir);
    cloth.updateWindForce(dir);
});

"这不挺好的?清晰,解耦。"

"是挺好的,但多了一层,"外交官说,"我们已经有一套完整的链路了:envState 存状态,setEnvState() 统一写入口,每帧有 ensureEnvUpdateObserver() 统一循环。再加一层事件系统是重复——没有实质收益。"

"那实际怎么做的?"


三件事

"三件小事。第一件——统一一个入口。"

他在一个新的文件里写了一小段代码:

typescript
// wind-utils.ts
export function getWindVector(): Vector3 {
    return windDirection.scale(windSpeed);
}

"所有子系统统一调 getWindVector()。不再各自算一遍 dir[0] * speed * factor。"

"第二件——把水面接上。"

他从 GLSL 里删掉了那四行硬编码,加了一个 uniform:

glsl
uniform vec2 uWindDir[4];

在 JS 端加了一个 computeWaveDirs() 函数——根据风向生成四个波向:主方向与风向对齐,三个副方向做微小偏移(+0.3, -0.2, +0.1 弧度):

"风向变了,波向马上跟着变。"

"第三件——给布料加风。"

在 XPBD 求解器的粒子循环里,对没有被固定的粒子施加风场位移:

typescript
particle.p += windVector * 0.2 * dt * random(0.6, 1.4);

"0.2 是布料的惯性系数——布料太轻,风大了吹飞;太重,风小了没反应。0.2 刚好。"

"那个 random 呢?"

"所有粒子都朝同一个方向飘?那看起来像一块布被风吹得平移——不自然。加上随机因子,每个粒子的偏移量有差异,布料才会起皱。"


调试

外交官把四系统都接上了,走到控制台前,拖动了风向角的滑条。

45 度。

云开始往东北飘。 粒子跟着往东北偏转。 水面的波浪方向开始旋转——主波向对齐了风向,三个副波向散开在周围。

布料鼓了起来,裙摆微微向北摆动。

"四系统同步响应,"AI 说。

"四系统同步响应,"外交官重复了一遍,"第一次。"

他又拖了拖滑条,从 45 度拖到 90 度。云转了。粒子转了。水面转了。布料转了。

"它们本来就应该一起转的——它们被同一阵风吹。"


最小的改动

"一共改了多少代码?"

"加起来不到 80 行,"外交官说,"没有架构变更,没有新增依赖。一个工具函数加三个接入口。"

"但效果特别明显。"

"对。因为问题不在代码量——问题是四个子系统不共享一个东西,它们各自造了一个。"

"统一入口这件事,说起来简单,做起来……"

"做起来也不难,"外交官接话,"难的是意识到'它们需要的其实是同一阵风'。"


教训:风是环境里唯一一个"广播式"的状态——一个输入驱动四个独立子系统。它们各自实现了一遍风力计算,就出了问题。统一入口 + 补齐缺口,比加一层事件系统更轻。有时候问题的解法不是加东西——是把重复的东西合并成一个。