• Create Account

Javascript's “Math.sin/Math.cos” and WebGL's “sin/cos” give different results

Old topic!

Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

12 replies to this topic

#1lallish  Members

Posted 08 July 2013 - 05:29 PM

I'm trying to generate fibonacci distributed points on a sphere. However when I try to do so in the shader on the GPU, the calculation of sin/cos becomes very different than if I would have calculated Math.sin/Math.cos on the CPU. Here is part of the vertex shader of the fibonacci function:

attribute float index;          //array with numbers between [1, 2500000]

float inc = 3.141592653589793238462643383279 * (3.0 - sqrt(5.0));
float off = 2.0 / 2500000;
float yy = index * off - 1.0 + (off / 2.0);
float rr = sqrt(1.0 - yy * yy);
float phi = index* inc;                     // big number, "5476389.695241543"-biggish
vec3 fibPoint = vec3(cos(phi) * rr, yy, sin(phi) * rr);  // calculates sin/cos wrong

This part gives the wrong fibPoint vectors of locations that looks like this: http://i.imgur.com/Z1crisy.png

And when I calculate the Math.sin(phi) and Math.cos(phi) of the fibPoint vector in javascript, and throw these values in as attributes into the shader instead, so the code looks like:

 /* short version of code from javascript */
var y = index * off - 1 + (off / 2.0);
var r = Math.sqrt(1 - y * y);
var phi = index * inc;
var cosphi = Math.cos(phi);
var sinphi = Math.sin(phi);
....
..

/* throw cosphi/sinphi into the shader as attributes along with the index */

attribute float index;
attribute float sinphi;
attribute float cosphi;

float inc = 3.141592653589793238462643383279 * (3.0 - sqrt(5.0));
float off = 2.0 / 2500000;
float yy = index * off - 1.0 + (off / 2.0);
float rr = sqrt(1.0 - yy * yy);
//float phi = index* inc;
vec3 fibPoint = vec3(cosphi * rr, yy, sinphi * rr);

This gives the correct fibPoint vectors. Now the locations looks like this: http://i.imgur.com/DeRoXkL.png

So my question is, why doesn't WebGL's sin/cos give same or similar result as javascript's Math.sin/cos? As far as I know, both input is radians, and both output [-1, 1]. May it be because "phi" are too large numbers so some part get truncated in the cos/sin?

#2Cornstalks  Members

Posted 08 July 2013 - 06:17 PM

POPULAR

The big number is an issue. If your vertex shader is working with 32-bit floats, that only gives you 6 decimal digits of precision. 5476389.695241543 to 6 decimal digits of precision is 5476380.000000 (truncating everything after the 6 digits). Pi is only ~3.14, and since sin/cos are periodic, using large numbers doesn't give you any benefit over using smaller numbers (because the large numbers just wrap around). However, your numbers are so large that they wrap around so much that they don't even precisely map to the [-pi, pi] (or [0, 2pi]) range. Basically, the wrapping around throws away all the "high" digits and only keeps the relevant low digits, but unfortunately for you all your low digits are junk because you spent all 6 of your precision digits on the ones that get thrown away, and now all your low (but most important) digits are meaningless.

In short, yes, those huge numbers will kill you.

However, in JavaScript, all floating point numbers are 64-bit, which gives you 15 decimal digits of precision. That means in JavaScript you can actually properly represent 5476389.69524154, so your trig calculations are actually accurate (assuming your JavaScript code is processing the same large values that your vertex shader is).

Edited by Cornstalks, 08 July 2013 - 06:26 PM.

[ I was ninja'd 71 times before I stopped counting a long time ago ] [ f.k.a. MikeTacular ] [ My Blog ] [ SWFer: Gaplessly looped MP3s in your Flash games ]

#3MarkS  Members

Posted 08 July 2013 - 06:59 PM

Maybe I am completely off here, but if your desire is for the cubes to be distributed across the surface of a sphere, why not make the inputs to sin and cos lie between -pi and pi? This would make the initial positions lie on a unit sphere and then you can scale them by the radius of the large sphere to get their final positions.

#4lallish  Members

Posted 09 July 2013 - 07:37 AM

Thanks for that information Cornstalks. Do you suggest calculating the fibPoint on the CPU instead and throw the vec3 in as an attribute? Because I'll be dealing with much larger numbers later, index might go as high as 400 000 000

Maybe I am completely off here, but if your desire is for the cubes to be distributed across the surface of a sphere, why not make the inputs to sin and cos lie between -pi and pi? This would make the initial positions lie on a unit sphere and then you can scale them by the radius of the large sphere to get their final positions.

Yes, I could do that, with something like:

float tphi = phi - twoPI*ceil(phi/twoPI - 1.0);

But I would still need the precision of phi to do that, and "yy" and "off" too if I expand my index in the future.

Edited by lallish, 09 July 2013 - 07:37 AM.

#5apatriarca  Members

Posted 09 July 2013 - 08:04 AM

Computing that number in Javascript on the CPU solves the problem only partially. At some point you may have problems using doubles too. Big numbers and trigonometric functions do not work really well together... Why are you using such formula to generate the points?

#6Cornstalks  Members

Posted 09 July 2013 - 08:26 AM

Thanks for that information Cornstalks. Do you suggest calculating the fibPoint on the CPU instead and throw the vec3 in as an attribute? Because I'll be dealing with much larger numbers later, index might go as high as 400 000 000

If the points don't move around and are static, that might make the most sense to do. Even if you simplify the formula to be θj = arccos(1 - (2j + 1) / N), Φj = 2jπφ-1, 0 <= j < N, you run into precision errors as j grows > 999,999 using 32-bit floats.

Computing that number in Javascript on the CPU solves the problem only partially. At some point you may have problems using doubles too. Big numbers and trigonometric functions do not work really well together... Why are you using such formula to generate the points?

It's a specific formula for generating a perfect distribution of points over a sphere.

[ I was ninja'd 71 times before I stopped counting a long time ago ] [ f.k.a. MikeTacular ] [ My Blog ] [ SWFer: Gaplessly looped MP3s in your Flash games ]

#7lallish  Members

Posted 09 July 2013 - 08:46 AM

Computing that number in Javascript on the CPU solves the problem only partially. At some point you may have problems using doubles too. Big numbers and trigonometric functions do not work really well together... Why are you using such formula to generate the points?

I want to distribute points equally over a sphere and I don't know another way of doing so, do you know of a better formula?

If the points don't move around and are static, that might make the most sense to do. Even if you simplify the formula to be θj = arccos(1 - (2j + 1) / N), Φj = 2jπφ-1, 0 <= j < N, you run into precision errors as j grows > 999,999 using 32-bit floats.

They are static yep. What I will try now then is to use Web Workers to calculate the points to off load the main thread in javascript, only thing I'm afraid of is how many attributes I can update at the same time without any major performance hit, as it is three floats now instead of just a single index float.

Edited by lallish, 09 July 2013 - 09:01 AM.

#8Cornstalks  Members

Posted 09 July 2013 - 09:14 AM

They are static yep. What I will try now then is to use Web Workers to calculate the points to off load the main thread in javascript, only thing I'm afraid of is how many attributes I can update at the same time without any major performance hit, as it is three floats now instead of just a single index float.

You could do it with 2 floats and 1 uniform: the two floats are phi (in range [0, pi]) and theta (in range [0, 2pi]), and the uniform is the sphere's radius.

If that turns out to be too many objects you may need to do some space partitioning.

[ I was ninja'd 71 times before I stopped counting a long time ago ] [ f.k.a. MikeTacular ] [ My Blog ] [ SWFer: Gaplessly looped MP3s in your Flash games ]

#9Bacterius  Members

Posted 09 July 2013 - 09:19 AM

If I understand properly the problem is that as "index" grows arbitrarily large, which means "index * inc" (where "inc" corresponds to some rotation in radians) loses all fractional precision, where it matters. The expected output is an aperiodic list of angles 0, inc, 2inc, 3inc, etc.. wrapping around the unit circle.

To that end, why not simply keep a running sum of the angle (phi) by adding "inc" at each iteration, and subtract 2 * pi every time phi exceeds 2 * pi (do that on the CPU). That will solve the problem nicely.

Or something like that. Maybe I misunderstood.

“If I understand the standard right it is legal and safe to do this but the resulting value could be anything.”

#10lallish  Members

Posted 09 July 2013 - 10:33 AM

You could do it with 2 floats and 1 uniform: the two floats are phi (in range [0, pi]) and theta (in range [0, 2pi]), and the uniform is the sphere's radius.

If that turns out to be too many objects you may need to do some space partitioning.

Yep, that's correct. Thanks!

If I understand properly the problem is that as "index" grows arbitrarily large, which means "index * inc" (where "inc" corresponds to some rotation in radians) loses all fractional precision, where it matters. The expected output is an aperiodic list of angles 0, inc, 2inc, 3inc, etc.. wrapping around the unit circle.

To that end, why not simply keep a running sum of the angle (phi) by adding "inc" at each iteration, and subtract 2 * pi every time phi exceeds 2 * pi (do that on the CPU). That will solve the problem nicely.

Or something like that. Maybe I misunderstood.

It would work, except I don't do it iterative, I fetch precomputed indices from surrounding and only generate the needed points.

Edited by lallish, 09 July 2013 - 10:37 AM.

#11apatriarca  Members

Posted 09 July 2013 - 12:40 PM

I didn't know this algorithm to generate uniform points on a sphere. That's interesting.. How do you assign indices to regions? Wouldn't be easier to just assign the precomputed points instead of the indices?

If all you need is generate a uniform set of points on some sub-region of the sphere, a set of spherical triangles for example, you can also use some kind of uniform sampling strategy on spherical triangles. I know one algorithm which can be found in the chapter "Stratified Sampling of 2-Manifolds" by James Arvo in the "State of the Art in Monte Carlo Ray Tracing for Realistic Image Synthesis" Siggraph 2001 course. This is quite expensive however. There may be faster methods which I don't know.

#12lallish  Members

Posted 09 July 2013 - 02:34 PM

I didn't know this algorithm to generate uniform points on a sphere. That's interesting.. How do you assign indices to regions? Wouldn't be easier to just assign the precomputed points instead of the indices?

If all you need is generate a uniform set of points on some sub-region of the sphere, a set of spherical triangles for example, you can also use some kind of uniform sampling strategy on spherical triangles. I know one algorithm which can be found in the chapter "Stratified Sampling of 2-Manifolds" by James Arvo in the "State of the Art in Monte Carlo Ray Tracing for Realistic Image Synthesis" Siggraph 2001 course. This is quite expensive however. There may be faster methods which I don't know.

I have an Icosahedron sphere, which is divided into almost equally big triangles, or hexagons with an occasional pentagon, so they create a tile system for you if you're inside the sphere. And each triangle have assigned indices to them.

And why I've precomputed indices and not the actual points is a good question! It's a matter of reducing the size of the files, especially if it's a file that a client will download from your webserver, reducing the file transfer by half might be a better choice than the small performance hit your client will get from computing the points him/herself. I'm still experimenting, Just now, the indices in the interval [0, 2500000] so far take 18 mb. But, as the indices grow higher, the points will remain with a consistent precision, and size. So in the future when I know more about Javascript compression, I'll probably consider precomputing the points instead.

#13apatriarca  Members

Posted 10 July 2013 - 06:37 AM

I'm not an expert on JavaScript but if your points are static, then I think it is probably better to create all the point once on the CPU side and then load them in some static buffer. Using this solution you can later decide to directly send the points without changing the rendering code and you can use doubles for the increased precision they provide.
Are you transmitting all the data at once or on demand? If you only need a small subset of the indices you may save some time requesting the indices only when needed. Have you tried compressing your file?

Old topic!

Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.