PBR: Fresnel, and grazing angle on rough surfaces

Started by
1 comment, last by MJP 3 years ago

So I've been trying to get up to speed with this physically based rendering stuffs, and one of the convention struck me, basically the fresnel part. I understand the fresnel effect as how much of reflectance was to be shown, based on the fresnel factor, which will be stronger at grazing angle, which is dictated by (Eye . Normal). this apparently also applies in PBR, in which at grazing angle, the surface becomes 100% reflective.

But what is the basis of that? suppose a fully rough surface, should it also follow the same rule? how would a rough surface becomes 100% reflective at grazing angle? for the love of god I just couldn't get it. Got a good reading on that? most of the sources I was reading (learnopengl.com, substance painter tutorial, and some blogs) didn't touch that deeply, only show how to incorporate fresnel factor into the equation. But does it have any reasonable basis? I mean for a glossy surface, yeah I could understand. For a fully rough surface? hmmm…

Advertisement

A rough surface does not actually become “100% reflective” at a grazing angle. The trick here is understanding how microfacet specular models work. With microfacet specular, there is not really just a single normal for any point on a surface. Instead you can imagine that the point you're shading is actually made up of a bunch of tiny surfaces, each with its own normal. These are “tiny surfaces” are the “microfacets” that the name comes from. You can basically picture that if you were to zoom in super closely on each pixel, it would like kinda like the surface of this rock:

Essentially there's a whole bunch of flat surfaces, but they're not necessarily aligned in the same direction. Now with a microfacet model there's a concept of “roughness”, which describes how well all of these little micro-surfaces are aligned. For a roughness of zero they are all perfectly aligned, and the micro-surface is totally flat. For a roughness of 1 they all point in random directions, similar to the picture I posted above. In more precise terms you would say that the roughness gives you a distribution of microfacet normals, where for each roughness you can determine the percentage of microfacets that would be aligned in a particular direction.

Ok so now let's say you're shading this microsurface with a point light. Point lights are easy, since they're infinitely small. In other words, from the shading surface's point of view there's only 1 exact ray direction that intersects with the light, which is your “L” vector that you use in your BRDF and shading code. So given this L direction, how much light reflects off the surface? For a microfacet BRDF this question naturally involves roughness. In basic terms, the microfacet BRDF asks “if I have surface with normal N, view direction V, and light direction L, what is the percentage of microfacets that will be aligned juuuuust right so that the light perfectly reflects of the micro-surface like a mirror and hits the eye/camera?”. They do this by first computing the “ideal” surface normal direction for perfect specular reflection, which is called the halfway vector or “H” for short. Then the normal distribution term of the BRDF says “given a halfway vector, here's the percentage of microfacets that will be aligned just right”. So for instance if 10% of the microfacets are “active” and are aligned just right so that the light reflects towards the eye, you end up with about 10% of the light being reflected. There's also some geometry and visibility terms that account for self-shadowing of the microfacets and foreshortening, but we can ignore those for a moment.

Ok so now we get to the fresnel term. If the material of the surface is 100% reflective it's easy, no need to care about fresnel. But in reality all materials either absorb or refract a portion of the incoming lighting, and reflect the rest. This is governed by the fresnel equations. These equations (and the popular Schlick's approximation) use the surface normal and viewing direction to determine the reflectance, with reflectance approaching 100% at grazing angles as you mentioned. The thing you're missing is that it gets more complicated with microfacet specular models. With microfacets there's not really just a single normal, there's a whole bunch of little micro-surface normals. I mentioned before that for point light specular there will be a percentage of “active microfacets” that are aligned with the halfway vector that will end up reflecting light towards the eye. This is why for microfacet models you compute fresnel using the light direction and the halfway vector, not the macro-surface normal!

float3 Fresnel(in float3 specAlbedo, in float3 h, in float3 l)
{
    return specAlbedo + (1.0f - specAlbedo) * pow((1.0f - saturate(dot(l, h))), 5.0f);
}

So let's say you have roughness of 0.0. This means the micro-surface is perfectly flat, and all of the microfacets are perfectly aligned. For a single point light, it's very unlikely that you will be looking at the surface at just the right angle that you see a specular reflection. But if you do get that exactly right, you'll end up 100% of the microfacets participating, and that will be multiplied with the fresnel factor. But now let's say you have a higher roughess. The microfacets are not aligned at all, and are pointed in random directions. In this case you could look at the surface from almost any direction and see a reflection, however only a small % of the microfacets will participate and so the reflection will look dull. You'll still have fresnel, but you'll never hit 100% reflectance since you'll never have all of the microfacets perfectly aligned. So you really shouldn't think of grazing angle as being “100% reflective”, even at a steep grazing angle.

Now the other thing that gets complicated is with environment lighting, which is commonly done with pre-computed probes represented as cubemaps. A common way to handle these probes is by pre-blurring the lower-resolution mip levels, and then choosing the appropriate mip level based on the roughness. The naive way to handle fresnel in this setup would be to compute the fresnel factor factor for the surface normal, and then multiply that with the pre-blurred cubemap. However this is can have really bad results for high roughness! Think about how you might implement specular reflection from a cubemap if you were to do it brute force, vs doing it with a pre-filtered cubemap:

TextureCube EnvLightingMap;

float3 BruteForce(in float3 V, in float3 N)
{
    float3 result = 0.0f;
    for(uint i = 0; i < NumRandomSamples; ++i)
    {
        float3 L = randomDirectionOnHemisphere(i, N);
        float3 H = normalize(L + V);
        float3 envLighting = EnvLightingMap.SampleLevel(EnvSampler, L, 0.0f).xyz;
        result += NDF(N, H Roughness) * Fresnel(L, H, SpecularAlbedo) * envLighting;
    }
    return result / NumRandomSamples;
}

float3 PreFiltered(in float3 V, in float3 N)
{
    float3 L = reflect(-V, N);
    float3 envLighting = EnvLightingMap.SampleLevel(EnvSampler, L, RoughnessToMip(Roughness)).xyz;
    return envLighting * Fresnel(L, N, SpecularAlbedo);
}

With the the brute force method you're computing the fresnel factor for each incoming lighting direction, based on the normal of the active microfacets. With the latter you're computing fresnel for the macrosurface normal, which only makes sense if all of your microfacet normals are perfectly aligned! A better solution is to use the “split-sum" approximation mentioned in these course notes, which is not perfect but does a better job of accounting for roughness in the fresnel factor.

This topic is closed to new replies.

Advertisement