Gregory Igehy

Dancing at hemisphere coordinate

Notes of frequency domain normal mapping vMF in The Order 1886

I have read course notes and sample codes to use frequency domain normal mapping vMF in The Order 1886.


The code for adjusting roughness by length of average normal is simple. Here are some codes quoted from the course notes.

// Quoted from "Crafting a Next-Gen Material Pipeline for The Order: 1886" SIGGRAPH 2013

float r     = length(avgNormal);
float kappa = 10000.0f;
if(r < 1.0f)
   kappa = (3 * r - r * r * r) / (1 - r * r);

// New roughness
roughness = sqrt(roughness * roughness + (1.0f / kappa));

But the tricky part is that the "roughness" parameter above code means α the "square of roughness". (α = roughness^2).

Here are some codes of frequency domain normal mapping vMF by MJP.
If you read those code, you will notice that the "roughness" parameter means α (α = roughness^2), because it is finally passed to GGX_Sppecular() and is used as α.

// Quoted from GenerateMap.hlsl
//
// Specular Antialiasing Sample by MJP
// https://mjp.codeplex.com/releases/view/109905

...

// Pre-compute roughness map values
//
// Roughness means α (α = roughness^2)
//
vmfRoughness = sqrt(Roughness * Roughness + (1.0f / (2.0f * kappa)));
...

// Write the result to the roughness map.
// α is converted to roughness by sqrt().
// Roughness map stores roughness not α.
OutputRoughnessMap[outputPos] = sqrt(float2(vmfRoughness, toksvigRoughness));
// Quoted from Mesh.hlsl
//
// Specular Antialiasing Sample by MJP
// https://mjp.codeplex.com/releases/view/109905

  // 
#elif UsePrecomputedVMF_
  // Fetch roughness from roughness map.
  roughness = RoughnessMap.Sample(LinearSampler, uv).x;

  // roughness to α
  roughness *= roughness;
 ...

  // Compute GGX
  // roughness means α
  float specular = GGX_Specular(roughness, normal, h, view, lightDir);

// the parameter m is α
float GGX_Specular(in float m, in float3 n, in float3 h, in float3 v, in float3 l)
{
    float nDotH = saturate(dot(n, h));
    float nDotL = saturate(dot(n, l));
    float nDotV = saturate(dot(n, v));

    float nDotH2 = nDotH * nDotH;
    float m2 = m * m;

    // Calculate the distribution term
    float d = m2 / (Pi * pow(nDotH * nDotH * (m2 - 1) + 1, 2.0f));

    // Calculate the matching visibility term
    float v1i = GGX_V1(m2, nDotL);
    float v1o = GGX_V1(m2, nDotV);
    float vis = v1i * v1o;

    return d * vis;
}