Skip to main content
vfx-smoke is a skill from the Summer skill library: a guide your AI agent reads when the task matches, so it builds the good version on the first try. Smoke effect: a rising column or puff of soft particles with a noise and density falloff shader.

When your agent uses it

  • Authoring a smoke effect — smoke trail, puff, chimney smoke, steam, fog cloud, explosion smoke
  • A chimney, campfire, explosion, or vent needs a rising smoke column or puff in a 3D scene

Install

summer setup installs every skill for your agent. To add only this one:
Swap claude-code for your agent, such as codex or cursor. See Skills for every option.

The skill

Smoke is the same particle pattern as fire — billboard quads emitted from a small disc, drifting upward — except the shader is dim, soft, low-emission, and tinted by depth instead of glowing. Slower spawn rate, longer lifetime, larger end scale. Pair it with vfx-fire for any campfire/torch/explosion. Used everywhere: chimney smoke, smoldering wreckage, smoke trails behind rockets, steam from vents, fog clouds.

When to use

  • “Smoke rising from the campfire.”
  • “Chimney smoke on the cottage.”
  • “Smoke trail behind the rocket.”
  • “Steam from the vent.”
  • “Smoke from the smoldering wreckage.”
  • “Fog cloud at the cave entrance.”
  • After spawning vfx-fire, smoke almost always belongs above it.

When NOT to use

  • The user wants flames, not smoke — use vfx-fire.
  • The user wants a giant ground-hugging fog volume — use a FogVolume node with volumetric fog enabled in the WorldEnvironment, not a particle system. Particles don’t scale to room-size fog.
  • The user wants a 2D smoke effect — canvas_item shader, not this.
  • The user wants the smoke to physically interact with the player (pushed by movement) — particles are visual; use a FogVolume + script that follows the player.
  • The user wants thick black bonfire smoke that completely occludes — bump density way up, but consider a FogVolume for genuine visual occlusion.

Recipe

1. Files to create

smoke.gdshader #includes the FBM noise file. A missing include is a hard compile error, so copy _building-blocks/noise-3d-fbm.gdshaderinc from this skill into res://addons/vfx/_building-blocks/ before writing the shader.

2. Shader code

addons/vfx/smoke/smoke.gdshader:
Note: for soft-particle depth fade against geometry behind the puff, the proper way is sampling DEPTH_TEXTURE; for clarity we keep the shader straightforward and rely on depth_draw_never + alpha for soft-look. If you need true soft-particle blending, add a DEPTH_TEXTURE sample in fragment — see Edge Cases.

3. GDScript controller

addons/vfx/smoke/smoke_controller.gd:

4. Node tree

process_material and draw_pass_1 are resource properties on the one node, not child nodes, and ParticleProcessMaterial is a Material — you cannot summer_add_node it.

5. Wire it in (MCP calls)

Write the three files, then wire the emitter with ONE summer_run_script call with an explicit scenePath — transactional, and it builds the QuadMesh + ShaderMaterial resources directly instead of .tres sidecars. For chimney smoke, parent the Smoke just above the heat source:
For pairing with a fire, run the same script with ctx.find("Campfire") as the parent, position = Vector3(0, 1.5, 0) (above the flame top) and rise_height 3.0. Verify with summer_snapshot_diff (exactly one node added) and a summer_screenshot.

5a. Verify

The shader and the controller are compiled code. Check them before tuning anything.
For a rocket trail (smoke follows the rocket):

6. Parameters to tune

Cookbook — named variants

chimney-smoke (default)

Light gray, gentle, drifts on a slight wind.

black-fire-smoke

Burning oil / tires. Pair with vfx-fire set to bonfire.

steam-vent

White, fast-rising, short-lived.

rocket-trail

Long-lived, narrow spawn, follows the rocket.

fog-bank

Slow, wide, ground-hugging. (For very large fog, use FogVolume instead.)

Anti-patterns

  • Using blend_add like fire. Smoke is blend_mix (alpha-blended). Additive smoke looks like glowing dust.
  • Forgetting cull_disabled. Billboarded quads disappear at certain camera angles otherwise.
  • puff_lifetime < 1.0. Smoke needs time to spread out and dilute. Short-lifetime smoke looks like spray.
  • No scale curve. Smoke that doesn’t grow looks like a row of identical balls. The controller’s _make_scale_curve ramps from 0.4× → 1.0× → end_mult.
  • A scale curve built without raising Curve.max_value. Curve defaults to max_value = 1.0 and silently clamps, so every end_scale_multiplier above 1.0 is inert and the smoke never grows. Set c.max_value before adding the points.
  • Reading CUSTOM in fragment(). In a spatial shader CUSTOM does not exist at all (“SHADER ERROR: Unknown identifier in expression: ‘CUSTOM’”). The particle built-in is INSTANCE_CUSTOM, it is vertex-stage only, and INSTANCE_CUSTOM.x is the rotation angle — the age is in .y.
  • emission_energy > 0 on the shader’s quad material. Smoke isn’t bright. Keep unshaded and let ALBEDO carry the look. (The shader as written has no EMISSION line — correct.)
  • Smoke local-space when paired with a moving emitter (rocket). Set local_coords = false so puffs stay where they were emitted.
  • Skipping the angular velocity. Static-rotation smoke looks weird. The controller adds ±25 deg/s for organic tumbling — but that only reaches the screen if the vertex billboard applies the INSTANCE_CUSTOM.x rotation, which is why section 2 uses the full block rather than the bare VIEW_MATRIX * mat4(INV_VIEW_MATRIX[0..2], MODEL_MATRIX[3]) form.

Performance notes

  • Default 48 particles per smoke source is sub-millisecond on desktop. That is an order-of-magnitude expectation, not a measurement — profile your own scene.
  • Heavy density at high count (128 particles, density 0.85) gets fillrate-bound — overdraw on a screen full of smoke. Budget ~256 smoke particles total visible at once.
  • Mobile: drop default to 24, drop puff_lifetime to 2.5, drop density to 0.35.
  • LOD: scale amount_ratio by clamp(1.5 - dist_to_camera / 40.0, 0.10, 1.0) for distant chimneys.
  • The depth-fade soft-particle technique (sampling DEPTH_TEXTURE in fragment) costs an extra texture read per pixel — only enable for foreground smoke close to walls.

Edge cases

  • Smoke clipping into walls/ceilings. The basic shader doesn’t depth-fade. Add this to the fragment for soft particles:
  • Wind-driven smoke on a ship at sea. Drive wind_direction from a global wind script polled in _process.
  • Smoke from an explosion (one-shot huge puff). Use puff() API, set one_shot = true, increase amount to 256 for the burst, then free the node after puff_lifetime.
  • Smoke at very low light (night scene). With unshaded, smoke is the same brightness day or night — looks bright at night. Either change to shading_mode lit and accept lighting cost, or tween density down with sun angle.
  • Smoke for an underwater plume (silt). Drop wind_strength to 0, gravity to slight upward, color tan: Color(0.55, 0.50, 0.40).

Fallback (no MCP)

Section 5 is fully automatable — summer_write_file writes the shader and the controller, summer_create_scene + summer_add_node + summer_set_prop + summer_save_scene build and save smoke.tscn. Do not hand these steps to the user when the MCP tools are available. Without the MCP connection there is no engine to drive, so the user does it manually in Summer Engine:
  1. Create addons/vfx/_building-blocks/ and addons/vfx/smoke/ with the files above.
  2. Add a GPUParticles3D, attach smoke_controller.gd.
  3. Set process_material to a new ParticleProcessMaterial (script configures it).
  4. Set draw_pass_1 to a new QuadMesh with its material = ShaderMaterial(smoke.gdshader).
  5. Save as smoke.tscn; instantiate at every smoke source.
  6. Check the Errors dock — a missing .gdshaderinc fails the whole shader.

Handoff

After firing this recipe, suggest:
  • vfx-fire — the partner; almost any fire wants smoke 0.5–2 m above the flame top.
  • vfx-dissolve — pair black-fire-smoke with a dissolving target for “the bandit camp burns and ashes drift away.”
  • vfx-lightning — for a smoke flash from a lightning strike (use puff() on impact).
  • sound-effect — generate gentle wind through smoke, soft hiss, 4s loop for chimneys.

See also

  • _building-blocks/noise-3d-fbm.gdshaderinc — the FBM noise this shader includes
  • vfx-fire — sister recipe, almost always paired
  • vfx-dissolve — for ash particles after disintegration
  • gdscript-patterns — for the controller idioms (signals, exports, @tool)

From a shell

No Summer MCP tools in this session? Run each one as a command; when summer --version is missing or below 4.0, use npx -y summer-engine@latest in place of summer. Fields are flags in kebab-case (--scene-path res://main.tscn) or one --input '<json>', and summer ops describe <op> prints the exact input. The summer-cli skill has the rest.

Files in this skill

  • scene-scripting: Build scenes, levels, HUDs and wiring with one editor GDScript (summer_run_script) instead of call chains.
  • sound-effect: Make sound effects (swings, hits, whooshes, reloads, UI): three versions to pick from, then wire them.
  • vfx-dissolve: Dissolve effect: a mesh burns away with a glowing edge, from a noise-threshold ShaderMaterial.
  • vfx-fire: Fire effect: animated flames from GPUParticles3D, a particle shader and a noise color ramp.
  • vfx-lightning: Lightning bolt effect: a jagged procedural path with a glow shader, end sparks and screen shake.

Source: library/skills/vfx-smoke/SKILL.md, rendered from summer-engine@4.3.0 on npm.