Lighting and materials

Back to index

When a JOGL scene is 'lit', the appearance is an interaction between light sources and materials. This means it is slightly intricate. We will take it slowly.

Feel the ambience

To start with we will just turn on lighting and draw something. In init we have

    public void init(GLAutoDrawable gLDrawable) {
        final GL gl = gLDrawable.getGL();
        whiteMaterial=make(new float[]{1.0f, 1.0f, 1.0f, 1.0f});
        gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
        gl.glShadeModel(GL.GL_SMOOTH);
        gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL.GL_AMBIENT, whiteMaterial);
        gl.glEnable(GL.GL_LIGHTING);
        
        gl.glEnable(GL.GL_DEPTH_TEST);
        gl.glClearDepth(1.0f);                      
        gl.glDepthFunc(GL.GL_LEQUAL);
        gl.glHint(GL.GL_PERSPECTIVE_CORRECTION_HINT, GL.GL_NICEST);

        gLDrawable.addKeyListener(this);
    }

whiteMaterial represents a white surface. It has been declared as a FloatBuffer. The statement

gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL.GL_AMBIENT, whiteMaterial);

means we want to use this material. The function needs a FloatBuffer, so a little utility changes a float array into a FloatBuffer:

private FloatBuffer make(float[] values) {
        FloatBuffer floatBuffer = BufferUtil.newFloatBuffer(values.length);
        for (int i = 0; i < values.length; i++) {
            floatBuffer.put(values[i]);
        }
        floatBuffer.rewind();
        return floatBuffer;
    }

Then in display we just draw a sphere:

    public void display(GLAutoDrawable gLDrawable) {
        final GL gl = gLDrawable.getGL();
        gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
        gl.glLoadIdentity();// Reset The View    

        glu.gluLookAt(cameraX, cameraY, cameraZ, 0, 0, 0.0, 0.0, 1.0, 0.0);
        glut.glutSolidSphere(1.0, 20, 20);
     
    }

The result is

l1

You are probably not too impressed - but it is simple. We've just set a material and turned on lighting. In fact this mostly uses default values for everything we have not set.

This first example uses ambient lighting. This is light which comes from everywhere, like sunlight on a cloudy day. Because it has no direction it produces no modelling, which is why our sphere looks flat. We can alter the default setting by choosing the colour of the ambient light:

FloatBuffer lmodel_ambient=make(new float[]{0.6f, 0.0f, 0.0f, 1.0f});

gl.glLightModelfv(GL.GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);

The first three parameters are the red, green and blue components - so this is red. The fourth parameter is the alpha value, unused except when using blending (which we are not, yet). The result is

2

Kinds of lighting

This is ambient lighting. JOGL also handles 2 other kinds - diffuse and specular. Diffuse lighting comes from one place or direction - not everywhere as ambient does. Specular is for sparkly reflections on metallic or glassy surfaces.

For a material, we can specify how it casts back ambient, diffuse and specular light. In the above example,

gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL.GL_AMBIENT, whiteMaterial);

means we are selecting the white material to reflect ambient light (which is all we have so far).

Light sources

JOGL lighting allows you to have at least 9 light sources in the scene - though usually 1 is enough. In addition there is the ambient light which has no light source - which is what we used in the last example.

For each light source, we can program what colour it sends out, for each of the 3 kinds of light - ambient, diffuse and specular. Since we get ambient light without a source, there is not much point setting up ambient light from a source, and it wastes processing time.

Light source example

Here is the sphere, with ambient light dull grey, and with a light source sending out red, above and to the right:

3

We now have a directional light, so the modelling finally reveals some 3D information.

Here is the init:

public void init(GLAutoDrawable gLDrawable) {
        final GL gl = gLDrawable.getGL();

        whiteMaterial = make(new float[]{1.0f, 1.0f, 1.0f, 1.0f});
       
        FloatBuffer lmodel_ambient = make(new float[]{0.2f, 0.2f, 0.2f, 1.0f});
        gl.glLightModelfv(GL.GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);


        gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
        gl.glShadeModel(GL.GL_SMOOTH);

        gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL.GL_AMBIENT, whiteMaterial);
        // light is positional
        light_position = make(new float[]{2.0f, 2.0f, 0.0f, 1.0f});
        // red diffuse light
        FloatBuffer light_diffuse = make(new float[]{1.0f, 0.0f, 0.0f, 1.0f});
        // on light0
        gl.glLightfv(GL.GL_LIGHT0, GL.GL_DIFFUSE, light_diffuse);

        gl.glEnable(GL.GL_LIGHTING);
        gl.glEnable(GL.GL_LIGHT0);
        gl.glEnable(GL.GL_DEPTH_TEST);

        gl.glClearDepth(1.0f);                      

        gl.glDepthFunc(GL.GL_LEQUAL);

        gl.glHint(GL.GL_PERSPECTIVE_CORRECTION_HINT, GL.GL_NICEST)

        gLDrawable.addKeyListener(this);
    }

We've set up here light_position, a FloatBuffer, which we will use to position the light. The first 3 parameters are x y and z, and the fourth being non-zero means it is a positioned light, located at x,y,z. If the 4th parameter had been zero, it would have been a directional light, located at infinity and with light coming from that direction - like the Sun on a cloudless day.

We've then set up light_diffuse, to be a red light, and told LIGHT_O to use that as its diffuse light (it has no ambient or specular at present).
We then enabled LIGHT_0 - switched it on.

    public void display(GLAutoDrawable gLDrawable) {
        final GL gl = gLDrawable.getGL();
        gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
        gl.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, light_position);
        gl.glLoadIdentity();// Reset The View    

        glu.gluLookAt(cameraX, cameraY, cameraZ, 0, 0, 0.0, 0.0, 1.0, 0.0);
        glut.glutSolidSphere(1.0, 20, 20);
  
    
    }

Drawing lit things

Look at this:

4

This draws a checkerboard as well. The code to do this is

    private void drawSheet(GL gl) {
        boolean flag = true;
        float[] n = {0.0f, 1.0f, 0.0f};
        for (int i = -10; i < 11; i++) {
            for (int j = -10; j < 11; j++) {
                if (flag) {
                    gl.glMaterialfv(GL.GL_FRONT, GL.GL_AMBIENT_AND_DIFFUSE, whiteMaterial);
                } else {
                    gl.glMaterialfv(GL.GL_FRONT, GL.GL_AMBIENT_AND_DIFFUSE, blackMaterial);
                }
                flag = !flag;
                gl.glBegin(GL.GL_QUADS);

                gl.glNormal3fv(n, 0);
                gl.glVertex3d(i, 0, j);
                gl.glNormal3fv(n, 0);
                gl.glVertex3d(i, 0, j - 1);
                gl.glNormal3fv(n, 0);
                gl.glVertex3d(i - 1, 0, j - 1);
                gl.glNormal3fv(n, 0);
                gl.glVertex3d(i - 1, 0, j);
                gl.glEnd();
            }
        }
    }

So this has a nested loop, where we draw 10 by 10 squares by using GL_QUADS. However we also need to specify the direction of the normal at each vertex. This is because the interaction with the light sources will depend on which way the surface is pointing. In this case its easy - the squares lie on the x,z plane and all normals just point up - so 0,1,0.

Also we alternate between using white and black material The black material was set up in init by:

blackMaterial =make(new float[]{0.0f, 0.0f, 0.0f, 1.0f});

Adding specular light

This produces:

5

We needed to tell LIGHT0 that it should send out blue specular light, and how it should be reflected:

        whiteMaterial = make(new float[]{1.0f, 1.0f, 1.0f, 1.0f});
        blackMaterial =make(new float[]{0.0f, 0.0f, 0.0f, 1.0f});
        FloatBuffer mat_shininess=make(new float[]{100.0f});

        FloatBuffer lmodel_ambient = make(new float[]{0.2f, 0.2f, 0.2f, 1.0f});
        gl.glLightModelfv(GL.GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);

        gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
        gl.glShadeModel(GL.GL_SMOOTH); 
        // how to reflect specular light
        gl.glMaterialfv(GL.GL_FRONT, GL.GL_SPECULAR, whiteMaterial);        // how shiny to be
        gl.glMaterialfv(GL.GL_FRONT, GL.GL_SHININESS, mat_shininess);
        gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL.GL_AMBIENT, whiteMaterial);
        // light is positional
        light_position = make(new float[]{2.0f, 2.0f, 0.0f, 1.0f});
        FloatBuffer light_diffuse = make(new float[]{1.0f, 0.0f, 0.0f, 1.0f});
        // specular light is blue
        FloatBuffer light_specular=make(new float[]{0.0f, 0.0f, 1.0f, 1.0f});
        gl.glLightfv(GL.GL_LIGHT0, GL.GL_DIFFUSE, light_diffuse);
        // LIGHT0 should use it
        gl.glLightfv(GL.GL_LIGHT0, GL.GL_SPECULAR, light_specular);..
..

We've said to use whiteMaterial to reflect specular light, from that point on, and nothing changes that. This means even the black squares on the checkerboard reflect the blue specular light.