OpenGL立方体贴图面顺序和采样问题

Posted

技术标签:

【中文标题】OpenGL立方体贴图面顺序和采样问题【英文标题】:OpenGL cubemap face order & sampling issue 【发布时间】:2019-04-07 11:00:43 【问题描述】:

我有一个基于 SDL2 和 OpenGL(3.3 核心配置文件)的渲染器,它在转换和纹理(2D)处理方面给了我预期的结果。

但是,当我尝试使用从 these textures 创建的立方体贴图来显示天空盒时(尽管我也尝试过其他方法),在这个过程中有两个步骤我没有遇到过其他教程或示例必须做,我无法解释:

1、上传时顶/底面要互换,即:上面上传为GL_TEXTURE_CUBEMAP_NEGATIVE_Y,下面上传为GL_TEXTURE_CUBEMAP_POSITIVE_Y; 2、在对立方体贴图进行采样时,我必须沿y反转顶点位置,但沿z也有

没有这个,我得到以下结果:

(注意,左下远顶点被缩放了 0.8 以表明我的坐标系是正确的)

图像文件命名正确。

立方体是我正在执行的唯一绘制。

如果我删除任何边的 [索引],我会得到预期的结果(即那里没有交换/镜像)。

我的集成和专用 GPU 似乎获得了相同的结果。

我的 OpenGL 常量,来自 glLoadGen (originally) 生成的标头:

#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516
#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518
#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A
#define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515
#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517
#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519

纹理上传代码(与LearnOpenGL's tutorial大致相同):

GLuint name;
glGenTextures(1, &name);
glBindTexture(GL_TEXTURE_CUBE_MAP, name);

glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR));

GLint target = GL_TEXTURE_CUBE_MAP_POSITIVE_X;
for (uint8_t i = 0; i < 6; ++i)

  glTexImage2D(target + i, 0, GL_RGB8, width, height, 0, GL_RGB,
    GL_UNSIGNED_BYTE, pixelData[i]));

顶点着色器:

#version 330

precision mediump float;
uniform mat4 uModelViewProjection;
in vec3 aPosition;
out vec3 vTexCoord;

void main()

  vec4 position = uModelViewProjection * vec4(aPosition, 1.f);
  gl_Position = position.xyww;
  
  vTexCoord = aPosition;

片段着色器:

#version 330

precision mediump float;
uniform samplerCube uTexture0;
in vec3 vTexCoord;
out vec4 FragColor;

void main()

  FragColor = texture(uTexture0, vTexCoord);
  // using textureCube() yields a compile error asking for #extension  GL_NV_shadow_samplers_cube : enable, but even with that, the issue perists.

网格设置(半伪代码):

//    4----5
//   /|   /|
//  6----7 |
//  | |  | |
//  | 0--|-1
//  |/   |/
//  2----3
VertexType vertices[8] = 
  Vector3(-1.f, -1.f, -1.f) * .8f, // debug coordinate system
  Vector3(1.f, -1.f, -1.f),

  Vector3(-1.f, -1.f, 1.f),
  Vector3(1.f, -1.f, 1.f),

  Vector3(-1.f, 1.f, -1.f),
  Vector3(1.f, 1.f, -1.f),

  Vector3(-1.f, 1.f, 1.f),
  Vector3(1.f, 1.f, 1.f),
;

uint16_t indices[] = 
  4, 0, 5,
  0, 1, 5,

  6, 2, 4,
  2, 0, 4,

  7, 3, 6,
  3, 2, 6,

  5, 1, 7,
  1, 3, 7,

  0, 2, 1,
  2, 3, 1,

  5, 7, 4,
  7, 6, 4,
;

// create buffers & upload data

渲染(伪代码):

// -clear color & depth buffers;
// -set the model transform to a translation of -10 units along z; 
//   view transform is identity; projection is perspective with .25
//   radians vertical FOV, zNear of .1, zFar of 100.; viewport is full screen
// -set shader program;
// -bind texture (same name, same target as upon uploading);
// -enable backface culling only (no depth test / write);
// -draw the cube
// -glFlush() and swap buffers;

究竟是什么导致了上述两个问题?

【问题讨论】:

【参考方案1】:

问题是由.str 纹理坐标到立方体贴图的映射引起的:

OpenGL 4.6 API Core Profile Specification, 8.13 Cube Map Texture Selection, page 253:

当对立方体贴图纹理进行采样时,(s, t, r) 纹理坐标被视为一个方向矢量 (rx, ry, rz)立方体的中心。 q 坐标被忽略。在纹理应用时,插值的每片段方向向量根据最大幅度坐标方向(长轴方向)选择立方体贴图面的二维图像之一。如果两个或多个坐标具有相同的量级,则实现可以定义规则以消除这种情况的歧义。该规则必须是确定性的并且仅依赖于(rx, ry, rz)。表 8.19 中的目标列解释了主轴方向如何映射到特定立方体贴图目标的二维图像。 使用由表 8.19 中指定的主轴方向确定的 sctcma,更新后的 (s, t ) 计算如下:

s = 1/2 (sc / |m_a| + 1)

t = 1/2 (tc / |m_a| + 1)

Major Axis Direction|        Target             |sc |tc |ma |
--------------------+---------------------------+---+---+---+
       +rx          |TEXTURE_CUBE_MAP_POSITIVE_X|−rz|−ry| rx|
       −rx          |TEXTURE_CUBE_MAP_NEGATIVE_X| rz|−ry| rx|
       +ry          |TEXTURE_CUBE_MAP_POSITIVE_Y| rx| rz| ry|
       −ry          |TEXTURE_CUBE_MAP_NEGATIVE_Y| rx|−rz| ry|
       +rz          |TEXTURE_CUBE_MAP_POSITIVE_Z| rx|−ry| rz|
       −rz          |TEXTURE_CUBE_MAP_NEGATIVE_Z|−rx|−ry| rz|
--------------------+---------------------------+---+---+---+

表 8.19:基于纹理主轴方向的立方体贴图图像选择 坐标

可以通过在将 6 个立方体贴图图像加载到立方体贴图采样器之前旋转它们或通过旋转纹理坐标来实现旋转。

立方体贴图用作场景中的环境贴图,纹理坐标通过方向向量获取,然后旋转图像是有意义的。如果立方体贴图包裹在网格上,则可以以正确的方式指定纹理坐标。

【讨论】:

感谢您的回复,这是很好的信息。请原谅我速度不够,但是你基本上是说我的Y脸实际上是可以的,我的X和Z脸都需要旋转才能上传?虽然这似乎有道理/有点道理,但我参考的库(bgfx,DALi)似乎不需要这样做,我想知道为什么?如果您能详细说明,我将不胜感激。 @zyndor 这取决于源图像。源图像可以以正确的方式“旋转”并且可以按原样加载。图像可以旋转(“photoshop”),或者它们必须由应用程序旋转。 @msc "十字图具有误导性" - 这表示您必须将立方体贴图的侧面放在一张纸上以折叠立方体的方式。我的论点没有任何问题。 @msc “我觉得这个说法是错误的” - 感觉不能证明投反对票是合理的。如果您从文件中读取 6 个单独的图块,或者如果您有一个图块交叉排列的单个位图(如纹理中的图像),则它们必须被旋转。如果您不旋转它们,那么结果就是问题中的图像。可以通过旋转图像或坐标来执行旋转。无论如何,在文档中提到立方体贴图的纹理坐标被视为从立方体中心开始的 3d 矢量。 @msc Cubemaps 是为环境贴图引入的,您没有纹理坐标。如果坐标来自方向矢量,则图像必须旋转。立方体贴图是为环境贴图引入的,而不是为我的世界立方体引入的。我在回答中提到了这一点。【参考方案2】:

上一个答案的推理来自引用的规范。文字错误。

如果你仔细看数学,引用的文本要求立方体贴图的图像具有自上而下的方向并排列在左手坐标系中+Y 向上。这意味着 +Y 处的天空,如果你面向 +Z,-X 应该在你的左边,+X 在你的右边。这显然是从最初出现立方体贴图的 Renderman 继承而来的。

您渲染为天空盒的立方体的坐标,将用于对立方体贴图进行采样,位于 OpenGL 的右手坐标系中。这些必须在采样之前转换为立方体贴图的左手系统。这是通过简单地将 Z 坐标缩放 -1 来完成的。不这样做意味着场景将是它应该是的镜像。我看过的样本中很常见的失败。

OP 颠倒图像是因为它们具有标准的 OpenGL 自下而上方向。

如果您使用的是 Vulkan,它有一个左手系统,但 Y 是向下的。因此,要在 Vulkan 上正确渲染立方体贴图,您仍然需要转换天空盒立方体的坐标,在这种情况下,通过围绕 X 轴将它们旋转 180°。否则,您将得到颠倒的图像。

【讨论】:

This: "所以要在 Vulkan 上正确渲染立方体贴图,您仍然需要转换天空盒立方体的坐标,在这种情况下,通过围绕 X 轴将它们旋转 180°。"并且:“这意味着在将侧面纹理加载到立方体贴图采样器之前必须对其进行旋转。”最终会导致相同的结果。一个将纹理坐标转换为立方体贴图的空间,另一个将立方体贴图的图像转换为纹理坐标的空间。所以我不明白为什么以前的答案不正确。 好吧,也许是错误的,呃,错误的选择。如果您要创建用于多个 API 的立方体贴图,则必须在加载一个或多个 API 时操作图像,我认为这是不可取的。转换纹理坐标要容易得多。 加上原始扩展规范。说立方体的内部(即立方体贴图)有一个左手坐标系。通过旋转图像,您不再拥有该图像,而其他在您的立方体贴图上绊倒的人可能不知道图像已被旋转。最好符合规范。 "这:"所以要在 Vulkan 上正确渲染立方体贴图,您仍然需要转换天空盒立方体的坐标,在这种情况下,通过将它们绕 X 轴旋转 180°。" 并且:"这意味着在将侧面纹理加载到立方体贴图采样器之前,必须对其进行旋转。”最终导致相同的结果。”其实不是。将坐标绕 x 轴旋转 180° 还可以有效地交换 +y 和 -y 图像,这就是为什么它使您能够在 OpenGL 或 Vulkan 中使用完全相同的立方体贴图。 您可能会认为,我已经改变了可能回答中的推理。反正答案没有错。用例与您的不同。你投反对票的理由有点奇怪。

以上是关于OpenGL立方体贴图面顺序和采样问题的主要内容,如果未能解决你的问题,请参考以下文章

索引顺序影响 OPENGL 中的立方体结构

OpenGL+OpenCV实现立方体贴图

◮OpenGL-抗锯齿

Skybox 渲染的 VkRenderPass 加载操作问题

OpenGL 立方体和金字塔

[计算机图形学 with OpenGL] Chapter10 OpenGL三维观察程序示例