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
