关于 vfx
vfx 是一项用于 3D 场景短时粒子特效设计的技能,说明何时选用程序化粒子或预制 flipbook。它介绍粒子池模式,以及如何为撞击、爆炸和拾取等事件匹配效果。
主要功能
- 01选择程序化粒子或 flipbook
- 02复用粒子池以避免事件时分配
- 03制作加法发光效果
- 04按特效寿命调整粒子行为
适用场景
- 01为撞击添加火花或尘土
- 02制作枪口闪光或爆炸碎片
- 03为拾取或治疗添加闪光
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ drawcall-ai/ skills --skill vfx安装后在 Claude Code 中输入/vfx确认已加载。
详细介绍
特效与粒子
重要时刻需要快速迸发一阵粒子,否则效果会显得平淡:开火时的枪口焰、子弹命中处的火花/尘土、脚步扬起的一小团尘、受击时迸出的血液或碎块、爆炸时的闪光与烟雾、拾取物品时的闪光。用一个静态网格体代替「闪光」会显得廉价;短促的粒子迸发则更像真实特效。
首先:使用预制特效,还是程序化迸发?
对于丰富且经过艺术设计的特效——翻涌的爆炸、火焰、魔法冲击——优先选择预渲染的精灵图集(翻页动画),而不是手工构建粒子:它的效果远胜代码驱动的四边形,而且运行时开销几乎为零。Market 提供了专为此用途制作的 flipbook 资源,flipbook 技能(@drawcall/flipbook)会将它们作为广告牌播放(Flipbook 类会加载 KTX2 图集,你需要在每帧调用 .update(delta))。如果存在合适的资源,或者该特效本质上是循环播放/单次播放的动画广告牌,就使用这种方式。
当特效必须具备参数化或数据驱动特性时,使用下面的程序化模式——例如沿表面法线飞溅的火花、带物理效果的碎片、每次事件中数量/方向各异的粒子、环境散射——这些场景都没有适用的单张精灵图集。两种方式可以组合:翻页动画爆炸加上程序化碎片迸发,效果会比单独使用任何一种都好。
模式:池化、加色、短生命周期
粒子特效由许多微小的四边形组成,它们同时生成,向外飞散,并在几分之一秒内淡出。只创建一次粒子池并重复使用——不要为每次事件分配内存(这会造成卡顿;每种特效的开销请参阅 lights 技能)。
- 池:预先创建固定数量的粒子(一个
Points点云,或一组Sprite)。事件发生时,在命中点激活 N 个粒子并赋予随机速度;粒子生命周期结束后将其停用并回收到池中。Points只需一次绘制调用,是相同广告牌迸发效果的默认选择;只有当粒子需要独立旋转或独立变换时,才使用Sprite。 - 加色发光:对于火焰、火花、魔法和能量,使用
blending: AdditiveBlending、depthWrite: false和柔和的圆形纹理,让重叠部分发光。(这也是无需添加真实光源即可模拟枪口焰或爆炸光效的方法。) - 生命周期:每个粒子的生命周期都很短(约 0.1–0.6s);在生命周期内,按其速度移动(碎片/血液效果可加上重力),使其淡出或缩小,然后释放它(每个粒子单独淡出/缩小需要使用
Sprite或自定义着色器——见下文)。
粒子池的核心目的是不为每次事件分配内存:只分配一次几何缓冲区,并且每帧只重写其中的内容。基于 Points 的迸发效果:
const MAX = 200
const PARKED_Y = -1e4 // park unused/dead particles here, off-screen below the scene
const positions = new Float32Array(MAX * 3)
for (let i = 0; i < MAX; i++) positions[i * 3 + 1] = PARKED_Y // park the whole pool until used — the buffer is zero-filled, so otherwise all MAX points render as a bright additive blob at the origin
const geometry = new THREE.BufferGeometry()
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3))
// soft round sprite, generated once — no asset needed
const c = document.createElement('canvas'); c.width = c.height = 64
const g = c.getContext('2d')!.createRadialGradient(32, 32, 0, 32, 32, 32)
g.addColorStop(0, 'rgba(255,255,255,1)'); g.addColorStop(1, 'rgba(255,255,255,0)')
const ctx2d = c.getContext('2d')!; ctx2d.fillStyle = g; ctx2d.fillRect(0, 0, 64, 64)
const sprite = new THREE.CanvasTexture(c)
const material = new THREE.PointsMaterial({
map: sprite, size: 0.3, transparent: true,
blending: THREE.AdditiveBlending, depthWrite: false,
})
const points = new THREE.Points(geometry, material)
points.frustumCulled = false // positions change every frame; the cached bounding sphere would cull the whole cloud wrongly
const vel = new Float32Array(MAX * 3)
const life = new Float32Array(MAX) // seconds remaining; 0 = free
function burst(at: THREE.Vector3, n: number) {
let spawned = 0
for (let i = 0; i < MAX && spawned < n; i++) {
if (life[i] > 0) continue
life[i] = 0.4
positions.set([at.x, at.y, at.z], i * 3)
vel.set([(Math.random() - 0.5) * 4, Math.random() * 4, (Math.random() - 0.5) * 4], i * 3)
spawned++
}
}
function update(delta: number) {
for (let i = 0; i < MAX; i++) {
if (life[i] <= 0) continue
life[i] -= delta
if (life[i] <= 0) { positions[i * 3 + 1] = PARKED_Y; continue } // died this frame — park it so it stops rendering
vel[i * 3 + 1] -= 9.8 * delta // accumulate gravity into velocity so debris accelerates and arcs — folding it straight into position (`(vel - 9.8*delta)*delta`) is a constant, frame-rate-dependent nudge, not a fall
positions[i * 3] += vel[i * 3] * delta
positions[i * 3 + 1] += vel[i * 3 + 1] * delta
positions[i * 3 + 2] += vel[i * 3 + 2] * delta
}
geometry.attributes.position.needsUpdate = true // the line agents forget — without it nothing moves
}
这个精简的 Points 版本会在粒子生命周期结束时将其停放在场景下方很远处,从而让粒子突然消失——对于大致水平的游戏摄像机来说,这个位置在屏幕之外;但俯视或倾斜角度较大的摄像机仍可能看到停放位置(带有 depthWrite: false 的加色点,仅靠移动并不能真正关闭显示)。使用共享的 PointsMaterial 无法让每个粒子平滑淡出或缩小,也无法实现不受视角影响的隐藏(将大小/不透明度设为 0);这需要使用带有逐顶点 alpha/大小属性的自定义着色器,或使用各自拥有 material.opacity 和 scale 的 Sprite。
让特效匹配事件
- 枪口焰:在枪管处放置一个持续几帧的短暂加色精灵。
- 命中效果:在命中点生成一小团沿表面法线飞散的火花/尘土——金属用火花,石头/地面用尘土,肉体用血液。
- 死亡/爆炸:生成更大的一团特效——明亮的闪光、向外飞散的碎片,随后留下缓慢上升并淡出的烟雾(深色、非加色)。
- 拾取/治疗:轻柔地向上飘散闪光。
保持粒子数量适中,并限制粒子池的大小,确保激烈的动作场面依然流畅。