PBRMaterial normal map looks incorrect at high angles

I noticed that PBRMaterial (and NodeMaterial) normal map implementation seems to have black fringes around areas where the normal has been heavily affected by the normal map.

Here is how the glTF-Sample-Models repository “NormalTangentTest” model looks like. See how there is a dark fringe around the right side normal mapped mesh, compared to the left side reference mesh that is pure geometry instead of normal map:

Here is a playground test model: https://playground.babylonjs.com/#BRN8WP#2

In my investigation I discovered that this seems to be related to how PBRMaterial also uses un-perturbed worldNormal for normal map calculation(?):

It all of a sudden looks identical to the reference:

I have always been wondering why PBRMetallicRoughness node needed 2 normal inputs, when only the perturbed normal (if available) should be used for any shading related calculations. But it apparently besides confusion, it also seems to cause this bug.

This is related to horizon occlusion a trick to simulate shadowing from small geometry: https://playground.babylonjs.com/#BRN8WP#3 Normal maps are not meant to generate fake geometries as in the sample and in 80% of the cases it improves rendering hence why it is on by default Marmoset Newsfeed • Horizon Occlusion for Normal Mapped Reflections

Both geometric normals and perturb ones actually provides usefull information and their delta is heavily used in the rendering for instance in the previous occlusion computation.

1 Like

Thanks. That seems to be it indeed. :+1:

@sebavan Would it be possible to expose the fade parameter as material option? The hard coded 1.1 seems to be bit too strong effect for my use case, but somewhere between 0 and that would likely be optimal.

EDIT: As horizon occlusion is just multiplier on the desired specular components, I was able to implement it with nodes. Looks like something around 0.7 works nicely. Not too strong to notice under normal circumstances but helps with the extreme angle shine and fully blocks “inside the surface” angles:

1 Like

Coooool this is exactly what NME is for :slight_smile: