Basic Lighting (Per Vertex)

Back to GLSL Tutorial Index

Discuss this Tutorial in the Forum

Jump To:

C++ Source
Vertex Shader Source
Fragment Shader Source
Download

CPP File:

For this lighting tutorial, all I am going to change in
this file is my Init() function. I am going to add the lines
glEnable (GL_LIGHTING);
glEnable (GL_LIGHT0);

So that my Init() function looks like:

void Init (void) {
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);

glEnable (GL_LIGHTING);
glEnable (GL_LIGHT0);
}

#include <GL/glew.h>
#include <GL/gl.h>
#include <GL/glut.h>
#include <windows.h>
#include <stdio.h>

#pragma comment(lib,"glew32.lib")

char *VertexShaderSource,*FragmentShaderSource;

int VertexShader,FragmentShader;

int ShaderProgram;

GLfloat angle = 0.0;

char *readShaderFile(char *FileName) {
FILE *fp;
char *DATA = NULL;

int flength = 0;

fp = fopen(FileName,"rt");

fseek(fp, 0, SEEK_END);
flength = ftell(fp);
rewind(fp);

DATA = (char *)malloc(sizeof(char) * (flength+1));
flength = fread(DATA, sizeof(char), flength, fp);
DATA[flength] = '\0';

fclose(fp);

return DATA;
}

void display (void) {
glClearColor (0.0,0.0,0.0,1.0);
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
glTranslatef(0,0,-5);
glRotatef(angle,1,1,1);
glRotatef(angle,0,1,1);
glutSolidCube(2);
glutSwapBuffers();
angle+=0.5;
}

void InitShader (void) {
GLEW_ARB_vertex_shader;
GLEW_ARB_fragment_shader;

VertexShader = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB);
FragmentShader = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB);

VertexShaderSource = readShaderFile("light-basic.vert");
FragmentShaderSource = readShaderFile("light-basic.frag");

const char * VS = VertexShaderSource;
const char * FS = FragmentShaderSource;

glShaderSourceARB(VertexShader, 1, &VS,NULL);
glShaderSourceARB(FragmentShader, 1, &FS,NULL);

free(VertexShaderSource);free(FragmentShaderSource);

glCompileShaderARB(VertexShader);
glCompileShaderARB(FragmentShader);

ShaderProgram = glCreateProgramObjectARB();

glAttachObjectARB(ShaderProgram,VertexShader);
glAttachObjectARB(ShaderProgram,FragmentShader);

glLinkProgramARB(ShaderProgram);
glUseProgramObjectARB(ShaderProgram);
}

void DeInitShader (void) {
glDetachObjectARB(ShaderProgram,VertexShader);
glDetachObjectARB(ShaderProgram,FragmentShader);

glDeleteObjectARB(ShaderProgram);
}

void Init (void) {
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);

glEnable (GL_LIGHTING);
glEnable (GL_LIGHT0);
}

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

int main (int argc, char **argv) {
glutInit (&argc, argv);
glutInitDisplayMode (GLUT_DOUBLE | GLUT_DEPTH);
glutInitWindowSize (500, 500);
glutInitWindowPosition (100, 100);
glutCreateWindow ("A basic OpenGL Window");
glewInit();
InitShader();
Init();
glutDisplayFunc (display);
glutIdleFunc (display);
glutReshapeFunc (reshape);
glutMainLoop ();
DeInitShader();
return 0;
}

Vertex Shader:

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
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 cube, these are already
calculated and sent off with the glNormal command. Any numbers
sent from OpenGL through glNormal(1,2,3); 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 Normal.
But first, I have to multiply the surface normal (gl_Normal) by
the gl_NormalMatrix. This places the normal in coordinates that
we can use. (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.
This makes sure that we have no scaling errors.

Next on, we are going to work with our light. I am storing this
in a variable called vec3 Light. In this, I am calling for the
position of glLight0. This is gathered by the call:
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, but we do have to normalize it still.

Now that we have our surface Normal and the position of our Light.
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 Normal and the Light 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).
Because this point is when the light is at its brightest, we need
to set the maximum value for our diffuse value to 0.0.
So our Diffuse equation becomes:
Diffuse = max(dot(Normal, Light),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 :)

varying float Diffuse;

void main(void)
{
vec3 Normal = normalize(gl_NormalMatrix * gl_Normal);

vec3 Light = normalize(gl_LightSource[0].position.xyz);

Diffuse = max(dot(Normal, Light),0.0);

gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}

Fragment Shader:

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.

After that, I am simply changing the line:
gl_FragColor = vec4(0,0,1,1)
to
gl_FragColor = Diffuse * vec4(0,0,1,1);
This multiplies the intensity of the light by the
color we originally set the cube to.

This can or cannot eliminate the need for materials,
depending on your needs, but just for the record
(and future tutorials), materials are worth while
and can be read from inside GLSL :)

varying float Diffuse;

void main(void)
{
//Multiply the light Diffuse intensity by the color of the cube
gl_FragColor = Diffuse * vec4(0,0,1,1);
}

Download:

Download C++ .CPP Source Code for this Tutorial

Download Vertex Shader Source Code for this Tutorial

Download Fragment Shader Source Code for this Tutorial

     

 

Copyright 2008, Donald Urquhart
Proudly supported by http://www.cdadc.com