# physically based shading, how to combine specular & diffuse parts?

After writing 'standard' phong & blinn shaders for a while, I recently started to dabble in physically based shading. A resource that helped me a lot are these course notes, especially this paper - it explains how to make blinn more shading physically plausible.

I implemted the blinn model propsed in the paper, and I really like how it looks. The most significant change proposed ( imo ) is the inclusion of the fresnel reflectance, and this is also the part that gives me problems. Unfortunately, the author chose to focus on the specular part only, omitting diffuse reflectance. Given e.g. a lambertian diffuse reflection, I just don't know how to combine it with the 'improved' blinn - because just adding diffuse & specular parts does not seem to be right any more.

In some shaders I've seen a floating point 'fresnel term' in range 0 - 1 being used, based on the indices of refraction of the participating media. Schlick's approximation is used every time:

float schlick( in vec3 v0, in vec3 v1, in float n1, in float n2 )
{
float f0 = ( n1 - n2 ) / ( n1 + n2 );
f0 *= f0;
return f0 + ( 1 - f0 ) * pow( dot( v0, v1 ), 5 );
}


Doing it like this, one can then linearly interpolate between diffuse and specular contribution based on the fresnel term, e.g.

float fresnel = schlick( L, H, 1.0002926 /*air*/, 1.5191 /*other material*/ );
vec3 color = mix( diffuseContrib, specularContrib, fresnel );


In the paper, the author states that this approach is incorrect - because it basically just darkens the specular color whenever L is parallel or nearly parallel to H - and that instead of computing a f0 based on the indices of refraction, you should treat the specular color itself as f0 and have your schlick approximation compute a vec3, like this:

vec3 schlick( in vec3 v0, in vec3 v1, in vec3 spec )
{
return spec + ( vec3( 1.0 ) - spec ) * pow( dot( v0, v1 ), 5 );
}


This results in the specular color going towards white at glancing angles.

Now my question is, how would I introduce a diffuse component into this? At 90° the specular contribution is fully white, this means all incoming light is reflected, so there can't be a diffuse contribution. For incidence angles < 90°, can I just multiply the whole diffuse part with ( vec3( 1 ) - schlick ), i.e. the proportion of light that isn't reflected?

vec3 diffuseContrib = max( dot( N, L ), 0.0 ) * kDiffuse * ( vec3( 1.0 ) - schlick( L, H, kSpec ) );


Or do I need a completely different approach?

• Did you ever solve this? I am having this exact problem right now.
– user36169
Oct 6 '16 at 2:03
• I would like an answer to that too. Lagarde has been suggesting a similar subraction of specular color as well (that he eventually "didn't use") but this all seems very arbitrary. Aug 7 '17 at 6:37

Does it looks good enough?

• Yes - Keep it.

• No - Fiddle with it some more.

Getting accurate realistic physical based shading requires more GPU power than is possible, you'll always have to resort to faking things for the simple reason that a computer cannot simulate the entire universe, not even the entire visible light spectrum.

It is the "uncanny valley" of shading.

Not just due to ambient lighting and occlusion but for example external environments both day and moonlight have a huge amount of UV spectrum light a tiny bit of which which gets shifted back to visible spectrum by various materials, mostly organic ones. Different types of fluorescent tube lighting and HID also emits a good amount of UV light.

The effect is subtle but thanks to years of evolution the brain instinctively process it as this meant the difference between a good meal and possibly a long excruciating death so if something is ever-so-slightly off our brains sound the "something is wrong" alarm.

Cartoony, unrealistic, and almost-realistic-but-intentionally-off-by-a-good-margin (eg: Halo) art styles avoid this issue.

Another issue is that no two humans have the same RYGBL receptor presence nor response curve while monitors are strictly RGB and therefore cannot perfectly reproduce images for all humans. (Red, Yellow, Green, Blue, and Luma, see Tetrachromacy - http://en.wikipedia.org/wiki/Tetrachromacy )

No matter how hard you work, there will be "something wrong" with it and room for improvement, at the very least for someone somewhere.

And this is one reason why there are so many shading models.

Because of all this a good artist always ends up cheating the levels of light and light response on environments to some degree to attain a "good enough" result (its never perfect), just give them access to fiddle with the settings and cheat their way to pleasing results.

• There's too much research in this area to give such a non-answer.
– Tara
Dec 16 '19 at 7:17
• @Tara, welcome to Stack Exchange. I'm sure that with all that "too much research in this area" you will take the time to write a proper non-"non-answer" and contribute to the community rather than just a one-line comment. There's a little button on this page that reads "Add Another Answer" if you can find it. Might need a bit of research to find it but it's there. Dec 18 '19 at 13:10
• Wow, how passive-aggressive. I didn't say I knew better. I said I know there's a bunch of research out there that is trying to address this issue by not just going "just do whatever" and going on tangents about the human brain and death.
– Tara
Dec 18 '19 at 13:17
• I still don't see a new answer other than that 2014 "non-answer" and the one-line, non constructive, active-aggressive comment posted 5 years later. You are 100% welcome to take all that research (of which you provided zero links so far, just the one line comment) that is perfectly applicable to real-time game engines and take the time to summarize it into a proper answer. Dec 18 '19 at 13:27
• I'm fine, thanks. I already downvoted the question and added my initial comment to explain why I did so, because people don't like unexplained downvotes.
– Tara
Dec 19 '19 at 1:11