Dealing with numerical artifacts in fragment shader (I think?). Not sure how to handle it

Hi there
I’ve got an atmospheric simulation that works great… except on a specific computer.
This is what I see

It happens when simulating Jupiter (which has a huge radius), but not smaller planets. So I’m thinking it’s some kind of floating point error, but I have no idea how to track it down, and what part of the shader would create this pattern.

I also have a flat rendering of the sky that projects onto a plane geometry and it looks fine on that same computer with the same settings.

If anyone has any ideas I’d appreciate any insight!

I think you’re correct in your guess that it’s a float precision problem… but there isn’t enough info to diagnose it. It could be anything from some depth buffer interaction, to some surface distance calculation that is over/underflowing… a divide that is getting too close to 0 on that surface…
Perhaps the planet vertex is too close Float Max for a 16 bit float…
perhaps the precision float value in the shader is 16 bit…
precision highp float; //et al…

Can you use the jupiter parameters but reduce the magnitude of the jupiter geometry vertices and see if the pattern changes?

Can you move jupiter and the camera closer to 0,0,0 and see if the pattern changes?

Perhaps, make a backup and try to strip the shader down until only the artifact in black and white remains?

Thanks those are great suggestions!
It’s tricky to test because it’s on my coworker’s computer. The fact that the shader works for different vertices makes me think that’s a huge factor. Currently I create an IcosohedronGeometry with radius 1 and 32 vertices, then set the mesh scale to be xyz = 1e11 (so that I can move the camera into a far orbit).

Do you have any sense why that triangular pattern appears? Is that a clue at all? It doesn’t look like it would correspond to the Icosehedron geometry…

That’s sad. Have you compared the WebGL1 or WebGL2 features of both computers? If there are differences, they might provide some hints: WebGL Report

If you have some minimal example (that just creates the pattern, without any fancy staff, lilguis, etc), I could try it tomorrow (or during the weekend) on an old computer with a weak GPU.

Thanks! I didn’t know about that site. I’ll see what it says…
What I did try is this report:


But it’s pretty much identical to my computer that runs the interactive just fine.

I’ll see if I can put together a minimal example, but I’m traveling soon so I may not have time.

Have you tried using a logarithmic depth buffer?

Yes that’s what I’ve currently got enabled

Okay I have a minimal replication:

https://codesandbox.io/p/sandbox/quirky-shader-pxmtd7

And here are the webgl test results:


Some funny results. Look at the third one.

Result 1

On my old machine, the example shows nothing (empty canvas). On my current machine I have two graphics cards. The worse one (some Intel integrated) shows the example well. The better one (some NVIDIA) shows the buggy pattern.

Result 2

I was able to minify the fragment shader via extreme brutality and still get the pattern. Here is the minified version and the pattern:

varying vec3 rayOrigin;
uniform float planetRadius;

void main() {
  if( dot(rayOrigin, rayOrigin) < (planetRadius * planetRadius) )
      gl_FragColor = vec4(1,0,0,1);
  else
      gl_FragColor = vec4(0,0,1,1);
}

My conclusion is that the length of ray origin and planet radius are too close to each other. So, it is a kind of precision issue. By creation GPUs hate big numbers and also, numbers that are too close to each other.

Result 3

When I change an offset in the vertex shader from 1.0 to 100.0, there is no pattern.

rayOrigin = cameraPosition - origin + vec3(0, 100.0, 0); // was (0, 1.0, 0)

I don’t have time to understand the meaning of this offset, but if 100.0 is OK, you can try with it to see what’s happening.

Some additional notes:

  • initially I tried with 10.0 and the pattern was significantly reduced, but still existing
  • if you simulate a larger planet or a more distant planet, maybe even 100.0 could be insufficient
  • you may want to covert 100.0 into uniform, if you need smaller offset for smaller planets
  • the numbers in the JS code are huge, GPU will (or already does) choke on them
2 Likes

Just a small note:

  vec4 worldPosition = modelMatrix * vec4(position, 1.0);
  gl_Position = projectionMatrix * modelViewMatrix * worldPosition;

modelViewMatrix = viewMatrix * modelMatrix
But you already apply modelMatrix to position in the first line, and then apply it again in the second line.
I think, the second line needs to be something like this:
gl_Position = projectionMatrix * viewMatrix * worldPosition;

3 Likes

Yup that looks like a good catch. Also reminds me of a similar / subtle issue I’ve seen before…

I’ve seen shaders that do that transformation in 2 different ways that make the math resolve differently with different precision, resulting in z-fighting:

for instance:

gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.);

vs

vec4 worldPosition = modelMatrix  = vec4(position,1.) ;
gl_Position = projectionMatrix * viewMatrix * worldPosition;

Will produce slightly different output causing “identical” objects to z-fight.

In a past version of threejs the BasicMaterial and StandardMaterial each used a different style, which made BasicMaterials zfight with standard. Don’t know if this is still the case.

@PavelBoytchev Thank you! That was extremely helpful in tracking down the issue.
@prisoner849 Good catch!

I looked into this some more and it looks like it may be to do with the ray-sphere intersection test. This precision issue is known and I found a solution in an nvidia paper:
Precision Improvements for Ray/Sphere Intersection
Eric Haines, Johannes Günther, and Tomas Akenine-Möller

I still need to verify this has fixed the issue. Will post again when i find out.

2 Likes