2

I wrote a simple shadertoy example of ray-plane intersection. I am using a checker pattern on the plane and I noticed odd artifacts. I know that this lacks anti-aliasing, so I was fully expecting jagged lines. However, there are random pixels along the edges that seem to stand out as being incorrect. I tried my best to recreate the same thing in Blender using the Cycles renderer with 1 sample per pixel, and it produced a result closer to what I was expecting. Can anyone explain this?

enter image description here

float intersect(in vec3 ro, in vec3 rd)
{
    return -ro.y / rd.y;
}

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    vec2 uv = ((fragCoord + vec2(0.5)) / iResolution.xy * 2.0 - 1.0) * vec2(16.0 / 9.0, 1.0);
    vec3 ro = vec3(0.0, 1.0, 0.0);
    vec3 rd = normalize(vec3(uv, 2.0));

    float t = intersect(ro, rd);

    vec3 col = vec3(0.0, 0.0, 0.0);

    if (t >= 0.0)
    { 
        vec3 pos = ro + t * rd;
        vec2 tex = floor(pos.xz);
        col = vec3(mod(tex.x + tex.y, 2.0));
    }

    fragColor = vec4(col, 1.0);
}
Chris_F
  • 4,991
  • 5
  • 33
  • 63
  • Might be floating rounding error problem to be sure try to use `double` and `dvec` instead of `float` and `vec` if the artifacts dissapear or diminish its the case. In such cases you have to preserve precision so order of math operations matters ... also see [ray and ellipsoid intersection accuracy improvement](https://stackoverflow.com/q/25470493/2521214) for some additional ideas. – Spektre May 29 '19 at 07:07
  • Some wild speculation which may or may not be remotely correct: recreation of the scene in Cycles with a mesh could be the explanation for its superior results. If polygons in two adjacent BVH nodes share an edge which the ray hits (to within numerical error), the traversal algorithm will **always** choose the node closest to the source of the ray, giving rise to a "consistent [fill rule](https://learn.microsoft.com/en-us/windows/desktop/direct3d9/rasterization-rules)" even with just 1 spp. – meowgoesthedog May 29 '19 at 14:38
  • Update: turns out using an analogous method to the above (always sampling the nearest tile if the ray hits an edge) works brilliantly. Was going to post an answer but I couldn't find any cases which broke `Alex`'s fudge factor remedy... wonder what your thoughts are. – meowgoesthedog May 29 '19 at 20:26
  • Hard to see any bug or unexpected behaivor here, i'm sure if you do the math with pen and paper for every pixel you will get the same output as your shader result. https://www.shadertoy.com/view/wtBGDD bleder osl shader. yes, without the need of iResolution computation your code works fine http://pasteall.org/blend/index.php?id=51880 – nabr Jun 03 '19 at 15:58
  • @nabr Alex was correct that the problem is due to floating point rounding errors. If you do the math by hand you do not get the same result. The maximum error seems to be on the order of ~1e-5. At this point I am simply researching what the best way is to deal with this problem in general. – Chris_F Jun 03 '19 at 22:01
  • @Chris_F ok, man glad you found ur answer. i just wonder why you need to do the hole thing that complicated, and of course sooner or later you run into some problems. i would just throw a small number somewhere. if you look at the raymarching guys they do define a constant eps = .001 https://www.shadertoy.com/view/wtBGDD (just had some time to waste, nevermind me), good luck – nabr Jun 04 '19 at 10:47

1 Answers1

1

If the grid lines fall directly on a sampling point then floating point error will determine which grid square is chosen for that pixel. Cycles uses random sampling points, while you are using integers (fragCoord + vec2(0.5)). Changing your sampling points to fragCoord + vec2(0.001234, 0.004321) removes the artifacts.

Alex - GlassEditor.com
  • 14,957
  • 5
  • 49
  • 49
  • Unfortunately I can't except this as the answer. Translating the shader code to C++ yields similar results with both 32-bit and 64-bit floats. Lack of precision in GLSL implementations is not the issue. Secondly, there is no explanation for why it would be "correct" to fudge a random constant like 16.0 to 16.0001 to fix the issue. – Chris_F May 28 '19 at 15:40
  • @Chris_F I changed my answer. – Alex - GlassEditor.com May 28 '19 at 18:42
  • If Cycles used random sample points then the image would be noisy, but it isn't. I modified my shader to use random sample points between [0, 1) and as I suspected things were worse, not better.There is nothing random about 0.001234 and 0.004321 except for the fact that they may have been chosen by you at random. – Chris_F May 28 '19 at 19:30
  • @Chris_F https://docs.blender.org/manual/en/dev/render/cycles/settings/scene/render/integrator.html says that cycles uses a random sampling pattern. I think it is constant for each pixel (the random pattern is chosen once then used for all the pixels) or at least just for the one sample case. – Alex - GlassEditor.com May 28 '19 at 21:08
  • So I did a bit of empirical testing with Blender since I didn't fancy my chances of finding any useful information in the documentation regarding this. My test was to render the scene twice with SPP set to 1 and using two different seed values (since different seeds will produce different random values.) The result was that both images were 100% identical. I then repeated the test with SPP set to 2 and the results were nonidentical images. Clearly with Cycles 1 SPP is a special case and there is no randomization of the sample points. Or maybe simply the first sample is never random. – Chris_F May 28 '19 at 22:03