Contents |
Gregory Ward published his model in 1992 and it’s interesting for two main reasons; firstly due to being an empirical approximation from the outset compared with other models which start with properties based on theoretical physics for example. He set out to create a simple mathematical equation that modelled observed results – much of the published material covers a novel new invention for collecting measurements from material samples.
Secondly, the previous four models have all been isotropic in nature and this chapter introduces the concept of an anisotropic lighting model. This is particularly important as it allows us to realistically model a whole new class of materials.

Anisotropy in the context of computer graphics is where the characteristics appear to change based on a rotation about the surface normal – previously covered models have been view-dependent and this is a similar concept. Ultimately an anisotropic model will appear to have a varying elliptical specular highlight where isotropic highlights will be circular. The orientation and size of this ellipse varies according to the viewing direction and the directional patterning of the material being represented.
An anisotropic model is more generalised than an isotropic one and with suitable input parameters it is possible for the anisotropic equation to generate the same results as an isotropic model would. The following is a list of materials that are good candidates; a common theme is that they all have some degree of natural or artificial patterning that is not always visible to the human eye – the “microfacet” model introduced with the Cook-Torrance model is similar. The difference is that the Cook-Torrance form is uniformly distributing reflection whereas anisotropic patterns have a distinct, structured arrangement.
Because an isotropic model is a specialisation it is possible to have an isotropic form of Ward’s anisotropic model. This chapter presents the isotropic form first as it is simpler and then goes on to cover the anisotropic equation.
The equation as presented in Ward’s original paper is as follows:
δ = Angle between normal and half vectors
The isotropic equation includes a roughness coefficient, α, which is used to control the size and distribution of the specular highlight. ρd and ρs are the diffuse and specular colours input into the model; in order for this model to be physically accurate the summation of these two terms should be less than 1.0.
As shown later in this chapter, the results from Ward’s isotropic equation are very similar to those obtained by the Phong and Blinn-Phong models. As is immediately obvious the specular term used by Ward is much more involved than the one put forward by Phong Bui Tuong and later simplified by James Blinn. The key advantage of Ward’s isotropic model is that it is symmetrical about the incidence and reflection vectors therefore matching the requirements for a physically correct BRDF. It is worth noting that some experimentation with Phong’s specular exponent and Ward’s roughness value are required in order to get comparable results.
Whether this physical accuracy is worth the added computation cost is entirely dependent on the intended usage – a good compromise could be to scale between the two specular terms according to some level-of-detail algorithm.
As with the other models discussed in this section it is most convenient to convert the pure mathematical representation to a vector-based form:
This equation can be written in HLSL as follows:
float4 psWardIsotropic
(
in VS_OUTPUT f,
uniform bool UseLookUpTexture
) : SV_TARGET
{
// Make sure the interpolated inputs and
// constant parameters are normalized
float3 n = normalize( f.normal );
float3 l = normalize( -vLightDirection );
float3 v = normalize( pCameraPosition - f.world );
float3 h = normalize( l + v );
// Generate any useful aliases
float VdotN = dot( v, n );
float LdotN = dot( l, n );
float HdotN = dot( h, n );
float r_sq = (fRoughness * fRoughness) + 1e-5f;
// (Adding a small bias to r_sq stops unexpected
// results caused by divide-by-zero)
// Define material properties
float3 Ps = float3( 1.0f, 1.0f, 1.0f );
// Compute the specular term
float exp_a;
if( UseLookUpTexture )
{
// Map the -1.0..+1.0 dot products to
// a 0.0..1.0 range suitable for a
// texture look-up.
float tc = float2
(
(HdotN + 1.0f) / 2.0f,
0.0f
);
exp_a = texIsotropicLookup.Sample( DefaultSampler, tc ).r;
}
else
{
// manually compute the complex term
exp_a = -pow( tan( acos( HdotN ) ), 2 );
}
float spec_num = exp( exp_a / r_sq );
float spec_den = 4.0f * 3.14159f * r_sq;
spec_den *= sqrt( LdotN * VdotN );
float3 Specular = Ps * ( spec_num / spec_den );
// Composite the final value:
return float4( dot( n, l ) * (cDiffuse + Specular ), 1.0f );
}
The above code uses a constant diffuse and specular input, but these would often be fetched from textures mapped to the geometry by artists. In addition to these textures it would be perfectly feasible to store the roughness value in a texture such that it can vary on a per-pixel basis. If a homogenous material is being modelled (one where ρd + ρs = 1.0) then a single RGB diffuse texture could allow the single roughness input to be stored in the remaining alpha channel – therefore requiring a single texture fetch to pull in all the necessary inputs for evaluation.
Ward’s model, even in vector form, still relies on two trigonometric functions and in the same way as previous models these can be replaced by simple texture look-ups. The only input into the numerator of the exponent is Normal•Half which has a maximum range of -1.0 to +1.0 making it a perfect candidate as the look-up into a 1D pre-computed texture. The following code fragment can be used to generate this resource:
HRESULT CreateIsotropicLookupTexture( ID3D10Device* pDevice ) { HRESULT hr = S_OK; const UINT LOOKUP_DIMENSION = 512; // Describe the texture D3D10_TEXTURE2D_DESC texDesc; texDesc.ArraySize = 1; texDesc.BindFlags = D3D10_BIND_SHADER_RESOURCE; texDesc.CPUAccessFlags = 0; texDesc.Format = DXGI_FORMAT_R32_FLOAT; texDesc.Height = 1; texDesc.Width = LOOKUP_DIMENSION; texDesc.MipLevels = 1; texDesc.MiscFlags = 0; texDesc.SampleDesc.Count = 1; texDesc.SampleDesc.Quality = 0; texDesc.Usage = D3D10_USAGE_IMMUTABLE; // Generate the initial data float* fLookup = new float[ LOOKUP_DIMENSION ]; for( UINT x = 0; x < LOOKUP_DIMENSION; ++x ) { // This following fragment is a direct conversion of // the code that appears in the HLSL shader float HdotN = static_cast< float >( x ) / static_cast< float >( LOOKUP_DIMENSION ); HdotN = ( HdotN * 2.0f ) - 1.0f; fLookup[ x ] = -powf( tanf( acosf( HdotN ) ), 2.0f ); } D3D10_SUBRESOURCE_DATA initialData; initialData.pSysMem = fLookup; initialData.SysMemPitch = sizeof(float) * LOOKUP_DIMENSION; initialData.SysMemSlicePitch = 0; // Create the actual texture hr = pDevice->CreateTexture2D ( &texDesc, &initialData, &g_pIsotropicLookupTex ); if( FAILED( hr ) ) { SAFE_DELETE_ARRAY( fLookup ); return hr; } // Create a view onto the texture ID3D10ShaderResourceView* pLookupRV = NULL; hr = pDevice->CreateShaderResourceView ( g_pIsotropicLookupTex, NULL, &pLookupRV ); if( FAILED( hr ) ) { SAFE_RELEASE( pLookupRV ); SAFE_RELEASE( g_pIsotropicLookupTex ); SAFE_DELETE_ARRAY( fLookup ); return hr; } // Bind it to the effect variable ID3D10EffectShaderResourceVariable *pFXVar = g_pEffect->GetVariableByName("texIsotropicLookup") ->AsShaderResource( ); if( !pFXVar->IsValid() ) { SAFE_RELEASE( pLookupRV ); SAFE_RELEASE( g_pIsotropicLookupTex ); SAFE_DELETE_ARRAY( fLookup ); return hr; } pFXVar->SetResource( pLookupRV ); // Clear up any intermediary resources SAFE_RELEASE( pLookupRV ); SAFE_DELETE_ARRAY( fLookup ); return hr; }
By following similar principles it would be possible to completely replace the specular term’s numerator with a single texture look-up.
The isotropic form of Ward’s model can be extended to have two perpendicular roughness coefficients and thus become anisotropic by using the following form:
As part of his publication Gregory Ward proposed a more computationally convenient form of his anisotropic model:
As previously mentioned the slope derivation directions must be perpendicular to each other; it is not required but it is convenient if we re-use the vectors defining the tangent-space (assuming this implementation is for a per-pixel evaluation in tangent-space of course!) which simplifies beta down to:
However this may not be the easiest form to use with respect to the artwork providing inputs into the model. Alternatively a simplification can be used to generate the necessary tangent and bitangent vectors. The following equation relies upon an arbitrary vector, ε, to start the process:
ε = Arbitrary Vector
In line with the previous four chapters, the HLSL code utilizes phong shading rather than a tangent-space per-pixel lighting method:
float4 psWardAnisotropic
(
in VS_OUTPUT f
) : SV_TARGET
{
// Make sure the interpolated inputs and
// constant parameters are normalized
float3 n = normalize( f.normal );
float3 l = normalize( -vLightDirection );
float3 v = normalize( pCameraPosition - f.world );
float3 h = normalize( l + v );
// Apply a small bias to the roughness
// coefficients to avoid divide-by-zero
fAnisotropicRoughness += float2( 1e-5f, 1e-5f );
// Define the coordinate frame
float3 epsilon = float3( 1.0f, 0.0f, 0.0f );
float3 tangent = normalize( cross( n, epsilon ) );
float3 bitangent = normalize( cross( n, tangent ) );
// Define material properties
float3 Ps = float3( 1.0f, 1.0f, 1.0f );
// Generate any useful aliases
float VdotN = dot( v, n );
float LdotN = dot( l, n );
float HdotN = dot( h, n );
float HdotT = dot( h, tangent );
float HdotB = dot( h, bitangent );
// Evaluate the specular exponent
float beta_a = HdotT / fAnisotropicRoughness.x;
beta_a *= beta_a;
float beta_b = HdotB / fAnisotropicRoughness.y;
beta_b *= beta_b;
float beta = -2.0f * ( ( beta_a + beta_b ) / ( 1.0f + HdotN ) );
// Evaluate the specular denominator
float s_den = 4.0f * 3.14159f;
s_den *= fAnisotropicRoughness.x;
s_den *= fAnisotropicRoughness.y;
s_den *= sqrt( LdotN * VdotN );
// Compute the final specular term
float3 Specular = Ps * ( exp( beta ) / s_den );
// Composite the final value:
return float4( dot( n, l ) * (cDiffuse + Specular ), 1.0f );
}
The following image shows Ward’s isotropic model in action. The top row has roughness values of 0.0 (left), 0.2 and 0.4 (right) whilst the bottom row continues with 0.6 (left), 0.8 and 1.0 (right). The opening comment about Ward’s specular term being interchangeable with Phong’s might seem incorrect in light of image 8.2 – with roughness values between 0.0 and 0.4 there is a degree of similarity though and any higher yields completely different results.

The complexity of the compiled isotropic shaders shows that the full evaluation requires 44 instructions whereas using a look-up texture results in only 33 instructions. A 25% reduction is a definite win and a good indication of just how expensive the numerator for the exponent was.

Ward’s anisotropic model has two primary variables – the X and Y derivatives. Image 8.3 shows a grid of samples taken under the exact same conditions but with varying X and Y inputs. It is immediately clear how the shape of the specular highlight changes depending on the input – a ratio favourable towards X generates a horizontal highlight and the opposite ratio favours a vertical highlight.
The samples on the top-left to bottom-right diagonal are the same as the previously discussed isotropic model – when the X and Y variables are equal the specular highlight’s shape ceases to be anisotropic.
The anisotropic form of Ward’s model weighs in at only four extra instructions for a total of 48 overall. Unfortunately this version is less well suited to look-up textures.
[Ward92] “Measuring and Modelling Anisotropic Reflection” Gregory J. Ward, Computer Graphics 26, 2, July 1992
Navigate to other chapters in this section:
| Foundation & Theory | Direct Light Sources | Techniques For Dynamic Per-Pixel Lighting | Phong and Blinn-Phong | Cook-Torrance | Oren-Nayar | Strauss | Ward | Ashikhmin-Shirley | Comparison and Summary |