Course outline
Particles and Instanced Effects
In one line
Sparks, dust and light beams are many small soft shapes. Draw them as one Points object or one InstancedMesh (one draw call), blend them additively, and stop them from writing depth, or they hide each other.
Think of it like this
A sparkler. Hundreds of tiny glowing specks, each almost see-through, and where two overlap the light adds up. Nobody draws each speck with a pencil: you stamp the same speck hundreds of times. Particles are that stamp.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- particle
- One small stamp of a repeated effect: a spark, a speck of dust.
- Points
- A three.js object that draws one screen-facing square per vertex.
- InstancedMesh
- One mesh drawn many times in one call, each copy with its own matrix and optionally colour.
- additive blending
- Adding the new colour to the one already on the screen, so overlaps brighten.
- depthWrite
- Whether drawing a surface records its distance, so later surfaces behind it are rejected.
- overdraw
- Drawing the same pixel many times in one frame, from overlapping shapes.
Step by step
01Make a soft spot
A 64 × 64 canvas gets a radial gradient: opaque white in the centre, fully transparent at the edge. CanvasTexture turns it into the particle's alpha picture.
02Two particles, one draw call
A single Points object holds a near blue particle (index 0) and a far orange one (index 1). The near one is drawn first. Each is 90 pixels across, and they overlap.
03Change only the depth writing and the blending
Normal blending with depthWrite: true: the orange spark is hidden under the blue square. depthWrite: false: the orange spark shows, drawn on top of the blue because it comes later in the list, even though it is farther away. AdditiveBlending: both lights add, the order stops mattering, the overlap is almost white.
04Count the draw calls
The second example renders the same 200 sparks three ways. The status bar and renderer.info.render show 200, 1 and 1 calls. To move an instanced spark, call setMatrixAt and set instanceMatrix.needsUpdate = true.
Try it yourself
- 1
Move the particles apart
In the first example change
size: 90tosize: 40. The squares no longer overlap at the sampled pixel, and all three lines print235, 160, 105: the depth problem only appears where particles overlap. - 2
A thousand more sparks
In the second example change
length: 200tolength: 2000. The first line printsdraw calls: 2000 | triangles: 4000 | points: 0. ThePointsline printsdraw calls: 1 | triangles: 0 | points: 2000, and theInstancedMeshlinedraw calls: 1 | triangles: 4000 | points: 0.
Code & diagrams
Pixel values are rounded to steps of 5. The sampled pixel is under the middle of the far (orange) particle, inside the near (blue) particle's square.
Console
Run the scene to see its console.Expected console output
normal blending, depthWrite true pixel under the far particle: 55, 105, 125
normal blending, depthWrite false pixel under the far particle: 245, 170, 115
additive blending, depthWrite false pixel under the far particle: 255, 255, 235Positions come from a seeded generator, so the numbers never change. Triangles for the instanced version: 200 quads × 2.
Console
Run the scene to see its console.Expected console output
200 separate meshes draw calls: 200 | triangles: 400 | points: 0
one Points object draw calls: 1 | triangles: 0 | points: 200
one InstancedMesh draw calls: 1 | triangles: 400 | points: 0Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Particles with depthWrite on
In the first example read the first line.
Break #2
One mesh per spark
In the second example read the first line.
Myth vs fact
Myth
Transparent objects never write depth.
Fact
They do by default. transparent: true only changes the draw order and blending; depthWrite is separate.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Transparent objects are sorted far to near by their object centre, once per frame. Inside one
PointsorInstancedMeshthree.js does not sort anything. That is why additive blending (order does not matter) is the safe choice for dense effects, and why smoke with normal blending needs sorting on the CPU or a few big layers. - ▸
A point's size is limited by the GPU and driver:
gl.getParameter(gl.ALIASED_POINT_SIZE_RANGE)gives the range for the machine you are on, and a request above the maximum is drawn at the maximum. The first example asks for 90-pixel points, so on a GPU whose limit is lower the spots come out smaller. For big soft spots useInstancedMeshquads that face the camera. - ▸
Moving, flickering sparks are motion. Respect the showroom's pause button and the reduced-motion setting (Phase 9): freeze the particles when the visitor asks for less movement. Positions changed each frame are re-sent to the GPU (
needsUpdate = true): thousands of animated particles cost bandwidth even in one draw call.
Remember this
- 1
The problem this solves: a hundred sparks drawn as a hundred meshes are a hundred draw calls, and the CPU, not the GPU, becomes the limit (Phase 17). Particles must be one object, and they must blend softly instead of showing hard square edges that cut each other.
- 2
Three ways to draw many small things.
Points: one vertex per particle, drawn as a screen-facing square whose size is set byPointsMaterial.size.InstancedMesh: one small mesh drawn N times, each with its own matrix and colour, so each particle can have its own size and turn.Sprite: one object per particle, simple but one draw call each. The second example draws 200 sparks as 200 meshes (200 draw calls), onePoints(1) and oneInstancedMesh(1). With 2,000 sparks the meshes cost 2,000 calls, the other two still 1. - 3
Soft spots need an alpha picture. A point is a square. To make it a round glow, give
PointsMaterialamapthat is white in the middle and fades to transparent at the edge, and settransparent: true. The first example draws it with a canvas gradient: no image file. - 4
Additive blending adds light. With
AdditiveBlendingeach particle's colour is added to what is already there, so overlaps get brighter and the result does not depend on the drawing order. It suits fire, sparks, glow and light beams. It cannot make anything darker, so smoke needs normal blending. - 5
Depth writing is the usual trap. A transparent particle still writes its depth by default. Particles in one
Pointsare drawn in index order, not far to near. So a near particle drawn first writes depth over its whole square, and a far particle drawn later is rejected wherever it overlaps that square, even where the near one is almost transparent. In the first example the far orange spark vanishes under the near blue one: the pixel reads55, 105, 125, a blue-grey. WithdepthWrite: falseit reads245, 170, 115; with additive blending,255, 255, 235.
Explain it without notes
Why can a near particle hide a far one even though the near one is almost transparent?
When do you pick Points, InstancedMesh and Sprite?
Practice
Make 500 seeded dust specks as one Points object that hide nothing.
Say why additive blending is a good fit for the headlight beam but not for fog.
Done when you can
Draw hundreds of particles in one draw call
Make a soft round spot from a gradient texture
Choose between normal and additive blending
Turn off depth writing so transparent particles do not hide each other