## 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)

### Anisotropy

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.

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)

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 pyglet.gl 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:

“Pyglet.py” 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, newCoords).rgb, 1.0);
vec3 norm     = normalize( texture2D(my_color_texture, newCoords).rgb - 0.5);
vec3 gloss    = texture2D(my_color_texture, 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:

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 ?

References:

Impressum & Disclaimer