Pythonstuff GLSL in English Pythonstuff GLSL auf Deutsch Pythonstuff GLSL Pythonstuff
PythonStuff Home


Anisotropic Specular Highlights for Realistic Hair with GLSL

If a surface is uneven in a specific direction (brushed metal, hair, silk), light will be reflected depending on the strain (or brush) direction. For a theoretical background see for example this resource.

Let's start with the program code:

and the textures used (rename the directory to “textures”):

If you “just try” the program, copy the following files to the same directory where you placed the program:

Also the Library "Pylget" must be installed, of course.

The result looks like this:

(left to right: Blinn/Phong, Phong, Blinn/Phong with Bumpmap)

Anisotropic Highlight - Blinn/Phong Anisotropic Highlight - Phong Anisotropic Highlight with Bumpmap

Anisotropic Highlight - wool Anisotropic Highlight - wool Anisotropic Highlight - Hair


In the program I have renamed the relevant local vectors:

  • E - the vector to the Eye
  • L - the vector to the Light (there may be L1, L2,.. for multiple lights)
  • H - the “Half-Vector”: the middle of E and L (norm(norm(E)+norm(L))
  • N - the Normal vector - usually (0,0,1), but modified by bumpmap data

The following image is canibalized from Ashikhmin,Shirley: An Anisotropic BRDF Model (2000), a paper you should definitely look at.

Local geometry and relevant vectors

The vectors E,L,H share a plane - that is usually not shared by N.

To explain my anisotropic highlight algorithm (is this a new one (8/2011) ? I am not sure) I added the vector

  • T - strain direction vector (in the base plane)

Anisotropic surface vectors

Look at the green plane defined by T and N. If E,L (and therefore H) lie within that plane, the reflection is brightest. The reflection will disappear if they are ortogonal. To achieve this, I project (red lines) the vectors to this plane and do the highlight calculation (Phong or Blinn-Phong) with the projected vectors E',L',H' (blue).

(Observe, that for Phong-Shading you need E',L', for Blinn-Phong-Shading only H', so only one projection must be calculated).

Ok ? So on to the implementation - to compare the Shading algorithms, I implemented the key 'M' (model) to switch between the two.

Coding of Anisotropy

Let's look at the source code - we expand the Example 5 that implemented “Parallax Mapping”.

We start out with the expanded help text:

I = Isotropic/Anisotropic Lighting
M = Model (Phong/Blinn-Phong)

The default values are set very low down in the program, we start with anisotropic Phong:

toggleaniso = True
togglemodel = False # Phong

The “import” statements have been clean up a little, because I switched to Pydev/Eclipse in the mean time and this ist quite noisy when it comes to unqualified imports:

from math import pi, sin, cos, sqrt
from euclid import Vector3
from pyglet import resource
from pyglet.window import * #@UnusedWildImport
from import *     #@UnusedWildImport
from shader import Shader  

So, in passing, you see how to suppress the Pydev warnings :-)

By the way - as some people have asked “where are the libraries you use all the time ?”, I have put the relevant py-files in the current directory - so you can keep your site-dependencies small:

“” should be installed in the “right” place, or you will have problems with updates.

Anyway - the two new toggles can be changed by the keys 'I' and 'M' and are passed to the shader program:

shader.uniformi('toggleaniso', toggleaniso)
shader.uniformi('togglemodel', togglemodel)

I have changed a few lines for placing the “Help”-Info and changed the lights, but this is not too interesting. The main part is, of course, the

Shader Program for Anisotropic Lighting

As before, I read the Color for every pixel (after the parallax mapping part - I kept this unchanged) and the Normal:

vec4 texColor = vec4(texture2D(my_color_texture[0], newCoords).rgb, 1.0);
vec3 norm     = normalize( texture2D(my_color_texture[1], newCoords).rgb - 0.5);
vec3 gloss    = texture2D(my_color_texture[3], newCoords).rgb; // Glossmap: r=brightness, g=specular exp, b=strand-dirx

New is only the “gloss”-Texture, that gives the

  • (R)ed - Brightness of the Reflection Spot
  • (G)reen - (the square root of) the specular exponent
  • (B)lue - the direction of the hair strand/metal grooves

The (B)lue-Channel is coded like this:

  • 0 means isotropic
  • 3..255 = strain positioned 1..180 degrees to tangent

Be aware that “jpg”-compressed Textures may overshoot, especially along edges in the image - so I keep a secure distance from “0”.

For calculating the Phong Formula I use something like

specular = pow ( max(0.0, R.E), gloss.g * gloss.g * 128.0)

where “R.E” is the inner product of “R” which is the Reflected light vector and “E” is the Eye vector.

The exponent is 32 for a “green” value of 128 (because this translates to gloss.g = 0.5, so 0.5*0.5*128.0 = 32.0) and a maximum of 128.

The Phong calculation will not surprise you in the “isotropic” case, but you may wonder about the anisotropic case, because it is exactly the same:

R•E for Phong
N•H for Blinn-Phong

This is because the “anisotropy” is done by manipulating the Eye Vector and the Light Vector before doing then “Phong” or “Blinn-Phong” calculation.

I project the vectors to the plane orthogonal to the strand direction. I do this by calculating the normal vector of this plane:

vec3 tplane = cross( N, vec3( straindir, 0.0 )); // normal vector of T-Plane 
E  = normalize( E  - dot(E,  tplane) * tplane ); // project eye   to texture plane

Because the “straindir” lies in the Tangent Plane, this vector also lies in the Tangent Plane if the Normal N is not modified by the Bump Map. Then the “E”-Vector is projected to this plane. This way you get a standard “Phong”-Reflection if you look along the strain direction.

As you can learn from Wikipedia, “Blinn-Phong” shading may be easier to calculate than “Phong” Shading in the case of infinitely far away lights, because Light and/or Eye will become constants.

“Blinn-Phong” seems to simulate a lot of materials better than “Phong” (see Experimental Validation of Analytical BRDF Functions), so I looked into that. The nice thing is that only one vector (“H”) must be projected to the Strain Plane - so the calculation of Blinn-Phong is less expensive and more realistic.

You can compare the images of Blinn/Phong (left) to Phong (right) in the isotropic case:

Isotropic Highlight - Blinn/Phong Isotropic Highlight - Phong

The difference is quite obvious in the case of grazing angles.

The source code has redundant calculations in it, because I wanted to demonstrate both lighting models in the isotropic and the anisotropic case. These calculations should be removed after deciding on one of the lighting models.

That's it so far..

And now to something completly different: Example 8 - Environmental Bump Mapping with GLSL Cube Mapping !

Have you seen Example 6 - Vertex Offset Shader for deformable Objects ?


Deutschsprachige Version, Start
Impressum & Disclaimer