Simulating Real-World Light Decay in Three.js

This article explores how Three.js utilizes light attenuation to mimic real-world physics, focusing on how light intensity diminishes over distance. We will examine the mathematical principles behind the inverse-square law, how Three.js implements these concepts through the decay and distance properties of point and spot lights, and how to configure your renderer to achieve physically accurate lighting in your 3D scenes.

The Physics of Light Attenuation

In the physical world, light spreads out as it travels away from a localized source, such as a lightbulb or a candle. Because the same amount of light energy is distributed over an ever-increasing spherical surface area, the brightness decreases. This phenomenon is governed by the inverse-square law, which states that the intensity of light is inversely proportional to the square of the distance from the source (\(I \propto 1/d^2\)). If you double your distance from a light source, the light intensity drops to one-quarter of its original value.

Implementing Attenuation in Three.js

Three.js simulates this physical behavior primarily using two types of lights: PointLight and SpotLight. Directional lights (like simulated sunlight) do not experience attenuation because their rays are parallel and represent a source infinitely far away.

To control how light fades over distance in Three.js, developers rely on two key properties:

  1. decay: This property defines how quickly the light dims.
    • A decay value of 2 mathematically matches the real-world physics of the inverse-square law.
    • A decay value of 1 results in a linear fade, which is less realistic but can be useful for stylized or low-performance rendering.
    • A decay value of 0 means the light does not fade at all, maintaining its full intensity regardless of distance.
  2. distance: This property defines the maximum range of the light.
    • When set to 0, the light decays infinitely according to the decay formula, meaning it never truly reaches zero intensity but becomes negligible at great distances. This is the most physically accurate setting.
    • When set to a value greater than 0, the light will attenuate normally until it hits that specific distance, at which point its intensity abruptly drops to zero.

Achieving Physically Correct Lighting

To ensure that Three.js calculates light attenuation using true physical formulas, you must configure the renderer correctly.

In modern versions of Three.js, physically correct rendering is the default. However, to guarantee accurate real-world light behavior, you should ensure the legacy lighting mode is disabled:

renderer.useLegacyLights = false;

(Note: In older versions of Three.js prior to r150, this was enabled using renderer.physicallyCorrectLights = true).

When physical rendering is active, light intensities are calculated in lumens (for spot/point lights) or candelas, and the decay exponent of 2 behaves exactly like real-world physics.

Practical Configuration

For a realistic setup, instantiate a light with a decay of 2, a distance of 0 (for infinite physical decay), and a high intensity to compensate for the rapid falloff:

const physicalLight = new THREE.PointLight(0xffffff, 100, 0, 2);
scene.add(physicalLight);

By understanding and utilizing these properties, you can transform flat, computer-generated scenes into highly realistic environments with natural depth and atmospheric lighting.