4. GLSL Lighting

Jump To:

main.cpp Source
shader.vert Source
shader.frag Source
Download

Ok, so we can create a shader program, and render a shape with a specified color. Lets move into lighting. In this tutorial, I am going to show you how to perform per vertex lighting, which is then easily modified to achieve per pixel lighting in the next tutorial.

Lets get moving 🙂

Main.CPP File:

There are going to be quite a few additions to this code, but you might recognise most of it from the lighting tutorials.

I am creating a directional light, that points in the direction [0,1,1]. It has a diffuse color of white, and an ambient color of black.

I have placed the calls to setup my lighting inside of a new method, which is called during our display method, *after* we set the camera position. I have a tip on the OpenGL Tips page, that explains why we do this.

Another minor change, is that I am now using a glutSolidSphere, I have kept it to a low polygon count, only 10 stacks and 10 slices. This is to show how per vertex will differ to per pixel lighting later on.

But enough of that, lets move onto our shaders. You should already know how to setup lighting in a standard OpenGL application.

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
    #if ( (defined(__MACH__)) && (defined(__APPLE__)) )   
#include <stdlib.h>
#include <OpenGL/gl.h>
#include <GLUT/glut.h>
#include <OpenGL/glext.h>
#else
#include <stdlib.h>
#include <GL/glew.h>
#include <GL/gl.h>
#include <GL/glut.h>
#include <GL/glext.h>
#endif

#include “shader.h”

Shader shader;

GLfloat angle = 0.0; //set the angle of rotation

//diffuse light color variables
GLfloat dlr = 1.0;
GLfloat dlg = 1.0;
GLfloat dlb = 1.0;

//ambient light color variables
GLfloat alr = 0.0;
GLfloat alg = 0.0;
GLfloat alb = 0.0;

//light position variables
GLfloat lx = 0.0;
GLfloat ly = 1.0;
GLfloat lz = 1.0;
GLfloat lw = 0.0;

void init(void) {
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);
    
    shader.init(“shader.vert”, “shader.frag”);
}

void cube (void) {
    glRotatef(angle, 1.0, 0.0, 0.0); //rotate on the x axis
    glRotatef(angle, 0.0, 1.0, 0.0); //rotate on the y axis
    glRotatef(angle, 0.0, 0.0, 1.0); //rotate on the z axis
    glColor4f(1.0, 0.0, 0.0, 1.0);
    glutSolidSphere(2, 10, 10);
}

void setLighting(void) {
    GLfloat DiffuseLight[] = {dlr, dlg, dlb}; //set DiffuseLight[] to the 
specified values

    GLfloat AmbientLight[] = {alr, alg, alb}; //set AmbientLight[] to the 
specified values

    
    glLightfv (GL_LIGHT0, GL_DIFFUSE, DiffuseLight); //change the light
 accordingly

    glLightfv (GL_LIGHT0, GL_AMBIENT, AmbientLight); //change the light
 accordingly

    
    GLfloat LightPosition[] = {lx, ly, lz, lw}; //set the LightPosition to 
the specified values

    
    glLightfv (GL_LIGHT0, GL_POSITION, LightPosition); //change the 
light accordingly

}

void display (void) {
    glClearColor (0.0,0.0,0.0,1.0);
    glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();  
    gluLookAt (0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
    
    setLighting();
    
    shader.bind();
    cube();
    shader.unbind();
    
    glutSwapBuffers();
    angle += 0.01f;
}

void reshape (int w, int h) {
    glViewport (0, 0, (GLsizei)w, (GLsizei)h);
    glMatrixMode (GL_PROJECTION);
    glLoadIdentity ();
    gluPerspective (60, (GLfloat)w / (GLfloat)h, 1.0, 100.0);
    glMatrixMode (GL_MODELVIEW);
}

int main (int argc, char **argv) {
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH); //set up
 the double buffering

    glutInitWindowSize(500, 500);
    glutInitWindowPosition(100, 100);
    glutCreateWindow(“A basic OpenGL Window);
    
    glutDisplayFunc(display);
    glutIdleFunc(display);
    
    glutReshapeFunc(reshape);
    
    init();
    
    glutMainLoop();
    
    return 0;
}

Vertex Shader Source:

For this tutorial, I am going show you how to perform per
vertex lighting using shaders. Yes, shaders support per pixel
but this is just a starting point.

First I am going to teach you about varying variables. These
are variables that can be shared between the vertex and fragment
shaders. These variables are set inside the vertex shader, and
then read from the fragment shader. In this case we are going
to share a float type variable from the Vertex shader to the
Fragment shader. This variable is going to be called diffuse_value
and will hold a number that will tell us how lit our vertex
should be.

Because we are using per vertex lighting, the GLSL shader will
automatically interpolate the color between the vertices. Just
like OpenGL does.

Now first off we need to know the surface normal of the current
vertex. Because I am using a GLUT sphere, these are already
calculated and sent off with the glNormal3f command. Any numbers
sent from OpenGL through glNormal*; can be read with
the variable gl_Normal. In later tutorials, I will be using
self calculated normals, using my own normal generation code.

Now as for our normal, I am storing it in the variable vec3 vertex_normal.
But first, I have to multiply the normal (gl_Normal) by
the gl_NormalMatrix. This places the normal into world coordinates so that
we can use it correctly. (Later on I will show you how to use tangent space
normals). We also then have to normalize this multiplication so
that all of the normal vectors are between (and including) -1 and 1. Our final call looks like:

vec3 vertex_normal = normalize(gl_NormalMatrix * gl_Normal);

Next on, we are going to work with our light. I am storing this
in a variable called vec3 vertex_light_position. In this, I am calling for the
position of glLight0. This is gathered by the call:

vec3 vertex_light_position = gl_LightSource[0].position.xyz;

This gives us the position of glLight0, and to get the position of
any other light, we just change the number 0 to the light number we
are after. We do not need to multiply this expression by the
gl_NormalMatrix, it is already in world coordinates, as it has been multiplied by the ModelView Matrix before we set it’s position, but we do have to normalize it still.

Now that we have our vertex_normal and the position of our vertex_light_position.
We can now calculate the diffuse_value of our lighting at the
given vertex. To do this we need to take the dot product of both
the vertex_normal and the vertex_light_position together. If you have any idea on
calculating normals, then you should have a fair idea of what the
dot product does. It works as such:
dot(a,b) = (x1 * x2) + (y1 * y2) + (z1 * z3)
If this happens to be equal to 0, then the vectors a and b are
perpendicular (the light meets the surface at a 90 degree angle).
We use the call max(), to make sure we do not get a negative value.
So our diffuse_value equation becomes:

diffuse_value = max(dot(vertex_normal, vertex_light_position), 0.0);

And from that, we have now calculated the intensity of the light
at the given vertex. That started to turn into a bit of a maths
lesson, but it even cleared up some stuff for me without even
thinking about it. GO MATHS 🙂

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.

    varying float diffuse_value;

void main() {            
    // Calculate the normal value for this vertex, in world coordinates (
multiply by gl_NormalMatrix)

    vec3 vertex_normal = normalize(gl_NormalMatrix * gl_Normal);
    // Calculate the light position for this vertex
    vec3 vertex_light_position = gl_LightSource[0].position.xyz;
    // Set the diffuse value (darkness). This is done with a dot product between
 the normal and the light

    // and the maths behind it is explained in the maths section of the site.
    diffuse_value = max(dot(vertex_normal, vertex_light_position), 0.0);

    // Set the front color to the color passed through with glColor*f
    gl_FrontColor = gl_Color;
    // Set the position of the current vertex 
    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}

 

Fragment Shader Source:

Because we are using the varying float Diffuse in
our vertex shader, if we wish to read it in here, we
need to set the same variable, so I have done this
first:

varying float diffuse_value;

After that, because I am building on the last tutorial, we will multiply
our colour value, by our new diffuse_value, so our final call to
gl_FragColor will look like:

gl_FragColor = gl_Color * diffuse_value;

Once you get this running, it should show a sphere that gets darker down the bottom, or if you add a camera, it will be dark behind as well. Which you can see in the image at the top of the page.

If you have any questions, please email me at swiftless@gmail.com

1.
2.
3.
4.
5.
6.
7.
    varying float diffuse_value;

void main() {
    // Set the output color of our current pixel
    gl_FragColor = gl_Color * diffuse_value;
}

 

Download:

Download shader.h Source Code for this Tutorial

Download shader.cpp Source Code for this Tutorial

Download main.cpp Source Code for this Tutorial

Download shader.vert Source Code for this Tutorial

Download shader.frag Source Code for this Tutorial

  • March 25, 2010
  • 7