OpenGL vs DirectX normal mapping orientations

example_files.zip (4.5 MB)

I am quite well versed with the semantics and math behind “OpenGL vs. DirectX” normal mapping differences.

The attached OmniPBR MDL shader references hollyprivet_normal.png as a normal map. I am at odds with this file and hence wish to ask about the internal policies used by the MDL SDK.

The input texture map obviously (to me) appears to be in OpenGL format since the protrusions are upwards. However, after I run this OmniPBR material through my distillation code (based on example_distilling.cpp) then the normal texture map has been converted into DirectX format (protrusions pointing inward).

I do understand that the policy of the MDL SDK is to assume and use “DirectX format normal maps”.

However, I am at odd’s with what’s happening with this file. What I really need to comprehend is “How to determine if and when I may need to swap/flip my texture RGB channels between OpenGL and DirectX”. In particular, it appears to me that there is no clear context that says “Yes, the source normal map is in OpenGL format and as such the MDL SDK knowingly is going to swap the red-green or invert the green channel when baking out the new normal map on disk” as is actually happening.

So, what information is available to me at the SDK level to figure out what the SDK is actually doing in this process during the baking of the new normal map to disk compared to what it is sourcing as the input image?

Strictly speaking there is no DirectX or OpenGL normalmap format, the real difference is the location of the image origin in DX vs OpenGL: top left vs lower left.

MDL has lower left, eg behaves mathematically like OpenGL.

If you want to use the baked texture in your own renderer, no analysis of the material is necessary, the decision whether you need to flip or not entirely depends on your renderers semantic for normalmapping. In a straightforward OpenGL implementation, the baked normalmap will always fit, in a straightforward DX renderer you will need to always flip.

The flipping you observe here is due to Omnisurface. Your material relies on default parameter settings:

uniform bool flip_tangent_v = true [[
    anno::display_name("Normal Map Flip V Tangent"),
    anno::in_group("Normal")
	]],

Ie Omnisurface by default expects “DX style” normalmaps and flips the tangent to fit MDL’s semantic