Three.js - Debug and Stats
Three.js - Debugging and Performance Monitoring
Section titled “Three.js - Debugging and Performance Monitoring”When building 3D applications, being able to tweak values in real-time and monitor performance is crucial. It drastically speeds up development and helps identify bottlenecks. In this chapter, we’ll explore two essential tools for this: a graphical user interface (GUI) for live code manipulation and a statistics panel for performance monitoring.
Modern GUI for Tweaking: lil-gui
Section titled “Modern GUI for Tweaking: lil-gui”Experimenting with variables like an object’s position, color, or scale by constantly changing code and refreshing the browser is a slow and tedious process. A debug UI panel solves this. The classic dat.GUI has been succeeded by the more modern and lightweight lil-gui, which we’ll use. It integrates seamlessly with Three.js and allows you to create a simple UI to change variables in your code live.
Installation
Section titled “Installation”In a modern JavaScript project, we manage dependencies using a package manager like npm or yarn. This is the recommended approach.
# Using npmnpm install three lil-gui
# Using yarnyarn add three lil-guiAfter installation, you can import it into your JavaScript module.
import * as THREE from 'three';import GUI from 'lil-gui';Basic Usage
Section titled “Basic Usage”First, initialize the GUI. This will create a panel, typically in the top-right corner of the screen.
const gui = new GUI();Next, you can add a controller for a property you want to manipulate. The .add() method takes the object and the property name (as a string) that you want to control.
// Assuming 'cube' is a THREE.Mesh objectgui.add(cube.position, 'y'); // Adds a controller for the cube's y-positionYou can enhance controllers by chaining methods to set minimum/maximum values, the step increment, and a display name.
gui.add(cube.position, 'x') .min(-3) .max(3) .step(0.01) .name('Cube X Position');For properties that are not numbers, lil-gui provides specific controllers. For example, use .addColor() for colors and a simple .add() for booleans (which creates a checkbox).
// For a boolean value (e.g., wireframe)gui.add(cube.material, 'wireframe');
// For colors, it's best to control a separate object and use an onChange eventconst colorParams = { color: 0xff0000}gui.addColor(colorParams, 'color').onChange(() => { cube.material.color.set(colorParams.color);});Organizing Your UI with Folders
Section titled “Organizing Your UI with Folders”When you have many controllable properties, your UI panel can get crowded. You can organize it using folders to group related controls.
const cubeFolder = gui.addFolder('Cube Properties');
const positionFolder = cubeFolder.addFolder('Position');positionFolder.add(cube.position, 'x', -5, 5);positionFolder.add(cube.position, 'y', -5, 5);positionFolder.add(cube.position, 'z', -5, 5);
const materialFolder = cubeFolder.addFolder('Material');materialFolder.add(cube.material, 'wireframe');// ... add color controller hereMonitoring Performance with Stats.js
Section titled “Monitoring Performance with Stats.js”Another crucial debugging tool is Stats.js, which provides a simple panel to monitor your application’s performance, showing frames per second (FPS), memory usage, and frame time. A high and stable FPS (typically 60) indicates a smooth experience.
Installation & Usage
Section titled “Installation & Usage”First, install the package:
npm install stats.jsThen, import it, create an instance, add it to your page, and update it on every frame within your animation loop.
import Stats from 'stats.js';
// 1. Initializationconst stats = new Stats();stats.showPanel(0); // 0: fps, 1: ms, 2: mb, 3+: customdocument.body.appendChild(stats.dom);
// 2. Update in animation loopfunction animate() { stats.begin(); // Start monitoring
// ... your animation code ... renderer.render(scene, camera);
stats.end(); // End monitoring
requestAnimationFrame(animate);}
animate();Integrated Example
Section titled “Integrated Example”Here is a complete example integrating Three.js, lil-gui, and stats.js in a modern project setup. This code would typically reside in a main.js file.
import * as THREE from 'three';import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';import GUI from 'lil-gui';import Stats from 'stats.js';
/** * Base Setup */// Debugconst gui = new GUI();const stats = new Stats();stats.showPanel(0);document.body.appendChild(stats.dom);
// Canvasconst canvas = document.querySelector('canvas.webgl');
// Sceneconst scene = new THREE.Scene();scene.background = new THREE.Color(0x262626);
/** * Object */const geometry = new THREE.BoxGeometry(1, 1, 1);const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });const cube = new THREE.Mesh(geometry, material);scene.add(cube);
/** * Lights */const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);scene.add(ambientLight);const pointLight = new THREE.PointLight(0xffffff, 0.8);pointLight.position.set(2, 3, 4);scene.add(pointLight);
/** * Sizes */const sizes = { width: window.innerWidth, height: window.innerHeight};
window.addEventListener('resize', () => { // Update sizes sizes.width = window.innerWidth; sizes.height = window.innerHeight;
// Update camera camera.aspect = sizes.width / sizes.height; camera.updateProjectionMatrix();
// Update renderer renderer.setSize(sizes.width, sizes.height); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));});
/** * Camera */const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 0.1, 100);camera.position.z = 3;scene.add(camera);
/** * Controls */const controls = new OrbitControls(camera, canvas);controls.enableDamping = true;
/** * Renderer */const renderer = new THREE.WebGLRenderer({ canvas: canvas});renderer.setSize(sizes.width, sizes.height);renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
/** * Debug UI */const cubeFolder = gui.addFolder('Cube');cubeFolder.add(cube.position, 'y').min(-3).max(3).step(0.01).name('elevation');cubeFolder.add(cube, 'visible');cubeFolder.add(material, 'wireframe');
const colorParams = { color: 0x00ff00 };cubeFolder.addColor(colorParams, 'color').onChange(() => { material.color.set(colorParams.color);});
/** * Animate */const clock = new THREE.Clock();
const tick = () => { stats.begin();
const elapsedTime = clock.getElapsedTime();
// Update controls controls.update();
// Render renderer.render(scene, camera);
stats.end();
// Call tick again on the next frame window.requestAnimationFrame(tick);};
tick();Common Errors and Debugging Tips
Section titled “Common Errors and Debugging Tips”- Nothing is visible: Check your browser’s developer console (F12) for errors. Often, it’s a typo in a property name or an incorrect import.
- GUI not appearing: Make sure you have instantiated it with
new GUI(). Check for any CSS on your page that might be hiding it (e.g.,overflow: hiddenon the<body>). - Performance is low: Use the Stats.js panel. If FPS is consistently below 60, you might have too many objects, too complex geometry, or an expensive operation in your animation loop. Try simplifying your scene to identify the cause.
- Visualizing Axes: To better understand object orientation and position, add an
AxesHelperto your scene:const axesHelper = new THREE.AxesHelper(2); scene.add(axesHelper);