Material and Lighting

The Lambert and Phong Models

Reading: Most of what I know about Light, Material and the Phong Model, I learned from Angel, chapter 6, and the Red Book (chapter 5 in the 3rd edition, chapter 6 in the 1st edition).

You should also read Dirksen, Chapter 3, on Light sources (pages 66-82) and Chapter 4 on Materials (pages 93-113).

The following description uses the term dot product, a mathematical operation on two vectors, to compute the cosine of the angle between the vectors. If you're mystified about that and don't want to take it on faith, you can go to the next reading on geometry.

Lighting Models

You'll notice that when we color objects directly using RGB, there is no shading or other realistic effects. They're just cartoon objects. In fact, since there is no shading, it's impossible to see where two faces meet unless they are different colors.

Lighting models are a replacement for direct color (where we directly specify what color something is using RGB). Instead, the actual RGB values are computed based on properties of the object, the lights in the scene, and so forth.

There are several kinds of lighting models used in Computer Graphics, and within those kinds, there are many algorithms. Let's first lay out the landscape, and then explore what's available in OpenGL and Three.js. The two primary categories of lighting are

Global lighting models take into account interactions of light with objects in the room. For example:

Global lighting algorithms fall into two basic categories:

Global lighting models are very expensive to compute. According to Tony DeRose, rendering a single frame of the Pixar movie Finding Nemo took four hours. For The Incredibles, the next Pixar movie, rendering each frame took ten hours, which means that the algorithms have gotten more expensive even though the hardware is speeding up.

Local lighting models are perfect for a pipeline architecture like OpenGL's, because very little information is taken into account in choosing the RGB. This enhances speed at the price of quality. To determine the color of a polygon, we need the following information:

Three.js manages to fall somewhere in between, because it can use the Scene Graph data structure to compute shadows, but it doesn't do the full ray-tracing or radiosity computation. We'll look at shadows later, or you can look it up in Dirksen's book. Three.js makes them quite easy.

The rest of this page describes local lighting models, as in Three.js. The mathematical lighting models that are used by Three.js are the Lambert Model and the Phong model. The Phong Model is a superset of the Lambert Model, so we'll start with the Lambert and then extend it to the Phong model. We're going to proceed in a bottom-up fashion, first explaining the conceptual building blocks, before we see how they all fit together.

Local Lighting

To see a demo of what we'll be able to accomplish with material and lighting, run the lit teddy bear demo:

TeddyBear.shtml

There are three lights that you can control: ambient light, a point light and a directional light. We'll learn more about those soon.

Material Types

Because local lighting is focussed on speed, a great many simplifications are made. Many of these may seem simplistic or even bizarre.

The first thing is to say that there are only three ways that light can reflect off a surface.

So, we really only need to understand diffuse and specular surfaces. The modeling of diffuse reflection of light has the brightness of the surface computed by Lambert's cosine law. See the Wikipedia article on Lambertian reflectance. The modeling of specular reflect uses a model by Bui Tuong Phong and so is called the Phong reflection model. (Click through to that last wikipedia entry, because the first figure is very good.) His computation includes Lambertian as a special case. Three.js calls these materials Lambertian and Phong, so we will too.

Kinds of Light

In talking about kinds of material, we divided them into diffuse (Lambert) and specular (Phong). (We're not going to talk about translucent in this reading.) Of course, most materials have some of each: you get color from the diffuse properties of, say, leather, but a shine of specular highlight at the right angle.

A major part of the Phong light model, then, is light interacting with these two properties of material. The model, therefore, divides light into different kinds, so thatdiffuse light interacts withdiffuse material property andspecular light interacts withspecular material property. It seems weird to say there are different kinds of light, but it's just to have a number corresponding to the numbers describing the materials; these numbers get multiplied together in the models.

The three kinds of light are:

As we just said, the diffuse and specular light components interact with the corresponding material properties.

What is ambient light? As you might guess from the name, it's the light all around us. In most real-world scenes, there is lots of light around, bouncing off objects and so forth. We can call this ambient light: light that comes from nowhere in particular. Thus, ambient lightindirectnon-directional. It's the local-lighting equivalent of radiosity.

Even though in local lighting, we don't trace ambient light rays back to a specific light source, there is still a connection. This is because, in the real world, when you turn on a light in a room, the whole room becomes a bit brighter. Thus, each Three.js light source can add a bit of ambient light to the scene, brightening every object.

That ambient light interacts with the ambient property of a material. Because of the way it's used, a material's ambient property is often exactly the same color as the diffuse property, but they need not be.

Thus, each material also has the three properties: ambient, diffuse, and specular. We'll get into the exact mathematics later, but for now, you can think of these properties as colors. For example, the ambient property of brown leather is, well, brown, so that when white ambient light falls on it, the leather looks brown. Similarly, the diffuse property is brown. The specular property of the leather is probably gray (colorless), because when white specular light reflects off shiny leather, the reflected light is white, not brown.

Light Sources

In Three.js, you create a light source as an object and add it to the scene. Lights that are part of the scene contribute to the lighting of the objects in the scene. The lights themselves are not visible, even if the camera is staring straight at them. Lights only manifest themselves by illuminating objects and interacting with the objects' materials. (You can of course, put a sphere or something at the location of a light, if you want.) Three.js has some helper objects that can do this for you.

Ambient Lights

As we said above, ambient light is generalized, non-directional light that illuminates all objects equally, regardless of their physical or geometrical relationship to any light source. Thus, the location of an ambient light source is irrelevant.

In Three.js, you can create an ambient light like this;

var light0 = new THREE.AmbientLight( 0x202020 ); // 10%

That light is a very dark gray, contributing just a little brightening to the scene.

Point Sources

A common source of light in Three.js is a point source. You can think of it as a small light bulb, radiating light in all directions around it. This is somewhat unrealistic, but it's easy to compute with.

To do this in Three.js, we use a PointLight:

// white light, 50% intensity, falling to zero in 80 units:
var light1 = new THREE.PointLight( TW.WHITE, 0.5, 80 );
light1.position.set( 0, 10, 10 );
scene.add(light1);

Like many of the light types in Three.js, but unlike the ambient light, a point light has an intensity. This is just a factor that the lighting computation is multiplied by, so you can use this to make a light brighter or dimmer.

Also, point lights can attenuate with distance, so that the intensity goes down for surfaces that are farther from the point light. In the real world, light attenuates inversely to the square of distance (because the light energy is spread out over the surface of a sphere). Thus, if there were a planet that were half the distance from the sun as the earth is, it would get four times as much solar energy. However, years of experience in Computer Graphics has shown that this bit of physics seems to make lights fade too quickly and so, instead, a linear attenuation is used. The third argument to THREE.PointLight is the distance at which this light's intensity attenuates to zero. In the example above, the light is at zero intensity 80 units from the point (0,10,10), half intensity at 40 units away, three-quarters intensity at 20 units away, and so forth.

Directional Lights

A directional light casts parallel light rays throughout the scene. You can think of it as a point source infinitely far away in a particular direction, and indeed, the mathematical model of it is like that.

To do this in Three.js, we use a DirectionalLight:

  var dirlight = new THREE.DirectionalLight( TW.WHITE, 0.3 );
  dirlight.position.set( 0, 1, 0 ); // the direction the light comes from
  scene.add(dirlight);

Lambert Model

Phong's model combines the ambient, diffuse and specular into one big, hairy equation. We'll first address the Lambertian part of it, and then get to the Phong model.

First, we need some notational building blocks:

In the model, we will have parameters captured in the vector L that represent the intensity of the incoming light. We saw these in the Three.js code above. Turn 'em up and the light gets brighter.

We also have to worry about how much of the incoming light gets reflected. Let this be a number called R. This number is a fraction, so if R=0.8, that means that 80 percent of the incoming light is reflected. (We actually have 9 such numbers, such as the reflection fraction for specular red, ambient green, and so on for all 9 combinations.)

As we discussed earlier, in general, R can depend on:

The light that gets reflected is the product of the incoming light intensity, L, and the fraction R:

I = L R

That is, the intensity of light that is reflected (and ends up on the image plane and the framebuffer) is the incoming light intensity multiplied by the reflection number.

(Remember, that the previous equation is just shorthand for the sum of 9 computations multiplying the ambient/diffuse/specular, red/green/blue light by the ambient/diffuse/specular, red/green/blue material.)

And that's just for one light! If we have multiple lights, we have add up more contributions.

This leads to the problem of overdriving the lighting, where every material turns white because there's so much light falling on it. This happens sometimes in practice: you have a decently lit scene, and you add another light, and then you have to turn down your original lights (and your ambient) to get the balance right.

Why does R depend on the light source? That is, why does the reflection fraction depend on which light we're talking about? Because the direction and distance change. But since all the light sources work the same way, we're not going to worry about which light source it is, and we'll just have our abstract Lambert Model:

I = Lamb Ramb + Ldiff Rdiff
Abstract Lambert Model

That is, the intensity of the color of an object is

That equation is our abstract Lambert model. Now, let's see how to compute the two R values.

Ambient

Reflection of ambient light obviously doesn't depend on direction or distance or orientation, so it's solely based on the material property: is the material dark or light? Note that it can be dark for blue and light for red and green. If white light falls on such a material, what does it look like? So, Ramb is a simple constant, which we will call kamb, just to remind ourselves that it's a constant:

Ramb = kamb

Note that 0 ≤ kamb ≤ 1. Why?

This constant is chosen by you the programmer as part of the material properties for an object, in the same way that you choose color. There are actually three such values, one each for red, green, and blue.

Diffuse/Lambertian

For Lambertian/diffuse surfaces, we assume that light scatters in all directions. (In lay person's vocabulary, such surfaces are often called matte.)

However, the angle of the light does matter, because the energy (photons) are spread over a larger area. Consequently, we have

Rdiff = kdiff LVNV

That is, the amount of reflection from a diffuse surface is the product of a constant, chosen by you the programmer, that is multiplied by the cosine of the angle between LV and NV. As before, there are actually 3 such constants, one each for red, green and blue. Recall that LV is the light vector, the direction to the light source, and that NV is the normal vector, the orientation of the surface, and the dot product of two normalized vectors gives the cosine of the angle between them. (Remember, the dot product is covered in the next reading on geometry.)

Now, we finally have our finished Lambert Model:

I = Lamb kamb + Ldiff kdiffLVNV
Concrete Lambert Model

Cosines

In the description of the Lambert model, the cosine function was critical to determining how the light was spread out over the surface, depending on the angle with the surface normal. The geometry handout explains how simple it can be to find the cosine of the angle between two vectors, especially if they are normalized. Assuming vectors are normalized, we can compute cosines by a simple dot product: three multiplies and two adds. So, that cosine is quick to compute.

Lambertian Materials in Three.js

In Three.js, you can make a Lambertian material like this:

var mat = new THREE.MeshLambertMaterial(
    {color: cyan,
     ambient: teal});

Of course, you'd substitute color specifications for the cyan and teal. The color parameter is the diffuse property. Typically, the ambient color matches the diffuse color, or is at least similar.

Note how using color specifications like this is a shorthand for specifying reflection coefficients for the red, green and blue primaries.

Phong Model

Now, let's turn to the full Phong model by adding in specular reflections. With specular reflections, the material is (locally) smooth and is acting like a mirror. The incoming light rays bounce off the material and head off at an angle equal to their incoming First, we need some more notational building blocks:

The following figure illustrates these vectors:

the eye vector (purple) and the reflection vector (red)
The blue vector is the incoming light. The red vector is RV, the reflection vector. The purple vector is EV, the vector from the surface to the eye. If RV and EV are close, the specular highlight will be visible.

If the direction of our view, EV, is near the reflection direction, RV, we should get a bunch of that reflected light. This is called a specular highlight.

Rspec = kspec (EVRV)e

The dot product is large when the two vectors are lined up. The e exponent is a number that gives the shininess. The higher the shininess, the smaller the spotlight, because the dot product (which is less than one), is raised to a higher power. OpenGL allows e to be between zero and 128. In addition to e, the OpenGL programmer gets to choose the specularity coefficient, kspec for each of red, green and blue. As usual, the specularity coefficient is between zero and one.

The Complete Phong Model

Adding this last part to the Lambert model gets us the Phong model:

I = Lamb Ramb + Ldiff Rdiff + Lspec Rspec
Abstract Phong Model

Filling in mathematical models, we get:

I = Lamb kamb + Ldiff kdiffLVNV + Lspec kspec(EVRV)e
Concrete Phong Model

Phong Materials in Three.js

All that work is to understand the following deceptively simple lines of code to set up a Phong Material in Three.js:

var mat = new THREE.MeshPhongMaterial(
    {color: cyan,
     ambient: teal,
     specular: 0xCCCCCC,
     shininess: 30});

In the example above, we used 0xCCCCCC (silver) as the specular color. Since 0xCC is 204, and 204/255 is 0.8, this amounts to a specularity coefficient of 80 percent, equally for Red, Green, and Blue. This material will reflect 80% of all specular light falling on it.

The dot product of the reflection vector and the eye vector is raised to the 30th power, which makes this a fairly shiny object.

Flat versus Smooth Shading

To begin, look at the following picture: Flat versus Phong shading

The graphics card renders triangles (fragments) based on the properties of the its vertices. In particular, if the surface normal at the three vertices is different, the lighting calculation will differ and the computed color at the three vertices will be different.

You can request that all the pixels of the triangle be given the same color as the first vertex, or the color can be interpolated across the triangle. Giving all the pixels the same color is called flat shading and interpolating the color is called smooth shading. That's because flat shading is appropriate for a polyhedron that is modeling a polyhedral (flat-sided) object, while smooth shading is appropriate for a polyhedron that is an approximation of a smooth surface.

Consider the following picture:

flat versus smooth shading
The magenta and teal objects have the same three vertices and the same shape, but the teal object is flat-sided, like a faceted jewel, while the magenta object is smooth-sided, like a ball. The surface normals (brown arrows) would be different for the jewel and the ball. More importantly, because the color between the pairs of vertices on the magenta object are different (different normals), the color will be interpolated between them. In the jewel, the normals are the same (the middle point has two vertices: one belonging to the upper face and one belonging to the lower face, each with a surface normal that is correct for that face).

Here is a demo using Three.js that contrasts smooth shading and flat shading: Jewel and Ball.

Spotlights

A spotlight is just a point source limited by a cone of angle θ (Think of those Luxo lights, made famous by Pixar; google for it.) Intensity is greatest in the direction that the spotlight is pointing and trailing off towards the sides, but dropping to zero when you read the cutoff angle θ. One way to implement that is let α be the angle between the spotlight direction and the direction that we're interested in. When α=0, the intensity is at its maximum (I=I0). The cosine function has that property, so we use it as a building block. To give us additional control of the speed at which the intensity drops off (therefore, how concentrated the spotlight is), we allow the user to choose an exponent, e, for the cosine function. The resulting function is:

I = I0 cose(α) if α < θ, else zero

To do this in Three.js, we use a SpotLight:

  var spotlight = new THREE.SpotLight( color, intensity, distance, cutoffAngle, exponent)
  spotlight.position.set( 1, 2, 3);
  scene.add(dirlight);

Summary