three.quarks 移动端触摸粒子教程:3 个配方 + 60fps 参数表
three.quarks 移动端触摸粒子教程3 个配方 60fps 参数表【免费下载链接】three.quarksThree.quarks is a general purpose particle system / VFX engine for three.js项目地址: https://gitcode.com/GitHub_Trending/th/three.quarks在手机上用 Three.js 做触摸粒子特效绕不开两个问题手指一动就要出效果以及低端机不能被拖垮。这篇教程用 three.quarks 把这两件事拆开处理——先跑通点按爆发再给滑动轨迹、多点触控各一套参数配方最后附机型分档表和排查速查表目标是让中端机上的触摸特效稳定在 60fps。10 分钟跑通点一下屏幕的粒子爆发最小闭环只需三步一个BatchedRenderer、一个挂在场景里的emitter、一次restart()。触摸坐标换算成场景内坐标后移动发射器位置再重启系统粒子就从指尖炸出来。import { Scene, PerspectiveCamera, WebGLRenderer, TextureLoader } from three; import { ParticleSystem, PointEmitter, ConstantValue, IntervalValue, BatchedParticleRenderer, } from three.quarks; const scene new Scene(); const camera new PerspectiveCamera(60, innerWidth / innerHeight, 0.1, 100); const renderer new WebGLRenderer({ antialias: false }); // 移动端关抗锯齿 renderer.setPixelRatio(Math.min(devicePixelRatio, 2)); camera.position.z 8; // 全局唯一所有粒子系统都挂到它下面按渲染管线自动合批 const batchRenderer new BatchedParticleRenderer(); scene.add(batchRenderer); // 纹理 128~256px 足够省显存 const texture new TextureLoader().load(textures/texture1.png); const ps new ParticleSystem({ duration: 1, looping: false, autoDestroy: false, // 复用系统不销毁 startLife: new IntervalValue(0.3, 0.5), startSpeed: new IntervalValue(1, 3), startSize: new ConstantValue(0.3), worldSpace: true, shape: new PointEmitter(), emissionOverTime: new ConstantValue(0), // 只靠 burst 出粒子 emissionBursts: [{ time: 0, count: new ConstantValue(24), cycle: 1, interval: 0.01, probability: 1 }], material: { map: texture, transparent: true }, }); batchRenderer.addSystem(ps); scene.add(ps.emitter); renderer.domElement.addEventListener(pointerdown, (e) { const x (e.clientX / innerWidth) * 2 - 1; const y -(e.clientY / innerHeight) * 2 1; const k -8 / Math.tan((camera.fov * Math.PI) / 360); // 换算系数 ps.emitter.position.set(x * k * 0.5, y * k * 0.5, 0); ps.restart(); // 每次触摸都重放 burst }); let last performance.now(); (function tick(now) { const delta Math.min((now - last) / 1000, 0.1); last now; batchRenderer.update(delta); // 每帧推进所有系统 renderer.render(scene, camera); requestAnimationFrame(tick); })(last);跑起来后点按屏幕会有 24 个粒子向四周散开。worldSpace: true让粒子留在世界坐标里不跟着发射器移动爆发感才成立。先搞懂三个核心件three.quarks 里所有配置最终都落在三件事上粒子从哪来、参数取什么值、怎么画到屏幕上。核心件解决什么问题典型用法Emitter 形状PointEmitter、CircleEmitter…决定粒子的出生位置和初速度方向shape: new PointEmitter()Value 包装ConstantValue/IntervalValue决定每个粒子参数的固定值或随机区间如寿命、速度、大小startLife: new IntervalValue(0.3, 0.5)BatchedRenderer把同管线的系统合并成少数几次 draw call并统一驱动更新batchRenderer.addSystem(ps)发射器只负责出生出生之后的一切交给 behaviors。Value 包装是理解配置的关键凡是startLife、startSpeed这类参数都不直接写数字而是包一层 Generator——ConstantValue(2)表示固定 2IntervalValue(1, 3)表示每个粒子在 1 到 3 之间随机一次且整个生命周期内保持同一随机值。这层设计让一批粒子各不相同但各自稳定成为默认行为。效果配方从点到轨迹再到多点点按爆发加形状和衰减目标让指尖的粒子更像火花而不是光点。关键参数shape换成ConeEmitter给初速度一个锥形方向爆发更有冲击感SpeedOverLife把速度从 2.5 倍降到 0.2 倍粒子先冲后坠SizeOverLife配合PiecewiseBezier控制缩小曲线最后 20% 寿命内快速消失burst 数量控制在 24~40手机上单点按超过 60 个粒子肉眼无差异纯浪费// 替换点按爆发里的三处配置 shape: new ConeEmitter({ radius: 0.1, angle: Math.PI / 6, thickness: 1, arc: Math.PI * 2 }), startSpeed: new IntervalValue(2, 4), ps.addBehavior(new SpeedOverLife(new PiecewiseBezier([ [new Bezier(2.5, 0.5, 0.2, 0.1), 0]]))); ps.addBehavior(new SizeOverLife(new PiecewiseBezier([ [new Bezier(1, 0.8, 0.3, 0), 0]])));滑动轨迹用距离发射目标手指划过屏幕时留下一条逐渐消散的粒子带。关键参数轨迹效果的核心是emissionOverDistance——按发射器移动距离出粒子而不是按时间。手指停住就不再产生粒子轨迹自然断开startLife压到 0.4s 左右粒子要短命轨迹长度才跟得上手指速度系统设looping: true常驻运行触摸时只更新emitter.position不需要重启想更顺滑可以切RenderMode.Trail渲染但 billboard 点粒子在手机上开销更小优先用默认模式const trail new ParticleSystem({ duration: 5, looping: true, // 常驻不随 duration 结束 startLife: new IntervalValue(0.25, 0.4), startSpeed: new ConstantValue(0.2), // 近静止让轨迹由手指带动 startSize: new IntervalValue(0.1, 0.2), startColor: new ColorRange(new Vector4(0.0, 0.6, 1.0, 1), new Vector4(0.6, 0.5, 1.0, 1)), worldSpace: true, shape: new PointEmitter(), emissionOverTime: new ConstantValue(0), emissionOverDistance: new ConstantValue(12), // 每单位路程出 12 个粒子 material: { map: texture, transparent: true }, }); batchRenderer.addSystem(trail); scene.add(trail.emitter); // 触摸移动时只改位置轨迹自动生成 renderer.domElement.addEventListener(pointermove, (e) { if (e.buttons 0 e.pointerType mouse) return; const x (e.clientX / innerWidth) * 2 - 1; const y -(e.clientY / innerHeight) * 2 1; const k -8 / Math.tan((camera.fov * Math.PI) / 360); trail.emitter.position.x x * k * 0.5; trail.emitter.position.y y * k * 0.5; });多点触控系统池而不是临时 new目标两根手指同时划各有一条独立轨迹互不干扰。关键参数不要每次触摸都new ParticleSystem——材质、几何体都会重建。预创建 4~5 个系统做池子按下时领一个抬起时归位用touch.identifier做键管理占用touchend时立刻回收上限 5 个并发系统再多对手机内存不友好且人手实际不会同时按 6 个点const pool []; // 空闲系统 const active new Map(); // identifier - 系统 for (let i 0; i 4; i) { // 池子大小即并发上限 const t makeTrailSystem(texture); // 上面的 trail 配置封装成函数 batchRenderer.addSystem(t); scene.add(t.emitter); pool.push(t); } function onDown(e) { const t pool.pop(); if (!t) return; // 池子空了直接放弃不扩容 moveEmitter(t, e); t.restart(); active.set(e.pointerId, t); } function onMove(e) { const t active.get(e.pointerId); if (t) moveEmitter(t, e); } function onUp(e) { const t active.get(e.pointerId); if (t) { t.stop(); active.delete(e.pointerId); pool.push(t); } } // pointerdown / pointermove / pointerup 分别绑上面三个函数机型分档与性能参数表参数没有银弹按机器分三档起步再根据实测微调。档位单系统粒子峰值发射率上限纹理策略高近 3 年旗舰80060/s256px可开软粒子中主流机型30025/s128px关闭softParticles低4GB 内存左右12010/s64px同时活跃系统 ≤ 3阈值建议连续 30 帧里平均帧耗超过 22ms就降到下一档只改数值不动结构function applyTier(tier, systems) { const t { high: [24, 60], mid: [10, 25], low: [4, 10] }[tier]; // [burst, 发射率] for (const ps of systems) { ps.emissionBursts[0].count new ConstantValue(t[0]); ps.emissionOverTime new ConstantValue(t[1]); ps.startLife new IntervalValue(0.25, 0.4); // 短命低驻留 } } // 帧耗滑动窗口每帧记录 performance.now() 差值保留最近 30 帧 if (avg(last30) 22) applyTier(low, allSystems);它为什么快draw call 是移动端粒子的头号杀手。three.quarks 的思路一个场景只放一个BatchedRenderer调用addSystem时按渲染管线签名材质、混合模式、渲染模式、tile 数、renderOrder 等比对现有批次签名相同的系统直接并入同一个实例化批次签名不同的才新建批次。100 个同管线的粒子系统最终可能只有 1~2 次 draw call。粒子死亡后其内存槽位被后续粒子复用不触发新的内存分配。这套机制在 packages/three.quarks/src/BatchedRenderer.ts 里可以看到完整的分组与合并逻辑。自定义粒子行为内置 behaviors 覆盖不了手感时写一个自己的。Behavior 就四个方法initialize、update、frameUpdate、toJSON外加clone/reset。下面这个把粒子拉向手指位置适合做指尖吸附的手感class PullToTouch { type PullToTouch; target new Vector3(); strength 4; tmp new Vector3(); initialize() {} update(particle, delta) { // 每帧对每个粒子调用 this.tmp.copy(this.target).sub(particle.position); particle.velocity.addScaledVector(this.tmp, this.strength * delta); } frameUpdate() {} // 需要按帧推进的状态写这里 toJSON() { return { type: this.type, strength: this.strength }; } clone() { return Object.assign(Object.create(PullToTouch.prototype), this); } reset() {} } // 用法 const pull new PullToTouch(); ps.addBehavior(pull); // 触摸 move 时pull.target.copy(当前场景坐标)update里只写纯数学、不分配对象tmp复用临时向量是保持帧率的关键习惯。排查速查表现象可能原因处理方式点按后响应慢半拍忘了restart()或系统还挂在旧位置每次触摸移动emitter.position后调用restart()帧率掉到 30 以下单系统粒子超 500或多系统不同材质导致批次增多按分档表降 burst 与发射率统一material与blending轨迹断断续续emissionOverDistance偏低粒子寿命太短距离发射率提到 12~20startLife下限提到 0.3同一效果有的设备发灰有的发亮纹理色彩空间与blending配置不一致纹理统一sRGBAdditiveBlending场景关闭色调映射影响下一步想直接看效果进 packages/quarks.examples/ 目录里面的每个 demo 文件爆发、轨迹、软粒子、自定义插件都可以本地跑起来对照参数Value 包装的完整列表在 packages/quarks.core/src/functions/发射器形状在 packages/quarks.core/src/shape/。把上面三个配方拼在一起——点按爆发、滑动轨迹、多点池化——就是一个可上生产的移动端触摸特效模块。剩下的事只有一件拿一台真实的中端机把分档阈值跑一遍。【免费下载链接】three.quarksThree.quarks is a general purpose particle system / VFX engine for three.js项目地址: https://gitcode.com/GitHub_Trending/th/three.quarks创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考