mmp / pbrt-v4

Source code to pbrt, the ray tracer described in the forthcoming 4th edition of the "Physically Based Rendering: From Theory to Implementation" book.
https://pbrt.org
Apache License 2.0
2.8k stars 429 forks source link

Normals: Tangent space to world space transforms errors #381

Closed TiernoGs closed 11 months ago

TiernoGs commented 1 year ago

Hello @mmp,

I recently stumbled accross a bug when using normal maps. Apparently, when using instances (or even different objects) that share the same uvs, normals applied through a normal map follows the active rotation of the object. This shouldn't be the case as source direction is unique for infinite lights.

Here is the scene that gives me an error: scene.zip Command: pbrt.exe scene.pbrt --spp 4 --log-level verbose --seed 0 --nthreads 8 --disableWavelengthJitter

The pbrt render:

render

And the expected result when using Blender Cycles:

blender

In my opinion, it doesn't come from the reading of the normal map texture but either from the transform in the surface intersection shading geometry setup or the tangent to world space transformation.

Maybe this is also related to the issue with normals on gpu #377 ?

Thanks

mmp commented 1 year ago

Very interesting. Thanks for reporting this!

The underlying issue comes from pbrt being a little weird in how it handles shading normals: it wants to have surface partial derivatives that, when you take their cross product, you get the desired shading normal. Then the way it handles normal maps is a bit unusual as well: by the time the normal map is evaluated, we no longer have access to the full object space->rendering space transformation, which would be nice to have around to bring the normal map's object-space normal into rendering space...

For this case, the problem comes from this line in src/pbrt/materials.h: https://github.com/mmp/pbrt-v4/blob/425faa0843d9f3518a7564b434cb340ad85bda41/src/pbrt/materials.h#L98 It constructs a coordinate frame about the pre-normal mapped shading normal and then calls Frame::FromLocal() to bring the normal mapped shading normal out of the tangent space coordinate system (with the normal aligned with (0,0,1)) into rendering space. The problem is that FromZ chooses arbitrary basis vectors for x and y, while we'd like at least x to be aligned with (1,0,0) in tangent space.

Sooooo..... Replacing that line with:

    Frame frame = Frame::FromXZ(Normalize(ctx.shading.dpdu), Vector3f(ctx.shading.n));

gives this image:

image

which is at least consistent regardless of the rotations, though it doesn't match blender.

I'm not sure what the discrepancy is (pbrt being left handed and blender right-handed?) and would like to figure that out, but will at least check in that fix after testing it with more scenes.

pbrt4bounty commented 1 year ago

I can tested on Blender since I have a full operative plugin. BTW.. in Blender, Z is up

TiernoGs commented 12 months ago

Hello, thank you for your reply, the updated line works just fine now :) No need to look deeper into the light orientation, the blender cycles render was a way to highlight the normal map bug. I'm closing the issue, thanks !

mmp commented 11 months ago

Interestingly, this fixes a number of errors in other scenes that use normal mapping; compare the curb, bistro glass doors, and menu to the left of the doors.

old new