A founder-voice technical guide to building a Three.js product viewer for premium ecommerce: tone mapping, lighting, depth, and the four parts most tutorials skip.

If you have followed a Three.js tutorial to add a 3D product viewer to a Shopify store and the result felt nothing like the demo, the cause is almost always the parts the tutorial skipped. Three.js is a low-level library; the four pieces that turn it into a production viewer are not in any single tutorial. This guide covers what a shippable Three.js product viewer needs and the order to build it.
A production Three.js product viewer needs four parts that most tutorials gloss over. A tone-mapped renderer set to ACES Filmic with exposure around 1.5 so the materials read close to professional product photography. Studio-style lighting that matches the photography brief for the catalog, not a generic environment map pulled from a sample. Depth isolation so 2D overlays (price chips, hotspots, captions) render cleanly on top of the model without z-fighting. A single GLB loader that streams the model from a CDN with Draco mesh compression and lazy texture upload so the buyer sees something in under 2 seconds on LTE. Skip any one of those and the viewer looks broken on a real product page next to professional photography.
The library itself is at threejs.org. The four parts above are not optional add-ons; they are the difference between a demo and a viewer that converts.
A Three.js scene rendered with default settings looks washed out next to product photography. The fix is two settings on the renderer:
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.5;
renderer.outputColorSpace = THREE.SRGBColorSpace;
ACES Filmic is the same tone curve most film cameras and modern photo editors use. Exposure around 1.5 brings the rendered output close to a daylight-balanced product photo. SRGB output color space ensures the colors are correct when the browser composites the canvas onto the page.
A viewer without these three settings reads as flat next to the hero photo. The buyer sees the inconsistency before they see the product.
The default Three.js scene has no lighting. Most tutorials add an AmbientLight and a DirectionalLight and call it done. The result looks like a 3D model under fluorescent office lighting, which is the wrong baseline for a product page.
The lighting setup that matches professional product photography:
This setup matches the lighting most premium DTC photography uses. The 3D viewer next to the photo reads consistent.
A 3D viewer next to overlay UI (price chips, "see in your space" buttons, hotspot labels) has to render the overlays on top of the model without depth conflicts. Without explicit depth handling, the overlays z-fight with the model surface; sometimes they show, sometimes they vanish.
The fix:
renderOrder = -1).colorWrite = false, depthWrite = true, depthTest = false. It clears the depth buffer so the next layer renders on top regardless of position.renderOrder = 0 and depthTest = true against the cleared depth.Most tutorials skip this entirely because the demo scene has no overlays. Production viewers always have overlays.
The model loader is the part of the viewer that decides whether the buyer waits 2 seconds or 9. A naive GLTFLoader call without compression streams a full GLB at the buyer.
The setup that ships:
A production viewer with these four settings ships a 2 to 4 MB total payload (code plus model) that loads in under 2 seconds on LTE.
OrbitControls is the default camera controller in the Three.js examples. It works for an editor where the developer is moving the camera around a scene. It does not work for a buyer-facing viewer because:
A buyer-facing controller should:
This is 30 to 50 lines of code in addition to or replacing OrbitControls. It is the difference between a viewer that feels professional and one that frustrates the buyer.
Tutorials often pile on features that ship slowly. The features to defer:
If you have a Three.js viewer prototype that does not look like the demos, walk the four-part checklist: tone mapping, studio lighting, depth isolation, GLB compression. Most prototypes are missing two or three of these.
If you have not started yet and the timeline matters more than the customization, ship Google's <model-viewer> web component first. It handles the four parts above out of the box for the rotate-and-AR use case. Move to Three.js when you need the rest of the page to react to what the buyer does in the viewer.
The three ways to embed a 3D model covers the choice between model-viewer, an iframe app, and a custom Three.js component. The WebGL product page performance guide covers the runtime budget the viewer has to fit inside.
The buyer is on a phone. The viewer either looks like a professional product photo with rotation, or it does not. The four parts above are the difference.