Cycles: Fresnel term is incorrect - changes are needed.
Posted: Thu May 30, 2013 1:26 pm
I was doing some 3D scanning and rendering recently, trying to match real photographs as closely as possible. But in a lot of cases Cycles would produce very strange and highly unrealistic results while other engines like YafaRay and Mitsuba don't have those problems.
Cycles is great, its node system is really powerful, it's easy to prototype and tune stuff in, especially on my nVidia Titan.
So I reduced it to a single sphere surrounded with uniform red background and two spherical light sources. No diffuse + glossy BSDF with GGX normal distribution and a standard Fresnel node.
This is what you get (4 spheres with roughness 0.01, 0.1, 0.3 and 1.0) :

This is what shader looks like. Pretty standard. Here I use Mix Shader node but you can also plug it directly into Color and connect BSDF directly to Surface. That would be the same:

You can see what happens there with edges as roughness increases. You get this very unnatural outline around the edge.
Then I synced the code and looked at that Fresnel node (in fact, Layer Weight is also messed up) and discovered that it uses average normal instead of microfacet normal for both GGX and Beckmann roughness models. In fact, all the comments and references to equations are correct in actual BSDF nodes except that they don't compute Fresnel and leave it for Fresnel node.
That's a fundamental issue there. For microfacet models one has to compute Fresnel term based on microfacet normal since that's what the light really bounces off but not the average normal (whether it is geometry or pixel normal).
When Fresnel term is computed based on microfacet normal this is what you should get:

All direct highlights are fixed up and background fresnel effect is preserved.
With current shader system the best I could do is to do pre-integrated (kind of) Fresnel curve (should be really roughness dependent there):

Then you get something like this:

It helps with the outline but at the same time it removes the background (environment) reflection which is really bad since the object is no longer "married" to the environment.
---
The reason for this post is to raise awareness of the issue and start a discussion. Changes that I made locally are not committable yet - a hack in a sense that IOR is fixed to 1.5 dielectric, it's only for Glossy, it doesn't handle layering properly, etc... It would be nice to figure out, especially from people who know Cycles internals well, how to implement/integrate those things correctly inside the kernel and SVM.
- Dmitry
Cycles is great, its node system is really powerful, it's easy to prototype and tune stuff in, especially on my nVidia Titan.
So I reduced it to a single sphere surrounded with uniform red background and two spherical light sources. No diffuse + glossy BSDF with GGX normal distribution and a standard Fresnel node.
This is what you get (4 spheres with roughness 0.01, 0.1, 0.3 and 1.0) :

This is what shader looks like. Pretty standard. Here I use Mix Shader node but you can also plug it directly into Color and connect BSDF directly to Surface. That would be the same:

You can see what happens there with edges as roughness increases. You get this very unnatural outline around the edge.
Then I synced the code and looked at that Fresnel node (in fact, Layer Weight is also messed up) and discovered that it uses average normal instead of microfacet normal for both GGX and Beckmann roughness models. In fact, all the comments and references to equations are correct in actual BSDF nodes except that they don't compute Fresnel and leave it for Fresnel node.
That's a fundamental issue there. For microfacet models one has to compute Fresnel term based on microfacet normal since that's what the light really bounces off but not the average normal (whether it is geometry or pixel normal).
When Fresnel term is computed based on microfacet normal this is what you should get:

All direct highlights are fixed up and background fresnel effect is preserved.
With current shader system the best I could do is to do pre-integrated (kind of) Fresnel curve (should be really roughness dependent there):

Then you get something like this:

It helps with the outline but at the same time it removes the background (environment) reflection which is really bad since the object is no longer "married" to the environment.
---
The reason for this post is to raise awareness of the issue and start a discussion. Changes that I made locally are not committable yet - a hack in a sense that IOR is fixed to 1.5 dielectric, it's only for Glossy, it doesn't handle layering properly, etc... It would be nice to figure out, especially from people who know Cycles internals well, how to implement/integrate those things correctly inside the kernel and SVM.
- Dmitry