Skip to main content

Chapter 63: Particle Systems

粒子系统把大量短生命周期的小对象组织成可控的视觉现象。火花、烟雾、雨滴、碎屑、能量轨迹、传送门边缘和脚步扬尘都可以归到这个模型里:每个粒子只保存少量状态,系统用发射器决定何时产生粒子,用生命周期函数决定粒子如何变化,用渲染阶段把这些状态变成 billboard、mesh、trail 或体积近似。

本章用一个贯穿例子展开:角色触发一次能量爆发,画面上出现 3 万个发光火花、低频烟雾片、少量 mesh 碎片和地面碰撞回弹。读完后应能追踪这类效果从事件输入、粒子生成、GPU 更新、透明渲染到调试证据的完整路径,并能判断它的主要瓶颈来自 CPU 提交、GPU 计算、透明 overdraw、排序、内存池还是资源同步。

粒子系统的核心问题是控制规模。单个粒子很简单,大量粒子叠加后会同时触发状态管理、随机性、透明混合、深度交互、排序和工具调试问题。稳定的工程做法是先定义粒子状态和发射器边界,再决定生命周期曲线与事件触发方式,随后把更新、压缩、排序和绘制放到合适的 CPU 或 GPU 路径中。

本章的结论是:粒子系统应被看作一条数据管线。事件产生 spawn 请求,emitter 写入初始状态,update pass 推进状态,compaction 生成可绘制集合,sort 或 binning 改善透明结果,draw pass 输出颜色和深度交互。每个环节都能被 frame capture、buffer inspection、GPU timer、overdraw view 或视觉对比复查。

63.1 Particle State Emitter Lifetime and Sorting Model

粒子系统的第一层结构是 emitter、particle state、lifetime 和 sorting。Emitter 是产生粒子的规则对象,它决定发射位置、方向分布、spawn rate、burst 数量、初始速度、初始颜色、初始大小和随机种子。Particle state 是每个粒子随时间变化的数据记录,常见字段包括 position、velocity、age、lifetime、color、size、rotation、angularVelocity、frameIndex 和 flags。Lifetime 把粒子从“刚生成”推进到“死亡回收”。Sorting 决定这些粒子以什么顺序进入透明混合或深度相关的绘制流程。

在贯穿例子里,能量爆发由三个 emitter 组成。Spark emitter 在触发帧一次性发射 2 万到 3 万个点状火花;smoke emitter 在 0.4 秒内持续生成较大 billboard;debris emitter 发射几十个 mesh particle。三个 emitter 共用同一个事件源,但 state layout 和渲染路径不同。火花关注速度、颜色衰减和 additive blend;烟雾关注深度软化、旋转、size curve 和 alpha blend;碎片关注 mesh instance、碰撞和受光照结果。

Particle state 的字段应只保存 update 和 render 都会使用的信息。位置和速度服务运动积分;age 和 lifetime 服务归一化生命周期 t=age/lifetimet=age/lifetime;color、size 和 rotation 服务材质与几何展开;random seed 服务可复现扰动。状态字段越多,GPU buffer 带宽越高,update pass 和 draw pass 都会受到影响。对 3 万个火花来说,每个粒子增加 16 字节字段,单帧 update 和 draw 之间会多传递约 480 KB 的状态数据;如果还有历史 buffer、sort key buffer 和 draw argument buffer,真实带宽会继续扩大。

Billboard particle 和 mesh particle 的差异在几何生成位置。Billboard particle 通常把一个粒子展开成面向相机的四边形,顶点可以由 vertex shader 根据粒子中心和 size 生成,也可以在 compute pass 中预生成 quad。Mesh particle 把粒子状态作为 instance data,绘制时复用同一个低模 mesh 或多个 mesh variant。Billboard 适合烟、光斑、尘土和魔法能量片;mesh particle 适合石块、玻璃碎片、弹壳和可受光照的实体碎屑。

Sorting 主要服务透明混合。Alpha blend 的颜色结果依赖绘制顺序,常见做法是用 view-space depth 生成 sort key,再把粒子从远到近绘制。Additive blend 对顺序的依赖弱,火花和能量辉光常用 additive 或 premultiplied additive 路径来减少全量排序压力。烟雾这种低频半透明层更依赖排序和 soft particle,mesh debris 则可以走普通 opaque 或 alpha test 路径,减少透明队列规模。

这个模型可以用一条最小数据流描述。图中每个节点都对应一类可观察资源或 pass,后续小节会继续使用这条路径定位问题。

这条路径的关键点是 Alive List。很多效果在视觉上只有几千个粒子可见,但每帧参与 update 的粒子池可能更大。Alive List 把“仍然有效且需要绘制”的粒子从状态池中提取出来,后续 sort 和 draw 都基于这个集合执行。GPU 粒子系统通常还会生成 indirect draw 参数,把 alive count 写入 draw argument buffer。Microsoft Learn 对 DrawInstancedIndirect 的说明把它定位为“绘制 GPU 生成的 primitives”,并要求参数来自专门的 buffer;Vulkan 的 vkCmdDrawIndirect 也明确 draw 参数在执行时从 buffer 读取,这些 API 事实支撑了“更新阶段生成绘制参数”的工程路径:D3D11 DrawInstancedIndirectVulkan vkCmdDrawIndirect

排序模型的选择应跟材质和视觉目标绑定。能量爆发里的火花可以按 emitter 分桶后直接 additive 绘制;烟雾 billboard 需要按大致深度排序或分层 binning;碎片 mesh 先进入 opaque pass,再让透明烟雾覆盖它。这样的拆分把排序成本集中在真正需要顺序的粒子上,保持火花的大规模吞吐。

63.2 粒子生命周期与属性驱动模型

粒子生命周期把离散事件转换成连续视觉变化。每个粒子出生时拿到初始 position、velocity、lifetime 和 random seed;update pass 每帧增加 age,根据速度和外力更新 position,再用归一化时间 t=age/lifetimet=age/lifetime 查询 color、size、rotation 和 alpha 曲线。死亡条件通常由 age、透明度、越界、碰撞结果或事件 flag 决定。

贯穿例子里的火花生命周期可以拆成三个阶段。出生后的前 0.1 秒保持高亮和高速外扩,颜色从白黄转向橙色;中段速度受到阻尼和重力影响,轨迹开始下坠;末段 size 和 alpha 快速下降,粒子进入死亡队列。烟雾生命周期更慢,出生时 alpha 较低,中段体积膨胀,末段变淡并向上漂移。碎片生命周期由碰撞和休眠控制,碰到地面后保留短时间可见状态,再被回收。

属性驱动模型的价值在于把美术控制和运行时状态分开。运行时只保存少量动态字段,颜色、大小、旋转速度、噪声强度和贴图帧可以由 curve 或 gradient 在 shader 中求值。这样 emitter 资产可以调整视觉节奏,而 state buffer 不需要把每一帧的颜色和大小预写入内存。常见做法是把 curve 采样成一维纹理或小型常量表,fragment shader 或 compute update 根据 tt 查表。

下面的简化 HLSL 展示了 update pass 的关键路径。它省略了平台绑定代码,只保留状态读取、生命周期推进、力场影响、死亡判断和 alive list 写入。

struct ParticleState {
float3 position;
float3 velocity;
float age;
float lifetime;
uint randomSeed;
uint flags;
};

RWStructuredBuffer<ParticleState> particleStates;
AppendStructuredBuffer<uint> aliveIndices;

cbuffer ParticleUpdateParams {
float deltaTime;
float3 gravity;
float damping;
float3 burstCenter;
float collisionY;
};

[numthreads(128, 1, 1)]
void CSMain(uint3 dispatchId : SV_DispatchThreadID) {
uint index = dispatchId.x;
ParticleState p = particleStates[index];

if ((p.flags & 1u) == 0u) {
return;
}

p.age += deltaTime;
float normalizedAge = saturate(p.age / max(p.lifetime, 0.0001));

float3 radial = normalize(p.position - burstCenter + 0.0001);
float forceScale = 1.0 - normalizedAge;
p.velocity += (gravity + radial * forceScale * 4.0) * deltaTime;
p.velocity *= exp(-damping * deltaTime);
p.position += p.velocity * deltaTime;

if (p.position.y < collisionY) {
p.position.y = collisionY;
p.velocity.y = abs(p.velocity.y) * 0.35;
p.velocity.xz *= 0.65;
}

if (p.age >= p.lifetime) {
p.flags = 0u;
} else {
aliveIndices.Append(index);
}

particleStates[index] = p;
}

这段代码对应的判断点有三个。第一,agelifetime 决定粒子是否继续参与渲染,Alive List 的数量变化可以直接作为效果密度证据。第二,randomSeed 没有在片段中展开,但它应参与 spawn 或 curve 扰动,让同一 emitter 产生稳定随机性。第三,碰撞只使用一个平面,这种简化适合地面火花,不适合复杂室内场景;复杂场景需要深度碰撞、signed distance field 或物理代理。

Event trigger 是生命周期的入口。角色攻击、子弹命中、天气系统、脚步接触、UI 状态变化都可以产生 spawn 请求。工程上应把事件变成紧凑的 spawn command,而非让每个系统直接修改 particle buffer。Spawn command 至少包含 emitter id、触发位置、触发方向、强度、时间戳和随机种子。粒子系统消费这些 command 后再写入状态池,调试时可以检查事件数量、spawn 数量和 alive 数量之间是否一致。

Curve 的使用也要受资源路径约束。CPU 曲线求值适合粒子数量少、需要复杂逻辑的 mesh debris;GPU 曲线查表适合大量 billboard 和火花。曲线纹理的分辨率决定视觉平滑度,过低会在 alpha 或 size 上出现跳变。调试时可以把 normalizedAge 输出为颜色,检查粒子是否按预期从出生推进到死亡;也可以把 size curve 固定为常数,排除尺寸变化对排序和 overdraw 的干扰。

生命周期系统的常见失败表现可以直接回到状态字段。粒子突然消失,多半是 lifetime、flags 或 alive list 写入错误;粒子闪烁,多半是 random seed 每帧重算、sort key 不稳定或 texture frame 跳变;粒子拖尾断裂,多半是 velocity、previous position 或 trail buffer 更新顺序错误;粒子密度随帧率变化,多半是 spawn rate 没有按 deltaTime 积累。

63.3 GPU 驱动粒子系统的高效实现

GPU 驱动粒子系统的目标是把高频更新、存活筛选、排序准备和绘制参数生成留在 GPU 侧完成。CPU 只提交少量 dispatch 和 draw 命令,并上传 emitter 参数与事件队列。这样做能降低 CPU per-particle 循环和 CPU 到 GPU 的同步压力,适合火花、烟雾、雨雪、能量流、群集碎片等大量粒子效果。

GPU 路径通常包含五类 buffer。State buffer 保存完整粒子状态;free list 保存可复用槽位;alive list 保存本帧存活粒子索引;sort key buffer 保存深度或分桶键;indirect argument buffer 保存 draw 或 dispatch 参数。D3D 的 AppendStructuredBuffer 是一种可追加输出流,Microsoft 文档说明它用于 shader append,并要求对应 UAV 使用 append 标志;这个对象正好对应 alive list 或 spawn list 的写入场景:AppendStructuredBuffer。Vulkan、Metal 和 WebGPU 的具体对象名称不同,但工程角色相同:用 storage buffer 或等价资源保存状态,用原子计数或前缀和生成紧凑列表。

贯穿例子可以使用如下 pass 组织:spawn pass 消费事件并从 free list 取槽位;update pass 推进所有 active particle;compact pass 生成 alive list 和 draw count;sort pass 为烟雾生成深度键;draw pass 根据 alive list 读取状态并展开几何。火花路径可以跳过全量排序,直接按 emitter 或 tile 分桶绘制。碎片 mesh 路径可以复用 instance draw,把粒子状态作为 instance buffer。

GPU 驱动路径的核心不是“所有事情都放到 GPU”,而是把粒子数量随时间变化这件事交给 GPU 自己闭环。CPU 如果每帧读取 alive count 再决定 draw 数量,会引入 readback 同步,破坏异步执行。更稳定的做法是让 compute pass 写 indirect argument buffer,随后 draw indirect 读取该 buffer。CPU 仍然负责命令顺序、barrier 和资源生命周期。

下面的伪代码描述一帧内的资源状态变化,重点是 pass 之间的数据依赖。

// Pseudocode for one particle frame.
recordComputePass("Spawn", [&] {
bind(eventBuffer);
bind(freeListBuffer);
bind(particleStateBuffer);
dispatch(spawnGroups);
});

barrier(particleStateBuffer, "storage-write", "storage-read-write");

recordComputePass("UpdateAndCompact", [&] {
bind(particleStateBuffer);
bind(aliveIndexBuffer);
bind(indirectArgsBuffer);
dispatch(updateGroups);
});

barrier(aliveIndexBuffer, "storage-write", "vertex-read");
barrier(indirectArgsBuffer, "storage-write", "indirect-argument-read");

recordGraphicsPass("ParticleDraw", [&] {
bind(particleStateBuffer);
bind(aliveIndexBuffer);
bind(particleMaterial);
drawIndirect(indirectArgsBuffer);
});

这段伪代码说明了两个边界。第一,GPU 生成 draw 参数后,需要资源屏障把 storage write 结果暴露给 indirect argument read。不同 API 的状态名不同,但依赖关系一致。第二,alive list 被 draw pass 作为 vertex 或 storage 输入读取,因此也需要从 compute write 转到图形读取状态。工具中如果看到粒子数量正确但 draw 没有输出,应优先检查 argument buffer 内容、barrier 顺序和 draw call 的 instance count。

排序是 GPU 粒子系统里最容易放大成本的环节。全量精确排序通常需要 radix sort、bitonic sort 或库级 GPU sort;对 3 万粒子可以接受,对 30 万烟雾片就会变成明显成本。工程上常用三种近似:按 emitter 粒度排序、按深度切片 binning、只对 alpha blend 烟雾排序。Additive 火花和 opaque debris 应从排序集合中分离出来。

碰撞场也应按精度需求选择。深度碰撞用上一帧或当前帧 depth buffer,把粒子投影到 screen space 后检查深度差,适合地面和大物体附近的火花。Signed distance field 适合需要空间距离和法线的烟雾或能量场,但需要额外内存和更新成本。简单 collision field 可以是平面、球、盒或高度场,适合游戏玩法中可预测的效果。选择碰撞场时要看粒子是否需要真实反弹、是否穿过近景几何、是否和相机视角相关。

GPU 实现的调试顺序应围绕 buffer 数量展开。先固定 emitter 只生成 100 个粒子,观察 alive count 是否从 0 上升并随 lifetime 下降;再把 update pass 的 position 输出到 debug color 或点渲染,确认空间范围;然后检查 indirect argument buffer 的 draw count 和 instance count;最后开启透明材质、sort pass 和 soft particle。这个顺序能把 spawn、update、draw、透明混合分开诊断。

性能上应区分 update 成本和 draw 成本。Update 成本主要来自 compute 线程数量、state buffer 带宽、随机访问、原子操作和排序;draw 成本主要来自 billboard 展开、texture sampling、overdraw、blend、depth read 和 render target bandwidth。火花数量增加但 GPU timer 主要涨在 draw pass,说明瓶颈在像素覆盖和混合;粒子数量增加但 compute pass 先变慢,说明状态更新、compaction 或排序更重。

63.4 粒子与渲染管线集成技巧

粒子系统进入渲染管线时,主要面对透明排序、深度交互、光照一致性、VFX authoring 和内存池管理。粒子效果通常跨越多个 pass:opaque pass 提供 depth buffer,particle update pass 生成状态,transparent pass 绘制烟雾和光斑,post-processing pass 处理 bloom、tonemap 和 motion blur。每个 pass 的输入输出决定粒子能否稳定贴合场景。

透明排序需要先按材质和混合模式分层。Additive 火花可以放在烟雾前后都保持接近的亮度结果,但 alpha blend 烟雾需要从远到近绘制。Premultiplied alpha 更适合有发光边缘的烟雾,因为颜色与 alpha 的关系更稳定。Depth write 通常关闭,depth test 仍然开启,这样烟雾会被前景 opaque 几何遮挡,同时不会阻止后续透明粒子绘制。

Soft particle 解决的是 billboard 和场景几何相交时的硬边问题。它在 fragment shader 中读取 scene depth,把当前粒子的 fragment depth 与场景深度比较,根据差值衰减 alpha。差值很小表示粒子片贴近或穿过几何,alpha 逐渐降低;差值足够大时保持原有透明度。这个方法依赖 depth buffer、相机投影参数和线性深度转换,深度范围或 reverse-Z 配置错误会直接导致烟雾边缘异常。

一个最小 soft particle 片段如下。它假设已经能得到当前 fragment 的线性深度和场景线性深度,实际工程中要按 API 的 clip space 和 depth convention 做转换。

float ComputeSoftParticleAlpha(
float particleLinearDepth,
float sceneLinearDepth,
float fadeDistance,
float baseAlpha)
{
float depthGap = sceneLinearDepth - particleLinearDepth;
float softFactor = saturate(depthGap / max(fadeDistance, 0.0001));
return baseAlpha * softFactor;
}

这段函数只解决几何相交边缘。它不会修复透明物体之间的排序问题,也不会让烟雾真正参与体积遮挡。调试时可以把 softFactor 直接输出为灰度,白色表示保留,黑色表示被深度软化。若整片烟雾都变黑,应检查深度采样、线性化和 particle depth 的空间是否一致。

Lighting 对粒子有三种常见层级。第一层是 unlit,火花和 UI 风格能量片直接使用颜色与贴图。第二层是近似 lighting,烟雾用主光方向、ambient probe 或体积雾 lighting 结果调整亮度。第三层是 mesh particle 受普通光照、阴影和反射探针影响。贯穿例子中,火花走 unlit additive,烟雾读取低频光照和 depth softening,碎片 mesh 进入普通 lighting path。这种分层让视觉统一,同时控制 shader 成本。

VFX authoring 的关键是把 emitter 参数、曲线、贴图、材质和事件接口变成稳定资产。美术需要能控制 spawn rate、burst count、lifetime range、velocity cone、size curve、color gradient、texture sheet、blend mode 和 collision mode。工程侧需要给这些参数定义上限和默认值,并把每个参数映射到 state buffer、constant buffer、texture 或 shader variant。参数没有上限会导致某个资产在低端平台生成过多粒子或过大的半透明覆盖面积。

Memory pool 决定粒子系统能否在密集场景里稳定运行。固定容量池适合实时渲染,因为它让 buffer 大小、descriptor、indirect 参数和调试视图保持稳定。池满时应有明确策略:丢弃低优先级 spawn、缩短远处粒子 lifetime、降低烟雾数量或切换低质量 emitter。这个策略要在工具和日志中可见,例如记录 requested spawn、accepted spawn、dropped spawn 和 alive count 峰值。

与 frame graph 集成时,粒子资源应明确生命周期。Event buffer 来自 gameplay 或 animation;state buffer 跨帧保留;alive list、sort key 和 indirect argument buffer 每帧重置;depth texture 来自 opaque pass;color target 是 transparent pass 的输出。若 temporal effects 需要 history buffer,还要声明上一帧粒子状态或 trail 状态。资源生命周期不清晰时,常见问题是上一帧 alive list 残留、计数器未清零、history 读取错帧或 barrier 覆盖不足。

工具复查可以按画面症状定位。烟雾穿透地面,检查 depth texture、soft factor 和 collision mode;火花数量忽多忽少,检查 spawn accumulator、random seed 和池容量;透明层颜色变脏,检查 blend state、draw order 和 premultiplied alpha;GPU 时间集中在 particle draw,开启 overdraw view 或降低 texture 分辨率观察变化;GPU 时间集中在 sort,改用 emitter binning 或降低排序集合进行对比。

63.5 交互与物理模拟结合实现更真实效果

粒子效果进入真实场景后,需要响应力场、碰撞、流体场、角色事件、天气系统和 gameplay trigger。交互不是把粒子改成完整物理对象,而是为视觉状态加入足够的外部输入,让粒子变化和场景事件一致。火花被爆炸冲击推出、烟雾沿风向漂移、雨滴被屋檐遮挡、雪花绕过角色、能量轨迹被技能方向拉伸,都是这种模型的结果。

Force field 是最常见的交互输入。它可以来自简单解析函数,例如径向爆炸力、涡旋力、风向和重力;也可以来自贴图或 3D volume,例如速度场、噪声场和流体模拟缓存。贯穿例子里的火花使用径向爆炸力和重力,烟雾使用低频噪声和向上浮力,碎片 mesh 使用重力、阻尼和地面反弹。三个路径共享事件强度,但力场采样频率和精度不同。

碰撞要按视觉需求控制成本。火花只需要在地面附近反弹几次,平面或 depth collision 已经足够。碎片 mesh 因为尺寸较大且更靠近前景,可以用简单物理代理或 CPU physics 产生初始速度,再由 GPU 粒子路径接管渲染状态。烟雾的碰撞更适合用 signed distance field 或障碍物 mask 近似,让它绕开墙面或在空间内扩散。碰撞精度越高,状态字段、采样资源和同步成本越高。

流体场和粒子系统的关系应看作“粒子采样外部速度场”。流体 solver 可以输出 velocity texture、density texture 或 curl noise field;粒子 update pass 读取这些场来改变 velocity 或 alpha。这样烟雾能呈现旋涡和扩散,但粒子本身仍然是 billboard 或点。若要让粒子反向影响流体,就需要额外的写入 pass 和同步,这会把系统从 VFX 近似推向模拟管线。

角色事件需要一个稳定的事件协议。动画脚底接触地面时产生 footstep dust event,武器命中时产生 impact spark event,角色冲刺时产生 trail event,技能蓄力时产生 energy aura event。每个事件都应包含 position、direction、surface id、velocity、strength 和 time。Surface id 可以选择不同材质贴图和颜色,velocity 可以让粒子继承角色运动,strength 可以控制 burst count 和 lifetime。

天气系统适合使用分层粒子。近处雨雪用真实粒子或短线 billboard,远处雨幕用屏幕空间或体积近似,地面溅水用事件驱动 burst。风场通过全局参数和局部 volume 影响粒子速度。密集天气的性能瓶颈通常来自 overdraw 和透明混合,而非单个粒子的运动计算。调试时可以分别关闭近处、远处和地面事件层,观察 GPU timer 和画面贡献。

Gameplay trigger 与粒子系统的边界要保持清晰。粒子可以反馈游戏状态,例如命中、受伤、燃烧、冻结、隐身或护盾破裂;粒子也可以提供可见提示,例如危险范围、风向、能量蓄积和交互点。但 gameplay 判定应由游戏逻辑或物理系统给出,粒子只消费事件并呈现结果。这样调试时可以分别确认“判定是否发生”和“效果是否显示”。

把交互粒子做稳定,需要一套复用检查顺序。先检查事件是否产生,再检查 emitter 是否接受事件,随后检查 spawn 数量与池容量,再检查力场和碰撞输入是否绑定,接着检查 update 后 position 是否合理,最后检查透明绘制与后处理。这个顺序能把玩法、模拟、渲染和后处理分层,减少在画面结果上盲目调整参数。

贯穿例子最终可以落成一帧内的判断:角色技能事件写入 event buffer;spark、smoke、debris 三类 emitter 生成不同 state;GPU update pass 用力场和碰撞推进状态;alive list 和 indirect argument buffer 决定本帧绘制规模;火花 additive 绘制,烟雾 alpha blend 并使用 soft particle,碎片 mesh 进入 lighting path;bloom 和 tonemap 扩展发光结果。若画面异常,按事件、状态、列表、排序、draw、blend、post-processing 的顺序复查。

最小自检任务

给定一个能量爆发效果:触发帧生成 2 万个 additive 火花,0.5 秒内生成 800 个烟雾 billboard,另有 60 个 mesh 碎片。当前画面中火花数量正确,烟雾穿过地面边缘有硬切线,GPU profiler 显示 transparent particle draw pass 时间明显升高,compute update pass 变化较小。请判断应该优先检查哪些资源和状态,并说明哪些问题属于渲染管线集成,哪些问题属于 GPU 更新路径。

答案要点

应先把问题拆成两个现象:烟雾与地面相交出现硬切线,transparent particle draw pass 时间升高。前者优先检查 opaque pass 输出的 depth texture、烟雾 fragment shader 的线性深度转换、soft particle fadeDistance、depth test 状态和 particle depth 所在空间。若把 soft factor 输出为灰度后整片偏黑或偏白,说明深度差计算或空间转换有误;若只有接触边缘异常,说明 fadeDistance、depth precision 或 billboard 尺寸需要调整。

GPU 时间集中在 transparent draw pass,说明主要瓶颈在像素覆盖、texture sampling、blend、depth read 或 render target bandwidth。compute update pass 变化较小,先不用把重点放在 velocity、lifetime、alive list 生成或 collision field 更新上。应临时关闭烟雾层、降低烟雾 size、切换低分辨率贴图、关闭 soft particle depth 采样、拆分 additive 火花和 alpha blend 烟雾分别计时,观察 draw pass 时间变化。

渲染管线集成问题包括 transparent queue 顺序、blend state、depth test、depth texture 绑定、soft particle shader、premultiplied alpha、overdraw 和 post-processing。GPU 更新路径问题包括 spawn pass、state buffer、alive list、indirect argument buffer、sort key buffer、collision field 和 barrier。当前题目中火花数量正确且 compute update pass 稳定,因此状态生成链路大概率成立;烟雾硬边和 draw pass 升高更指向深度交互与透明绘制成本。

最终检查顺序是:确认 opaque depth 可被 particle pass 正确读取;用 debug 输出验证 soft factor;按材质拆分火花和烟雾计时;检查烟雾排序或分桶是否只作用于 alpha blend 集合;降低烟雾屏幕面积观察 overdraw;最后再回到 update pass 检查 alive count 和 sort buffer。这个顺序把可见错误和性能症状都落到具体资源与 pass。

本章知识点总结

  • Emitter 边界:Emitter 负责把事件转换成 spawn 规则,粒子状态负责保存运行时需要更新和渲染的最小数据。
  • 状态字段:Position、velocity、age、lifetime、random seed 和 flags 是生命周期、运动、随机性和回收判断的核心字段。
  • 生命周期:Normalized age 把出生、成长、衰减和死亡统一成曲线查询与状态更新问题。
  • Billboard 粒子:Billboard 适合烟雾、光斑和尘土,几何通常在 shader 或 compute 路径中由中心点展开。
  • Mesh 粒子:Mesh particle 复用 instance draw,更适合碎片、弹壳和可受光照的小实体。
  • 排序模型:透明 alpha blend 粒子依赖排序或分桶,additive 火花对顺序依赖较弱,可以从全量排序集合中分离。
  • GPU 驱动:GPU particle path 应让 update、alive list、sort key 和 indirect draw 参数在 GPU 侧闭环,CPU 主要负责提交和资源依赖。
  • 资源屏障:Compute 写入的 alive list 和 indirect argument buffer 进入 draw pass 前,需要显式表达写后读依赖。
  • Soft Particle:Soft particle 通过 scene depth 与粒子 depth 的差值衰减 alpha,用于处理 billboard 与 opaque 几何的硬交界。
  • 透明成本:Particle draw pass 的性能常由 overdraw、blend、depth read、texture sampling 和 render target bandwidth 决定。
  • 内存池:固定容量池让粒子资源、descriptor 和调试视图保持稳定,池满时需要可见的降级策略。
  • 交互输入:力场、碰撞、流体场、天气系统和 gameplay trigger 应作为外部输入影响粒子状态,粒子只承担视觉呈现。
  • 调试顺序:粒子问题应按事件、spawn、state、alive list、sort、draw、blend 和 post-processing 的路径逐层复查。