Tuesday, October 1, 2013

Adding Lights with Three.js (part 1)

Now it's time for one of the most important elements in 3D games and graphics:  lights and lighting.   The Three.js library offers us some of the latest and most realistic lighting and shading options available in today's web browsers.   There are a lot of options for types of lights, types of surface materials on the subjects that are lit,  and equations/algorithms that affect the final look of the lighted scene.  This will be a multi-part post because we have to cover quite a bit of setup for lighting to work well in Three.js.  
First, we have to change the material (or skin) of our colored cube so that it will correctly respond to lights in the game world.  If you recall, here is the old way of setting up the material/skin of our green-colored cube:
var material = new THREE.MeshBasicMaterial({ color: 'rgb(0,255,0)' });
Unfortunately, this way of setting up a 'Basic' material will only shade the cube with the rgb color that you manually give to it. It will not respond to lighting situations like a cube would in real life.  It was fun a couple of posts ago, when we manually changed the color from dark to bright red, but to achieve realistic lighting and shading, we have to change the material.  Here is the new way of declaring our material/skin:  
var material = new THREE.MeshLambertMaterial({ color: 'rgb(0,255,0)' });
It almost looks the same doesn't it?  The only word that has changed is the 'Basic' is now replaced with 'Lambert'.  Without getting too deep into lighting theory and algorithms, 'Lambert' is a lighting/shading mathematical model that takes the value of the incoming light (it's intensity and angle of approach) and shades the sides of our cube accordingly.  Let's say we have the Sun rising on our right-hand side.  And we are looking straight ahead toward a cube sitting on the ground in front of us.  Since the Sun is on the right, the cube's right side will be brightly lit, while its left-hand sides that are facing away from the Sun will be darker, or in shadow.  The Lambert model does all the lighting math for us and appropriately shades the cube.
There are many other lighting mathematical models out there, but for now we will stick with Lambert because it is fast and efficient.  Later we will also look at the 'Phong' model, which is even more realistic because it calculates specular reflections (highlights on shiny surfaces) as well.  But it is more taxing on your device's display/graphics card, so we will use the 'Phong' model only sparingly in the future.

We now have a material skin that will respond to lights.  But if we were to run the program, our green cube would appear black on all sides!  This is because the 'Lambert' material needs a light source for us to see anything.  Lights in Three.js are declared and set up much like everything else in Three.js.  The only difference is that Three.js lights take some different parameters inside the parentheses ( ) that I will explain in a moment.  Here's what the light setup code looks like:
var light = new THREE.PointLight( 'rgb(255,255,255)', 1, 0 );
By now most of this coding style should look familiar.  We use the 'var' keyword followed by a descriptive name for our light object, in this case 'light'.  Then we fill it by typing ' = new ' followed by a call to THREE.PointLight(); A point light in 3D graphics is a light source that has a definite position or location in the world.  Think of it as a really bright light-bulb.  In the real world, a light-bulb emits light in all directions equally.  And in the real world you can pick up a light-bulb and place it anywhere you want.  The same holds true for the 'PointLight' in Three.js.  It radiates light outward in all directions, and we can later specify the PointLight's location using its x, y, and z coordinates.
A Three.js 'PointLight' accepts optional parameters inside its parentheses when you first declare it in your program. These are: ( 'Color', Intensity, and Falloff-distance ).  Looking at the above code example, I typed 'rgb(255,255,255)' for the Color parameter, which is the rgb code for the brightest white color possible. Next I typed a '1' for the Intensity parameter (which can range from 0 to 1), meaning I want this to be a strong, powerful light source (think of the Sun).  Lastly I put a '0' for the Falloff distance parameter.  This means I don't want any Falloff at all, specified by a '0' in Three.js.  To explain; every light source in the real world has a falloff-distance at which the light runs out and becomes too weak to substantially light up a surface.  For computer games played in an Earth environment, the Sun point light has no falloff-distance and for us humans it is equally bright no matter where we move to on the Earth.  Technically, if we were to blast off in a super-fast spaceship and get really far away from the Sun, we would begin to notice it does have a falloff where it can no longer provide light and warmth for us.  But for all practical purposes, we can assume that games modeled with the Sun in mind, have the light source as not falling off at all, so we use a '0' for this parameter.  However, if you wanted to have a tiny LED light or light-bulb in your game, this realistically does have a noticeable falloff distance.  So, to create that type of light, we would type '100' (or similar value) for the Falloff-distance parameter, which sets the reaches of the light to no more than 100 units.  The power/intensity of the light will also smoothly decrease as it reaches this outer limit.   This means that if you start traveling away from the light source, it will get darker and darker around you, and if you go out farther than '100' units from the light, you will be in total darkness.

Next, we must position our new PointLight in the game world.  We do this by using the name we gave it, then typing '.position.set(x,y,z); '  - where 'x,y,z' are the coordinates where you would like the light to be located at.  Let's say that we want to pick up the light and move it to the right of us, and a little upwards in the sky, and a little bit behind our backs.  All values in this case will be positive numbers.  Remember that positive X goes to the right, positive Y goes up, and positive Z goes behind you. Here's what this light positioning looks like in code:
light.position.set(50,50,50);
In the above code snippet, we type the name of our light, which is 'light', followed by '.position' which accesses the light's position property, followed by '.set( )' which sets the location of the light source.  Inside the parentheses, I put '50' for the x, y, and z coordinates, thus moving the light right, upward, and behind us.  In the next post we will tinker with these numbers to dynamically move the light (the Sun) up and down.

Finally we have to add our newly created 'light' to the scene, so that it can start lighting up the game objects. Here's how that looks:  
scene.add(light);
Remember that 'scene' is the name we gave to our Three.js scene at the beginning of the program.  At this point, we have added both the 'cube' and the 'light' now to the 'scene' object.  Because we have added everything, Three.js will do its magic with the help of the 'Lambert' lighting model and correctly shade our green-colored cube.  I removed the camera animation code from earlier examples, so that the cube will just sit in one place and rotate smoothly.  Copy and Paste the following code and then save it as 'tempGame.js' , overwriting the old one:   

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);

var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

var geometry = new THREE.CubeGeometry(1,1,1);
var material = new THREE.MeshLambertMaterial({ color: 'rgb(0,255,0)' });
var cube = new THREE.Mesh(geometry, material);
scene.add(cube);

var light = new THREE.PointLight( 'rgb(255,255,255)', 1, 0 );
light.position.set(50,50,50);
scene.add(light);

camera.position.z = 3;
animate();

function animate(){
   
   requestAnimationFrame(animate);

   cube.rotation.x = cube.rotation.x + 0.02;
   cube.rotation.y = cube.rotation.y + 0.03;

   renderer.render( scene, camera );

}

Once you have saved it, go back to your example01.html file and open with your Chrome browser.  If you saved everything correctly, you should see our old green cube now magically lit by artificial sunlight - pretty cool!  And since we placed the light source on the right, upward, and behind us, the right side of the cube and the top of the cube is more brightly lit than the left and bottom, which are in shadow.

In part 2 we will use our trusty oscillation code to move the Sun quickly up and down, and watch how it affects the shading of the cube.  Although, we mustn't let all this power of moving Suns go to our heads!  :-) 

continued in part 2...