Article / Lighting

Lighting 3D Products on the Web: Why Your Model Looks Cheap and How to Fix It

The same glb file can look like a phone screenshot or a studio photograph, and the difference is lighting. Environment maps, tone mapping, shadows, and a copy-paste starting recipe for three.js product scenes.

Here is an experiment I run with clients who think their 3D model is the problem. I take their "cheap looking" glb, change nothing about the geometry or textures, and relight it: a proper environment map, corrected tone mapping, a soft contact shadow. Thirty minutes. The reaction is always the same, and it is always some version of "what did you do to the model."

Nothing. Lighting is doing what lighting does. Product photographers have known for a century that the light is the picture, and the same holds in real time rendering: lighting is the highest-leverage, lowest-cost quality upgrade available to any product scene on the web. This article is the practical version: what actually produces the studio look in three.js, in the order I apply it.

Stop adding lights

The instinct from tutorials is to build lighting out of point lights and spotlights: a key here, a fill there, another one because the back looks dark. Six lights later the scene looks like a car park and runs like one, because every punctual light adds real per-pixel cost, which matters enormously on phones.

Real product lighting on the web is image-based. An environment map, a panoramic HDR image of a real or designed space, illuminates the model from every direction at once, with all the soft gradation and subtle color variation of the space it captured. It is what makes metals look like metal and plastics look like products instead of toys. One good environment replaces the entire light rig, costs less per frame, and cannot produce the flat, sourceless look that stacked point lights converge on.

The environment map, in practice

Three.js makes this nearly free. For a neutral studio look you do not even need an HDRI file: the built-in RoomEnvironment generates a softbox-style studio around your model at runtime. For more character, Poly Haven has hundreds of free HDRIs, and the studio category is the product-scene goldmine.

import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';

const pmrem = new THREE.PMREMGenerator(renderer);
scene.environment = pmrem.fromScene(new RoomEnvironment()).texture;

// or, with a Poly Haven HDRI for more character:
import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';

new RGBELoader().load('/hdri/studio_small_08_1k.hdr', (tex) => {
  tex.mapping = THREE.EquirectangularReflectionMapping;
  scene.environment = tex;
});

Two practical notes that save hours. First, a 1k HDRI is almost always enough for lighting: the environment gets blurred by roughness anyway, and the 4k versions cost real memory for no visible gain unless the environment is directly visible as a background. Second, choose the environment for the product, not for realism. A rug wants warm interior light. Brushed metal wants a studio with big soft windows, because those windows become the highlights that draw the form. You are choosing the reflections, not just the brightness.

Tone mapping, the setting everyone skips

If your scene looks washed out, or clips to white the moment anything gets bright, lighting is probably innocent and tone mapping is guilty. Tone mapping is the step that compresses the wide range of light the scene computes into the narrow range a monitor can show, and the default in three.js is no tone mapping at all, which is the screenshot look: harsh clips, dead midtones.

renderer.toneMapping = THREE.ACESFilmicToneMapping; // or AgXToneMapping
renderer.toneMappingExposure = 1.0; // your brightness dial
renderer.outputColorSpace = THREE.SRGBColorSpace; // default in modern three, verify anyway

ACES Filmic is the long-standing safe choice: filmic rolloff, pleasant saturation, hard to make ugly. AgX is the newer option and handles intense highlights and saturated light sources more gracefully, at the cost of a slightly flatter, more cinematic base look that sometimes needs a saturation nudge. Try both on your actual product, adjust exposure until the whites are bright without clipping, and you have done more for the image than any additional light would.

Shadows sell the weight

A product floating on white with no shadow reads as a sticker. The shadow is what tells the eye the object has mass and a location. The good news for product scenes: you rarely need real-time shadow maps at all. What you need is a contact shadow, the soft dark pooling under the object, and for a static or turntable product that can be a cheap blurred plane rather than a full shadow pipeline. Drei ships a ContactShadows helper that does exactly this, and for fully static scenes, baking the shadow into a texture in Blender is even cheaper at runtime.

If the product moves or reconfigures, as in the configurators I build, a single directional light with a tuned shadow map, combined with the environment doing the actual illumination, covers it: the environment lights the product, the one directional exists mostly to anchor it to the ground.

Two shadow-map settings cause most of the visible trouble. Resolution: the default 512 map produces the blocky shadow edges that scream real-time, and 1024 or 2048 on a single light is affordable on anything modern. And bias: shadow acne, the striped self-shadowing pattern on curved surfaces, disappears with a small negative bias adjustment, and peter-panning, the shadow detaching from the object, means you pushed the bias too far. Thirty seconds of tuning each, and both defects are the kind viewers register as cheapness without knowing why.

Materials meet light halfway

Lighting cannot rescue dishonest materials, and the dishonesty is usually in two sliders. Roughness at the extremes is the amateur tell: real products are almost never mirror-perfect nor perfectly matte, and the interesting surfaces live between 0.15 and 0.6, often varied across the surface by a roughness map rather than one flat value. Metalness is close to binary: metals are 1, everything else is 0, and the in-between values that beginners reach for produce the specific plasticky wrongness that no light fixes.

For furniture and product finishes, clearcoat is the underused parameter: a thin glossy layer over a rougher base is exactly how lacquered wood, car paint, and coated plastics behave, and MeshPhysicalMaterial exposes it directly. And envMapIntensity is your per-material dimmer for the environment, useful when one material drinks too much light while the rest of the scene sits right.

The white background problem

Product sites live on white, and white is the hardest background in real time rendering: no environment reflections to hide in, every shading artifact exposed, and a white product on a white page can dissolve entirely. Three things keep it working. The contact shadow becomes mandatory, because it is the only thing separating object from page. The environment needs enough directional variation that white surfaces shade from bright to dim across the form instead of rendering as flat paper. And the background itself is better as a very subtle gradient or vignette than pure 255 white, which gives highlights somewhere to exist. My Laylo configurator lives entirely in this regime, and its lighting is tuned almost exclusively around making white furniture read against water and sky.

Debugging light like a photographer

When a scene looks wrong and you cannot say why, borrow the photographer's calibration trick. Drop two test spheres next to the product: one middle-gray and fully rough, one white and fully smooth. The gray sphere shows you what the light itself is doing, its direction, softness, and color, stripped of any material opinion. The chrome-like sphere shows you the environment map raw, so you can see exactly which window or lamp is producing that highlight you cannot explain. Most mystery lighting problems stop being mysteries the moment the spheres are in frame.

The other habit worth stealing is reference. Open a product photo you admire, from the brand you wish your render resembled, and compare it honestly against your scene: where the brightest highlight sits, how fast light falls off across a curved surface, how dark the darkest visible tone actually is. Renders drift wrong in absolute terms but obvious ways when compared to nothing. Against a reference, the fix is usually visible in seconds.

One environment, whole catalog

If you light more than one product, consistency quietly outranks beauty. A catalog where every model shares one environment, one exposure, and one shadow treatment reads as a professional studio session, while per-product tweaking, each model individually flattered, reads as a collage even when every single frame is technically better. Lock the lighting rig as shared setup code, treat it like a brand asset alongside the logo and the type, and let the products differ only in the ways the products actually differ.

Post-processing, sparingly

A restrained ambient occlusion pass helps furniture and interiors, and a whisper of bloom can lift jewelry and glass. But post-processing multiplies per-pixel cost on exactly the devices that can least afford it, and every effect stacked on a product scene moves it further from photography and closer to a video game. My rule: get the image right with environment, tone mapping, and shadow first. If post-processing is doing the heavy lifting, the lighting underneath is wrong.

The starting recipe

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.1;

const pmrem = new THREE.PMREMGenerator(renderer);
scene.environment = pmrem.fromScene(new RoomEnvironment()).texture;
scene.background = new THREE.Color('#f4f4f4');

// one directional light, for the ground shadow only
const sun = new THREE.DirectionalLight('#ffffff', 0.4);
sun.position.set(3, 6, 2);
sun.castShadow = true;
scene.add(sun);

// soft contact shadow plane under the product
// (drei <ContactShadows /> in R3F, or a baked blur plane in vanilla)

That is a competent studio starting point in a dozen lines: environment for illumination, filmic tone mapping for range, one light for grounding. From there it is taste, exposure, and choosing the HDRI whose reflections flatter your particular product. Which is the real lesson under all of this: the tools are nearly free and nearly instant, and what separates the cheap look from the expensive one is not budget or hardware. It is knowing that the light is the picture, and spending an afternoon on it.