Improving Fresnel term attenuation in a PBR engine

Physically based rendering is now used by a majority of 3D engines in video game industry. I wont go over the core of a PBR engine (BRDF, material model…), because there is already a lot of very good references all over the internet (see ref. below), I will just focus on one particular term: the Fresnel.

Glossary:

  • cSpec is the minimal amount of spec a 0 incidence angle
  • H is the half way vector, so H = normalize(View+Light)
  • F = 1-cos (\theta), where \theta is the angle between the direction from which the incident light is coming and the BRDF half way vector (H).

Most engine use the Shlick Fresnel approximation:
Fresnel = cSpec + (1-cSpec) * pow(F, 5)
It’s a very fast approximation, found about 20 years ago, it’s good, but when you use it with normalized Blinn-Phong or GGX BRDF for example, it tends to make the flat and rough surfaces, like a ground with a sunset light setup, looks a bit “milky”, is there a way to improve this?

Real world analysis:
Let’s take a look at what’s going on in the world we try to simulate, here is the curve resulting from the MERL (ref.5) database. The red dot represent the classical Schlick approximation for Fresnel term, other lines are results of observations for a bunch of di-electric material.

FresnelObsMERL

Hypothesis:

Fresnel term becomes higher at grazing angle, but it seems to be modulated by the roughness of the surface. With rougher surfaces it seems the Fresnel peak is lower but appear earlier, considering the incidence angle of light and normal.
Let’s see how it is handled by modern game 3D engine:

Lagarde (Frostbite):
cSpec + ( max( Gloss, cSpec) – cSpec ) * pow(F, 5)

FresnelAttLagardeRBred is for glossier surfaces, blue for rougher ones

Lazarov (Call of duty):
cSpec + (1-cSpec) * pow(F, 5) / ( 4 – 3 * Gloss )

FresnelAttLazarovRB

My formula:
cSpec + ( (1-cSpec) * pow(F,6) * ( 1 + pow ( Gloss, 2 ) – (F)  ) )

FresnelAttEngineRB

3D view of the curve with x the angle, y the roughness and z the fresnel result

curve3d

Optimizations:
The cost of this formula can be cheaper than Lazarov, here is how we compute it

On modern GPU, the fastest way to compute pow(x,6) is like this,and it s the same amount of operation as a pow(x,5)

const float xPow2 = x*x;

const float xPow5 = xPow2 *xPow2 * x;
const float xPow6 = xPow2 *xPow2 *xPow2 ; // As expensive as pow5

So our formula HLSL code looks like this:

float ComputeFresnelAtt(const float dotLH, const float F0, const float Glossiness)
{
const float F = 1.0f - dotLH;
const float FPow2 = F * F;
const float FPow6 = FPow2 * FPow2 * FPow2 ;
return F0 + ( 1 - F0 ) * FPow6 * ( 1 - F + Glossiness * Glossiness);
}

 

Results:
Results look great, our artists really love it, I will provide screenshots in a near future

 

References

(1) http://www.filmicworlds.com/2014/04/21/optimizing-ggx-shaders-with-dotlh/

(2) http://www.manufato.com/?p=902

(3) http://content.gpwiki.org/D3DBook:(Lighting)_Cook-Torrance

(4) https://seblagarde.wordpress.com/2011/08/17/hello-world/

(5) http://www.merl.com/brdf/

(6) https://en.wikibooks.org/wiki/GLSL_Programming/Unity/Specular_Highlights_at_Silhouettes

2 thoughts on “Improving Fresnel term attenuation in a PBR engine

  1. Dropped this in last night, I have both of the previously mentioned versions as well as an implementation of the BRDF integration texture that Unreal uses, thats our default.

    When i put yours in, it seemed to pump up the direct reflectance for smoother objects. Id attach some screenshots if I could.

    Like

    1. Yes, if you can provide me screen shots, I ll be glad to help you. Anyway, if all parameters are normalized, it should give you something very close to the schlick (F0 + (1-F0)pow(x,5)) approximation for smoothers objects (gloss~=(1.0-Epsilon)), I only tryed the formula replacing the fresnel term in the GGX brdf but it should produce good result whatever brdf you use with shlick approximation.

      Like

Leave a comment