Directional Light per Pixel
| Prev: Directional Lights II | Next: Point Light Per Pixel |
In this section we’ll modify the previous shaders to compute the directional light per pixel. Basically we’re going to split the work between the two shaders, so that some operations are done per pixel.
First lets take a look at the information we receive per vertex:
- normal
- half vector
- light direction
We have to transform the normal to eye space, and normalize it. We also have to normalize both the half vector and the light direction, both of which are already in eye space. These normalized vectors are to be interpolated and then sent to the fragment shader so we need to declare varying variables to hold the normalized vectors.
We can also perform some computations combining the lights settings with the materials in the vertex shader, hence helping to split the load between the vertex and fragment shader.
The vertex shader could be:
varying vec4 diffuse,ambient;
varying vec3 normal,lightDir,halfVector;
void main()
{
/* first transform the normal into eye space and
normalize the result */
normal = normalize(gl_NormalMatrix * gl_Normal);
/* now normalize the light's direction. Note that
according to the OpenGL specification, the light
is stored in eye space. Also since we're talking about
a directional light, the position field is actually direction */
lightDir = normalize(vec3(gl_LightSource[0].position));
/* Normalize the halfVector to pass it to the fragment shader */
halfVector = normalize(gl_LightSource[0].halfVector.xyz);
/* Compute the diffuse, ambient and globalAmbient terms */
diffuse = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse;
ambient = gl_FrontMaterial.ambient * gl_LightSource[0].ambient;
ambient += gl_LightModel.ambient * gl_FrontMaterial.ambient;
gl_Position = ftransform();
}
Now for the fragment shader. The same varying variables have to be declared. We have to normalize again the normal. Note that there is no need to normalize again the light direction. This last vector is common to all vertices since we’re talking about a directional light. The interpolation between two equal vectors yields the same vector, so there is no need to normalize again. Then we compute the dot product between the interpolated normalized normal and the light direction.
varying vec4 diffuse,ambient;
varying vec3 normal,lightDir,halfVector;
void main()
{
vec3 n,halfV;
float NdotL,NdotHV;
/* The ambient term will always be present */
vec4 color = ambient;
/* a fragment shader can't write a varying variable, hence we need
a new variable to store the normalized interpolated normal */
n = normalize(normal);
/* compute the dot product between normal and ldir */
NdotL = max(dot(n,lightDir),0.0);
....
}
If the dot product NdotL is greater than zero then we must compute the diffuse component, which is the diffuse setting we received from the vertex shader multiplied by the dot product. We must also compute the specular term. To compute the specular component we must first normalize the halfvector we received from the vertex shader, and also compute the dot product between the normalized halfvector and the normal.
...
if (NdotL > 0.0) {
color += diffuse * NdotL;
halfV = normalize(halfVector);
NdotHV = max(dot(n,halfV),0.0);
color += gl_FrontMaterial.specular *
gl_LightSource[0].specular *
pow(NdotHV, gl_FrontMaterial.shininess);
}
gl_FragColor = color;
The following images show the difference in terms of visual results between computing the lighting per vertex versus per pixel.
![]() |
![]() |
| Per Vertex | Per Pixel |
A Shader Designer project containing the shaders for the directional light per pixel can be found in here.
| Prev: Directional Lights II | Next: Point Light Per Pixel |



Hello,
Thank you for your execellent tutorial.
I have one question.
In th vertex shader, “nomal” is transformed into eye space and
normalized but that is not necessary. The “normal” is not used in the
vetex shader and it is normalized in fragment shader.
Am I missing sometning?
—
Vertex shader,
void main()
{
/* first transform the normal into eye space and
normalize the result */
normal = normalize(gl_NormalMatrix * gl_Normal);
Fragment shader,
varying vec4 diffuse,ambient;
varying vec3 normal,lightDir,halfVector;
void main()
{
… snip
/* a fragment shader can’t write a varying variable, hence we need
a new variable to store the normalized interpolated normal */
n = normalize(normal);
If the normal is not normalized in the vertex shader then the interpolated normal, in the fragment shader, will not have the right direction.