Three.js Lights and Cameras

Lights can really make the difference between a seemingly flat scene and a visual masterpiece. Think of any photo-realistic painting or photograph and then imagine it with poor lighting and the impact is just not the same. Cameras change the way we view our scenes altogether, think of the different types of lenses photographers use and how they can influence the perspective and depth of a photo. Lights do not work on all materials. Lights do work with MeshLambertMaterial, MeshPhongMaterial and MeshStandardMaterial.

Lights

Lights are often an after thought, but lighting can really be the most important aspect of your scene. Three.js has a variety of built in lights to apply to materials like MeshLambertMaterial and MeshPhongMaterial. These lights effect the materials in different ways depending on the type of light and their properties as well as the properties of the materials themselves.

AmbientLight

The ambient light gets applied to all the objects in your scene globally. Ambient lights are often useful for filling parts of your scene not hit by other lights. This is because there’s no relevant position or direction, it lights everything equally. You can set the color and the intensity of the ambient light.

var light = new THREE.AmbientLight(color, intensity);

PointLight

Point lights are like light bulbs in your scene. They are positioned at a specific location and radiate light outwards in all directions from that postion. They have a color and intensity. Point lights allow you to set the distance from the light at which point it’s intensity is zero. You can also set the decay which is the amount the light dims along the distance.

var light = new THREE.PointLight(color, intensity, distance, decay);

DirectionalLight

Direcional lights resemble the Sun. They’re a distant powerful light source pointing in one direction.

An important thing to note about directional lights is that its object rotation does not actually influence where it is pointing, instead it uses a target property which is an Object3D. This allows you to influence the direction of the light. Note this may change in the future, as of version 79 there are some issues filed here and here around target and the reflected matrix in the light object.

var light = new THREE.DirectionalLight(color, intensity);

SpotLight

Spotlights are just that, spotlights. They are lights that point in one direction radiating out in a cone shape. We can pass an angle to the light as a parameter which is the maximum angle of the light cone in radians. Penumbra affects the intensity or softness of outer shadow or light fall-off as it fades to darkness.

Because spotlights have a direction they suffer from the same rotation behavior as directional lights, requiring them to point to a target object’s position.

var light = new THREE.SpotLight(color, intensity, distance, angle, penumbra, decay);

HemisphereLight

Like ambient light, hemisphere light has no position or direction. But unlike ambient light it has two colors. One to simulate the color coming from above, like a sun or ceiling light source. The other is the color of light coming from below, to simulate the light reflected off the floor or ground surface. This is to provide a bit more realism than a globally applied ambient light.

var light = new THREE.HemisphereLight(skyColor, groundColor, intensity);

Shadows

Shadows occur when certain kinds of lights are blocked by an object. They can be cast by and received by objects. And can be cast on any material that supports lights. Shadows can only be cast by a DirectionalLight or SpotLight. To support shadows you need to perform some configuration on your renderer. First we need to enable shadowMap on our renderer by setting renderer.shadowMap.enabled to true. Then we need to specify the type of shadowMap we want to use. The default is PCFShadowMap, but Three.js also supports BasicShadowMap and PCFSoftShadowMap which provide different results.

renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFShadowMap;

We then need to set up some properties on the light that will cast shadows. The first is to set the castShadow property to true. Then we set the shadow object which is a LightShadow that takes in a camera object defining the field of view, aspect ratio, near and far frustum planes. Then we can set other properties like the resolution of the shadows in the mapSize. And the bias which determines how much to add or subtract from the normalized depth to determine if a surface is in a shadow.

light.castShadow = true;
light.shadow = new THREE.LightShadow( new THREE.PerspectiveCamera( 50, 1, 10, 2500 ) );
light.shadow.bias = 0.0001;
light.shadow.mapSize.width = 2048;
light.shadow.mapSize.height = 1024;

We then need to define which objects will cast shadows and which will receive shadows. We do this by setting these properties to true on the relevant objects. In the example below the cube will cast shadows but not receive shadows. The floor will recieve shadows from all objects casting shadows. The sphere will both cast and receive shadows.

cube.castShadow = true;

sphere.castShadow = true;
sphere.receiveShadow = true;

floor.receiveShadow = true;

Three.js also has a helper utility for visualizing what the shadow camera sees called ShadowMapViewer. Which can be found in their github repository. It’s usage will also require the inclusion of a special UnpackDepthRGBAShader shader found in the github examples. You then need to set up the viewer object by passing it a reference to your light and setting the position and size of the viewer. You then call the update method and add its render call, passing in your renderer, inside your render loop.

shadowMapViewer = new THREE.ShadowMapViewer( light );  
shadowMapViewer.position.x = 10;
shadowMapViewer.position.y = 10;
shadowMapViewer.size.width = 2048 / 4;
shadowMapViewer.size.height = 1024 / 4;
shadowMapViewer.update();

//Note: this goes inside render loop
shadowMapViewer.render(renderer);


Cameras

Cameras are a key tool for 3D influencing the user’s vantage point and the range of their view. By manipulating the camera’s position we can navigate our scene. Three.js has two basic types of cameras, the most common is PerspectiveCamera the other is OrthographicCamera

PerspectiveCamera

This creates a camera with perspective projection. This is a normal camera type where objects get smaller the further away they are. The first parameter is the camera’s vertical field of view, from the bottom to top in degrees. The next is the aspect ratio of the camera so the viewport width divided by the height. The near and far plane determine the planes at which clipping occurs when objects are too close or too far away from the camera.

var camera = new THREE.PerspectiveCamera(fov, aspect, near, far);

OrthographicCamera

An OrthographicCamera creates a camera with an orthographic projection, with no visible perspective. The result is an isometric view. This means that all lines along the z-axis of a cube will be drawn parallel. The properties passed here are the left, right, top, bottom, near and far frustum planes. So we’re defining the visible area of our viewport.

var camera = new THREE.OrthographicCamera(left, right, top, bottom, near, far);

CameraHelper

Cameras can be difficult to test and debug. Because of this Three.js has a CameraHelper which allows you to visualize the camera in space as well as it’s frustum (viewable area). To use it you just add your camera to your scene and then pass it to the CameraHelper object and add that to your scene.

let helper = new THREE.CameraHelper(camera);
scene.add(helper);

Click here to download the demo.

Note: these examples were created using Three.js v79. Three.js is known to update frequently and sometimes cause breaking changes to the API so it may be worth checking the version you are using.