Command Palette

Search for a command to run...

Hectal
Course outline
Topic 4.6Beginner ~21 min · step 6 of 7

Editing Geometry

In one line

A geometry is not frozen. You can change vertices every frame (and must tell the GPU with needsUpdate), draw only part of a shape with setDrawRange, and merge many shapes into one so the GPU draws them in a single call.

0/7 · 0%

Think of it like this

A puppeteer moves the strings, and the audience sees the puppet move only because the stage lights are on it. Editing a vertex array is moving the strings. needsUpdate is telling the stage manager "the puppet moved, light it again". If you forget, the puppet moves backstage and the audience sees nothing change.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

needsUpdate
A flag on an attribute (or texture). Setting it to true makes the renderer upload the changed data to the GPU on the next draw.
version
A number on an attribute that goes up each time needsUpdate is set. The renderer compares it with the version it last uploaded.
draw range
The slice of a geometry that is drawn, set with setDrawRange(start, count).
mergeGeometries
A helper that joins several geometries with the same attributes into one geometry, so they are drawn in one call.

Step by step

01Move every vertex

wave(seconds) sets each vertex's height from two sine and cosine waves of its x and z. position.setY(i, ...) writes into the array. Nothing has reached the GPU yet.

02Tell the GPU

position.needsUpdate = true raises the version. computeVertexNormals() recomputes the arrows for lighting and flags them too. The console shows the version go from 1 to 4 after three edits.

03Draw part of a ring

The torus has 3,456 index entries (1,152 triangles). setDrawRange(0, count) with a multiple of three draws that many entries. Rendering once per setting and reading renderer.info.render.triangles gives 1,152, 576, 288 and 0.

04Merge twelve spokes

Each spoke is a thin cylinder moved so one end is at the origin and then fanned round with rotateZ. Twelve meshes make twelve draw calls. mergeGeometries of the same twelve geometries makes one geometry and one draw call. The two attribute mismatches at the end return null and log an error.

Try it yourself

  1. 1

    A hundred and twenty spokes

    In the merge example change both 12 in spokeGeometries (i < 12 and i / 12) to 120. The console says 120 draw calls, 2880 triangles (the label still says 12) and, after the merge, 1 draw call, 2880 triangles: the same triangles in 119 fewer draw calls.

  2. 2

    Start the draw range in the middle

    In the draw-range example change geometry.setDrawRange(0, count); inside show to geometry.setDrawRange(1500, count);. The 100% line now says 652 triangles drawn: the slice starts at entry 1500 and stops at the end of the 3,456 entries (1,956 entries). The 50% line still says 576.

Code & diagrams

A cloth that waves: edit, flag, recompute New tab

Every frame edits all 625 vertices. In the status bar the draw call count is 1. Set `SET_NEEDS_UPDATE` to false and run again.

idle

Console

Run the scene to see its console.

Expected console output

vertices: 625 | numbers uploaded each time: 1875
version before: 1
version after 3 edits: 4
Drawing only part of a ring New tab

The ring grows and shrinks by changing only the draw range. Watch the triangle count in the status bar go up and down while the geometry count stays 1.

idle

Console

Run the scene to see its console.

Expected console output

index entries: 3456 = triangles: 1152
100% -> 1152 triangles drawn
 50% -> 576 triangles drawn
 25% -> 288 triangles drawn
  0% -> 0 triangles drawn
count 10 -> 3.3333333333333335 triangles counted
Twelve draw calls into one, and merges that fail New tab

The wheel on the right is the merged geometry (spokes only, no rim). The two [error] lines are what mergeGeometries really logs.

idle

Console

Run the scene to see its console.

Expected console output

12 meshes:   12 draw calls, 288 triangles
1 merged:    1 draw call, 288 triangles
THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index 1. Make sure all geometries have the same number of attributes.
different attributes -> null
THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index 1. All geometries must have compatible attributes; make sure index attribute exists among all geometries, or in none of them.
indexed + not indexed -> null
merged geometry: 480 vertices

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Forget needsUpdate

In the cloth example set SET_NEEDS_UPDATE to false.

terminal
$ Run
── what you'll see ──
vertices: 625 | numbers uploaded each time: 1875
version before: 1
version after 3 edits: 1
No error. The version stays at 1, and the cloth stops following your edits on screen.

Myth vs fact

Myth

Changing position.array is enough to move a mesh's vertices on screen.

Fact

It changes only your copy. The GPU copy is replaced when you set needsUpdate = true (or never, if you forget).

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Each needsUpdate re-uploads the whole array by default. attribute.addUpdateRange(start, count) (in numbers, not vertices) limits it to a slice, and attribute.setUsage(THREE.DynamicDrawUsage) hints that the data changes often so the driver can place it in suitable memory. Do both for a large mesh that you edit every frame.

  • ▸

    Many identical objects (a hundred spokes, a thousand trees) are better as an InstancedMesh, which draws them in one call and still lets each instance move. Merging is right for different shapes that stay put; instancing is right for the same shape in many places (Phase 17).

Remember this

  1. 1

    The problem this solves: waving cloth, a stretching rubber band, a growing line, a ring that fills up like a progress bar, a hundred small parts that should be one: all need geometry that changes, or geometry that is joined. Knowing the three tools (needsUpdate, setDrawRange, mergeGeometries) covers almost every case.

  2. 2

    Two copies of every array. Your Float32Array lives in JavaScript memory. The GPU has its own copy in its own memory, made the first time the mesh is drawn. Changing a number in your array changes only your copy. Setting attribute.needsUpdate = true raises a version number; at the next draw the renderer sees the new version and uploads the whole array again.

  3. 3

    What it costs. Uploading is a copy from the CPU to the GPU. The cloth's 625 vertices are 1,875 numbers, which is tiny. A million-vertex mesh edited every frame would copy about 12 MB sixty times a second, which is far too much for a phone. For big arrays, edit only what changed (attribute.addUpdateRange(start, count) marks a part) or move the work to a shader (Phase 12).

  4. 4

    **setDrawRange(start, count).** It tells the GPU to draw only a slice of the geometry: with an index it counts index entries (three per triangle), without one it counts vertices. The ring in the second example draws 100%, 50%, 25% and 0% of its 1,152 triangles without changing a single vertex. It is a cheap way to grow a line, fill a progress ring or reveal a model.

  5. 5

    Merging. mergeGeometries([g1, g2, ...]) from three/addons/utils/BufferGeometryUtils.js joins geometries into one. Twelve spoke meshes are twelve draw calls; merged they are one, with the same 288 triangles. The cost is independence: the merged result moves, hides and is clicked as a single object. Bake each part's position into its geometry first (geometry.translate, rotateZ, applyMatrix4) because a merged geometry has no per-part transform.

Explain it without notes

01

Why must you set needsUpdate = true after editing a position array?

02

When would you use setDrawRange and when mergeGeometries?

Practice

01

Make the cloth's wave stop when the user prefers reduced motion. Which line changes?

02

Write a function that grows a polyline: it keeps a big Float32Array for 1,000 points and draws only the first n.

Done when you can

  • Edit a position array and make the change reach the screen

  • Explain what version and needsUpdate do

  • Draw part of a geometry with setDrawRange