6. GLSL Materials
GLSL Materials
Materials in OpenGL define how objects interact with light, controlling their visual appearance under various lighting conditions. Using GLSL shaders, we can create realistic lighting effects by combining ambient, diffuse, and specular components. This tutorial implements these concepts and explains how the shaders work in detail.
Main Program
The main program initializes OpenGL, sets up shaders, and applies material properties to the teapot. Below is the full code:
#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 shader;
// Rotation angle for animation
GLfloat angle = 0.0;
// Light position
GLfloat lx = 0.0, ly = 1.0, lz = 1.0, lw = 0.0;
void init(void) {
glEnable(GL_DEPTH_TEST); // Enable depth testing
glEnable(GL_LIGHTING); // Enable lighting calculations
glEnable(GL_LIGHT0); // Enable light source 0
shader.init("shader.vert", "shader.frag"); // Load shaders
}
void cube(void) {
glRotatef(angle, 1.0, 0.0, 0.0); // Rotate on X-axis
glRotatef(angle, 0.0, 1.0, 0.0); // Rotate on Y-axis
glRotatef(angle, 0.0, 0.0, 1.0); // Rotate on Z-axis
GLfloat mShininess[] = {50}; // Shininess
GLfloat DiffuseMaterial[] = {1.0, 0.0, 0.0}; // Red diffuse reflection
GLfloat AmbientMaterial[] = {0.2, 0.2, 0.2}; // Subtle ambient reflection
GLfloat SpecularMaterial[] = {1.0, 1.0, 1.0}; // White specular highlights
glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, DiffuseMaterial);
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, AmbientMaterial);
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, SpecularMaterial);
glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, mShininess);
glutSolidTeapot(2.0); // Render teapot
}
void setLighting(void) {
GLfloat DiffuseLight[] = {1.0, 1.0, 1.0};
GLfloat AmbientLight[] = {0.2, 0.2, 0.2};
GLfloat SpecularLight[] = {1.0, 1.0, 1.0};
GLfloat LightPosition[] = {lx, ly, lz, lw};
glLightfv(GL_LIGHT0, GL_DIFFUSE, DiffuseLight);
glLightfv(GL_LIGHT0, GL_AMBIENT, AmbientLight);
glLightfv(GL_LIGHT0, GL_SPECULAR, SpecularLight);
glLightfv(GL_LIGHT0, GL_POSITION, LightPosition);
}
void display(void) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear the screen
glLoadIdentity();
gluLookAt(0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); // Camera position
setLighting(); // Configure lighting
shader.bind(); // Activate the shader program
cube(); // Render the teapot
shader.unbind(); // Deactivate the shader program
glutSwapBuffers(); // Swap buffers for smooth rendering
angle += 0.1f; // Increment rotation angle
}
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 Materials Example");
glewInit();
init();
glutDisplayFunc(display);
glutIdleFunc(display);
glutReshapeFunc(reshape);
glutMainLoop();
return 0;
}
Vertex Shader
The vertex shader calculates and passes data needed by the fragment shader, including the transformed normal vector and the half-vector for specular lighting.
varying vec3 vertex_light_half_vector;
varying vec3 vertex_normal;
void main() {
// Transform the normal vector to world space
vertex_normal = normalize(gl_NormalMatrix * gl_Normal);
// Calculate the half-vector for the light
vertex_light_half_vector = normalize(gl_LightSource[0].halfVector.xyz);
// Pass vertex color to the fragment shader
gl_FrontColor = gl_Color;
// Transform vertex position to clip space
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
Fragment Shader
The fragment shader computes the final lighting for each pixel, combining ambient, diffuse, and specular terms. Here’s a breakdown of its components:
Ambient Term
Ambient lighting provides a base level of light that uniformly illuminates the object.
vec4 ambient_color = gl_FrontMaterial.ambient * gl_LightSource[0].ambient +
gl_LightModel.ambient * gl_FrontMaterial.ambient;
Diffuse Term
Diffuse lighting simulates light that scatters equally in all directions from a surface.
vec4 diffuse_color = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse; float diffuse_value = max(dot(vertex_normal, vertex_light_position), 0.0);
Specular Term
Specular lighting creates shiny highlights and depends on the angle between the light’s reflection and the viewer’s direction.
vec4 specular_color = gl_FrontMaterial.specular * gl_LightSource[0].specular *
pow(max(dot(vertex_normal, vertex_light_half_vector), 0.0), gl_FrontMaterial.shininess);
Final Color
The final fragment color combines the ambient, diffuse, and specular terms:
gl_FragColor = ambient_color + diffuse_color * diffuse_value + specular_color;
Full Fragment Shader
Here is the complete fragment shader, combining all the components:
varying vec3 vertex_light_half_vector;
varying vec3 vertex_normal;
void main() {
// Ambient term
vec4 ambient_color = gl_FrontMaterial.ambient * gl_LightSource[0].ambient +
gl_LightModel.ambient * gl_FrontMaterial.ambient;
// Diffuse term
vec4 diffuse_color = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse;
float diffuse_value = max(dot(vertex_normal, vertex_light_position), 0.0);
// Specular term
vec4 specular_color = gl_FrontMaterial.specular * gl_LightSource[0].specular *
pow(max(dot(vertex_normal, vertex_light_half_vector), 0.0), gl_FrontMaterial.shininess);
// Combine terms
gl_FragColor = ambient_color + diffuse_color * diffuse_value + specular_color;
}
Download Links
If you have any questions, feel free to email me at swiftless@gmail.com.
Thanks for the helpful tutorials. My question is: how to detect lights count to calculate illumination in loop
for (int i = 0; i < numLights; ++i) { // where to get numLights?
…
}
Thanks
Igors
The easiest way is using a uniform and setting it in the application code. Normally you want to have fewer lights enabled on lower performance machines so you can keep frame rate.
This is very helpful, thank you.
I have heard from Lighthouse3D that the normal may be normalized twice: once in the vertex shader and again in the fragment shader. I know I have proven to be somewhat of a newbie (as I am), but when I substitute vertex_normal for vec3 fragment_normal = normalize(vertex_normal); I see a less vertex-ish lighting. Let me know what you think.
Hi Mitch,
You are totally correct with what you have read, and I should update the tutorial to comply, thats a mistake on my part.
It is required to normalize your variables in your fragment shader instead of your vertex shader, and this is explained on Lighthouse3D in detail as to why.
Cheers,
Swiftless
Love your new website, congratulations!