TaaTT4 wrote: ↑Sat Apr 09, 2022 6:09 pm
I'm taking a look both at direct and indirect lighting. Give me some more days and I'll come back with a report (and hopefully some improvements).
So the "days" turned into months... but hey, better late than never!
I'll use this post to present all the fixes and changes I've made to OGRE to make it closer/similar to the other rendering engines. This was all necessary because our artists weren't entirely happy with the way OGRE rendered (especially in borderline situations); partly due to bugs and partly due to some (peculiar) approaches taken in the OGRE rendering pipeline. To do this work I studied (again...) the PBR theory from scratch, especially the papers by Sébastien Lagarde and Brian Karis as well as the Filament documentation; I also used Learn OpenGL, Marmoset Toolbag (version 3) and UE4 for code references and comparisons.
For clarity and readability, I will divide this post into three main sections:
In the links above you can find all the source code (three branches I created in my fork of OGRE; I'm not sure, but they should be easily cherry pickable to a vanilla OGRE without conflicts). I won't detail all the code (the commit messages should be self explanatory, I hope), but I will focus instead on explaining the more subtle aspects. In any case, feel free to ask any questions or request further explanations.
The first discrepancy concerns the reflectance value for dielectric surfaces. A hardcoded value of 4% is "traditionally" used in the literature (see: https://seblagarde.files.wordpress.com/ ... br_v32.pdf, pag. 13); I don't know why, but OGRE uses 3% instead. Since there are no appreciable visual differences, I've only corrected it for consistency.
Another inconsistency concerns the parametrization of the term k in the computation of the geometric shadowing (specular G). OGRE uses k = roughness4, Marmoset uses k = roughness2 / 2, but the "standard" (or at least, the most used value) seems to be k = (roughness + 1)2 / 8 (see: https://blog.selfshadow.com/publication ... tes_v2.pdf, pag. 3).
From left to right (direct lighting only): OGRE default BRDF (height uncorrelated); w/ 4% reflectance for dielectric surfaces; w/ k = (roughness + 1)2 / 8.
And now the tricky part (probably what causes the most visual difference between OGRE and other rendering engines). In both the direct lighting pass and the IBL pass, OGRE "weights" the diffuse lighting contribution by a factor of 1 - fresnelS. None of the rendering engines I've reviewed do this; all use the diffuse lighting contribution as-is, without any weighting factors. Based on a response Sébastien Lagarde gave to a user who commented on a post on his blog, it appears that 1 - fresnelS is a better approximation, mathematically speaking, than doing nothing.
The "downside" of this increased mathematical accuracy is that it leads to a visual appearance that is not what artists expect or are used to.
From left to right (direct lighting only): OGRE default BRDF; w/o any weighting factor on the diffuse lighting contribution.
Unlike Filament and UE4 (which use the Lambertian model for diffuse lighting), OGRE uses the Disney model. Sébastien Lagarde paper states that, as is done for the specular part, the DFG term must also be considered in diffuse preintegration (see: pag 58-73; especially pag. 67 and listing 18, 24). Similarly to the specular part, also in this case the DFG term depends only on the viewing angle and the roughness and therefore can be precalculated. I then modified Filament cmgen (the program used by OGRE to generate its BRDF LUT map) to also insert the diffuse DFG term into the map (blue channel) containing the precalculated DFG terms. The application of the diffuse DFG term then becomes straightforward.
Here you can find the maps (please ignore the .txt extension, it's just to overcome the limitations of the OGRE forum with attachments) with which to overwrite the brtfLutDfg.dds file:
Ideally, you should use the same GGX formulation in geometric shadowing (specular G) calculation, for both direct lighting and IBL. The DFG_Smith.dds file was generated using the Smith GGX formulation which, in OGRE terms, corresponds to the default BRDF; on the contrary, the DFG_Schlick.dds file was generated using the Schlick approximation of the Smith GGX formulation which, in OGRE terms, is the default BRDF with no height correlation. I haven't checked if there's any difference (and how big) in being consistent vs mixing GGX formulations; I've attached both files just for completeness.
As I did in direct lighting pass, also in IBL I removed the weighting of the diffuse lighting contribution.
From left to right (IBL diffuse only): OGRE IBL; w/ diffuse DFG term applied; w/o any weighting factor on the diffuse lighting contribution.
There is a bug in the computation of the normal distribution function (specular D) during prefilter convolution. If you compare the theory with OGRE code, you can clearly see that a PI is missing in the denominator. Also, OGRE still use linear roughness while in the theory perceptual roughness (alpha = roughness2) is used. I replaced linear roughness with perceptual roughness as the latter seems to be the de facto standard, but a better (and more flexible) approach would have been to use something like a @property( perceptual_roughness ) as OGRE supports both workflows. Finally, the probability distribution function can be simplified as pdf = specular_d / 4 (see: https://seblagarde.files.wordpress.com/ ... br_v32.pdf, listing 19).
Another problem with roughness is that in get(Diffuse|Specular)FresnelWithRoughness pieces OGRE always uses linear roughness (ignoring if perceptual roughness is enabled).
The last quirk is that Schlick fresnel (fresnelS) is used instead of f0 (amount of light reflected back at normal incidence). I have no idea of the reasons behind this choice; my guess is that it's taken from Learn OpenGL since it's the only rendering engine that does the same thing.
Besides all this, I made two more cosmetic changes both taken from Filament. They don't fix any bugs, but our artists prefer the way Filament works over the behaviour of OGRE. The first modification is to use a different perceptual roughness to LOD mapping. Filament describes it as:
The mapping below is a quadratic fit for log2(perceptualRoughness) + iblRoughnessOneLevel when iblRoughnessOneLevel is 4. We found empirically that this mapping works very well for a 256 cubemap with 5 levels used. But also scales well for other iblRoughnessOneLevel values.
The second change is to allow some overlap between samples when convoluting the environment map.
From left to right, top to bottom (IBL specular only): OGRE IBL; w/ NDF fixed; w/ linear/perceptual roughness fixed; w/ Filament perceptual roughness to LOD mapping; w/ samples overlapping; w/ Schlick fresnel replaced by f0.
The final result of all this work.
From left to right: OGRE (IBL only); w/ all the above fixes/changes; OGRE (direct lighting and IBL); w/ all the above fixes/changes.
Since I'm the one who added the clear coat feature... shame on me! Seriously, I don't know if I made a mistake then or Filament (where have I looked to implement this feature) has made some improvements over time. The bug was that specular lighting contribution of IBL in clear coat layer was not treated as a "common" IBL (but with different NdotV, perceptual roughness and f0 values).
For consistency, I've added the same perceptual roughness to LOD mapping in the clear coat layer as well.
I keep insisting: I'm pretty sure there is a missing multiplication by PI in diffuse lighting contribution of IBL. All screenshots were taken with that fix in place.
I worked on this stuff months ago so my memories might be a little faded, but at that time I tried to be super meticulous and to double check everything at least twice. Please let me know if you find errors or something is not clear.