Glsl tutorial
Directional Light per pixel
In this section we'll modify the previous shaders to compute the directional light per pixel. basically we're 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:
// We can receive three values from each vertex.
- Normal // vertex Normal Vector
- Half vector // sum of eye coordinates and light directions
- Light Direction // 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 cocould 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 re talking about a directional light, the position field is actually direction */lightdir = normalize (vec3 (gl_lightsource [0]. position); // gl_lightsource [0]. position is stored in the eye coordinate/* normalize the halfvector to pass it to the fragment shader */halfvector = normalize (gl_lightsource [0]. halfvector. XYZ );
// Pass halfvector to the bitwise colorator/* 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; // Global Environment light 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 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 needa 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 productNdotlIs greater than zero then we must compute the diffuse component, which is the diffuse setting we have Ed 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 Have ed 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.
A shader designer project containing the shaders for the directional light per pixel can be found in here