Converting an equiangular cubemap to an equirectangular one

878 views Asked by At

I am making a retro-style game with OpenGL, and I want to draw my own cubemaps for it. Here is an example of one:

As you can tell, there is no perspective warping anywhere; each face is fully equiangular. When using this as a cubemap, the result is this:

As you can see, it looks box-y, and not spherical at all. I know of a solution to this, which is to remap each point on the cubemap to a a sphere position. I have done this manually by creating a sphere mesh and mapping the cubemap texture onto it (and then rendering that to an environment map), but this is time-consuming and complicated.

I seek a different solution: in my fragment shader, I hope to remap the sampling ray to a sphere position, instead of a cube position. Here is my original fragment shader, without any changes:

#version 400 core

in vec3 cube_edge;

out vec3 color;

uniform samplerCube skybox_sampler;

void main(void) {
    color = texture(skybox_sampler, cube_edge).rgb;
}

I can get a ray that maps to the sphere by just normalizing cube_edge, but that doesn't change anything, for some reason. After messing around a bit, I tried this mapping, which almost works, but not quite:

vec3 sphere_edge = vec3(cube_edge.x, normalize(cube_edge).y, cube_edge.z);

As you can see, some faces become spherical in nature, whereas the top face warps inwards, instead of outwards.

I also tried the results from this site: http://mathproofs.blogspot.com/2005/07/mapping-cube-to-sphere.html, but the faces were not curved outwards enough.

I have been stuck on this for so long now - if you know how I can change my cube to sphere mapping in my fragment shader, or if that's even possible, please let me know!

2

There are 2 answers

4
Yakov Galka On BEST ANSWER

As you can tell, there is no perspective warping anywhere; each face is fully equiangular.

This premise is incorrect. You hand-drew some images; this doesn't make them equiangular.

'Equiangular cubemap' (EAC) specifically means a cubemap remapped by this formula (section 2.4):

u = 4/pi * atan(u)
v = 4/pi * atan(v)

Let's recognize first that the term is misleading, because even though EAC aims at reducing the variation in sampling rate, the sampling rate is not constant. In fact no 2d projection of any part of a sphere can truly be equi-angular; this is a mathematical fact.

Nonetheless, we can try to apply this correction. Implemented in GLSL fragment shader as:

d /= max(abs(d.x), max(abs(d.y), abs(d.z));
d = atan(d)/atan(1);

gives the following result:

EAC EAC

Compare it with the uncorrected d:

uncorrected uncorrected

As you can see the EAC projection shrinks the pixels in the middle by a little bit, and expands them near the corners, so that they cover more equal area. However, EAC is not a smooth mapping near the edges of the cube -- i.e. it can make straight lines pointy -- which seems like the artifact you want to avoid.

Instead, it appears that you want a cylindrical projection around the horizon, which is smooth. It can be implemented like so:

d /= length(d.xy);
d.xy /= max(abs(d.x), abs(d.y));
d.xy = atan(d.xy)/atan(1);

And it gives the following result:

cylindrical cylindrical

However there's no artifact-free way to fit the top/bottom square faces of the cube onto the circular faces of the cylinder -- which is why you see the artifacts there.

Bottom-line: you cannot fit the image that you drew onto a sphere in a visually pleasing way. You should instead re-focus your effort on alternative ways of authoring your environment map. I recommend you try using an equidistant cylindrical projection for the horizon, cap it with solid colors above/below a fixed latitude, and use billboards for objects that cannot be represented in that projection.

For example, using the following 2D texture:

cylindrical

And the following formula:

float tau = 6.283185;
float u = atan(d.y, d.x);
float v = atan(d.z, length(d.xy));
OUT = texture(TEX, vec2(u/tau, 2*v/tau + 0.5));

I can get these results:

cylindrical cylindrical

I think this will get you further than any cubemap based tweaks.

4
Rabbid76 On

Your problem is that the size of the geometry on which the environment is placed is too small. You are not looking at the environment but at the inside of a small cube in which you are sitting. The environment map should behave as if you are always in the center of the map and the environment is infinitely far away. I suggest to draw the environment map on the far plane of the viewing frustum. You can do this by setting the z-component of the clip space position equal to the w-component in the vertex shader. If you set z to w, you guarantee that the final z value of the position will be 1.0. This is the z value of the far plane. (You can do that with Swizzling gl_Position = clipPos.xyww). It is quite sufficient to draw a cube and wrap the environment by looking up the map with the interpolated vertices of the cube. In the case of a samplerCube, the 3-dimensional texture coordinate is treated as a direction vector. You can use the vertex coordinate of the cube to look up the texture.

Vertex shader:

cube_edge = inVertex.xyz;

vec4 clipPos = projection * view * vec4(inVertex.xyz, 1.0);
gl_Position = clipPos.xyww;

Fragment shader:

color = texture(skybox_sampler, cube_edge).rgb;

The solution is also explained in detail at LearnOpenGL - Cubemap.