4. GLSL Lighting

GLSL Lighting

Lighting is an essential part of creating realistic 3D scenes. With GLSL shaders, you can achieve more precise control over lighting, including custom effects. This tutorial introduces per-vertex lighting using GLSL, which mimics OpenGL’s fixed-function pipeline but with greater flexibility.

In per-vertex lighting, the lighting calculations are done at each vertex and interpolated across the surface of the geometry. This tutorial sets up a directional light source and calculates the diffuse lighting intensity at each vertex.

Main Program

Here is the main program (`main.cpp`) that sets up the OpenGL context, initializes the shaders, and renders a rotating sphere with per-vertex lighting.

#if (defined(__MACH__) && defined(__APPLE__))
#include <cstdlib>
#include <OpenGL/gl.h>
#include <GLUT/glut.h>
#include <OpenGL/glext.h>
#else
#include <cstdlib>
#include <GL/glew.h>
#include <GL/gl.h>
#include <GL/glut.h>
#include <GL/glext.h>
#endif

#include "shader.h"

// Shader instance
Shader shader;

// Rotation angle
GLfloat angle = 0.0;

// Diffuse light color variables
GLfloat dlr = 1.0, dlg = 1.0, dlb = 1.0;

// Ambient light color variables
GLfloat alr = 0.0, alg = 0.0, alb = 0.0;

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

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

void sphere(void) {
    glRotatef(angle, 1.0, 0.0, 0.0);
    glRotatef(angle, 0.0, 1.0, 0.0);
    glRotatef(angle, 0.0, 0.0, 1.0);
    glutSolidSphere(2, 10, 10);
}

void setLighting(void) {
    GLfloat DiffuseLight[] = {dlr, dlg, dlb};
    GLfloat AmbientLight[] = {alr, alg, alb};
    GLfloat LightPosition[] = {lx, ly, lz, lw};

    glLightfv(GL_LIGHT0, GL_DIFFUSE, DiffuseLight);
    glLightfv(GL_LIGHT0, GL_AMBIENT, AmbientLight);
    glLightfv(GL_LIGHT0, GL_POSITION, LightPosition);
}

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();
    sphere();
    shader.unbind();

    glutSwapBuffers();
    angle += 0.01f;
}

void reshape(int w, int h) {
    glViewport(0, 0, w, 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);
    glutInitWindowSize(500, 500);
    glutCreateWindow("GLSL Lighting Example");

    glewInit();
    init();

    glutDisplayFunc(display);
    glutIdleFunc(display);
    glutReshapeFunc(reshape);

    glutMainLoop();
    return 0;
}

Vertex Shader

The vertex shader calculates the lighting intensity at each vertex based on the diffuse lighting model. This intensity is passed to the fragment shader using a `varying` variable.

varying float diffuse_value;

void main() {
    // Calculate the normal in world coordinates
    vec3 vertex_normal = normalize(gl_NormalMatrix * gl_Normal);

    // Get the light position
    vec3 vertex_light_position = gl_LightSource[0].position.xyz;

    // Calculate diffuse lighting using the dot product
    diffuse_value = max(dot(vertex_normal, vertex_light_position), 0.0);

    // Pass the vertex color to the fragment shader
    gl_FrontColor = gl_Color;

    // Transform the vertex position
    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}

Fragment Shader

The fragment shader uses the interpolated `diffuse_value` to calculate the final pixel color, creating the appearance of lighting across the surface.

varying float diffuse_value;

void main() {
    // Multiply the vertex color by the diffuse lighting value
    gl_FragColor = gl_Color * diffuse_value;
}

Understanding the Code

1. **Vertex Shader:**
– The dot product between the vertex normal and light direction calculates the intensity of light on the surface.
– `normalize` ensures vectors are unit length, critical for accurate lighting.

2. **Fragment Shader:**
– The interpolated `diffuse_value` creates smooth transitions between lit and shadowed areas.

3. **Lighting Setup:**
– `setLighting` initializes the diffuse and ambient light colors and sets the light position.

Output Behavior

When you run this program, you should see a sphere with a gradient lighting effect, brighter where the light hits directly and darker where it falls off. The lighting changes dynamically as the sphere rotates.

Download Links

If you have any questions, feel free to email me at swiftless@gmail.com.

  • March 25, 2010
  • 7