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 (), where 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.
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)
red is for glossier surfaces, blue for rougher ones
Lazarov (Call of duty):
cSpec + (1-cSpec) * pow(F, 5) / ( 4 – 3 * Gloss )
My formula:
cSpec + ( (1-cSpec) * pow(F,6) * ( 1 + pow ( Gloss, 2 ) – (F) ) )
3D view of the curve with x the angle, y the roughness and z the fresnel result
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/
(6) https://en.wikibooks.org/wiki/GLSL_Programming/Unity/Specular_Highlights_at_Silhouettes
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.
LikeLike
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.
LikeLike