I was looking through the book Graphics Programming Methods (2003) the other day when I came across a paper titled “A Modified Phong-Blinn Light Model for Shadowed Areas” and thought it was pretty good, so I implemented it. It’s a really short paper, but what’s it’s basically saying is that by not allowing ambient light to be modified by geometry normals, any characteristics of the geometry are lost because we calculate the diffuse intensity as the dot product of the light location and the surface normal and **limit the result between 0 and 1** (i.e. we only take diffuse lighting into consideration for surfaces facing our light source).

What the paper proposes instead is that we allow the dot product to run as -1.0 to +1.0, and when the value is within the range 0.0 to 1.0 (i.e. facing the light) we shade as usual, but when it’s -1.0 to 0.0 (facing away from the light) we calculate the lighting as the ambient light plus the negative diffuse intensity multiplied by the ambient light multiplied by q, where q is a value between 0.0 and 1.0. When q is 0.0, it’s like we’re using a traditional Phong-Blinn model, when it’s 1.0 we’re using the fully modified version, and any value in between is a sliding-scale combination of them.

To put that another way, if our surface is facing away from the light source, we use: Lighting = Ambient + (Diffuse * Ambient * q)

In the following images, the light source is roughly in line with the geometry on the Y and Z axis’, and is a way over on the positive X axis (i.e. to the right). Check out the difference…

To create this effect, I used the following fragment shader:

**Modified Phong-Blinn Light Model Fragment Shader**

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
#version 330 // Uniforms uniform vec4 ambientColour; uniform vec4 diffuseColour; uniform vec4 specularColour; uniform sampler2D colourMap; uniform sampler2D normalMap; uniform float qUniform; // A value to manipulate our lighting by when surface is facing away from light source // Input from our vertex shader smooth in vec3 vVaryingNormal; smooth in vec3 vVaryingLightDir; smooth in vec2 vTexCoords; // Output fragments out vec4 vFragColour; void main(void) { const float maxVariance = 2.0; // Mess around with this value to increase/decrease Normal pertubation const float minVariance = maxVariance / 2.0; // Create a normal which is our standard normal + the normal map perturbation (which is going to be either positive or negative) vec3 normalAdjusted = vVaryingNormal + normalize(texture2D(normalMap, vTexCoords.st).rgb * maxVariance - minVariance); // Get our diffuse intensity as a dot product which is NOT limited 0..1 float diffuseIntensity = dot(normalize(normalAdjusted), normalize(vVaryingLightDir)); vec3 colour = vec3(0.0); // If the surface is facing the light then shade as normal if (diffuseIntensity > 0.0) { colour = (diffuseIntensity * diffuseColour.rgb) * texture2D(colourMap, vTexCoords.st).rgb + ambientColour.rgb; } else // Otherwise use the modified light model { colour = (diffuseIntensity * diffuseColour.rgb * ambientColour.rgb * qUniform) + ambientColour.rgb; } // Set the almost final output color as a vec4 - only specular to go vFragColour = vec4(colour, 1.0); // Specular light vec3 vReflection = normalize(reflect(-normalize(normalAdjusted), normalize(vVaryingLightDir))); float specularIntensity = max(0.0, dot(normalize(normalAdjusted), vReflection)); // Add in specular component if (diffuseIntensity > 0.0) { float fSpec = pow(specularIntensity, 64.0); vFragColour.rgb += vec3(fSpec * specularColour.rgb); } } |

Credits for this method go to Anders Hast, Tony Barrera, and Ewer Bengtsson – good work, fellas! =D