Tutorial8-1: Light [Part I]

This tutorial is the first part on the light series tutorials.
It shows how to create a simple light that light up an object.

Lesson 6 describe more technical informations about light and material. This can helps you understand how light/material are modelized by OpenGl.
 

Light properties can be defined with :
    gl.glLight*(light, parameter, value)

light is the light GL_LIGHTi, i goes from 0 to 7. Each indice is an identifier to a light. In OpenGl, we can use up to 8 lights.
parameter is a light property. Here, we will use only light with ambient and diffuse color. Other properties will be shown in next tutorials.
    GL_POSITION light position
    GL_AMBIENT  ambient light color
    GL_DIFFUSE  diffuse light color

Like with texture, to enable lighting we have to call glEnable :
    gl.glEnable(GL_LIGHTING)
Now lighting is enabled, we will enabled light source we want to use :
    gl.glEnable(GL_LIGHTi)

This code creates the light source at the position of the small sphere representing light source position :

Light source

    float[] lightDiffuse = {1.0f, 1.0f, 0.0f, 1.0f};    //yellow diffuse : color where light hit directly the object's surface
    float[] lightAmbient = {1.0f, 0.0f, 0.0f, 1.0f};    //red ambient : color applied everywhere
    float[] lightPosition= {2.0f, 2.0f, -7.0f, 1.0f};

    //Ambient light component
    gl.glLightfv(GL.GL_LIGHT0, GL.GL_AMBIENT, lightAmbient);
    //Diffuse light component
    gl.glLightfv(GL.GL_LIGHT0, GL.GL_DIFFUSE, lightDiffuse);
    //Light position
    gl.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, lightPosition);
   
    //Enable the first light and the lighting mode
    gl.glEnable(GL.GL_LIGHT0);
    gl.glEnable(GL.GL_LIGHTING);

The light source (tiny sphere) illuminating the cube :


Ambiant/Diffuse light source

 

Light illuminate a surface depending on the surface orientation (surface normal).
To light-up a shape, we need to define surface normals.
 

The normal is the perpendicular vector of the surface tangent plane (in case of the surface is curvated). If the surface is already a plane (ie traingle, quad...), the normal is just the perpendicular of the plane.
Normal must be unit vector (to optimize performance).

Normals points towards the outside of the surface. This define the side that should be lit by light rays.
In case of two sides lighting model, the inside will also be lit.

Normal vectors of the sphere are already include in the glu method.
Normals are represented in yellow on the picture of the quad :

 

Quad of Tutorial 04

Quad with normal vectors

 

Normal vector is applied to a vertice. Normal vector is used same as for color and texture coordinates that normal vector applied to a vertex is the current normal vector.
The current normal vector is defined by this :
    gl.glNormal3f(x, y, z)
(x, y, z) is the normal vector

Here, we have 6 surfaces so 6 normal vectors (one by surface).
    N1 (normal of the face 1) is along the x positive axis : (1, 0, 0)
    N2 is along the z negative axis : (0, 0, -1)
    N3 = (-1, 0, 0)
    ...
I let you search the other normal vectors.

Here is all the code to draw a white quad that can be ligh up by a source :

Quad + Normal vector

gl.glBegin(GL.GL_QUADS);
  //Quad 1
    gl.glNormal3f(1.0f, 0.0f, 0.0f);   //N1
    gl.glTexCoord2f(0.0f, 1.0f); gl.glVertex3f( size/2, size/2, size/2);   //V2
    gl.glTexCoord2f(0.0f, 0.0f); gl.glVertex3f( size/2,-size/2, size/2);   //V1
    gl.glTexCoord2f(1.0f, 0.0f); gl.glVertex3f( size/2,-size/2,-size/2);   //V3
    gl.glTexCoord2f(1.0f, 1.0f); gl.glVertex3f( size/2, size/2,-size/2);   //V4
  //Quad 2
    gl.glNormal3f(0.0f, 0.0f, -1.0f);  //N2
    gl.glTexCoord2f(0.0f, 1.0f); gl.glVertex3f( size/2, size/2,-size/2);   //V4
    gl.glTexCoord2f(0.0f, 0.0f); gl.glVertex3f( size/2,-size/2,-size/2);   //V3
    gl.glTexCoord2f(1.0f, 0.0f); gl.glVertex3f(-size/2,-size/2,-size/2);   //V5
    gl.glTexCoord2f(1.0f, 1.0f); gl.glVertex3f(-size/2, size/2,-size/2);   //V6
  //Quad 3
    gl.glNormal3f(-1.0f, 0.0f, 0.0f);  //N3
    gl.glTexCoord2f(0.0f, 1.0f); gl.glVertex3f(-size/2, size/2,-size/2);   //V6
    gl.glTexCoord2f(0.0f, 0.0f); gl.glVertex3f(-size/2,-size/2,-size/2);   //V5
    gl.glTexCoord2f(1.0f, 0.0f); gl.glVertex3f(-size/2,-size/2, size/2);   //V7
    gl.glTexCoord2f(1.0f, 1.0f); gl.glVertex3f(-size/2, size/2, size/2);   //V8
  //Quad 4
    gl.glNormal3f(0.0f, 0.0f, 1.0f);   //N4
    gl.glTexCoord2f(0.0f, 1.0f); gl.glVertex3f(-size/2, size/2, size/2);   //V8
    gl.glTexCoord2f(0.0f, 0.0f); gl.glVertex3f(-size/2,-size/2, size/2);   //V7
    gl.glTexCoord2f(1.0f, 0.0f); gl.glVertex3f( size/2,-size/2, size/2);   //V1
    gl.glTexCoord2f(1.0f, 1.0f); gl.glVertex3f( size/2, size/2, size/2);   //V2
  //Quad 5
    gl.glNormal3f(0.0f, 1.0f, 0.0f);   //N5
    gl.glTexCoord2f(0.0f, 1.0f); gl.glVertex3f(-size/2, size/2,-size/2);   //V6
    gl.glTexCoord2f(0.0f, 0.0f); gl.glVertex3f(-size/2, size/2, size/2);   //V8
    gl.glTexCoord2f(1.0f, 0.0f); gl.glVertex3f( size/2, size/2, size/2);   //V2
    gl.glTexCoord2f(1.0f, 1.0f); gl.glVertex3f( size/2, size/2,-size/2);   //V4
  //Quad 6
    gl.glNormal3f(1.0f, -1.0f, 0.0f);  //N6
    gl.glTexCoord2f(0.0f, 1.0f); gl.glVertex3f(-size/2,-size/2, size/2);   //V7
    gl.glTexCoord2f(0.0f, 0.0f); gl.glVertex3f(-size/2,-size/2,-size/2);   //V5
    gl.glTexCoord2f(1.0f, 0.0f); gl.glVertex3f( size/2,-size/2,-size/2);   //V3
    gl.glTexCoord2f(1.0f, 1.0f); gl.glVertex3f( size/2,-size/2, size/2);   //V1
gl.glEnd();


 

Previously, we have seen how to intuitively find normals on a simple cube.

To find normals on more complex shapes, this approach become unusable. That's why I will describe a pure mathematic approach to find normals for your shapes.
Complex shapes are mostly made of a lot of tiny triangles and/or quads, they are called face. This is an approximation of the real shape. For example, a sphere is composed of many quads. Number of quads is in direct relation to the sphere quality.

Now, supposed any plane for which we now 3 points (ex 3 vertices of a triangle).
With these points, we can trace 2 lines on the plane (edge1 and edge2). With a cross product of these two lines, we find a vector perpendicular to these line ie perpendicular to the surface. So, we have found a normal vector of the surface.
 

Triangle/Quad normals

    //Extract 3 vertices of a quad/triangle
    Vector3f v0 = ...;
    Vector3f v1 = ...;
    Vector3f v2 = ...;

    ///Calculate two edge of the triangle
    Vector3f edge1 = subtract(v0, v1);
    Vector3f edge2 = subtract(v1, v2);

    //Calculate face normal
    Vector3f normal = normalize(crossProduct(edge1, edge2));


Does the normal found point towards the outside or the inside of the shape ?

The answer can't be determinated because outside or inside notions are abstract visual notions.
This question can be reformulated as Does the normal found is the front face's normal or the back face normal ?

If the face is a triangle/quad/polygon, the normal calculated here is the normal of the front face if the face is drawn this way :

Face rendering for front face normal

    //Face rendering (per face lighting)
    gl.glNormal3f(normal.x; normal.y, normal.z);                      //Normal
    gl.glTexCoord2f(uv0.t, uv0.t); gl.glVertex3f(v0.x, v0.y, v0.z);   //v0
    gl.glTexCoord2f(uv1.t, uv1.t); gl.glVertex3f(v1.x, v1.y, v1.z);   //v1
    gl.glTexCoord2f(uv2.t, uv2.t); gl.glVertex3f(v2.x, v2.y, v2.z);   //v2
    ....

 

Before, we have calculated face normals. Resulting lighting is a per face lighting, a face have the same normal everywhere. This result to a flat transition between face.

Per vertex lighting is a smooth version of per face lighting. Instead of defining a normal at each face, we define a normal at each vertice.
This result to a smoother rendering result. Such lighting contribte to improve rendering quality of curvated shapes.
To compute per vertex lighting, start by calculating each face normals. Then, loop over each vertices and find all face which share this vertice. The vertice normal is the mean of all face's normal found. Calculating per vertex normal at runtime is intensive cpu calculation, especially for big shapes.
 

Face rendering for front face normal

    //Face rendering (per vertex lighting)
    gl.glNormal3f(n0.x; n0.y, n0.z); gl.glTexCoord2f(uv0.t, uv0.t); gl.glVertex3f(v0.x, v0.y, v0.z);   //v0
    gl.glNormal3f(n1.x; n1.y, n1.z); gl.glTexCoord2f(uv1.t, uv1.t); gl.glVertex3f(v1.x, v1.y, v1.z);   //v1
    gl.glNormal3f(n2.x; n2.y, n2.z); gl.glTexCoord2f(uv2.t, uv2.t); gl.glVertex3f(v2.x, v2.y, v2.z);   //v2
    ....

Per pixel lighting is the bester lighting model. This can't be implemented in a classic rendering because we have only have access to vertice.
Such lighting model can be implemented on (recent) gpu using shaders.