Math Proofs: Mapping a Cube to a Sphere, by Phil
Here's the c++ version where x,y,z are the cube coords and sx,sy,sz are the sphere coords:
sx = x * sqrtf(1.0f - y * y * 0.5f - z * z * 0.5f + y * y * z * z / 3.0f); sy = y * sqrtf(1.0f - z * z * 0.5f - x * x * 0.5f + z * z * x * x / 3.0f); sz = z * sqrtf(1.0f - x * x * 0.5f - y * y * 0.5f + x * x * y * y / 3.0f);
Recently, I've been working on the reverse mapping (from unit sphere to unit cube) and have come up with this solution:
First determine the cube face the sphere point projects to. This step is simple - just find the component of the sphere vector with the greatest length.
Next, for each face, take the remaining cube vector components denoted as s and t and solve for them using these equations, which are based on the remaining sphere vector components denoted as a and b:
s = sqrt(-sqrt((2 a^2-2 b^2-3)^2-24 a^2)+2 a^2-2 b^2+3)/sqrt(2)
t = sqrt(-sqrt((2 a^2-2 b^2-3)^2-24 a^2)-2 a^2+2 b^2+3)/sqrt(2)
You should see that the inner square root is used in both equations, so only do that part once.
Here's the final function with the equations thrown in and checks for 0.0 and -0.0 and the code to properly set the sign of the cube component - it should be equal to the sign of the sphere component.
void cubizePoint(Vector3& position) { double x,y,z; x = position.x; y = position.y; z = position.z; double fx, fy, fz; fx = fabsf(x); fy = fabsf(y); fz = fabsf(z); const double inverseSqrt2 = 0.70710676908493042; if (fy >= fx && fy >= fz) { double a2 = x * x * 2.0; double b2 = z * z * 2.0; double inner = -a2 + b2 -3; double innersqrt = -sqrtf((inner * inner) - 12.0 * a2); if(x == 0.0 || x == -0.0) { position.x = 0.0; } else { position.x = sqrtf(innersqrt + a2 - b2 + 3.0) * inverseSqrt2; } if(z == 0.0 || z == -0.0) { position.z = 0.0; } else { position.z = sqrtf(innersqrt - a2 + b2 + 3.0) * inverseSqrt2; } if(position.x > 1.0) position.x = 1.0; if(position.z > 1.0) position.z = 1.0; if(x < 0) position.x = -position.x; if(z < 0) position.z = -position.z; if (y > 0) { // top face position.y = 1.0; } else { // bottom face position.y = -1.0; } } else if (fx >= fy && fx >= fz) { double a2 = y * y * 2.0; double b2 = z * z * 2.0; double inner = -a2 + b2 -3; double innersqrt = -sqrtf((inner * inner) - 12.0 * a2); if(y == 0.0 || y == -0.0) { position.y = 0.0; } else { position.y = sqrtf(innersqrt + a2 - b2 + 3.0) * inverseSqrt2; } if(z == 0.0 || z == -0.0) { position.z = 0.0; } else { position.z = sqrtf(innersqrt - a2 + b2 + 3.0) * inverseSqrt2; } if(position.y > 1.0) position.y = 1.0; if(position.z > 1.0) position.z = 1.0; if(y < 0) position.y = -position.y; if(z < 0) position.z = -position.z; if (x > 0) { // right face position.x = 1.0; } else { // left face position.x = -1.0; } } else { double a2 = x * x * 2.0; double b2 = y * y * 2.0; double inner = -a2 + b2 -3; double innersqrt = -sqrtf((inner * inner) - 12.0 * a2); if(x == 0.0 || x == -0.0) { position.x = 0.0; } else { position.x = sqrtf(innersqrt + a2 - b2 + 3.0) * inverseSqrt2; } if(y == 0.0 || y == -0.0) { position.y = 0.0; } else { position.y = sqrtf(innersqrt - a2 + b2 + 3.0) * inverseSqrt2; } if(position.x > 1.0) position.x = 1.0; if(position.y > 1.0) position.y = 1.0; if(x < 0) position.x = -position.x; if(y < 0) position.y = -position.y; if (z > 0) { // front face position.z = 1.0; } else { // back face position.z = -1.0; } }
I posted a stackoverflow question and got a lot of help from there and from mathoverflow too. I used wolframalpha.com to get the equations for s and t.
My thanks to gmatt and Leonid Kovalev!
1 comment:
Thanks a lot for this post. It helped me to correct an often overlooked issue with cube mapping: with no or few filtering applied, one can still make out the individual faces and spot the seems due to the varying number of texels per samplingfield-angle. I applied your mapping to overcome this drawback with an optimized glsl fragment shader function - the outcome is identical to your code:
const float isqrt2 = 0.70710676908493042;
vec3 cubify(const in vec3 s)
{
float xx2 = s.x * s.x * 2.0;
float yy2 = s.y * s.y * 2.0;
vec2 v = vec2(xx2 - yy2, yy2 - xx2);
float ii = v.y - 3.0;
ii *= ii;
float isqrt = -sqrt(ii - 12.0 * xx2) + 3.0;
v = sqrt(v + isqrt);
v *= isqrt2;
return sign(s) * vec3(v, 1.0);
}
vec3 sphere2cube(const in vec3 sphere)
{
vec3 f = abs(sphere);
bool a = f.y >= f.x && f.y >= f.z;
bool b = f.x >= f.z;
return a ? cubify(sphere.xzy).xzy : b ? cubify(sphere.yzx).zxy : cubify(sphere);
}
Note: this is used for screen aligned quad, if you use a sphere as geometry, you can modify the texcoords on cpu much faster.
Post a Comment