Creating a GLSL Library
The Phong Lighting ShaderThis library is A LOT more complicated than our texturing library. There are a couple of auxillary functions used by this library to simplify the code. The first of which determines whether a light is enabled.
/**
* \file PhongLighting.frag
*/
const vec4 AMBIENT_BLACK = vec4(0.0, 0.0, 0.0, 1.0);
const vec4 DEFAULT_BLACK = vec4(0.0, 0.0, 0.0, 0.0);
bool isLightEnabled(in int i)
{
// A separate variable is used to get
// rid of a linker error.
bool enabled = true;
// If all the colors of the Light are set
// to BLACK then we know we don't need to bother
// doing a lighting calculation on it.
if ((gl_LightSource[i].ambient == AMBIENT_BLACK) &&
(gl_LightSource[i].diffuse == DEFAULT_BLACK) &&
(gl_LightSource[i].specular == DEFAULT_BLACK))
enabled = false;
return(enabled);
}
A good chunk of the state information that is passed via an OpenGL program is available within a shader. Unfortunately there is no flag for whether or not a light is enabled. We can determine whether a light is enabled by viewing its ambient, diffuse and specular contributions. OpenGL defines the default value of the ambient color of a light to be {0.0, 0.0, 0.0, 1.0} and the default value for the diffuse and specular colors to be {0.0, 0.0, 0.0, 0.0} so if a light has those values we can be sure that it has no contribution to the scene. Otherwise its contribution needs to be calculated. This information is used in the calculateLighting function to decrease the number of lights that need to be passed over.
/**
* \file PhongLighting.frag
*/
void calculateLighting(in int numLights, in vec3 N, in vec3 V, in float shininess,
inout vec4 ambient, inout vec4 diffuse, inout vec4 specular)
{
// Just loop through each light, and if its enabled add
// its contributions to the color of the pixel.
for (int i = 0; i < numLights; i++)
{
if (isLightEnabled(i))
{
if (gl_LightSource[i].position.w == 0.0)
directionalLight(i, N, shininess, ambient, diffuse, specular);
else if (gl_LightSource[i].spotCutoff == 180.0)
pointLight(i, N, V, shininess, ambient, diffuse, specular);
else
spotLight(i, N, V, shininess, ambient, diffuse, specular);
}
}
}
OpenGL uses some cues to determine what type of light is being passed to it. Directional lights are assumed to have a position where the w value corresponds to 0.0. A Point Light has a w value of 1.0 in its position and a value of 180.0 for their spotCutoff value. And finally a Spot Light has a value in its spotCutoff of something other than 180.0. So if the light is enabled we use these cues to determine what type of light we're encountering. Finally lets take a look at the actual lighting calculations for each type of light. The actual Phong Lighting equations are not gone over as they are covered in much greater detail in the references.
/**
* \file PhongLighting.frag
*/
float calculateAttenuation(in int i, in float dist)
{
return(1.0 / (gl_LightSource[i].constantAttenuation +
gl_LightSource[i].linearAttenuation * dist +
gl_LightSource[i].quadraticAttenuation * dist * dist));
}
void directionalLight(in int i, in vec3 N, in float shininess,
inout vec4 ambient, inout vec4 diffuse, inout vec4 specular)
{
vec3 L = normalize(gl_LightSource[i].position.xyz);
float nDotL = dot(N, L);
if (nDotL > 0.0)
{
vec3 H = gl_LightSource[i].halfVector.xyz;
float pf = pow(max(dot(N,H), 0.0), shininess);
diffuse += gl_LightSource[i].diffuse * nDotL;
specular += gl_LightSource[i].specular * pf;
}
ambient += gl_LightSource[i].ambient;
}
void pointLight(in int i, in vec3 N, in vec3 V, in float shininess,
inout vec4 ambient, inout vec4 diffuse, inout vec4 specular)
{
vec3 D = gl_LightSource[i].position.xyz - V;
vec3 L = normalize(D);
float dist = length(D);
float attenuation = calculateAttenuation(i, dist);
float nDotL = dot(N,L);
if (nDotL > 0.0)
{
vec3 E = normalize(-V);
vec3 R = reflect(-L, N);
float pf = pow(max(dot(R,E), 0.0), shininess);
diffuse += gl_LightSource[i].diffuse * attenuation * nDotL;
specular += gl_LightSource[i].specular * attenuation * pf;
}
ambient += gl_LightSource[i].ambient * attenuation;
}
void spotLight(in int i, in vec3 N, in vec3 V, in float shininess,
inout vec4 ambient, inout vec4 diffuse, inout vec4 specular)
{
vec3 D = gl_LightSource[i].position.xyz - V;
vec3 L = normalize(D);
float dist = length(D);
float attenuation = calculateAttenuation(i, dist);
float nDotL = dot(N,L);
if (nDotL > 0.0)
{
float spotEffect = dot(normalize(gl_LightSource[i].spotDirection), -L);
if (spotEffect > gl_LightSource[i].spotCosCutoff)
{
attenuation *= pow(spotEffect, gl_LightSource[i].spotExponent);
vec3 E = normalize(-V);
vec3 R = reflect(-L, N);
float pf = pow(max(dot(R,E), 0.0), shininess);
diffuse += gl_LightSource[i].diffuse * attenuation * nDotL;
specular += gl_LightSource[i].specular * attenuation * pf;
}
}
ambient += gl_LightSource[i].ambient * attenuation;
}
The attenuation factor of the light is calculated in a different function since it is used when calculating the contribution of both the Point and Spot lights.
Now we have a library that handles per-pixel Phong Lighting. So lets take it a step further.
|