D3DBook:(Lighting) Ward

From GDWiki
Jump to: navigation, search

Contents

Ward

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.

image:Diagram_8.1.png
Diagram 8.1

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.

  1. Clothing, for example velvet.
  2. Machined metals such as brushed steel
  3. Wood grain. Although the colour and pattern can be expressed fairly easily it is difficult to express the lighting characteristics without considering the effect the grain has.
  4. Paint and varnish applied using a paintbrush will usually leave a directional pattern matching the brush-stroke.
  5. Hair might not be an obvious choice, but it is unlikely to be feasible to render individual hairs in real-time; instead a single primitive will represent thousands of hairs and as such the surface of this primitive has a directional pattern matching that of the hair being represented.

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.

Isotropic Equation

The equation as presented in Ward’s original paper is as follows:

I_0 = (\rho _d + Specular) \times L_i \times \cos \theta_i

Specular = \rho _s \times \frac {e^{-\frac{\tan^2 \delta}{\alpha^2}}}{4 \times \pi \times \alpha^2 \times \sqrt{\cos \theta_i \times \cos \theta_r}}

δ = 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.

Isotropic Implementation

As with the other models discussed in this section it is most convenient to convert the pure mathematical representation to a vector-based form:

I_0 = (Diffuse + Specular) \times L_i \times (Normal \bullet Light)

Specular = \rho _s \times \frac {e^{-\frac{\tan^2(\cos^{-1}(Normal \bullet Half))}{\alpha^2}}}{4 \times \pi \times \alpha^2 \times \sqrt{(Normal \bullet Light) \times (Normal \bullet View)}}

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.

Anisotropic Equation

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:

I_0 = (\rho_d + Specular) \times L_i \times cos \theta_i

Specular = \rho_s \times \frac{e^{-tan^2(\delta) \times (\frac{\cos^2 \phi}{\alpha_x^2} + \frac{\sin^2 \phi}{\alpha_y^2})}}{4 \times \pi \times \alpha_x \times \alpha_y \times \sqrt{\cos \theta_i \times \cos \theta_r}}

Anisotropic Implementation

As part of his publication Gregory Ward proposed a more computationally convenient form of his anisotropic model:

I_0 = (\rho_d + Specular) \times L_i \times (Normal \bullet Light)

Specular = \rho_s \times \frac{1}{4 \times \pi \times \alpha_x \times \alpha_y \times \sqrt{(Normal \bullet Light) \times (Normal \bullet View)}} \times e^\beta

\beta = -2 \times \frac{{(\frac{Half \bullet Tangent}{\alpha_x})}^2 + {(\frac{Half \bullet Bitangent}{\alpha_y})}^2}{1 + (Half \bullet Normal)}

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:

\beta = -2 \times \frac{{(\frac{Half_x}{\alpha_x})}^2 + {(\frac{Half_y}{\alpha_y})}^2}{1 + (Half \bullet Normal)}

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:

\beta = -2 \times \frac{{(\frac{Half \bullet T}{\alpha_x})}^2 + {(\frac{Half \bullet B}{\alpha_y})}^2}{1 + (Half \bullet Normal)}

ε = Arbitrary Vector

T = \frac{Normal \times \epsilon}{\left \Vert Normal \times \epsilon \right \|}

B = \frac{Normal \times T}{\left \Vert Normal \times T \right \|}

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 );
}

Results

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.

image:Image_8.2.png
Image 8.2

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.

image:Image_8.3.png
Image 8.3

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.

References

[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
Personal tools