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);
}

显示效果如下:

相关推荐
Sandman6z1 小时前
快速上手:国内通过 Gitee 学习使用在线托管平台
学习·gitee
●VON1 小时前
基于 Electron 模拟鸿蒙设备硬件信息查询的可行性探索
javascript·学习·electron·openharmony
('-')1 小时前
《从根上理解MySQL是怎样运行的》第八章学习笔记
笔记·学习·mysql
im_AMBER1 小时前
数据结构 12 图
数据结构·笔记·学习·算法·深度优先
im_AMBER2 小时前
Leetcode 63 定长子串中元音的最大数目
c++·笔记·学习·算法·leetcode
"菠萝"2 小时前
C#知识学习-020(访问关键字)
开发语言·学习·c#
●VON3 小时前
Electron 项目在“鸿蒙端”与“桌面端”运行的区别
javascript·学习·electron·openharmony
لا معنى له3 小时前
残差网络论文学习笔记:Deep Residual Learning for Image Recognition全文翻译
网络·人工智能·笔记·深度学习·学习·机器学习
b***66614 小时前
【golang学习之旅】使用VScode安装配置Go开发环境
vscode·学习·golang