GLSL: Phong Multi-Light
Posted by Torin Kampa | Filed under GLSL
This shader program aims to completely replicate OpenGL’s fixed function lighting pipeline using per-pixel calculations. For more explanation, see my previous article on Per-Pixel Lighting (PPL).
Vertex Shader
This is a simple vertex shader that transforms the vertex position and normal into the appropriate spaces. All the lighting calculations are done per-pixel in the fragment shader.
varying vec3 normal;
varying vec3 position;
void main()
{
gl_Position = ftransform();
normal = gl_NormalMatrix * gl_Normal;
position = gl_ModelViewMatrix * gl_Vertex;
}
Fragment Shader
The fragment shader calculates and accumulates ambient, diffuse, and specular for each light. See this link for more information on how the specular component is calculated.
varying vec3 normal;
varying vec3 position;
void main()
{
// normalize the vertex normal and the view vector
normal = normalize(normal);
vec3 view = normalize(-position);
// these variables will accumulate for each light
vec4 ambient = gl_FrontLightModelProduct.sceneColor;
vec4 diffuse = 0;
vec4 specular = 0;
// accumulate ambient, diffuse, and specular for each light
for (int i = 0; i < gl_MaxLights; i++)
{
// early out if the current light is disabled
if (gl_LightSource[i].diffuse[3] == 0)
continue;
// determine the light and light reflection vectors
vec3 light =
normalize(gl_LightSource[i].position - position);
vec3 reflected = -reflect(light, normal);
// add the current light's ambient value
ambient += gl_FrontLightProduct[i].ambient;
// calculate and add the current light's diffuse value
vec4 calculatedDiffuse = max(dot(normal, light), 0.0);
diffuse += gl_FrontLightProduct[i].diffuse *
calculatedDiffuse;
// calculate and add the current light's specular value
vec4 calculatedSpecular = pow(max(dot(reflected, view), 0.0),
0.3 * gl_FrontMaterial.shininess);
specular += clamp(gl_FrontLightProduct[i].specular *
calculatedSpecular, 0.0, 1.0);
}
gl_FragColor = ambient + diffuse + specular;
}
The nifty part of the shader is its early out condition. GLSL provides many light constants but lacks an enabled boolean. Fortunately, we can get around that. A light’s diffuse color is a vec4 with the first three components used for red, green, and blue. In this shader, we hijack the unused fourth component and treat it as an light enabled/disabled bit. The following C/C++ function shows how modify this fourth component without affecting the light’s diffuse color.
void setLightEnabled(GLushort light, bool enabled)
{
// enable/disable the light for fixed function
if (enabled)
glEnable(light);
else
glDisable(light);
// use the light's 4th diffuse component to store an enabled bit
GLfloat lightDiffuse[4];
glGetLightfv(light, GL_DIFFUSE, lightDiffuse);
lightDiffuse[3] = enabled ? 1 : 0;
glLightfv(light, GL_DIFFUSE, lightDiffuse);
}
See Also
Download: bin, src
Tags: c++, fragment, glew, GLSL, glut, light reflection, lighting calculations, opengl, ppl, shader, shaders, shading language, specular component, tutorial, vertex, vertex position, vertex shader
GLSL: Per-Pixel Lighting (PPL)
Posted by Torin Kampa | Filed under GLSL
In OpenGL’s fixed function pipeline, lighting (N dot L) is computed per-vertex and interpolated over the surface of the polygon. The result is decent when using high-poly models and can be further disguised by texturing. Per-vertex lighting is, however, unacceptable on some low-poly models such as single-quad floors or walls. In this case, you must resort to using baked textures or per-pixel lighting.
Using PPL, lighting (N dot L) is computed per-pixel. Regardless of the polygon density, the light “falls off” naturally. Minor artifacts may remain if an object is particularly low-poly and approximates a curved surface such as a sphere. There is a performance hit when using PPL but that cost is minimized on newer hardware.
Vertex Shader
The vertex shader is executed for each vertex in the OpenGL pipeline. GLSL’s ftransfrom() convenience function is used transform the vertex position into model-view-projection space. We transform the normal into world normal space and the vertex in the world space so that the fragment shader can compute the lighting vector. The keyword varying allows a variable to be accessed in the fragment shader.
varying vec3 normal;
varying vec3 position;
void main()
{
gl_Position = ftransform();
normal = gl_NormalMatrix * gl_Normal;
position = gl_ModelViewMatrix * gl_Vertex;
}
Fragment Shader
The fragment shader is executed for each fragment (pixel). Whatever is assigned to gl_FragColor will be the resulting color of the pixel. The varying variables that were defined in the vertex shader are interpolated between vertices. Therefore, even though “position” is only calculated for each vertex, it is interpolated and actually corresponds to the position of each fragment.
In the fragment shader, we calculate the light vector which is the light position minus the fragment position. We can then determine the diffuse component by taking the dot product of the normal vector and the light vector.
varying vec3 normal;
varying vec3 position;
void main()
{
vec3 light = gl_LightSource[0].position - position;
gl_FragColor = dot(normalize(normal), normalize(light));
}
Note: This fragment shader is a simple example and does not take into account any light properties (ambient, diffuse, specular, attenuation) or material properties (color, texture), and only uses one light (LIGHT0).
C/C++ Implementation
Using GLSL shaders in C/C++ is straightforward. During initialization, both the vertex and fragment shaders must be compiled and then linked together into a “program.”
// load and compile the ppl vertex shader
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
const GLchar* vertexShaderSource =
readFile("..\\..\\Content\\ppl_v.glsl");
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
free((void*)vertexShaderSource);
// load and compile the ppl fragment shader
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
const GLchar* fragmentShaderSource =
readFile("..\\..\\Content\\ppl_f.glsl");
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
free((void*)fragmentShaderSource);
// create and link the ppl shader program
pplProgram = glCreateProgram();
glAttachShader(pplProgram, fragmentShader);
glAttachShader(pplProgram, vertexShader);
glLinkProgram(pplProgram);
Once the shaders are compiled and linked, you can enable/disable the shader program by calling glUseProgram.
void onKeyboard(unsigned char key, int x, int y)
{
switch (key)
{
case '1':
glUseProgram(NULL);
break;
case '2':
glUseProgram(pplProgram);
break;
}
}
See Also
Download: bin, src
Tags: c++, diffuse component, dot product, fragment, glew, GLSL, glut, light position, model view, opengl, ppl, shader, shaders, shading language, tutorial, vertex, vertex lighting, vertex position, vertex shader
Project: Bump Sampler
Posted by Torin Kampa | Filed under Portfolio, Projects
Description
Platform: PC
Language: C++
Technology: OpenGL, GLSL
Development: 3 months
Team: Solo
Interactively demonstrates the effectiveness of real-time, shader-based lighting techniques including phong, bump mapping, and parallax bump mapping.
The Maya-like environment features:
- Translation of objects and lights using a “gizmo”
- Interactive camera using the alt key
- Pixel-perfect object selection/picking
- Creation and deletion of up to eight lights
Screenshots
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |







