Unpacking normal map in shaderMaterial

I am a threejs developer and I am not familiar with the babylon API. When I use shaderMaterial and try to unpack the normal map in the fragment shader, I use the threejs approach. I also checked the source code of babylon. The reference is the same article, but babylon has some special processing that puzzles me. By the way, I tried to load a gltf model and replace its material, and finally output the color of the normal map, but it is different from what I expected.

in bumpFragmentMainFunctions.fx

mat3 cotangent_frame(vec3 normal, vec3 p, vec2 uv, vec2 tangentSpaceParams)

I can get the tbn matrix by this function,but when I looked up the source code to find out why the unexpected results occurred, I found that when invertNormalMapX is true, the x component of tangentSpaceParams is -1. Why is this necessary? And why does Babylon load a gltf model and replace its material and the output normal map looks wrong?

Hey and welcome!

I think everything you mentioned comes from the fact that we are left handed where Three is right handed

I’m not sure to quite follow you there :slight_smile: Can you illustrate it with a repro in the playground?

I am not familiar with playground, I can share some pictures for comparison

Here you are: https://playground.babylonjs.com/

precision highp float;

uniform sampler2D uDiffuse;
uniform sampler2D uNormalMap;

varying vec2 vUv;
varying vec4 vWorldPosition;
varying vec4 vWorldNormal;

mat3 cotangent_frame(vec3 normal, vec3 p, vec2 uv, vec2 tangentSpaceParams) {
		// get edge vectors of the pixel triangle
    vec3 dp1 = dFdx(p);
    vec3 dp2 = dFdy(p);
    vec2 duv1 = dFdx(uv);
    vec2 duv2 = dFdy(uv);

		// solve the linear system
    vec3 dp2perp = cross(dp2, normal);
    vec3 dp1perp = cross(normal, dp1);
    vec3 tangent = dp2perp * duv1.x + dp1perp * duv2.x;
    vec3 bitangent = dp2perp * duv1.y + dp1perp * duv2.y;

		// invert the tangent/bitangent if requested
    tangent *= tangentSpaceParams.x;
    bitangent *= tangentSpaceParams.y;

		// construct a scale-invariant frame
    float det = max(dot(tangent, tangent), dot(bitangent, bitangent));
    float invmax = det == 0.0 ? 0.0 : inversesqrt(det);
    return mat3(tangent * invmax, bitangent * invmax, normal);
}

vec3 UnpackNormal(sampler2D tex, vec2 uv) {
    vec4 normalTex = texture2D(tex, vec2(uv.x, 1. - uv.y));
    vec3 normalTs = normalTex.rgb * 2.0 - 1.0;
    return normalTs;
}

void main() {

    vec3 normal = UnpackNormal(uNormalMap, vUv);

    mat3 tbn = cotangent_frame(normalize(vWorldNormal.xyz), vWorldPosition.xyz, vUv, vec2(-1., 1.));

    normal = normalize(tbn * normal);

    normal.z *= -1.;

    gl_FragColor = vec4(pow(normal, vec3(1. / 2.2)), 1.);

}

here is fragment shader


this is babylon output


this is threejs output

Is there something I did wrong that caused this difference?

Can you repro your code in the playground?

This should also help you understand what convention we are using:
Normal Maps | Babylon.js Documentation

Sorry, I used a local model file. I don’t know if there is an online link, but I can open a github repo to show my code.

Well I guess you should have all you need now. i believe you assumed opengl convention for the normal map where we use dx one as stated in my link

I read the link you gave. The reason for this difference is that the normalmap required by Babylon is from DX while the normalmap required by Threejs is from OpenGL. So the output to the screen will look inconsistent, right?

if you use our gltf loader it will make the expected changes so it looks good and respectful of the gltf spec

In your case if you want to write your own shader you have to take that in account

We can of course load the gltf data correctly :slight_smile:
example: glTF Loader Demo | Babylon.js Playground

I wonder if this is where your problem lies?

Did you check what we do in the perturbNormal in the shader

Thank you. I understand the reason for the difference and I am also reading the source code. However, I only found that the tangentSpaceParams parameter of the cotangent_frame function has changed. I tried to fill in the same parameters, but the actual display effect is still unexpected.
I’ll try to read what you did in perturbNormal. Can you give me some source code links?

you should find it here:
Babylon.js/packages/dev/core/src/Shaders/ShadersInclude/bumpFragmentMainFunctions.fx at 0ff88f11f5bfc2f298074161f713bde1f289f4db · BabylonJS/Babylon.js

I checked the source code of this part and finally pointed to the function cotangent_frame. The fourth parameter of this function is tangentSpaceParams. I found the value he passed in by searching in the source code. Here is Babylon.js/packages/dev/core/src/Materials/PBR/pbrBaseMaterial.ts at 0ff88f11f5bfc2f298074161f713bde1f289f4db · BabylonJS/Babylon.js · GitHub. I tried to check it with a breakpoint and fill in the same value, but the output results are still different. This is what confuses me. Am I missing something?

As I provided above, the fragment shader code, vec2 (-1, 1) is the value I captured after debugging through the breakpoint

I see

do you mind editing this PG with your shader? So we can debug with you:
glTF Loader Demo | Babylon.js Playground

I’ll give it a try. I’ve also prepared a github repo for you to check out GitHub - KallkaGo/Babylonjs-Template: Native Babylonjs Template in the test branch

1 Like

try https://playground.babylonjs.com/#WGZLGJ#11161