Inaccuracy between shader and GDScript

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By loipesmas

I’m trying to generate a planet’s shape from code.
I’m using Sprite for the image and CollisionPolygon2D for the collision.
But the Sprite and the CollisionPolygon2D don’t exactly line up.
Zoomed in image
Shader code looks like that:

vec2 st = (UV-0.5);
float r = length(st)*2.;
float angle = atan(st.x, st.y)*1./(PI*2.);
float r_a = r;
r_a *= (1.+hills_1_a*sin((angle)*round(hills_1_d)*2.*PI));
r_a *= (1.+hills_2_a*sin((angle)*round(hills_2_d)*2.*PI));
r_a *= (1.+hills_3_a*sin((angle)*round(hills_3_d)*2.*PI));
COLOR.a = float(r_a < rad_thresh*radius);

Where hills_[1-3]_[a,d] are just float scalars.

For the CollisionPolygon2D the code looks like this:

var arr:PoolVector2Array = PoolVector2Array()
var angle:float = 0
var angle_delta = 2*PI/shape_points
while angle <= 2*PI:
    var r = radius
    r *= (1+hills_1_a*sin((angle)*round(hills_1_d)));
    r *= (1+hills_2_a*sin((angle)*round(hills_2_d)));
    r *= (1+hills_3_a*sin((angle)*round(hills_3_d)));
    var p = Vector2.UP.rotated(angle) * r
    angle += angle_delta
$CollisionPolygon2D.polygon = arr

The hills_[1-3]_[a,d] variables are the same values as in the shader (they are passed as uniform in other part of the code). shape_points is the number of points in the polygon (I use around 1000 but it doesn’t affect the difference)
So as you can see the code is essentially the same. But as seen on the image above the values are off. I’ll admit, it’s pretty zoomed in. Here’s how it looks zoomed out:
Zoomed out image
But since it’s going to be a planet, I need it to be accurate when zoomed in.

What’s the reason behind this? (I’m guessing cpu vs gpu rounding difference, but I hope I’m wrong)
Is there a way to fix this?
If not, how can I work around it? How can I get accurate procedurally generated planet that has same sprite and collision?
I’m okay with having to write a lot more code if needed, even modifying the engine/writing cpp modules.
Thanks in advance.

Your case seems to be consistent with that ~5 mm (?) thick white line, is it the same all around? How big is a pixel compared to the white line? I mean, if you zoom to 1:1 (did you take the screenshot in the editor?) how many pixels is that approximately? It doesn’t look like rounding error, because it wouldn’t be that consistent. I’d try artificially making the collision a bit bigger maybe, and see if it’s closer to the sprite. Or, first try making the planet 10× or 20× bigger (as in not zooming in, but making its size bigger) and the line thickness should remain the same, while everything is scaled up, possibly scaling the problem down, maybe that works. (also try keeping the collision as simple as possible, it may be bad for the performance, but you’ll see that if it is or not)

1234ab | 2021-04-13 16:29

I forgot to mention that the inaccuracy isn’t consistent. At some points it lines up perfectly, while at others it’s as seen on the pictures. So scaling up the collision would create “gaps” in different parts. Here’s the “transition”. transition
(This picture is taken 100% editor zoom)
Using bigger texture and thus smaller Sprite scale yielded same results. Scaling both the polygon and the Sprite also yielded same results.
I will worry about performance later, if I get it to work.

loipesmas | 2021-04-13 18:54

Ahh yeah, I understand it better now. And if you make the radius parameter bigger, while keeping the bumpssize-to-radius the same, so it’s effectively scaling up, but without scaling up (that’s what I meant, sorry I wasn’t clear)?
If that doesn’t work, maybe you could figure out which one is the imprecise.
Another thing I can think of, maybe try calculating simpler stuff, like a rectangle or circle, and see if they have the same problem, or add the calculations one at a time and see which one adds the inconsistency?

1234ab | 2021-04-13 19:56

I’m not sure what exactly you mean by that scaling, but I’ve tried every combination I could think of and got the same results.
Not sure how to figure out which one is the imprecise, since I can’t get numerical values out of the shader (unless I would render it to a texture and save it and do something with it, but then there is a lot of things on the way that could change the measurements).
It seems that with lower amplitudes (hills_[1-3]_a in my code) there is no difference (or at least not noticeable), but it makes sense, since the bigger the amplitude the bigger the difference. Maybe I’ll find a “magic number” that when I multiply the wave function by it, it will line up. But I’m not sure.

loipesmas | 2021-04-14 00:01

Oh so the sin part is bad? It’d make sense for the GPU to calculate trigonometry differently, maybe it uses a lookup table or an approximation formula…
(I’m not sure that that’s the case, but it seems like a good guess)
in that case… well I know very little about shaders so idk if you can force it to calculate sin more precisely
or maybe you can try to replicate the inconsistency with gdscript but that seems like a bad idea and it may even be different with different GPUs

you could replace sine with something else that is consistent (with both implementations), maybe a sine approximation? (I just googled this, idk if it’s good) Bhāskara I's sine approximation formula - Wikipedia

1234ab | 2021-04-14 08:00

Using the Bhaskara I approximation in both the shader and GDScript gave exactly (I think) same results.
I’ve tried multiplying one of them by some value, or adding some values, but it seems that whenever I “fix” it in one place, at another it breaks.
I think I’ll have to find other way to achieve what I want.
(Unless I find out why is there a difference).

loipesmas | 2021-04-14 11:56

Super, your welcome :smiley:
the difference then is possibly just because the GPU approximates the sine, but that’s probably even vendor dependent (the impreciseness)
You could just use that approximation (make that your own sine function)? I mean, it’s probably not that much slower compared to the gpu’s own and it may even be faster in gdscript then the original implementation, though probably not. And it’s not like you need 1% preciseness when generating the terrain, only that the 1% be the exact same with both implementations
or yeah, you could use something else then sin too, but using opensimplexnoise or other pseudo-random things you’re probably better off generating the terrain once and passing it to the shader and the collision generator
(take everything I said with a grain of salt ofc, I’m just speculating)

1234ab | 2021-04-14 12:26

I think you misunderstood, due to unfortunate wording.
“exactly same results” meant “same as with the built-in sin functions”, i.e. nothing changed.
So sadly it doesn’t fix anything.

loipesmas | 2021-04-14 12:32

awww yeah thats sad indeed, I have no other ideas

1234ab | 2021-04-14 13:24