three.js: Bug in default shader with normal map

Description of the problem

There are rending issues due to the default shader (checked with MeshStandardMaterial and MeshPhongMaterial) when using the normal map if all the texture coordinates for the face are exactly the same.

I had exactly the same issue with other shaders and this was related to the dFdx(uv) and dFdy(uv) shader functions returning 0 and leading to a division by 0.

Live example:

Three.js version
  • Dev
  • r108
  • r104
Browser
  • All of them
  • Chrome
  • Firefox
  • Internet Explorer
OS
  • All of them
  • Windows
  • macOS
  • Linux
  • Android
  • iOS
Hardware Requirements (graphics card, VR Device, …)
OpenGL vendor: NVIDIA Corporation (0x10de)
OpenGL renderer: GeForce RTX 2060/PCIe/SSE2 (0x1f08)
OpenGL version: 4.6.0 NVIDIA 430.40

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 32 (8 by maintainers)

Most upvoted comments

This is what I believe is happening…

Even if the uv’s at each vertex are identical, interpolation of uv’s across the face of the primitive can result in different values due to numerical roundoff.

Consequently,

vec2 st0 = dFdx( vUv.st );

may not be the zero vector we would hope it would be.

The next line

float scale = sign( st1.t * st0.s - st0.t * st1.s );

will return - 1, 0, or 1.

And the following line is normalizing something that is very close to zero

vec3 S = normalize( ( q0 * st1.t - q1 * st0.t ) * scale );

which just amplifies the numerical errors since it returns a unit-length vector.

Just wanna say that additional if statements in shader code can degrade performance.

@Mugen87 The OP is only trying to help locate the issue at this point.

I found out that the problematic operation is the normalize function with zero-vector here (both S and T): https://github.com/mrdoob/three.js/blob/84f9c5145548f67ffebf6e82478df97118c7bfd3/src/renderers/shaders/ShaderChunk/normalmap_pars_fragment.glsl.js#L26-L32

if I change line 31 and 32 to the following the weird effect is solved:

vec3 S = ( q0 * st1.t - q1 * st0.t ) * scale;
vec3 T = ( - q0 * st1.s + q1 * st0.s ) * scale;
if (S != vec3(0.0))
  S = normalize(S);
if (T != vec3(0.0))
  T = normalize(T);

It seems like the current implementation of the normalize function of the Nvidia driver I use doesn’t handle zero-vectors.

@Mugen87 I think @WestLangley means that it’s better to not worry about performance issues while we try to pinpoint where the artifacts are coming from.

This is what I see on my iPhone.

IMG_0323

I fixed the last fiddle to fix the CORS error: https://jsfiddle.net/q4rwtvjy/

We just can’t reproduce. But seems like @stefaniapedrazzi may be able to help out with this?

If I have some time I will try to find out where does the issue come from and detect if there are invalid operation (at least on my system).

I thought you might be interested in making your shaders more robust (even if the problem comes from the NVIDIA driver) by adding some checks to avoid division by 0 or other undefined operations.

You can tweak these shaders directly by modifying THREE.ShaderChunk.normalmap_pars_fragment and/or THREE.ShaderChunk.normal_fragment_begin before creating a material.

Let us know if you find a workaround that solves the bug in your system and depending on the performance implications we can consider it.

Fixing the driver issue is definitely more expedient than building something around it.

Unfortunately seems to be a bug in the driver and/or device. You may want to report the issue to Nvidia instead.

the black dots in the appearance.

Can you please share a screenshot that shows how the fiddle is rendered on your computer? I’m not sure I can see them. The triangle looks like so on my iMac:

image

TBH, I do not understand the bug. Especially since uv_grid_opengl.jpg is not a normal map. It’s a map for debugging texture coordinates. Why do you assign it to .normalMap?