mcpField
V
vfx

vfx

github.com/drawcall-ai创意与媒体128 次安装
代码仓库

关于 vfx

vfx 是一项用于 3D 场景短时粒子特效设计的技能,说明何时选用程序化粒子或预制 flipbook。它介绍粒子池模式,以及如何为撞击、爆炸和拾取等事件匹配效果。

主要功能

  1. 01选择程序化粒子或 flipbook
  2. 02复用粒子池以避免事件时分配
  3. 03制作加法发光效果
  4. 04按特效寿命调整粒子行为

适用场景

  1. 01为撞击添加火花或尘土
  2. 02制作枪口闪光或爆炸碎片
  3. 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。

让特效匹配事件

  • 枪口焰:在枪管处放置一个持续几帧的短暂加色精灵。
  • 命中效果:在命中点生成一小团沿表面法线飞散的火花/尘土——金属用火花,石头/地面用尘土,肉体用血液。
  • 死亡/爆炸:生成更大的一团特效——明亮的闪光、向外飞散的碎片,随后留下缓慢上升并淡出的烟雾(深色、非加色)。
  • 拾取/治疗:轻柔地向上飘散闪光。

保持粒子数量适中,并限制粒子池的大小,确保激烈的动作场面依然流畅。

评论

支持 Markdown