OpenGL - 将立方体贴图的面渲染为四边形

OpenGL - Render face of cube map to a quad

出于调试目的,我需要将立方体贴图的特定面渲染为四边形。

来自 this 类似的问题,我了解到您使用三维纹理坐标,但我需要一些信息来克服这个障碍。我现在得到的只是黑色,有时是完全不同的颜色。

到目前为止,我已经使用以下方法绘制了二维四边形。

GL.ActiveTexture(TextureUnit.Texture0);
GL.BindTexture(TextureTarget.Texture2D, textureID);

GL.BindVertexArray(VAO);
GL.DrawArrays(PrimitiveType.Triangles, 0, vertices.Length);

这里需要调整吗?执行此操作的一般方法是什么?

需要做一些改动,都很简单。

纹理目标

您目前使用 TextureTarget.Texture2D 的地方,您将改用 TextureTarget.TextureCubeMap。例如,假设您已经拥有给定的立方体贴图纹理:

GL.BindTexture(TextureTarget.TextureCubeMap, textureID);

着色器代码中的采样器类型

在您的片段着色器中,您当前定义了一个采样器变量,它看起来像这样:

uniform sampler2D Tex;

您将修改为:

uniform samplerCube Tex;

纹理坐标

最大的变化与你的纹理坐标有关。用于立方体贴图的纹理坐标有 3 个分量,可以解释为方向向量。想象立方体对应于以原点为中心的立方体贴图。由纹理坐标给出的方向向量然后指向将被采样的纹素。

一种选择是修改客户端代码生成的顶点属性中的纹理坐标。您可以将它们扩展到 3 个组件而不是以前的 2 个,并选择适当的值来隔离要渲染的面。

相比于这样做,根据您已经传递给 2D 纹理着色器代码的现有纹理坐标来计算新纹理坐标可能会更容易一些。如果当前纹理坐标在两个坐标方向上跨越一个范围为 [0.0, 1.0] 的正方形,则需要将该范围映射到立方体的面,其中立方体以原点为中心,范围为 [-1.0, 1.0 ]在各个坐标方向。

为此,您使用 -1.0 或 1.0 作为匹配您要隔离的面的坐标方向,并且 scale/shift 输入纹理坐标范围从 [0.0, 1.0] 到范围 [-1.0, 1.0]为其他两个坐标方向。

假设您在 2D 纹理案例的着色器代码中具有以下内容:

uniform sampler2D Tex;
in vec2 TexCoord;
...
    vec4 val = texture(Tex, TexCoord);

然后,对于 GL_TEXTURE_CUBE_MAP_POSITIVE_X 面,使用 1.0 作为立方体纹理坐标的 x 坐标,scale/shift 其余两个坐标:

uniform samplerCube Tex;
in vec2 TexCoord;
...
    vec2 mapCoord = 2.0 * TexCoord - 1.0;
    vec4 val = texture(Tex, vec3(1.0, mapCoord.xy));

GL_TEXTURE_CUBE_MAP_NEGATIVE_X 面的等价物:

    vec4 val = texture(Tex, vec3(-1.0, mapCoord.xy));

对于GL_TEXTURE_CUBE_MAP_POSITIVE_Y脸:

    vec4 val = texture(Tex, vec3(mapCoord.x, 1.0, mapCoord.y));

对于GL_TEXTURE_CUBE_MAP_NEGATIVE_Y脸:

    vec4 val = texture(Tex, vec3(mapCoord.x, -1.0, mapCoord.y));

对于GL_TEXTURE_CUBE_MAP_POSITIVE_Z脸:

    vec4 val = texture(Tex, vec3(mapCoord.xy, 1.0));

对于GL_TEXTURE_CUBE_MAP_NEGATIVE_Z脸:

    vec4 val = texture(Tex, vec3(mapCoord.xy, -1.0));

请注意,立方体贴图面的方向有点模糊。如果您对结果输出的方向有特定的期望,您可能需要 permute/mirror 上面代码中的一些值。

基于上面 Reto 的回答,我想出了以下(伪)代码来将立方体贴图的给定面渲染为具有正确方向的四边形。

四边形构建器示例 (C):

typedef struct {
    vec3 position;
    vec2 uv;
} shader_quads_vertex;

typedef struct {
    shader_quads_vertex verts[6];
} vert_quad;

// ...

// Don't forget to set vertex attributes correctly
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE,
    sizeof(shader_quads_vertex), 0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE,
    sizeof(shader_quads_vertex), (void *)sizeof(vec3));

// ...

// v3 *----* v2
//    |    |
// v0 *----* v1
//
// v0, v1, v2, v0, v2, v3

vert_quad quad = { 0 };
quad.verts[0].position.x = x0;  // v0 (0,0)
quad.verts[0].position.y = y0;
quad.verts[0].uv.u = 0.0f;
quad.verts[0].uv.v = 0.0f;
quad.verts[1].position.x = x1;  // v1 (1,0)
quad.verts[1].position.y = y0;
quad.verts[1].uv.u = 1.0f;
quad.verts[1].uv.v = 0.0f;
quad.verts[2].position.x = x1;  // v2 (1,1)
quad.verts[2].position.y = y1;
quad.verts[2].uv.u = 1.0f;
quad.verts[2].uv.v = 1.0f;
quad.verts[3].position.x = x0;  // v0 (0,0)
quad.verts[3].position.y = y0;
quad.verts[3].uv.u = 0.0f;
quad.verts[3].uv.v = 0.0f;
quad.verts[4].position.x = x1;  // v2 (1,1)
quad.verts[4].position.y = y1;
quad.verts[4].uv.u = 1.0f;
quad.verts[4].uv.v = 1.0f;
quad.verts[5].position.x = x0;  // v3 (0,1)
quad.verts[5].position.y = y1;
quad.verts[5].uv.u = 0.0f;
quad.verts[5].uv.v = 1.0f;

// TODO: Bind shader, set uniforms, push quad into your vertex buffer
//glBindBuffer(GL_ARRAY_BUFFER, ...);
//glBufferData(...)

// TODO: Draw 1 face, or 6 faces, or whatever you like. E.g. draw 6 faces:
//for (int face = 0; face < 6; face++) {
//    TODO: Set "u_face" to face index via uniform via glUniform1i()
//    glDrawArrays(GL_TRIANGLES, 0, 6);
//}

顶点着色器(GLSL):

#version 330 core

layout(location = 0) in vec3 attr_position;
layout(location = 1) in vec2 attr_uv;

uniform int u_face;

out vs_out {
    vec3 uvw;
} vertex;

void main()
{
    gl_Position = vec4(attr_position, 1.0);

    vec2 uv_cube = attr_uv.xy * 2.0 - 1.0;

    switch (u_face) {
    case 0:
        vertex.uvw = vec3(1.0, uv_cube.y, uv_cube.x);
        break;
    case 1:
        vertex.uvw = vec3(-1.0, uv_cube.y, -uv_cube.x);
        break;
    case 2:
        vertex.uvw = vec3(uv_cube.x, 1.0, uv_cube.y);
        break;
    case 3:
        vertex.uvw = vec3(uv_cube.x, -1.0, -uv_cube.y);
        break;
    case 4:
        vertex.uvw = vec3(-uv_cube.x, uv_cube.y, 1.0);
        break;
    case 5:
        vertex.uvw = vec3(uv_cube.x, uv_cube.y, -1.0);
        break;
    };
}

片段着色器 (GLSL):

#version 330 core

in vs_out {
    vec3 uvw;
} vertex;

uniform samplerCube u_tex;

out vec4 final_color;

void main()
{
    vec4 tex_color = texture(u_tex, vertex.uvw);
    final_color = vec4(vec3(tex_color.r), 1.0);
}

注意:以上代码对您的坐标系做了以下假设:

  • 四边形的顶点 positions/UVs 总是从四边形的左下角开始逆时针旋转。
  • Right-hand 坐标(+X 向右,+Y 向上,+Z 离开屏幕朝向观察者)
  • u_face 被解释为相对于 GL_TEXTURE_CUBE_MAP_POSITIVE_X 的偏移量(0:+X,1:-X,2:+Y,3:-Y,4:+Z,5:- Z)
  • +Y 呈现为好像向下凝视 -Z(向前)然后将头向后倾斜 90 度以向上看(即 "up" 矢量是 +Z for u_face = 2)
  • -Y 呈现为好像向下凝视 -Z(向前)然后将头向下倾斜 90 度以直视下方(即 "up" 向量为 -Z 表示 u_face = 3)
  • 其他 4 个面的向上矢量是 +Y,根据假设 #2

我确实测试了这段代码,但为了简洁起见,我省略了很多样板 GL 代码。希望你觉得这有帮助。