Advertisement

GGX Normalization Factor

Started by July 03, 2019 01:52 PM
1 comment, last by Mailbox 5 years, 6 months ago

I'm trying to implement sphere light shading using the representative point technique described in Brian Karis' course notes for Real Shading in Unreal Engine 4.
So far I implemented the widening effect, but I'm stuck at normalizing the NDF.

The paper mentions that the normalization factor for GGX is 1/(πα²) and an approximate normalization for the representative point operation they used (equation 14) is (α/α′)². However, I'm unsure what α and α′ is referring to, since in the description it mentions "divide the new widened normalization factor by the original", but α stands for the roughness in the other equation (10) they're referring to, not the normalization factor. Also, in the equation the operands seem to be swapped, compared to the description. Furthermore, I don't know where the normalization factor he is talking about is coming from, since the equation for the GGX NDF is α²/(π((n.h)²(α²-1)+1)²), so I don't know how to swap the normalization in the first place.
I tried to change the equation to 1/(π²((n.h)²(α²-1)+1)²(1/(πα²))) so that it contains the mentioned normalization factor and I can swap it out, but I'm not sure whether that's how it's supposed to be done.

So, anyone know the equation for the adjusted NDF or can help me understand what is meant exactly in the paper?

 

 

I tried to figure out how it's being done in the Unreal engine. It seems to simply multiply the normal GGX distribution term with the new normalization factor, instead of replacing the existing one. However, that factor is calculated quite differently from how it's done in the course notes, from what I could tell, so I'm still not entirely sure. It does seem to work more or less though, with the intensity spreading over the larger area:

6LEDawJ.jpgO0OxQMM.jpg

While the normalization might be correct now, I still have an ugly issue where the diffuse gets somehow clipped as the light radius increases, taking the shape of the specular reflection:

fTD8i9O.jpg

I don't think this has anything to do with the normalization, but again I'm not sure. I've checked the code that calculates the representative point, but I don't see what I'm doing wrong.

I've also tried it with the Smoothed Representative Point technique, but I got the same issue.

This is roughly the code I'm using (edited for brevity):


vec3 cameraDirection = normalize(cameraEye - position);
vec3 reflection = reflect(-cameraDirection, normal);
vec3 lightDelta = lightPosition - position;
vec3 centerToRay = dot(lightDelta, reflection) * reflection - lightDelta;
vec3 closestPoint = lightDelta + centerToRay * clamp(lightRadius / length(centerToRay), 0.0, 1.0);
float lightDistance = length(closestPoint);
vec3 lightDirection = closestPoint / lightDistance;
vec3 halfwayNormal = normalize(cameraDirection + lightDirection);

vec3 specular = SpecularCookTorrance(...);
vec3 diffuse = DiffuseLambert(...);

totalLight += (specular + diffuse * (1.0 - fresnel)) * lightEmission * max(dot(normal, lightNormal), 0.0);

vec3 SpecularCookTorrance(...)
{
    float distributionNormalization = pow(roughness / clamp(roughness + lightRadius / (2.0 * lightDistance), 0.0, 1.0), 2.0);
    // Fresnel reflectance term
    vec3 fresnel = SpecularFresnelSchlick(specular, halfwayNormal, lightDirection);
    // Shadowing-masking (geometry) term
    float geometry = SpecularGeometrySmith(roughness, halfwayNormal, lightDirection, cameraDirection);
    // Normal distribution term
    float distribution = SpecularDistributionGGX(roughness, normal, halfwayNormal) * distributionNormalization;
    // Cook-Torrance BRDF
    vec3 numerator = fresnel * geometry * distribution;
    float denominator = 4.0 * max(dot(normal, lightDirection), 0.0) * max(dot(normal, cameraDirection), 0.0);
    return numerator / max(denominator, 0.001);
}

vec3 DiffuseLambert(vec3 diffuse)
{
    return diffuse / Pi;
}

 

This topic is closed to new replies.

Advertisement