Adding new lights and shadows in Three.js is very simple and is implemented in pretty much the same way as we explained in the previous section. We start by adding a light source to the scene as follows:
Spotlights are effectively a point light with a cone attached where the light only shines inside the cone. There's actually 2 cones. An outer cone and an inner cone. Between the inner cone and the outer cone the light fades from full intensity to zero.
To use a SpotLight we need a target just like the directional light. The light's cone will open toward the target.
// White spotlight shining from the side, casting a shadow
var spotLight
=
new
THREE.SpotLight
(0xFFFFFF);
spotlight.position.set(70, 70, 70);
spotLight.castShadow = true;
spotlight.shadow.mapSize
=
new
THREE.Vector2
(1024, 1024);
spotLight.shadow.camera.far = 130;
spotLight.shadow.camera.near = 30;
THREE.SpotLight illuminates our scene from its position "spotLight.position.set( 70, 70, 70 )". We tell it that we want it to cast a shadow by setting the castShadow to true. In the code, you can see that we also set some additional properties on the light: shadow.mapSize, shadow.camera.far and shadow.camera.near.
Without going into too much detail, these properties define how sharp and detailed our rendered shadow will appear. If we render the scene this time, however, you won't see any difference from the previous one. The reason is that different materials respond differently to light.
Shadows on computers can be a complicated topic. There are various solutions and all of them have tradeoffs including the solutions available in three.js.
Three.js by default uses shadow maps. The way a shadow map works is, for every light that casts shadows all objects marked to cast shadows are rendered from the point of view of the light.
In other words, if you have 20 objects, and 5 lights, and all 20 objects are casting shadows and all 5 lights are casting shadows then your entire scene will be drawn 6 times. All 20 objects will be drawn for light #1, then all 20 objects will be drawn for light #2, then #3, etc and finally the actual scene will be drawn using data from the first 5 renders.
It gets worse, if you have a point light casting shadows the scene has to be drawn 6 times just for that light!
For these reasons it's common to find other solutions than to have a bunch of lights all generating shadows. One common solution is to have multiple lights but only one directional light generating shadows.
What is still missing, though, are the shadows. Rendering shadows takes a lot of computing power and, for that reason, shadows are disabled by default in Three.js. Enabling them, though, is very easy. For shadows, we have to change the source in a couple of places, as follows:
var renderer
=
new
renderer.WebGLRenderer();
renderer.setClearColor
(0x000000); // Background color
renderer.setSize
(window.innerWidth, window.innerHeight); // Size of canvas
renderer.shadowMap.
Enabled = true;
The first change we need to make is to tell renderer that we want shadows. You do this by setting the shadowMap.Enabled property to true. If you look at the result from this change, you won't notice anything different yet. That is because we need to explicitly define which objects cast shadows and which objects receive shadows. In our example, we want the sphere and the cube to cast shadows on the ground plane. You do this by setting the corresponding properties on those objects:
// Create a scene
var scene
=
new
THREE.Scene();
// The ground
var plane
=
new
THREE.PlaneGeometry
(width = 127, height = 127, depth = 127);
var material
=
new
THREE.MeshLambertMaterial(0xffffff);
var planeMesh
=
new
THREE.Mesh
(plane, material);
planeMesh.position.set(0,0,0);
planeMesh.receiveShadow =
true;
scene.add(planeMesh); // Add the plane to the scene
// The cube
var cubeGeometry
=
new
THREE.BoxGeometry
(width = 13, height = 13, depth = 13);
material
=
new
THREE.MeshLambertMaterial({color: 0xff3300});
var cubeMesh
=
new
THREE.Mesh
(cubeGeometry, material);
cubeMesh.castShadow =
true;
cubeMesh.position.set(x = 0, y = 0, z = 17);
scene.add(cubeMesh);
Now, there is just one more thing to do to get the shadows. We need to define which light sources in our scene will cast shadows. We only need to set the correct property, as shown in the following line of code, and the shadows will finally be rendered:
spotLight.castShadow = true;
scene.add(spotLight);
And, with this, we get a scene complete with shadows from our light source, as follows:
Demo