Where to start... with the help of you guys on my earlier "PBR / Specular reflectance" questions here.
Probably I never fully understand as I slept too much during math/physics classes a billion years ago, but I made some steps nevertheless. Instead of just summing up good old Lambert & Blinn, I looked into Cook Torrance.
I find it hard to verify if I'm doing it correct. Every implementation I see is different, and being stuck in the past with simple Phong or Blinn, the results are quite different anyway. Yeah a pointLight generates a wide specular reflection on rough surfaces, and a "sharp" highlight on smooth surfaces. But overall, 4 issues are bugging me mainly:
1- Cook Torrance producing negative or values higher than 100%
Mainly the "Distrubution" term in the formula is confusing me. For example, I'm currently using this:
float roughness2= roughness * roughness;
float roughness4= roughness2 * roughness2;
float denom = NdotH * NdotH * ( roughness4 - 1.f) + 1.f;
float Distr = brdf.roughness4 / (3.14159f * denom * denom);
From the example here (the first non-optimized version):
The "Distr" result can result in a very high value at a glancing angle + lower roughness. And yes, NdotH is clamped between 0 and 1 (so are all dot products I use). Also found other ways, but I'm not sure if that falls correctly in the formula as a whole.
Maybe I should ask it differently: is there a nice demo program somewhere with HLSL or GLSL code included, so I have a good reference? I know, plenty of papers and samples out there, yet I didn't really find a working program so far.
2- Fresnel & Reflections for highly (non metal) reflective, polished materials
Got some bathroom tiles and a polished wooden floor, which should be pretty reflective. Being a non-metal, they have a F0 value of 0.03. The (Schlick) Fresnel formula produces near black values, except at glancing angles.
As expected. But this means you'll never have a reflection in front of you. I guess this is sort of correct for most cases, but I really have to get down on the floor to see the window getting reflected on the floor, or take a big distance. And then the reflections usually rapidly get way too bright for my taste.
Yet in reality, I do see my ugly mug when looking straight forward at some bathroom tiles... (though vague / dark). Decreasing the roughness will produce "more" reflection. I like my materials not being too shiny/reflective, but a high roughness practically gives no reflections at all.
Again, I guess this is correct, but I miss the degree of control, and the more important it is to have the math correct. Right now the reflections are either gone, or too sharp & colorful (in the past I could control the color / saturation of the reflected color as well via material-parameters). I'm only encoding a "metal y/n" and "roughness" value into my G-Buffers btw. When looking at my oak floor here, the reflections are somewhat vague, and dark. The TV for example gets reflected, but the color seems "saturated". But in my program, reflections appear at full color when the angle is steep enough. Which doesn't look good.
3- IBL lighting (Diffuse / Specular ambient)
Examples usually only show how to do a pointlight or something. But I also have IBL "Probes" (cubeMaps) for Specular reflections and Diffuse (irradiance cubeMap).
Should I just use the same CookTorrance / Lambert formula's? Normally I would feed my "getLight" function with vectors such as NdotL, NdotV, HalfAngle, et cetera. But in the case of probes, what should those vectors be, as the light doesn't come from 1 specific point-in-space here. Or should I just simplify and do it like this:
iblSpecular = probeSpecular( reflVector, lodBasedOnRougness ) * Fresnel( NdotV )
iblDiffuse = probeDiffuse( normal ) * materialDiffuseColor;
result = iblSpecular + (1-F0) * iblDiffuse;
But that wouldn't take the roughness into account to control the amount of reflection. Note the specularProbe LOD level is still based on the rougness though.
4- Energy conservation
Right now I do:
result = cookTorranceSpecular + (1 - F0) * lambertDiffuse * materialDiffuse
Where "materialDiffuse" is black for metals (thus no diffuse light at all... is that correct???), and F0 is the *input* for the Fresnel formula (IOR converted). Thus 0.03 for non metals, a relative high (RGB) value for metals.
But... as for metals, don't I miss some degree of diffuse, since "materialDiffuse" is black for them? And for non-metals, I can still end up with very bright results if the specularpart was near 100% (at a glancing angle). Since F0 is "always" around 0.03, I could get
F0 = 0.03
ONE_MIN_F0 = 97%
specular = ~100% (smooth surface, glancing angle)
diffuse = 100% (light shining straight on it)
result = specular + 97% * diffuse = 197%
- edit -
Bad example. If the light shines straight on it, the Fresnel outcome would be low... see attached pic for a better example please.
Oh btw, if you guys would like to see the shader code, just call. And as always, Sorry for the long post!