GLSL: Phong Multi-Light

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: , , , , , , , , , , , , , , , ,

Comments are closed.