OpenGL-ES 学习(17) ---- CubeMap 纹理

目录

CubeMap 简介

立方体贴图就是一个包含了 62D 纹理的纹理,每个 2D 纹理都组成了立方体的一个面:一个有纹理的立方体

立方体贴图有一个非常有用的特性,它可以通过一个方向向量来进行索引/采样。

假设我们有一个1x1x1的单位立方体,方向向量的原点位于它的中心

使用一个方向向量来从立方体贴图上采样一个纹理值会像是这样

note:方向向量的大小并不重要,只要提供了方向,OpenGL_ES 就会获取方向向量(最终)所击中的纹素,并返回对应的采样纹理值

如果我们假设将这样的立方体贴图应用到一个立方体上,采样立方体贴图所使用的方向向量将和立方体(插值的)顶点位置非常相像

这样子,只要立方体的中心位于原点,我们就能使用立方体的实际位置向量来对立方体贴图进行采样了

接下来,我们可以将所有顶点的纹理坐标当做是立方体的顶点位置

最终得到的结果就是可以访问立方体贴图上正确面(Face)纹理的一个纹理坐标

法向量定义

在片段着色器中,我们使用了一个不同类型的采样器,samplerCube,我们将使用 texture 函数使用它进行采样,但这次我们将使用一个 vec3 的方向向量而不是 vec2, 使用立方体贴图的片段着色器会像是这样的

c 复制代码
"#version 300 es                            \n"
"precision mediump float;\n"
// 代表3D纹理坐标的方向向量
"in vec3 v_normal;                                   \n"
"layout(location = 0) out vec4 outColor;             \n"
// 立方体贴图的纹理采样器
"uniform samplerCube s_texture;                      \n"
"void main() {\n"
"   outColor = texture( s_texture, v_normal );       \n"
"}\n";
}

VertexShader 如下:

c 复制代码
"#version 300 es                            \n"
"precision mediump float;\n"
"uniform mat4 u_mvpMatrix;                   \n"
"layout(location = 0) in vec3 a_position;   \n"
// 输入法向量的位置
"layout(location = 1) in vec3 a_normal;      \n"
"out vec3 v_normal;                          \n"
"void main() {\n"
"    gl_Position = u_mvpMatrix*vec4(a_position,1.0);\n"
"    v_normal = a_normal;                     \n"
"}\n";

法向量或者法线的本质:

  • 法线是一个单位向量(长度为 1),表示 表面在某一点的朝向。
  • 在立方体贴图反射/环境采样中,法线被当作从原点出发的方向向量,用来看向立方体的某一面。

立方体贴图的 6 个面对应的标准方向(OpenGL_ES 坐标系)

法线方向 vec3(x, y, z) 含义 采样的立方体贴图面
(1, 0, 0) 指向正 X 轴(右) GL_TEXTURE_CUBE_MAP_POSITIVE_X
(-1, 0, 0) 指向负 X 轴(左) GL_TEXTURE_CUBE_MAP_NEGATIVE_X
(0, 1, 0) 指向 正 Y 轴(上) GL_TEXTURE_CUBE_MAP_POSITIVE_Y
(0, -1, 0) 指向 负 Y 轴(下) GL_TEXTURE_CUBE_MAP_NEGATIVE_Y
(0, 0, 1) 指向 正 Z 轴(前 / 朝向观察者) GL_TEXTURE_CUBE_MAP_POSITIVE_Z
(0, 0, -1) 指向 负 Z 轴(后 / 远离观察者) GL_TEXTURE_CUBE_MAP_NEGATIVE_Z
CubeMap 纹理

立方体贴图是和其它纹理一样的,所以如果想创建一个立方体贴图的话,我们需要生成一个纹理,并将其绑定到纹理目标上,之后再做其它的纹理操作,这次要绑定到 GL_TEXTURE_CUBE_MAP

c 复制代码
unsigned int textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);

因为立方体贴图包含有 6 个纹理,每个面一个,我们需要调用 glTexImage2D 函数6次,参数和之前教程中很类似

但这一次我们将纹理目标(target)参数设置为立方体贴图的一个特定的面,

告诉 OpenGL_ES 我们在对立方体贴图的哪一个面创建纹理,这就意味着我们需要对立方体贴图的每一个面都调用一次 glTexImage2D

由于我们有 6 个面,OpenGL_ES 给我们提供了 6 个特殊的纹理目标,专门对应立方体贴图的一个面

c 复制代码
//GL_TEXTURE_CUBE_MAP_POSITIVE_X	右
//GL_TEXTURE_CUBE_MAP_NEGATIVE_X	左
//GL_TEXTURE_CUBE_MAP_POSITIVE_Y	上
//GL_TEXTURE_CUBE_MAP_NEGATIVE_Y	下
//GL_TEXTURE_CUBE_MAP_POSITIVE_Z	后
//GL_TEXTURE_CUBE_MAP_NEGATIVE_Z	前

OpenGL_ES 的很多枚举(Enum)一样,它们背后的 int 值是线性递增的,所以如果我们有一个纹理位置的数组或者 vector,我们就可以从GL_TEXTURE_CUBE_MAP_POSITIVE_X 开始遍历它们,在每个迭代中对枚举值加1,遍历了整个纹理目标

c 复制代码
int width, height, nrChannels;
unsigned char *data;  
for(unsigned int i = 0; i < textures_faces.size(); i++)
{
    data = stbi_load(textures_faces[i].c_str(), &width, &height, &nrChannels, 0);
    glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data
    );
}

这里我们有一个叫做 textures_facesvector,它包含了立方体贴图所需的所有纹理路径,并以表中的顺序排列

这将为当前绑定的立方体贴图中的每个面生成一个纹理

因为立方体贴图和其它纹理没什么不同,我们也需要设定它的环绕和过滤方式

c 复制代码
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
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);

不要被 GL_TEXTURE_WRAP_R 吓到,它仅仅是为纹理的 R 坐标设置了环绕方式,它对应的是纹理的第三个维度(和位置的z一样)

我们将环绕方式设置为 GL_CLAMP_TO_EDGE,这是因为正好处于两个面之间的纹理坐标可能不能击中一个面(由于一些硬件限制),

所以通过使用 GL_CLAMP_TO_EDGEOpenGL_ES 将在我们对两个面之间采样的时候,永远返回它们的边界值

参考实现
c 复制代码
static const char* vertexShaderSource =
        "#version 300 es                            \n"
        "precision mediump float;\n"
        "uniform mat4 u_mvpMatrix;                   \n"
        "layout(location = 0) in vec3 a_position;   \n"
        "layout(location = 1) in vec3 a_normal;      \n"
        "out vec3 v_normal;                          \n"
        "void main() {\n"
        "    gl_Position = u_mvpMatrix*vec4(a_position,1.0);\n"
        "    v_normal = a_normal;                     \n"
        "}\n";

// Fragment Shader source code
static const char* fragmentShaderSource =
        "#version 300 es                            \n"
        "precision mediump float;\n"
        "in vec3 v_normal;                                   \n"
        "layout(location = 0) out vec4 outColor;             \n"
        "uniform samplerCube s_texture;                      \n"
        "void main() {\n"
        "   outColor = texture( s_texture, v_normal );       \n"
        "}\n";

static GLuint CreateColorTextureCube() {
    GLuint textureId;
    // Six 1x1 RGB faces
    GLubyte cubePixels[6][3] = {
     // Face 0 - Red
     255, 0, 0,
     // Face 1 - Green,
     0, 255, 0,
     // Face 2 - Blue
     0, 0, 255,
     // Face 3 - Yellow
     255, 255, 0,
     // Face 4 - Purple
     255, 0, 255,
     // Face 5 - White
     255, 255, 255 };

    // Generate a texture object
    glGenTextures(1, &textureId);
    // Bind the texture object
    glBindTexture(GL_TEXTURE_CUBE_MAP, textureId);

    // Load the cube face - Positive X
    glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, &cubePixels[0]);
    // Load the cube face - Negative X
    glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, &cubePixels[1]);
    // Load the cube face - Positive Y
    glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, &cubePixels[2]);
    // Load the cube face - Negative Y
    glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, 1, 1, 0,  GL_RGB, GL_UNSIGNED_BYTE, &cubePixels[3]);
    // Load the cube face - Positive Z
    glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, 1, 1, 0,  GL_RGB, GL_UNSIGNED_BYTE, &cubePixels[4]);
    // Load the cube face - Negative Z
    glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, 1, 1, 0,  GL_RGB, GL_UNSIGNED_BYTE, &cubePixels[5]);

    // Set the filtering mode
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    return textureId;
}

static void DrawPrimitives(ESContext *esContext) {
    UserData *userData = esContext->userData;
    GLuint   offset = 0;
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(GL_FLOAT), userData->vertices);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3*sizeof(GL_FLOAT), userData->normals);
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);

    glUniformMatrix4fv(userData->mvpLoc, 1, GL_FALSE, (GLfloat *)&userData->mvpMatrix.m[0][0]);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_CUBE_MAP, userData->textureId);
    glUniform1i(userData->samplerLoc, 0);

    glDrawElements(GL_TRIANGLES, userData->numIndices, GL_UNSIGNED_INT, userData->indices);
}

显示效果如下:

相关推荐
西岸行者6 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意6 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码6 天前
嵌入式学习路线
学习
毛小茛6 天前
计算机系统概论——校验码
学习
babe小鑫6 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms6 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下6 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。6 天前
2026.2.25监控学习
学习
im_AMBER6 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J6 天前
从“Hello World“ 开始 C++
c语言·c++·学习