Lighting beyond Blinn-Phong

I have, with some regularity, encountered the perspective that Blinn-Phong lighting is the way to compute light in real time 3d rendering and that to use a different way is wrong. Well - it's not - it's just one particular model to compute light, and a fairly simple one at that.

First of all, a lighting scheme is good whenever it gets you the output you want and does this as fast as possible. Shaders don't need to be used to compute the real physics of light scattering, you can equally well use them for fictional scenarios. Below is for instance a rendering of light inspired Discworld where it acts like a fluid dragged by the sun --- and in the morning, it just fills the lower valleys.

An example for non-physical lighting - light as a fluid.

If you're after such a scheme, Blinn-Phong doesn't work well at all. But even if you aim to re-create real light scattering physics, in its simple form it's not particularly good at that.

Light scattering physics

In the real world, if light hits a surface, it gets reflected to a distribution of angles. Among other things that has to do with the roughness of the surface - if microscopically the normal of a surface varies a lot (because the surface is really tiny grains of sand on which light reflects for instance) then the distribution might be very wide because for each grain the orientation is random - we get close to Lambertian reflectance in which the reflected intensity is independent on viewing angle - which is the diffuse light channel.

If the angular distribution is narrow on the other hand, we get mirror-like behavior and specular reflectance. So rendering every surface as a combination of diffuse and specular channels is an approximation which might or might not capture the characteristics of the surface.

One important point to note here is that the distinction between diffuse and specular is purely a property of the surface and how it reflects, i.e. physically the distinction between specular and diffuse light channel makes no sense at all - the incident light is just the same.

One striking light scattering effect beyond Blinn Phong for instance is Fresnel reflection - most material reflect much better under a shallow incident angle than under a large angle. That's not so important for rough, grainy surfaces, but turns out to be crucial for water (or in fact any film-covered surface).

Water is the primary example where Fresnel refection is important - under shallow angles it reflects really well, making the water appear bright as it reflects the clear sky - but under larger angles reflectance goes down, and the water appears much darker close by.

Fresnel reflection for water, note the darkening in the foreground.

The same effect can be observed for surfaces covered by a water film. Compare this runway rendered in a Blinn-Phong scheme

A wet runway rendered using Blinn-Phong shading.

with the same scene rendered including Fresnel reflection for the water film:

A wet runway including a Fresnel reflection term.

Even a rather simplistic Fresnel parametrization helps - the above runway was done with the following specular computation in the fragment shader (where water_factor determines how much water is on the particular pixel):

vec3 lightDir = gl_LightSource[0].position.xyz;
vec3 E = normalize(ecViewdir);
vec3 n = normalize(normal);
vec3 halfVector = normalize(normalize(lightDir) + E);
NdotHV = max(dot(n, halfVector), 0.0);

fresnel = 1.0 + 5.0 * (1.0-smoothstep(0.0,0.2, dot(E,n)));
specular.rgb = ((vec3 (0.2,0.2,0.2) * fresnel
            + (water_factor * vec3 (1.0, 1.0, 1.0)))
            * light_specular.rgb
            * pow(NdotHV, max(4.0, (20.0 * water_factor))));

Ambient light physics

In a real scene, ambient light can be considered all light that doesn't come directly from a light source but has been scattered at least once, possibly more. A second conceptional weakness of Blinn-Phong is hence to assoicate it too closely to a lightsource, as it's really a property of the scene. Resonating with what's been said above, there isn't really a physical property of a material corresponding to an ambient color channel. Rather, the surface reflects indirect light just as it reflects direct light, i.e. with a certain angular distribution which can be approximated as a combination of specular and Lambertian terms. So, while there's no such thing as 'specular light' in nature, there's also no such thing as an 'ambient color'.

In Blinn-Phong, the ambient light channel is supposed to be non-directional. In reality, that's usually not true. In an outside scene, the main source of light illuminating everything that's not directly reached by the Sun is the sky. Since on a fair day the sky is bright blue, deep shadows also appear blue as they're illuminated by sky light. On an overcast day, the light diffusing through the cloud layer is the primary light source - its color is no longer sky blue which also affects the color of shadows. The relative balance between direct light from the Sun and indirect light is dramatically different - on a strong overcast day, nearly all light is indirect.

That doesn't however mean it's directionless - as you can easily verify by looking underneath a car on an overcast day, there is a shadow cast, which means the indirect sky light comes predominently from above. If you're inside on an overcast day without a lamp on, you still only get to see indirect light - but it primarily filters in through the windows, which means that its distribution has been altered.

Such behaviour of indirect light can be captured by irradiance maps - simple modulations of the ambient light distribution with the steepness (i.e. the tilt relative to the vertical in the case of sky light). An example of such an irradiance map is below:

steepness = dot(normalize(gl_Normal), vec3 (0.0, 0.0, 1.0));
light_ambient = light_ambient * ((1.0+steepness)/2.0 * 1.2 + (1.0-steepness)/2.0 * 0.2);

However, that's not all. In the absence of direct light, Blinn-Phong removes the specular highlights as they're caused by direct light only. That means that all the glossiness of a surface and in fact all the surface structure (because the ambient channel never depends on the surface normal) is lost on the shaded side of an object:

A helicopter rendered using Blinn-Phong.

Real objects however still appear glossy under an overcast sky, or even on their side not in indirect light. That is because they don't only reflect the sun, they also reflect the sky light - which in reality is not directionless (otherwise it wouldn't matter). This can be mimicked reasonably well at the simple expense of introducing a weak secondary specular only light source pointing right down from the zenith - and suddenly this sculptures the whole 3-d structure of the object out even on the shaded side and preserves the appearance of glossiness and even gets some of the surface structure.

A helicopter rendered adding specular sky light.

A GLSL realization of secondary specular illumination by the ambient light is given below (note also that this shader uses the diffuse channel for the specular computation based on the physics reasoning that they have to be identical. It's this kind of shaders which drives people who are too attached to Blinn-Phong crazy - but that's how nature really does it!

vec3 N = normalize(normal);

float nDotVP = max(0.0, dot(N, normalize(gl_LightSource[0].position.xyz)));
float nDotHV = max(0.0, dot(N, normalize(gl_LightSource[0].halfVector.xyz)));

// try specular reflection of sky irradiance
float nDotVP1 = max(0.0, dot(N, up));
float nDotHV1 = max(0.0, dot(N, normalize(normalize(up) + normalize(-vertVec))));

if (nDotVP == 0.0)
      {pf = 0.0;}
else
      {pf = pow(nDotHV, gl_FrontMaterial.shininess);}

if (nDotVP1 == 0.0)
      {pf1 = 0.0;}
else
      {pf1 = pow(nDotHV1, 0.5*gl_FrontMaterial.shininess);}

vec4 Specular = gl_FrontMaterial.specular * light_diffuse * pf
     + gl_FrontMaterial.specular * light_ambient * pf1;

Specular+= gl_FrontMaterial.specular

(it's implicit that 'up' is an upward-pointing vector here).

Special situations

There are cases which are completely outside the realm of the Blinn-Phong model. Often they have to do with translucent surfaces and forward scattering of light. Translucent objects seem to glow whenever the sun is behind them (that's forward scattering). Such a behavior is not at all part of the scheme (and we'll talk about rendering such surfaces later).

Also, often the geometry of the rendered object is not equal to the geometry of the true object. Consider for instance a needle tree. Chances are that you would represent it by modeling the branches as meshes, with all the twigs and needles painted on them by a texture (if you'd model ever single needle, you're definitely patient, but perhaps you should question your good judgement on what amount of vertices a graphics card can reasonably process...)

In this situation, the normal of the triangle on which a twig and its needles are painted is well defined in the mesh - but not in reality. Here the surface normals vary even within a pixel dramatically because each needle is so small and irregularly shaped. Moreover, the tree is partially translucent. The combined message is that just because a normal formally points towards the light, it doesn't mean all that's painted on it is in light, and just because the normal points away from the light it doesn't mean all appears dark.

If you have such a situation, just dropping direct light all together and using an ambient channel with a carefully modulated irradiance map might be the best solution.

A similar situation is created by rapidly moving objects. Consider a spinning propeller with shiny blades. if it's spinning at a few thousand rpm, it goes around about once per frame. So looking at any given point, the normal varies through all its possible orientations it can have as the propeller spins (since the propeller usually spins in one axis, it can't have all possible orientations, and it can have some more than others - there's a distribution).

If you can't resolve it during the frame, you need to average over all configurations it can take as it spins to get the shiny reflective circle. That requires you to sit down and do some computations offline, and then insert the results into your specularity model where you don't use a single normal but a weighted sum computing for various orientations (or of course you can just fake the result...).

The take-away line is that Blinn-Phong usually doesn't capture quite what happens in nature - sometimes it fails mildly, sometimes badly, but you need to be ready to deviate from the scheme whenever there is a good reason to do so.

Continue with Fogging.


Back to main index     Back to rendering     Back to GLSL

Created by Thorsten Renk 2016 - see the disclaimer and contact information.