Lighting equation coefficients that are dependent on a specific source of light can be optionally multiplied by an attenuation
factor. This attenuation factor allows to simulate the light fading with distance.
If = (As*Am) + ((Al*Am) + Id + Is) * att
There are several ways to calculate the attenuation factor. We're going to see two of them.
4.1 - The standard method
This method exploits OpenGL coefficients of each light in GLSL:
- gl_LightSource[0].constantAttenuation or Kc
- gl_LightSource[0].linearAttenuation or Kl
- gl_LightSource[0].quadraticAttenuation or Kq
From these coefficients, the attenuation factor can be obtained by:
att = 1.0 / (Kc + Kl*d + Kq*d2)
where d stands for the distance between the position of the light and the currenlty processed vertex:
lightDir = vec3(gl_LightSource[0].position.xyz - vVertex);
float d = lenght(lightDir);
In OpenGL terms, attenuation factors are defined by:
glLightf( GL_LIGHT0, GL_CONSTANT_ATTENUATION, 0.0f );
glLightf( GL_LIGHT0, GL_LINEAR_ATTENUATION , 0.0f );
glLightf( GL_LIGHT0, GL_QUADRATIC_ATTENUATION , 0.0002 );
And in Demoniak3D, these factors are defined in the light node:
<light name="Light_01"
constant_att="0.0" linear_att="0.0" quadratic_att="0.0002" />
Here is now our attenuated point light vertex shader:
[Vertex_Shader]
varying vec3 normal, lightDir, eyeVec;
varying float att;
void main()
{
normal = gl_NormalMatrix * gl_Normal;
vec3 vVertex = vec3(gl_ModelViewMatrix * gl_Vertex);
lightDir = vec3(gl_LightSource[0].position.xyz - vVertex);
eyeVec = -vVertex;
float d = length(lightDir);
att = 1.0 / ( gl_LightSource[0].constantAttenuation +
(gl_LightSource[0].linearAttenuation*d) +
(gl_LightSource[0].quadraticAttenuation*d*d) );
gl_Position = ftransform();
}
And here is our attenuated point light pixel shader:
[Pixel_Shader]
varying vec3 normal, lightDir, eyeVec;
varying float att;
void main (void)
{
vec4 final_color =
(gl_FrontLightModelProduct.sceneColor * gl_FrontMaterial.ambient) +
(gl_LightSource[0].ambient * gl_FrontMaterial.ambient)*att;
vec3 N = normalize(normal);
vec3 L = normalize(lightDir);
float lambertTerm = dot(N,L);
if(lambertTerm > 0.0)
{
final_color += gl_LightSource[0].diffuse *
gl_FrontMaterial.diffuse *
lambertTerm * att;
vec3 E = normalize(eyeVec);
vec3 R = reflect(-L, N);
float specular = pow( max(dot(R, E), 0.0),
gl_FrontMaterial.shininess );
final_color += gl_LightSource[0].specular *
gl_FrontMaterial.specular * specular * att;
}
gl_FragColor = final_color;
}
The attenuation factor is computed in the vertex shader and then is passed to the pixel shader through the varying float att; variable.
Fig.5 - The point_light_att.xml demo.4.2 - Light Radius
This method uses an imaginary sphere in which the light lies in. This sphere represents the light's influence
area (i.e the farthest distance an object can be influenced by the light). The attenuation factor decreases along the radius, and
becomes equal to zero on the edge of and outside the sphere. This factor is calculated in the pixel shader and assumes that
a variable holding the inverse of the radius of the sphere is defined (in order to avoid a division):
float distSqr = dot(lightDir, lightDir);
float att = clamp(1.0 - invRadius * sqrt(distSqr), 0.0, 1.0);
vec3 L = lightDir * inversesqrt(distSqr);
This piece of code needs to be explained. The norm of the lightDir vector, is given by:
dist = sqrt(lightDir.x2 + lightDir.y2 + lightDir.z2)
This norm can be written using a dot product:
dist = sqrt( dot(lightDir, lightDir) )
and
distSqr = dot(lightDir, lightDir)
distSqr and invRadius allow to calculate the attenuation factor. The L vector, that is the normalized
direction vector, can be obtenaid by the usual normalization formula:
magnitude: |r| = sqrt( x2 + y2 + z2 )
magnitude: |r| = sqrt( distSqr )
unit vector u: u = (1/|r|) * U
unit vector u: u = inversesqrt(distSqr) * U
A value of invRadius=0.001 (i.e 1000.0 units radius) will allow you to start your tests.
The final word: all vertex / pixel shaders previously seen are for all-purposes (i.e they are not bound to Demoniak3D), do not take input
parameters (i.e no uniform variables) and so they can be effortless used in any OpenGL application.
That said, have a good coding!