Advertisement

Shader float precision error - how to resolve?

Started by April 13, 2023 11:17 PM
4 comments, last by Gnollrunner 1 year, 7 months ago

Two GIFs of the problem (second is most pronounced):
https://giphy.com/gifs/5x2iJK0wxEGjWR8RUS
https://giphy.com/gifs/N3aqlCIs1fSrbQowPS

I am pulling my hair out over an issue that I believe is related to single float precision in Unity shader.

I adapted this shadertoy (https://www.shadertoy.com/view/sdjBRy) into my unity project (converting glsl to hlsl) and everything worked fine at first. The shader creates a shape, filled with color, defined by a closed loop of bezier curves. I pass the float2 positions of the bezier control points into shader via a buffer. Fine.

However, a problem arises when I move the control points (and thus redefine the shape) at runtime by interpolating between different values for the control point positions. At certain positions, the fill-rule suddenly breaks and I get streaks of pixels across the UV x-axis (which is the direction that the shader sends 'rays' in to perform the intersection test to determine if a pixel is inside the shape, for fill color). The shape also becomes oddly pixelated. These streaks occur at the positions of bezier segment start and end points.

The relevant lines in the shadertoy start at line 188 where the "intersection count" function sends out rays from each pixel to see how many times the ray crosses a segment that defines the shape. If it intersects the shape border an even number of times, the pixel is outside the shape, if odd, inside the shape. This even-odd rule is common in SVG program: https://en.wikipedia.org/wiki/Even–odd_rule

Line 203 defines an epsilon, which I believe is to take into account single-float precision issues when comparing float values. THE PROBLEM is that while this epsilon works in shadertoy just fine, it is not sufficient inside my unity project to prevent artifacts. In the shadertoy, change eps to 1e-2 for an example of the problem I am having. There appears to be no value for epsilon small enough to prevent these artifacts in Unity project, however.

Is there ANY possible way I can overcome this precision limitation and prevent these completely unacceptable artifacts? I was thinking about using structured buffers to pass double precision into shader, however this would likely prevent me from using node-based graphs for shader.

Thanks for any guidance!

I don't use Unity or Bezier curves, however I have done polygon fill using the odd even rule. This looks like a classic case where a scan crosses a point where two edge lines meet and counts them twice instead of once, throwing the whole sequence off. You can also get this in more complex cases. For instance, say you have a self-intersecting shape. I typically solved this after I got down to integer coordinates because at that point you have a solid begin and end numbers to compare for a double cross.

You also have to check how the lines you are crossing are positioned. For instance, if they cross at a point that goes up/down, or down/up you do in fact want to count that twice. If they cross lines going up/up or down/down, you only count that once. Doing that kind of check in float is pretty iffy, but maybe there is a solution.

I use DirectX so I can use PIX. However, in the past I have even coded the same algorithm on the CPU just to debug it. It's not a guarantee that the same thing will happen but I have found bugs that way. Finally I have used double on the GPU. It is super SLOW. You can get away with a few simple calculations but you should try to get things into float ASAP for the rest of your shader. I basically use it for tiling noise. I send in double and quickly mod it into float before doing the rest of the noise calc, not that this applies to you. But again, keep double calculations to a minimum GPU side.

My guess is you don't need double anyway. Try to find a way to debug it and figure out what's actually going on. Blindly playing with fudge factors is usually pretty shaky. Not sure if any of this helps. good luck.

Advertisement

Thanks for your reply. It is more help than I have recieved on the Unity forum lol. I am interested in learning a little more about the double intersection count for points that overlap. I am not exactly sure on the details of how you say you solved this previously. Could you expand a little?

I was assuming that these artifacts were caused by gaps where the intersection test was not picking up the very end bits of the segments thus allowing for a little gap between segments. But the double counting possibility sounds likely. Thanks for your help so far. Again I would appreciate if you would be willing to expand a little on how you solved this before? Not sure what you did by setting the values to integers. Thanks!

Also, not sure why it would only cause these artifacts at certain positions and not others? Or why it works fine in shadertoy version but not in unity project?

jsmithir123 said:

I am interested in learning a little more about the double intersection count for points that overlap. I am not exactly sure on the details of how you say you solved this previously. Could you expand a little?

Well you can see in the shape below that the scan line passes though the shape 3 times. The first time it passes through the ends of two segment so we count that twice. The second time it passes through the middle of a segment, so we count that once. The third time it also passes through the ends of two segments, but in this case we only count it once since the orientation of the segments are down/down (assuming counter-clockwise winding). In the first crossing we had an up/down orientation. If we add all crossings left of the point in question, for point #1 we have a value of 2 which is even, so it's considered out. For point #2 we have a value of 3 which is odd, so it's considered in.

I was assuming that these artifacts were caused by gaps where the intersection test was not picking up the very end bits of the segments thus allowing for a little gap between segments.

It's also possible it could be due to gaps. I really can't say without looking at the code and running it myself.

This topic is closed to new replies.

Advertisement