Ashikhmin-Shirley Anisotropic BRDF in GLSL
This article details the mathematical formulation of the Ashikhmin-Shirley anisotropic specular Bidirectional Reflectance Distribution Function (BRDF) and demonstrates how to implement it directly in OpenGL Shading Language (GLSL). Designed to simulate anisotropic surfaces such as brushed metal, woven fabrics, and hair, this microfacet-based model provides realistic specular highlights shaped by distinct directional roughness parameters.
Mathematical Formulation
The Ashikhmin-Shirley model defines the specular component of a BRDF using surface normal \(\mathbf{n}\), tangent \(\mathbf{u}\), bitangent \(\mathbf{v}\), incoming light direction \(\mathbf{l}\), and outgoing view direction \(\mathbf{v}_o\).
Given the half-vector \(\mathbf{h} = \frac{\mathbf{l} + \mathbf{v}_o}{\|\mathbf{l} + \mathbf{v}_o\|}\), the specular BRDF term \(\rho_s(\mathbf{l}, \mathbf{v}_o)\) is defined as:
\[\rho_s(\mathbf{l}, \mathbf{v}_o) = \frac{\sqrt{(n_u + 1)(n_v + 1)}}{8\pi} \cdot \frac{(\mathbf{n} \cdot \mathbf{h})^{\frac{n_u (\mathbf{h} \cdot \mathbf{u})^2 + n_v (\mathbf{h} \cdot \mathbf{v})^2}{1 - (\mathbf{h} \cdot \mathbf{n})^2}}}{(\mathbf{h} \cdot \mathbf{l}) \max(\mathbf{n} \cdot \mathbf{l}, \mathbf{n} \cdot \mathbf{v}_o)} \cdot F(\mathbf{h} \cdot \mathbf{l})\]
Components:
- Specular Exponents (\(n_u, n_v\)): Control surface roughness along the tangent \(\mathbf{u}\) and bitangent \(\mathbf{v}\) directions.
- Normalization Factor: \(\frac{\sqrt{(n_u + 1)(n_v + 1)}}{8\pi}\) ensures the BRDF conserves energy.
- Anisotropic Exponent: \(\frac{n_u (\mathbf{h} \cdot \mathbf{u})^2 + n_v (\mathbf{h} \cdot \mathbf{v})^2}{1 - (\mathbf{h} \cdot \mathbf{n})^2}\) stretches the highlight along the tangent axes. When \(\mathbf{h} = \mathbf{n}\), \(1 - (\mathbf{h} \cdot \mathbf{n})^2 = 0\); taking the limit resolves this singularity to zero.
- Denominator: Attenuates the reflection based on the geometric interaction of view and light angles, avoiding standard microfacet shadowing approximations.
- Fresnel Term \(F(\mathbf{h} \cdot \mathbf{l})\): Evaluated using Schlick's approximation: \[F(\mathbf{h} \cdot \mathbf{l}) = R_0 + (1 - R_0)(1 - (\mathbf{h} \cdot \mathbf{l}))^5\] where \(R_0\) is the specular reflectance at normal incidence.
GLSL Implementation
The following GLSL function computes the anisotropic specular reflection. It assumes input vectors are normalized and incorporates numerical safeguards against division by zero.
#define PI 3.14159265359
vec3 ashikhminShirleySpecular(
vec3 N, // Surface normal
vec3 T, // Tangent
vec3 B, // Bitangent
vec3 V, // View direction (pointing toward camera)
vec3 L, // Light direction (pointing toward light)
float nu, // Specular exponent along tangent T
float nv, // Specular exponent along bitangent B
vec3 R0 // Specular reflectance at normal incidence
) {
vec3 H = normalize(L + V);
float NdotL = max(dot(N, L), 0.0);
float NdotV = max(dot(N, V), 0.0);
float NdotH = max(dot(N, H), 0.0);
float HdotL = max(dot(H, L), 0.0);
float HdotT = dot(H, T);
float HdotB = dot(H, B);
// Prevent evaluation below horizon
if (NdotL <= 0.0 || NdotV <= 0.0 || HdotL <= 0.0) {
return vec3(0.0);
}
// Normalization constant
float normalization = sqrt((nu + 1.0) * (nv + 1.0)) / (8.0 * PI);
// Anisotropic exponent computation
float denominatorExp = 1.0 - (NdotH * NdotH);
float exponent = 0.0;
if (denominatorExp > 1e-5) {
exponent = (nu * HdotT * HdotT + nv * HdotB * HdotB) / denominatorExp;
}
// Specular distribution term
float specularDist = pow(NdotH, exponent);
// Denominator with geometric attenuation factor
float denom = HdotL * max(NdotL, NdotV);
// Schlick's Fresnel approximation
vec3 F = R0 + (vec3(1.0) - R0) * pow(1.0 - HdotL, 5.0);
// Final specular BRDF
return normalization * (specularDist / max(denom, 1e-5)) * F;
}Integration into the Rendering Equation
To evaluate the reflected radiance \(L_o\), multiply the resulting BRDF by the incoming light intensity \(L_i\) and the cosine attenuation factor \((\mathbf{n} \cdot \mathbf{l})\):
vec3 specularLight = ashikhminShirleySpecular(N, T, B, V, L, nu, nv, R0) * lightColor * NdotL;