[Solved] Normal map on Android

I am having problem with normal maps on Android(9.0.6 oxygen OS) Google Chrome (84.0.4147.125).
I have a model in glb format that I load from GLTFLoader in my code. The problem is when I open my app on mobile directional light doesn’t work as expected only when there is a normal map is enabled. Contrary to this the model loads fine and light works correctly on macOS(10.15.4) Google Chrome (84.0.4147.135). I thought it might the problem with the normal map coordinate system. So, I inverted the normal map and the results where correct on Mobile(Android) but not on macOS.

Here is the screenshot with default normal map.

Normal map in above screenshot

And following is the screenshot with flipped normal map.

Normal map in above screenshot

Also, if set material side to THREE.FrontSide it fixes the problem

I wan’t to keep materials as THREE.DoubleSide and work with same normal map. I know there is difference between Direct-X and OpenGL coordinate system and having normal maps in one coordinate system gives you opposite results in other. But where I am confused is that why is that happening only on Android. Is it something related to OS? Or I am missing something.

I hope I have explained my problem well. Feel free to question in case you don’t understand.

Can you please test if the following conformance test work on your mobile device?

https://www.khronos.org/registry/webgl/sdk/tests/conformance/glsl/misc/shader-with-dfdx-no-ext.frag.html?webglVersion=2&quiet=0&quick=1
https://www.khronos.org/registry/webgl/sdk/tests/deqp/functional/gles3/shaderderivate_dfdx.html?webglVersion=2&quiet=0&quick=1
https://www.khronos.org/registry/webgl/sdk/tests/deqp/functional/gles3/shaderderivate_dfdy.html?webglVersion=2&quiet=0&quick=1

Besides, are you using the latest version of three.js? What mobile phone are you testing with? Does it have an Adreno GPU?

1 Like

If you apply no normal map, please try to compute tangents via BufferGeometryUtils.computeTangents() and set Material.vertexTangent to true for the respective materials.

Your issue could be a GPU driver bug which is only fixable by using precomputed tangent vectors (instead of computing them on-the-fly in the shader).

2 Likes

Thankyou @Mugen87 for the response.

Passed these tests on my mobile. Can’t share screenshots. As, list of tests was long. I am using three.js version 0.117.1

Mobile is OnePlus 3T.
Yes, it have an Adreno 530.

In this case, you have to give BufferGeometryUtils.computeTangents() a try.

@Mugen87 I am trying to import the BufferGeometryUtils but getting the following error.

It seems you are using WebPack, right? Please try to use the ES6 module version since all global script files will be deleted at the end of the year. Try it with:

1 Like

@Mugen87 Yes, you are right. I am using webpack. So, I tried BufferGeometryUtils.computeTangents with material.vertexTangent set to true. Still no luck.

Here is the code

ChangeTextureEncoding: function(content, textureEncoding) {
  content.children.forEach((child)=>{
    child.children.forEach((child)=>{
      BufferGeometryUtils.computeTangents(child.geometry);
    });
  });
  
  this.TraverseMaterials(content, material => {
    if (material.map && !material.normalMap){
      material.map.encoding = textureEncoding;
    }
    // material.side = THREE.FrontSide;
    material.vertexTangent = true;
    material.needsUpdate = true;
  });
},

Thankyou @Mugen87 So, I was able to fix this issue with the following code.

CheckDoubleSideIssue: function(object3D){
  
  var gpuHasFrontFacingDoubleSidedBug = false;
  var debugInfo = this.renderer.getContext().getExtension('WEBGL_debug_renderer_info');
  if (debugInfo !== null)
  {
    var gpu = this.renderer.getContext().getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
    gpuHasFrontFacingDoubleSidedBug = gpu.match(/adreno.+(5|6)[0-9][0-9]/gi) !== null;
  }
  
  object3D.children.forEach((child)=>{
    child.children.forEach((child)=>{
      if (gpuHasFrontFacingDoubleSidedBug)
      {
        BufferGeometryUtils.computeTangents(child.geometry);
        child.material.vertexTangents = true;
        child.material.needsUpdate = true;
      }
    });
  });
},

Marking it as solved.

Just adding that including tangents in the GLTF file during exporting also fixes this one.

Screenshot 2020-09-08 at 09.14.22

2 Likes

@ismaeel I am struck with the same problem. The above code doesn’t seem to work for me. I am using version R117. Any help would be appreciated.

Thanks

Sure @Balamurugan_Sethuraman , The above code worked for me. Though this problem can be Solved with either code or asset change. About the code not working. I have two suggestions. Now that I have better grip on js and three.js I can see two problems with the code I shared above.

  1. Using traverse function instead of looping through child of the loaded object.
  2. The flag gpuHasFrontFacingDoubleSidedBug is checking the gpu name string for the adreno gpu models between 500 to 699.

Here is the updated code for 1.

CheckDoubleSideIssue: function(object3D){
  
  var gpuHasFrontFacingDoubleSidedBug = false;
  var debugInfo = this.renderer.getContext().getExtension('WEBGL_debug_renderer_info');
  if (debugInfo !== null)
  {
    var gpu = this.renderer.getContext().getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
    gpuHasFrontFacingDoubleSidedBug = gpu.match(/adreno.+(5|6)[0-9][0-9]/gi) !== null;
  }
  
  object3D.traverse((child)=>{
    if (gpuHasFrontFacingDoubleSidedBug)
    {
      BufferGeometryUtils.computeTangents(child.geometry);
      child.material.vertexTangents = true;
      child.material.transparent = true;
      child.material.needsUpdate = true;
    }
  });
},

For the 2nd you can see your GPU name and update the string check accordingly in your code.

Lastly if you have the option to modify the asset. You can follow the answer by @hm711 above.