10 More Three.JS Tips

This article features 10 tips when using the WebGL library Three.JS and follow on from my previous article. Live examples are available via this CodePen collection or via the links on this page

No. 11 DRACOLoader

When exporting a glb scene from Blender you have the option to use compression. This can often reduce the file size by 50% or more. When loading a compressed glb file you need to initialise and add a DRACOLoader. Take a look at the loadModel function.

function loadModel(){
  const loader = new GLTFLoader().setPath(
  const dracoLoader = new DRACOLoader();
  'https://unpkg.com/[email protected]/examples/js/libs/draco/' );
  loader.setDRACOLoader( dracoLoader );

After creating a DRACOLoader we need to set the decoder path. This is part of the complete Three.JS library and is found in examples/js/libs/draco. Once the DRACOLoader is initialised you use the setDRACOLoader method of the GLTFLoader instance. Now you can use the GLTFLoader load method as you would normally.

See the Pen ThreeJS Tips: 10-12 by Nik Lever (@nik-lever) on CodePen.

No. 12 Renderer pixelRatio
Some devices have a pixelRatio of 5 to 1. That means if the window.innerWidth x window.innerHeight is 500 x 1000 if you set the pixelRatio to window.devicePixelRatio then the number of pixels the renderer is rendering is 2500 x 5000! Don’t expect 60fps for a complex scene. Better to limit the pixelRatio to 2 using Math.min

renderer.setPixelRatio( Math.min(2, window.devicePixelRatio ));

See the Pen ThreeJS Tips: 10-12 by Nik Lever (@nik-lever) on CodePen.

No.13 Make sure to move the camera

All objects are initialised at (0,0,0), including the camera. Which means you might not see the objects in your scene. The GUI in this example allows us to adjust the z value for the camera.position.z.

gui.add(options, 'z', 0, 5).step(0.1).onChange( value => {
    camera.position.set( 1, 0.8, value );

Notice if z is too low the model is outside the cameras frustum and is clipped.

See the Pen ThreeJS Tips: 13 by Nik Lever (@nik-lever) on CodePen.

No.14 Code Style

When working on Three.JS apps consider using Mr Doob’s Code Style. Mr Doob, Ricardo Capello, is the original author of the library. Using these Code conventions will mean your code matches the conventions used in the library code.


No. 15 Z fighting

If two triangles occupy the same world space the renderer will get confused about which is in front of which. This artefact is called z-fighting and is easily fix by moving one object by the smallest amount. The CodePen example includes a GUI button that toggles a z offset of just 0.001

const options = { 'toggle offset': () => {
    plane.position.z = (plane.position.z == 0) ? 0.001 : 0;
} };
gui.add(options, 'toggle offset');

See the Pen ThreeJS Tips: 15 by Nik Lever (@nik-lever) on CodePen.

No. 16 Use a CameraHelper to visualize the shadow camera’s viewing frustum

Shadows can cause a lot of problems for Three.JS developers. They use a camera attached to the light that is casting shadows. While developing, a CameraHelper attached to the lights shadow camera, can help you visualise the positioning of the shadow camera.

cameraHelper = new THREE.CameraHelper(light.shadow.camera);

In the CodePen example it uses a DirectionalLight which has parallel rays. The shadow camera is an OrthographicCamera using no perspective. Notice the clipping of the avatars head in the shadow.

See the Pen ThreeJS Tips: 16-19 by Nik Lever (@nik-lever) on CodePen.

No. 17 Make the shadow frustum as small as possible.

Continuing from tip 16. You should set the frustum to be as small as possible without causing clipping. That will give you the best shadows. Too big and the shadow texture will show pixellation as it is spread over too big an area. Too small and you’ll see clipping. In this example a small tweak to top fixes the clipping of the avatars head.

function updateShadowCamera( left, right, top, bottom, near, far){
  const camera = light.shadow.camera;
  left = (left==null) ? camera.left : left;
  right = (right==null) ? camera.right : right;
  top = (top==null) ? camera.top: top;
  bottom = (bottom==null) ? camera.bottom : bottom;
  near = (near==null) ? camera.near : near;
  far = (far==null) ? camera.far : far;
  camera.left = left;
  camera.right = right;
  camera.top = top;
  camera.bottom = bottom;
  camera.near = near;
  camera.far = far;

See the Pen ThreeJS Tips: 16-19 by Nik Lever (@nik-lever) on CodePen.

No. 18 Shadow texture resolution.

To improve performance keep your shadow mapSize as small as possible. The example allows you to change the mapSize using a gui. The number is the power that 2 is raised to to set the mapSize. If the number is 8 then 2^8=256. At 2^4=16, the shadow is just a blurred smudge whereas 2^10=1024 the shadow is unnecessarily sharp.

See the Pen ThreeJS Tips: 16-19 by Nik Lever (@nik-lever) on CodePen.

No. 19 Change shadow mapSize

If you dynamically change the mapSize of a shadow in code make sure to dispose of the old map and set the map to null. This will cause the map to be reinitialised at the new size.

gui.add(options, 'shadow map size', 4, 11 ).step(1).onChange( 
  value => {
    const size = Math.pow(2, value);
    light.shadow.mapSize.width = size;
    light.shadow.mapSize.height = size;
    light.shadow.map.dispose(); // important
    light.shadow.map = null; 

See the Pen ThreeJS Tips: 16-19 by Nik Lever (@nik-lever) on CodePen.

No. 20 Texture POT

If using WebGL 1.0 then make sure that all texture dimensions are a power of 2. They don’t need to be square. 8 x 256 is fine, but 8 x 255 would be resized to 8 x 128

See the Pen ThreeJS Tips: 20-22 by Nik Lever (@nik-lever) on CodePen.

Hope you enjoyed these tips. I have more to come. I create video courses and have several about Three.JS check them out at niklever.com/courses