目录
CubeMap 简介
立方体贴图就是一个包含了 6 个 2D 纹理的纹理,每个 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_faces 的 vector,它包含了立方体贴图所需的所有纹理路径,并以表中的顺序排列
这将为当前绑定的立方体贴图中的每个面生成一个纹理
因为立方体贴图和其它纹理没什么不同,我们也需要设定它的环绕和过滤方式
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_EDGE ,OpenGL_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);
}
显示效果如下:
