7.3 粒子系统:批量小件的喷洒工艺


文档摘要

7.3 粒子系统:批量小件的喷洒工艺 本节摘要:一万个同款小件的正确画法不是一万个网格,而是一次提交:粒子用 Points 加缓冲区,同款实体用 InstancedMesh。本节用一场灰尘雨走通粒子全流程,把实例化讲成通用工艺,并给出"复制网格"这一反模式的判决书。 一万个网格的死刑判决 新手做"满场灰尘",第一直觉是循环一万次 new Mesh——于是渲染循环里躺着一万个绘制调用,显卡排队排到窒息。绘制调用(draw call)是渲染开销的真实单位:一万个简单的方块,比一个百万面的精细模型贵得多,因为前者要一万次"开工令"。批量小件的正确工艺只有两种:不透明的成群实体走 InstancedMesh(实例化),半透明的小光点与尘雾走 Points(粒子点精灵)。

7.3 粒子系统:批量小件的喷洒工艺

本节摘要:一万个同款小件的正确画法不是一万个网格,而是一次提交:粒子用 Points 加缓冲区,同款实体用 InstancedMesh。本节用一场灰尘雨走通粒子全流程,把实例化讲成通用工艺,并给出"复制网格"这一反模式的判决书。

一万个网格的死刑判决

新手做"满场灰尘",第一直觉是循环一万次 new Mesh——于是渲染循环里躺着一万个绘制调用,显卡排队排到窒息。绘制调用(draw call)是渲染开销的真实单位:一万个简单的方块,比一个百万面的精细模型贵得多,因为前者要一万次"开工令"。批量小件的正确工艺只有两种:不透明的成群实体走 InstancedMesh(实例化),半透明的小光点与尘雾走 Points(粒子点精灵)。两者的共同哲学是一次提交,万份复用

Points 是极简主义的作品:一颗粒子就是一个顶点,位置数组一次性交给显卡,形状由一张圆形贴图(或着色器程序)在像素阶段"画"出来。因为它没有真正的几何面,天生适合尘埃、雨雪、火星这类"看得见摸不着"的效果。

图:批量小件的三种工艺对比

图:批量小件的三种工艺对比

动手:下一场仓库灰尘雨

背景:夜间巡检模式(2.2)上线后客户追加需求:灯光锥里要有浮尘,灰尘密度决定了"仓库空气感"。预算:一万颗,帧开销不超过半毫秒。

操作:Points 加 BufferGeometry 的位置数组,CPU 批量更新,一次 needsUpdate 提交。

const COUNT = 10000; const positions = new Float32Array(COUNT * 3); // 每颗 xyz 三个分量 const speeds = new Float32Array(COUNT); for (let i = 0; i < COUNT; i++) { positions[i * 3] = (Math.random() - 0.5) * 12; // x 铺满 positions[i * 3 + 1] = Math.random() * 6; // y 从高处起 positions[i * 3 + 2] = (Math.random() - 0.5) * 10; // z 铺满 speeds[i] = 0.2 + Math.random() * 0.4; // 每颗各落各的 } const dustGeo = new THREE.BufferGeometry(); dustGeo.setAttribute('position', new THREE.BufferAttribute(positions, 3)); const dustMat = new THREE.PointsMaterial({ size: 0.035, // 尘埃要小,大了就是雪 color: 0xfff2d0, // 暖白,贴巡检灯色 transparent: true, opacity: 0.55, depthWrite: false, // 关深度写入:尘不遮挡尘,透明排序免烦恼 blending: THREE.AdditiveBlending // 叠亮混合:灯锥里的尘更亮 }); const dust = new THREE.Points(dustGeo, dustMat); stage.scene.add(dust); stage.loop((dt) => { for (let i = 0; i < COUNT; i++) { positions[i * 3 + 1] -= speeds[i] * dt; // 批量下落 if (positions[i * 3 + 1] < 0) positions[i * 3 + 1] = 6; // 落底回顶 } dustGeo.attributes.position.needsUpdate = true; // 一次提交全部 });

结果:一万颗微尘缓缓飘落,灯光锥内因叠亮混合明显更亮,"仓库空气感"一步到位;一万次位置更新加一次提交,帧开销远低于半毫秒预算。

解读:两处配置是粒子观感的分水岭。depthWrite 关闭:透明物不写深度缓冲,彼此不会互相"切割"出硬边——粒子与粒子交叠时只做颜色叠加,这行不写,尘雾会闪现成片的方形断面。AdditiveBlending 叠亮:颜色相加而非遮挡,暗处粒子几乎隐形、亮处自然变亮,灯光锥的体积感就是它"免费"画出来的。性能心算再强调一次结构:贵的是绘制调用次数而不是顶点数,一万个顶点对显卡是洒洒水,一万个调用才是死刑。

变式:成群实体的同款小件(一万个货箱、一千盏灯)走 InstancedMesh:一份几何一份材质,配一个矩阵数组,每个实例一份独立变换,setMatrixAt 写入后 instanceMatrix.needsUpdate 提交——依然是一次绘制调用。粒子雨想加"横向漂移",给 x 也配速度数组并加一个正弦摆动即可。粒子上限的心算是 8.1 节的内容,先给结论:十万颗以内 CPU 更新仍轻松,再往上就该把更新逻辑搬进着色器(GPU 粒子),那是进阶课题。

需求到工艺的对照速查

接到"加个粒子"的需求,先按效果形态对工艺,再动手写码:静态散布(草丛、碎石、货架尘点)用实例化一次摆好,不参与逐帧更新,帧开销近似为零;缓慢漂浮(灰尘、萤火、气泡)用 Points 加 CPU 数组逐帧小步更新,一万颗以内都轻松;定向流动(雨、雪、传送带碎屑)同样 Points,但速度向量按方向分配,落底回顶循环;爆发消散(撞击火星、开箱彩带)用"寿命"字段——每颗带一个递减的生命值,归零回炉或直接停用,配合叠加混合出亮色爆点。四类之外的效果,多数是这四类的参数变奏:把"效果描述"翻译成"哪类工艺加什么参数",比直接搜索现成插件更快也更可控。粒子是精装修里最容易过量加戏的工位,动笔前先问一句:这颗粒子在画面叙事里负责什么?答不上来的粒子,删掉比保留便宜。

本节要点回顾

  • 绘制调用是真实账单:一万个简单网格比一个百万面模型更贵;
  • Points 管光点尘雾:一颗粒子一个顶点,形状像素阶段画,天生适合透明小件;
  • InstancedMesh 管实体成群:一份几何万份矩阵,一次绘制,小件仓库首选;
  • 透明粒子关 depthWrite:叠亮混合加关深度写入,尘雾才不会切成方块;
  • 更新在数组、提交一次:needsUpdate 是批量工位的发车铃,别逐颗动接口。

批量工艺已备。下一节请仿真工头进场:物理引擎与 Three.js 的对账合同怎么签,翻斗卸货的碰撞仿真怎么搭。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U