Skip to main content

Lights

A lit material, such as Material3D, shows the light that reaches it. An environment lights a scene from its surroundings. With no lights and no environment, a Material3D mesh draws black. Only its emissive glow and what shows through glass stay visible. An unlit material, such as FlatMaterial, ignores lights.

Lights are Container3D objects, like meshes. Add one anywhere under view3d.root and it can light any lit mesh in that view, not just its siblings. Setting visible to false on the light, or on a parent, switches it off. The light itself draws nothing. Scene objects shows how to give one a glowing bulb mesh as a child.

Light types​

  • DirectionalLight: parallel light from one direction, like the sun.
  • PointLight: light in every direction from a point, like a bulb.
  • SpotLight: a cone of light from a point, like a torch.
  • AmbientLight: the same light on every surface, from no direction.

Light types lights one scene with all four, and shows each alone so you can see what it adds.

Directional and spot lights can cast shadows, which Shadows covers. A typical scene has one directional light, plus a dimmer fill light for the sides it doesn't reach:

import { AmbientLight, DirectionalLight } from '@pixi/3d';

const sun = new DirectionalLight({ color: 0xfff4e0, intensity: 3, castShadow: true });

sun.position.set(5, 10, 5);
view3d.root.addChild(sun);
sun.lookAt({ x: 0, y: 0, z: 0 });

view3d.root.addChild(new AmbientLight({ intensity: 0.3 }));

Colour and intensity​

Every light has a color, which takes any colour PixiJS accepts, and an intensity that scales it. You can change both at any time:

sun.color = 0xff9a4d; // sunset
sun.intensity = 2;

Intensities above 1 are normal. Tone mapping is how the view turns lit colour into screen colour. The view's default toneMapping is 'none'. Under it, a white Material3D surface facing a directional light of intensity 1 shows mid grey. An intensity of about 3 brings it to white, and anything brighter clips to white. A curve such as 'aces' compresses bright light gradually instead of clipping it; see Tone mapping and exposure.

Point and spot lights dim with the square of the distance. A lamp 3 units from a surface needs about 9 times a directional light's intensity to light it as brightly.

The same rule makes a small, very bright spot on a surface close to the light, such as the ground under a lantern or a campfire. Closer to the light than its radius (PointLight.radius, SpotLight.radius), brightness levels off. To soften the spot, raise the radius past the light's distance from the surface. For a lantern 0.3 above the ground, try 0.6. Surfaces farther away than about twice the radius barely change.

Aiming lights and making them follow​

Directional and spot lights shine along their local -Z axis, the way a camera looks, so lookAt aims them at a point. For a directional light, only the direction it faces matters. Its position just gives lookAt a point to aim from. A point light shines in every direction, so only its position matters.

To make a light follow an object, add the light as a child of that object. A directional light under the camera lights whatever the camera faces:

view3d.camera.addChild(new DirectionalLight({ intensity: 2 }));

Range​

Give point and spot lights a range (PointLight.range, SpotLight.range), the smallest one that still looks right. The light fades smoothly to nothing at that distance, and the view skips it on the parts of the screen it doesn't reach. A light with no range never quite fades to zero, so the view shades it on every lit pixel. With many lights, that cost adds up.

import { PointLight } from '@pixi/3d';

const lamp = new PointLight({ color: 0xffd180, intensity: 8, range: 12 });

lamp.position.set(0, 2.5, 0);
view3d.root.addChild(lamp);

Spot lights​

A spot light's coneAngle is the angle from the cone's centre to its edge, in degrees, so the whole cone spans twice that angle. Its penumbra, from 0 to 1, softens the edge.

import { SpotLight } from '@pixi/3d';

const spot = new SpotLight({ intensity: 40, range: 12, coneAngle: 28, penumbra: 0.4, castShadow: true });

spot.position.set(3, 5, 3);
view3d.root.addChild(spot);
spot.lookAt({ x: 0, y: 0, z: 0 });

Point lights cast no shadows. For a lamp that casts a shadow, aim a spot light at what the lamp lights.

Ambient light​

An AmbientLight keeps shadowed sides from going pure black. On its own, it makes shapes look flat, since every side gets the same amount of light.

On a Material3D, ambient light fades as metallic rises, so a fully metallic surface, at 1, gets none of it. Light metals with an environment instead. A Hemisphere environment also works as a fill light that doesn't flatten shapes, since it lights surfaces facing up and surfaces facing down in different colours.

Light limit​

A view shades at most 32 directional, point and spot lights. Ambient and hidden lights don't count. Past the limit, the view leaves out the visible lights that come last in view3d.lights, and gives no warning. That list holds the lights in the order they joined the scene. If you have more than the limit, hide the ones that matter least, such as those far from the camera.

Shadows have a limit of their own, which the shadow budget explains.

Lights from model files​

A glTF file can bring its own directional, point and spot lights; Loading models covers them. When the file has none, View3D.from adds a DirectionalLight under the camera. Set its light option to false when you add your own lights.

Next steps​

  • Shadows: casting shadows, their quality, and cascades for large worlds.
  • Environment and IBL: light from the surroundings, tone mapping and exposure.
  • Materials: how surfaces respond to light.