Course outline
Debug Tools
In one line
Sliders, axes, grids and counters let you see a scene instead of guessing. You use lil-gui to tweak values live, axes and grid helpers to read directions, renderer.info to count the GPU's work, and meet the three.js editor, DevTools and Spector.js.
Think of it like this
A mechanic does not guess why an engine rattles. They put a gauge on it, listen with a stethoscope and turn one screw at a time. Debug tools are your gauges: they let you change one thing and watch what it does.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- lil-gui
- A tiny library that shows sliders, checkboxes and colour pickers bound to your objects' properties.
- helper
- An object that draws something you cannot normally see: axes, a grid, a light's direction, a bounding box.
- renderer.info
- Counters kept by the renderer: draw calls, triangles, geometries, textures and shader programs.
- AxesHelper
- Three coloured lines: red along x, green along y, blue along z, from the origin.
Step by step
01Draw the axes and a grid
scene.add(new THREE.AxesHelper(1.5), new THREE.GridHelper(4, 8)). Now you can see where y = 0 is, and which way x and z go. Red is x, green is y, blue is z (RGB = XYZ).
02Add sliders for the values you are unsure about
const gui = new GUI(); gui.add(cube.position, "x", -1.5, 1.5, 0.01); makes a slider that writes straight into cube.position.x. A checkbox is made from a boolean property: gui.add(cube, "visible").
03Add a colour
Keep the colour in a plain object (settings.color), make the picker with addColor, and in onChange call cube.material.color.set(value).
04Read the receipt after a render
renderer.render(scene, camera), then renderer.info.render.calls. Read it after, not before: the counters describe the last render.
Try it yourself
- 1
Add a slider that turns the cube
In the first example add
gui.add(cube.rotation, "y", -3.14, 3.14, 0.01);before the loop. The console says 6 controls, but the slider seems to do nothing: the loop writescube.rotation.yevery frame (two writers for one value). Delete the loop'scube.rotation.y = ...line and the slider turns the cube. - 2
Share one geometry
In the second example change the second loop to create meshes with
shapeinstead of a newBoxGeometry. Now the line+5 meshes with their own shapeshowscalls 10andgeometries 1: draw calls still grow with meshes, but the GPU holds one shape.
Code & diagrams
Use the panel: drag x, change the height, turn spin to 0 and back. Both helpers are objects in the scene, so they are in the draw call count.
Console
Run the scene to see its console.Expected console output
controls in the panel: 5
helpers are objects too: scene holds 5 children (2 lights, 2 helpers, 1 cube)The same scene grows in steps and prints `renderer.info` after each. The last two lines are the "value that does nothing" and the autoReset trap.
Console
Run the scene to see its console.Expected console output
empty scene | calls 0 | triangles 0 | geometries 0 | programs 0
5 meshes sharing a shape | calls 5 | triangles 60 | geometries 1 | programs 1
+5 meshes with their own shape | calls 10 | triangles 120 | geometries 6 | programs 1
+1 sphere with another material | calls 11 | triangles 1080 | geometries 7 | programs 2
parameters.width = 3, real width: 0.5
calls after two renders: 11 (it resets on every render())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
Bind a slider to a copy
In the first example replace gui.add(cube.position, "x", -1.5, 1.5, 0.01); with const p = cube.position.clone(); gui.add(p, "x", -1.5, 1.5, 0.01);.
Myth vs fact
Myth
renderer.info.render.calls is the number of objects in the scene.
Fact
It counts draw calls for the last render. Hidden and culled objects add none; a mesh with a material array adds one per group; helpers add theirs.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Every lil-gui control has
onChange,onFinishChangeandlisten(), which polls the property each frame so the panel follows code that changes it. Uselisten()only while debugging. - ▸
info.memory.geometriesand.texturesare the numbers to watch for leaks: if they climb every time you open and close a viewer, you are not callingdispose()(Phase 17).
Remember this
- 1
The problem this solves: most 3D bugs are not errors, they are wrong pictures. Reading code does not find them. You need to see numbers, directions and costs, and change them while the page runs.
- 2
lil-gui (
import GUI from "lil-gui") is a panel of sliders, checkboxes and colour pickers.gui.add(object, "property", min, max, step)binds a slider to a property;gui.addColor(settings, "color")makes a picker (copy it into a three.jsColorinonChange). Bind to the live object (cube.position, not a copy) or the slider moves a number nobody reads. - 3
Helpers draw what you cannot see.
AxesHelper(size)draws red x, green y and blue z;GridHelper(size, divisions)draws the floor grid (y = 0). They are ordinary objects, so they are in the tree and cost draw calls (the example's scene holds two). Others show a light (DirectionalLightHelper), a camera frustum (CameraHelper) and a box (BoxHelper). Remove them before shipping. - 4
**
renderer.info** is the GPU's receipt:info.renderhascalls,triangles,points,lines,framefor the last render;info.memoryhasgeometriesandtexturesheld on the GPU;info.programslists compiled shader programs. In the second example five meshes sharing a shape give 5 calls and 1 geometry; five with their own shapes give 10 calls and 6 geometries; a new material compiles a second program. - 5
Two traps.
info.renderresets on everyrender()(info.autoReset), so with two renders per frame (Phase 15) you see only the last; setautoReset = falseand callinfo.reset()once per frame to total them. Andinfo.memorycounts what is uploaded, not what JavaScript still holds. - 6
A value that "does nothing" usually means you changed a copy or a record.
BoxGeometry.parametersonly records the constructor's arguments: changingparameters.widthdoes not change the shape (the example prints the real width, 0.5). Changescaleor build a new geometry. Other causes: a value read only once (needsUpdate, Phase 5), or an object not in the scene. - 7
Outside the code. The three.js editor (threejs.org/editor) inspects and tweaks a model. DevTools show the canvas size, the console and the Performance frame timeline. Spector.js records every WebGL call of one frame: use it when
renderer.infosays what and you need why. Phase 17 returns to them.
Explain it without notes
Your slider moves but the object does not. Give two possible causes.
What do info.render.calls and info.memory.geometries tell you, and why can the first be larger than the second?
Practice
Add a Box3Helper or BoxHelper around the cube in the first example. How does it change the child count?
Write a loop line that shows the draw call count in the page title every frame.
Done when you can
Add a lil-gui panel bound to live objects
Use axes and grid helpers to read directions and the floor
Read
renderer.infoand explain calls, triangles, geometries and programsName what the three.js editor, DevTools and Spector.js are for