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:
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 withvfx-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
FogVolumenode with volumetric fog enabled in theWorldEnvironment, not a particle system. Particles don’t scale to room-size fog. - The user wants a 2D smoke effect —
canvas_itemshader, 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
FogVolumefor 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:
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 ONEsummer_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:
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.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 withvfx-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, useFogVolume instead.)
Anti-patterns
- Using
blend_addlike fire. Smoke isblend_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_curveramps from 0.4× → 1.0× → end_mult. - A scale curve built without raising
Curve.max_value.Curvedefaults tomax_value = 1.0and silently clamps, so everyend_scale_multiplierabove 1.0 is inert and the smoke never grows. Setc.max_valuebefore adding the points. - Reading
CUSTOMinfragment(). In aspatialshaderCUSTOMdoes not exist at all (“SHADER ERROR: Unknown identifier in expression: ‘CUSTOM’”). The particle built-in isINSTANCE_CUSTOM, it is vertex-stage only, andINSTANCE_CUSTOM.xis the rotation angle — the age is in.y. emission_energy > 0on the shader’s quad material. Smoke isn’t bright. Keepunshadedand letALBEDOcarry the look. (The shader as written has no EMISSION line — correct.)- Smoke local-space when paired with a moving emitter (rocket). Set
local_coords = falseso 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.xrotation, which is why section 2 uses the full block rather than the bareVIEW_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
densityat 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_lifetimeto 2.5, dropdensityto 0.35. - LOD: scale
amount_ratiobyclamp(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_directionfrom a global wind script polled in_process. - Smoke from an explosion (one-shot huge puff). Use
puff()API, setone_shot = true, increaseamountto 256 for the burst, then free the node afterpuff_lifetime. - Smoke at very low light (night scene). With
unshaded, smoke is the same brightness day or night — looks bright at night. Either change toshading_mode litand accept lighting cost, or tweendensitydown with sun angle. - Smoke for an underwater plume (silt). Drop
wind_strengthto 0,gravityto 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:
- Create
addons/vfx/_building-blocks/andaddons/vfx/smoke/with the files above. - Add a
GPUParticles3D, attachsmoke_controller.gd. - Set
process_materialto a newParticleProcessMaterial(script configures it). - Set
draw_pass_1to a newQuadMeshwith itsmaterial=ShaderMaterial(smoke.gdshader). - Save as
smoke.tscn; instantiate at every smoke source. - Check the Errors dock — a missing
.gdshaderincfails 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— pairblack-fire-smokewith a dissolving target for “the bandit camp burns and ashes drift away.”vfx-lightning— for a smoke flash from a lightning strike (usepuff()on impact).sound-effect— generategentle wind through smoke, soft hiss, 4s loopfor chimneys.
See also
_building-blocks/noise-3d-fbm.gdshaderinc— the FBM noise this shader includesvfx-fire— sister recipe, almost always pairedvfx-dissolve— for ash particles after disintegrationgdscript-patterns— for the controller idioms (signals, exports,@tool)
From a shell
No Summer MCP tools in this session? Run each one as a command; whensummer --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
Related skills
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.
