英文:
Converting an equiangular cubemap to an equirectangular one
问题
我正在使用OpenGL制作一个复古风格的游戏,想要为它绘制自己的立方体贴图。以下是一个示例:
正如你所看到的,没有任何透视畸变;每个面都是完全等角的。当将其用作立方体贴图时,结果如下:
如你所见,它看起来很立方,一点都不球形。我知道有一个解决办法,那就是将立方贴图上的每个点重新映射到球面位置。我已经手动完成了这个过程,创建了一个球体网格并将立方体贴图纹理映射到上面(然后将其渲染到环境贴图中),但这很耗时和复杂。
我寻求一个不同的解决办法:在我的片段着色器中,我希望将采样射线重新映射到球体位置,而不是立方体位置。以下是我的原始片段着色器,没有任何更改:
#version 400 core
in vec3 cube_edge;
out vec3 color;
uniform samplerCube skybox_sampler;
void main(void) {
color = texture(skybox_sampler, cube_edge).rgb;
}
我可以通过简单地对cube_edge
进行归一化来获得映射到球面的射线,但出于某种原因,这并不改变任何东西。经过一些尝试,我尝试了以下映射,几乎有效,但还不够完美:
vec3 sphere_edge = vec3(cube_edge.x, normalize(cube_edge).y, cube_edge.z);
如你所见,一些面变得球形,而顶面则向内弯曲,而不是向外。
我还尝试了此网站的结果:http://mathproofs.blogspot.com/2005/07/mapping-cube-to-sphere.html,但面的弯曲程度不够。
我已经陷入这个问题很长时间了 - 如果你知道如何在我的片段着色器中更改立方体到球体的映射,或者是否可能实现这一目标,请告诉我!
1: https://i.stack.imgur.com/zbDUL.png
2: https://i.stack.imgur.com/mROWv.png
3: https://i.stack.imgur.com/AHL5O.png
英文:
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!
答案1
得分: 2
如您所见,任何地方都没有透视畸变;每个面都是完全等角的。
这个前提是不正确的。您手绘了一些图像;这并不意味着它们是等角的。
'等角立方图'(EAC)特指通过以下公式重新映射的立方图(第2.4节):
u = 4/pi * atan(u)
v = 4/pi * atan(v)
首先让我们认识到这个术语是具有误导性的,因为尽管EAC旨在减少采样率的变化,但采样率并不是恒定的。事实上,任何球体的任何部分的2D投影都无法真正等角;这是一个数学事实。
尽管如此,我们可以尝试应用这个修正。在GLSL片段着色器中实现如下:
d /= max(abs(d.x), max(abs(d.y), abs(d.z));
d = atan(d)/atan(1);
给出了以下结果:
将其与未经校正的 d
进行比较:
正如您所看到的,EAC投影会略微缩小中间的像素,扩大靠近角落的像素,以便它们覆盖更多的相等面积。然而,在立方体的边缘附近,EAC不是一种平滑的映射——即它可能会使直线变尖锐——这似乎是您想要避免的伪影。
相反,似乎您想要一个环形投影,围绕地平线平滑。它可以这样实现:
d /= length(d.xy);
d.xy /= max(abs(d.x), abs(d.y));
d.xy = atan(d.xy)/atan(1);
并给出以下结果:
然而,没有一种无伪影的方法可以将立方体的顶部/底部正方形面适配到圆柱面的圆形面上,这就是您在那里看到伪影的原因。
底线是,您不能以一种视觉上令人愉悦的方式将您绘制的图像适配到一个球体上。相反,您应该重新调整精力,寻找其他创作环境地图的方法。我建议您尝试使用等距圆柱投影来表示地平线,用固定纬度上下方的纯色来封顶,对于无法在该投影中表示的对象,可以使用广告牌。
例如,使用以下2D纹理:
和以下公式:
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));
我可以得到以下结果:
我认为这会比任何基于立方图的调整更有帮助。
英文:
> 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:
Compare it with the uncorrected d
:
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:
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:
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:
I think this will get you further than any cubemap based tweaks.
答案2
得分: 1
你的问题是环境所放置的几何体尺寸太小。你看到的不是环境本身,而是一个你坐在其中的小立方体的内部。环境贴图应该表现得好像你一直在地图的中心,而环境则无限远。我建议将环境贴图绘制在视景体的远平面上。你可以通过在顶点着色器中设置剪切空间位置的 z 分量等于 w 分量来实现这一点。如果你将 z
设置为 w
,你就可以确保位置的最终 z
值为 1.0。这是远平面的 z
值。(你可以使用Swizzling gl_Position = clipPos.xyww
来实现)。只需绘制一个立方体,并通过查找立方体的插值顶点来包裹环境。在使用 samplerCube
的情况下,将三维纹理坐标视为方向向量。你可以使用立方体的顶点坐标来查找纹理。
顶点着色器:
cube_edge = inVertex.xyz;
vec4 clipPos = projection * view * vec4(inVertex.xyz, 1.0);
gl_Position = clipPos.xyww;
片段着色器:
color = texture(skybox_sampler, cube_edge).rgb;
这个解决方案也在LearnOpenGL - Cubemap中有详细说明。
英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论