粒子:一个点怎么长成一团烟
全课骨架:特效 = 一大堆各自独立的小东西,每一个只做三件事 —— 按自己的速度挪一步、 按自己的寿命决定还剩多少不透明、最后按「从远到近」的顺序盖到画面上。 前面六个课题讲的都是"一个东西怎么被画对":一条管线、一个球、一个场景、一堆刚体、一具骨架。 这一课开始讲"一堆":每个粒子都微不足道,一团的观感全来自它们怎么叠加。 「挪一步」是第 5 课的积分、「盖上去」是第 3 课的混合 —— 语言全是旧的,新的是"数量"。 保真度:画面走 CPU 软光栅(示意 · 等价重现);读数真机可证 —— 存活/回收计数、合成结果、顺序差异都是本页真算出来的数,且都有闭式对照: 合成式 c ← c + τ·Cᵢαᵢ ; τ ← τ(1−αᵢ)、相邻交换的差 τ·αaαb(Ca−Cb)、 稳态存活 R·T —— 读者能拿笔复核。
★ 画面这一侧按工业做法重做过一轮(2026-10-08,起因是"烟太假"): 粒子形状不再是一个数学上的完美圆盘,而是 4 张现场烘出来的噪声戳记(等效 flipbook 的 4 帧) × 每颗粒子自己的旋转;尺寸随寿命膨胀而不是收缩;力模型是浮力 + 阻力而不是常数重力。 三条都配了机器判据(⑨a/⑨b/⑨c),并且每一条都拿"旧档"当对照 —— 详见判据⑨。
本页的原理与数值表照常可读(画面只是把同一个数画出来)。
(脚本启动中…)
定点观察:先点「7.3 必须从远到近」,再在「画序」三档之间来回切 → 盯住画面里粒子重叠的那几块,以及下方读数里的「与正确序之差(层和)」 → 反序不是"看起来差不多":它有确定的差(相邻两层交换的差 = τ·αaαb(Ca−Cb),表③ 能拿笔算)。
本帧的读数
(脚本启动中…)
7.1 一个粒子的一生:它凭什么会淡掉
本段只增加一样东西:寿命曲线(不透明度随 t 怎么衰减;单发模式,画面上只有一个粒子,其余控件与整页一致)。
上一课结束时你已经能让一具骨架动起来了,可那是"一个"角色。特效要的是一团 —— 而一团里最省事的起点是
一个粒子:它从哪出发、往哪飞、什么时候消失。位置这一步第 5 课讲过(p ← p + v·dt,v ← v + a·dt),
本课不再重复;真正决定"像不像烟"的是另一件事 —— 它不能"啪"地消失,得越到后面淡得越快。
★ 有一点顺手改了,而改的方向是查出来的:力的模型从"常数重力 g"换成了
浮力 A + 空气阻力 k。因为常数重力给出的是对称抛物线,粒子会"先上后下"—— 那是礼花的弹道;
烟是热浮力把它托上去、阻力让它越升越慢(Unreal 给烟的 Acceleration Force 与 Drag、
GPU Gems 3 §30 的浮力项都是这个形状)。这一换是可量的:新档 2 s 处净加速度只剩 +9.1 px/s²(趋近力平衡),
旧档却恒有 +120 px/s² 的向下重力、4 s 处已经把粒子拉成朝下。注意这两行不是"调参调好看",
是判据⑨c 卡住的:净加速度必须衰减到旧档的 1/5 以下。
| t (s) | u | α | x (px) | y (px) | 离屏? |
|---|
7.2 从 1 到 N:发射速率与环形缓冲
本段只增加一样东西:发射速率 R(个/秒;时间轴与画序、算子沿用上一段,画面从单发变成连发)。
上一段把"一个粒子的一生"讲完了,可一个点永远不成"团"。要让粒子多起来,只要每秒多生几个就行 ——
但这立刻带出一个上一段没有的问题:粒子会死,死了的槽位谁来收?
如果只生不收,占用会一直涨;所以真实实现都用一条环形缓冲:写指针转圈,
写到哪个槽就把那个槽里的旧粒子顶掉。这一顶,就产生了一条必须成立、而且可以逐帧核对的账。
| R (个/s) | 闭式 R·T | 实测存活 | 差 | 累计发射 | 已回收 | 发射−存活−回收 |
|---|
7.3 一团粒子为什么必须从远到近画
本段只增加一样东西:画序(按深度从远到近 / 交换前两层 / 完全反序;发射速率、时间轴沿用上一段)。
粒子多起来之后,画面上的问题第一次从"每个粒子长什么样"变成了"谁盖谁"。
只要两个粒子有重叠,先画谁后画谁就会给出不同的颜色 —— 而 α 混合又是顺序敏感的。
本段要把它做成一件可量化的事:差多少有闭式,表③ 能拿笔算。
| 序 | 第 1 层 | 第 2 层 | 第 3 层 | 结果 c | 正确序 − 本序 | 闭式对照 |
|---|
7.4 加法还是混合:让粒子发光还是遮挡
本段只增加一样东西:叠加算子(α 混合 / 加法;画序三档沿用上一段,而这一段的重点正是拿它做对照)。
上一段说"α 混合必须从远到近",可它没说清另一半:为什么有的叠加方式根本不在乎顺序。
把合成式从 `c += τ·Cα; τ *= (1−α)` 换成 `c += Cα`(不再打折)之后,它变成了一个加法 ——
而加法可交换,所以画序自然无关。这一换,两种完全不同的观感就出来了:一种像光,一种像烟。
| 算子 | 画序 | 合成结果(最亮通道) | 是否越出上界 | 与正确序的差 |
|---|
这一课的位置
四段只做了三件事:积分(第 5 课学过)、寿命(本课新加)、排序 + 混合(本课新加)。
一整团粒子的观感,全在这三件事的参数里;它们各自都能拿笔算,也各自都有实测对照。
本页那一格"如实说明":画面是 CPU 软光栅逐点采样、逐层混合写进帧缓冲的(示意 · 等价重现);
真机上"更新粒子"通常在 compute、"画粒子"是实例化点精灵,本页不声称复现那两条内部路径。
粒子的颜色全部取自 token 色(`--beam` / `--ink` / `--ink-mute` / `--void`),
显示不做色调映射 —— 这样画面上的数与表里的数能一一对应。
本课题还没做的四课(留在 stub,不在站首页占行):流场(让粒子自己成流)、
软粒子(粒子不能有硬边)、后处理链(bloom:光为什么要溢出来)、屏幕空间(SSAO 与 SSR)。
本页画面这一层用了哪些做法,出处在哪
画面的每一条都是照着工业做法的最小复现做的,不是"调参调好看"。列出来是为了让读者能自己去核, 也是为了说清哪一条本页故意没做、留给哪一课:
- 形状:带内部结构的 alpha 图 + 序列帧 + 每颗粒子自己的旋转 —— Unreal 的 Sprite Smoke 教程 正是用 Sub Image Size + Sub UV Blending + Sprite Rotation Mode(随机) 三件套来消掉"棉花球"感; 本页没有贴图资产,改用确定性哈希现场烘 4 张 64² 戳记(= 内置 flipbook 的 4 帧)+每颗粒子的出生朝向与角速度。
- 尺寸随寿命"扩散" —— Unity 的 Particle System 文档 原话是"代表气体/火焰/烟的粒子会随寿命扩散并占据更大体积"。旧档那条曲线方向是反的(越老越小),已改。
- 浮力 + 空气阻力(而不是常数重力) —— Unreal 的 Particle Spawn Group 参考 里 Acceleration Force(给烟沿 +Z 持续加速)与 Update Group 参考 里的 Drag;浮力项的物理出处是 GPU Gems 3 第 30 章 (热烟上升、冷烟下沉)。
- 绕自身滚动,让烟"往里卷" —— NVIDIA GPU Gems 第 6 章 Fire in the "Vulcan" Demo 里那句"给烟粒子加绕 x 轴的滚动,烟看起来就在自己往里折" —— 本页每颗粒子一个随机角速度,就是这件事。
- 本页故意没做的一条:软粒子(深度淡出) —— 它需要一张场景深度,而本页没有场景几何。 它的标准做法(NVIDIA Soft Particles 白皮书 / GPU Gems 3 第 23 章: alpha *= saturate((z_scene − z_particle) / fadeRange))正是第 3 课「软粒子」的正题,本页不抢。 同理 curl 噪声(Bridson 等,SIGGRAPH 2007) 留给第 2 课「流场」:它是"让粒子自己成流",不是"让一个粒子像烟"。