Publishing gate

Run the WebGL scene health check

Use the checklist to score assets, camera behavior, rendering, mobile performance, loading states, and fallback content before publishing.

Run Scene Check Compare lab tools

Do not debug everything at once

A blank canvas can come from a missing canvas size, an offscreen camera, a clipped object, a black material, a failed import, or a render loop that never runs. If you change all of those at once, you may hide the actual cause. Debug in layers and keep each test boring.

First prove the renderer can draw a basic cube with a MeshNormalMaterial. This material does not need lights, so it removes lighting from the question. If the cube appears, the renderer, camera, and loop are basically alive. If it does not, look at canvas sizing, WebGL context errors, and camera placement.

Check the camera next

A camera can look in the wrong direction, sit inside the object, or clip the object with near and far planes. Add a CameraHelper when using shadow cameras or complex rigs. For a PerspectiveCamera, start with a visible position, call `lookAt(0, 0, 0)`, and render a known object at the origin.

When loading unknown models, compute a bounding box and log its size. If the box is enormous or tiny, the camera may be fine and the scale may be the problem. If the box center is far from the origin, the object may be outside the camera's view.

Separate material and light issues

MeshStandardMaterial needs light. MeshBasicMaterial does not. MeshNormalMaterial provides an even stronger diagnostic because it colors surfaces by normal direction. Swap materials temporarily. If the object appears with a diagnostic material but disappears with its original material, inspect textures, color management, alpha settings, side settings, and lights.

For imported models, traverse the scene and count meshes. Log material names and visibility. It is possible for a loader to succeed while the visible content is hidden inside a nested hierarchy or assigned a fully transparent material.

Use helpers without shipping them

GridHelper, AxesHelper, BoxHelper, and light helpers are excellent during development. They reveal scale, orientation, and positions that are otherwise invisible. Add them through a debug flag so they can be toggled without editing the scene.

Before publishing, remove or hide debug helpers. A production page should feel intentional, but a development workflow should be full of instruments. The balance is simple: make problems visible while building, then keep only the helpful visual structure for users.

Return to a known-good rendered object

The GLB Viewer sample proves that the renderer, camera, geometry, material, and lights can produce a visible frame.

  • Visible cube
  • Non-zero geometry metrics
  • Camera fitted from bounds
Render the control sample

Replace the unknown scene with a diagnostic cube

Debugging every imported asset and material assumption at once hides the first failing layer.

Fragile pattern
loader.load(assetUrl, ({ scene: model }) => {
  scene.add(model);
  startPostprocessing();
});
Testable pattern
scene.add(new THREE.Mesh(
  new THREE.BoxGeometry(1, 1, 1),
  new THREE.MeshBasicMaterial({ color: 0xff5533 })
));
camera.position.set(0, 0, 3);
renderer.render(scene, camera);

Diagnostic layer record

A layer passes only when it produces a visible or inspectable result.

LayerPass evidence
RendererCanvas has a non-empty frame
CameraDiagnostic cube is inside frustum
AssetBounds and mesh count are finite
Material / lightBasic material works before PBR

Bisect a blank canvas

Keep each successful layer in place while adding the next one back.

  1. Set a clear color and render a BasicMaterial cube.
  2. Add camera and BoxHelper diagnostics.
  3. Load the asset but keep the diagnostic material.
  4. Restore project materials, lights, controls, and effects one group at a time.

Known-good diagnostic cube

scene.add(new THREE.GridHelper(10, 10));
scene.add(new THREE.AxesHelper(2));

const cube = new THREE.Mesh(
  new THREE.BoxGeometry(1, 1, 1),
  new THREE.MeshNormalMaterial()
);
scene.add(cube);

camera.position.set(3, 2, 4);
camera.lookAt(0, 0, 0);
renderer.render(scene, camera);

Sources and further reading

Update record

  • : Rewritten around a reproducible demo, explicit test record, code comparison, and known platform limits.

Related guides