OpenGL ES教程——立方体贴图

咱们已经学习过2D纹路了,今日来学习立方关心图,立方体纹路便是多个纹路组合起来映射到一张纹路。

简略来说,立方关心图便是一个包含了6个2D纹路的纹路,每个2D纹路都组成了立方体的一个面:一个有纹路的立方体

想一想,显现2D纹路时需求指定纹路坐标,那这个立方关心图怎样指定?立方关心图有一个非常有用的特性,它能够通过一个方向向量来进行索引/采样。假定咱们有一个1x1x1的单位立方体,方向向量的原点坐落它的中心。运用一个橘黄色的方向向量来从立方关心图上采样一个纹路值会像是这样:

OpenGL ES教程——立方体贴图

那立方关心图有什么用呢?游戏中天空盒经常会用到这个

1、创建立方关心图

立方关心图在glsl代码中声明方法和2D纹路不一样了,别的它的纹路坐标也不再是vec2,而是vec3

uniform samplerCube skybox;
in vec3 TexCoords;

其它方法和2D纹路差不多,都是先生成,再绑定,再设置参数,只不过参数会略有不同:

    std::vector<std::string> faces
            {
                    "res/right.jpg",
                    "res/left.jpg",
                    "res/top.jpg",
                    "res/bottom.jpg",
                    "res/front.jpg",
                    "res/back.jpg"
            };
    glGenTextures(1, &mSkyId);
    glBindTexture(GL_TEXTURE_CUBE_MAP, mSkyId);
    int width, height;
    for (int i = 0; i < faces.size(); i++) {
        void *pixel;
        MyGlRenderContext::getInstance()->getBitmap(faces[i].data(), &pixel, width, height);
        LOGI("prepareTexture width = %d, height = %d", width, height);
        glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGBA, width, height, 0, GL_RGBA,
                     GL_UNSIGNED_BYTE, pixel);
    }
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_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_CUBE_MAP

立方体有6个面,需求给每个面指定图像buf

纹路目标 方位
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

由于这些枚举值都是递加的,所以上面代码就在for循环中运用递加来指定纹路目标了。

2、天空盒

想象下用户在一个大的立方体空间中,空间上下前后左右有不同的背景,用户能够随意看不同的方向,能看到不同的景色,这便是天空盒

现在咱们要写一个天空盒,还要在这个天空盒中制作一个立方体,滑动页面时,整个天空盒也跟着调整方向

首要,咱们要制作天空盒,也要制作立方体,制作这两个东东的glsl代码会有明显不同,所以咱们需求准备不同的glsl代码:

制作天空盒的glsl代码:

#version 300 es
out vec4 FragColor;
in vec3 TexCoords;
uniform samplerCube skybox;
void main()
{
    FragColor = texture(skybox, TexCoords);
}
#version 300 es
layout (location = 0) in vec3 aPos;
out vec3 TexCoords;
uniform mat4 projection;
uniform mat4 view;
void main()
{
    TexCoords = aPos;
    vec4 pos = projection * view * vec4(aPos, 1.0);
    gl_Position = pos.xyww;
}

制作盒子的glsl代码:

#version 300 es
layout(location = 0) in vec3 a_position;
layout(location = 1) in vec2 a_texCoord;
out vec2 v_texCoord;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main() {
    gl_Position = projection * view * model * vec4(a_position, 1.0);
    v_texCoord = vec2(a_texCoord.x, 1.0 - a_texCoord.y);
}
#version 300 es
precision mediump float;
in vec2 v_texCoord;
out vec4 outColor;
uniform sampler2D cubeId;
void main() {
    outColor = texture(cubeId, v_texCoord);
}

真正制作代码:

void SkyBoxSample::draw() {
    glEnable(GL_DEPTH_TEST);
    glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    auto rat = MyGlRenderContext::getInstance()->getWidth() * 1.0f /
               MyGlRenderContext::getInstance()->getHeight();
    glm::mat4 model = glm::mat4(1.0f);
    glm::mat4 projection = glm::perspective(glm::radians(45.0f), rat, 0.1f, 100.0f);
    glm::mat4 view = camera.getViewMatrix();
    cubeShader.use();
    cubeShader.setMat4("model", model);
    cubeShader.setMat4("view", view);
    cubeShader.setMat4("projection", projection);
    glBindVertexArray(mCubeVao);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, mCubeId);
    glDrawArrays(GL_TRIANGLES, 0, 36);
    glBindVertexArray(0);
    glDepthFunc(GL_LEQUAL);
    shader.use();
    view = glm::mat4(glm::mat3(camera.getViewMatrix()));
    shader.setMat4("view", view);
    shader.setMat4("projection", projection);
    glBindVertexArray(mSkyVao);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_CUBE_MAP, mSkyId);
    shader.setInt("skybox", 0);
    glDrawArrays(GL_TRIANGLES, 0, 36);
    glBindVertexArray(0);
    glDepthFunc(GL_LESS);
}

2.1、深度处理

注意到制作代码中,咱们是先制作盒子再制作天空盒。正常情况下,天空盒会把盒子盖住,但这显现不是咱们想要的。天空盒只是一个大的背景,任何东西都应该显现在天空盒之上。

怎么让天空盒显现在最下面呢?结合深度测验逻辑,只需天空盒的深度缓冲值永远是最大值1.0,天空盒就会永远显现在最下面。那怎样让天空盒的深度值是最大值呢?

透视除法是在极点着色器运行之后履行的,将gl_Positionxyz坐标除以w分量。咱们又从深度测验知识知道,相除成果的z分量等于极点的深度值。所以,假如咱们把天空图的z值一直置为w,那么它的深度值必定就会一直为1.0,永远在最下面了。

gl_Position = pos.xyww;

2.2、天空盒不能移动

天空盒不能移动,这个很好理解吧。

但当咱们手指移动时,view 矩阵就会变化,会产生旋转、缩放和位移。view 矩阵变化了就会影响天空盒。咱们需求去掉天空盒的位移作用:

view = glm::mat4(glm::mat3(camera.getViewMatrix()));

为什么这么做就能够去掉位移作用呢?

旋转、缩放两个操作均能够运用矩阵做乘法操作完成,但位移不可,假如矩阵不增加齐次坐标就只能运用加法完成。为了统一矩阵的乘法操作,所以opengl里的坐标是四维的,xyzw,增加了一个w坐标,即齐次坐标,通过齐次坐标才完成运用乘法来完成位移,现在咱们去掉齐次坐标,天然就能去掉位移作用。

齐次坐标原理可参见这篇文章:OPENGL–快速理解齐次坐标作用 – 知乎 (zhihu.com)

3、反射

盒子上的纹路是咱们自己增加上去的,能不能反射周边的环境

下面这张图展现了咱们怎么核算反射向量,并怎么运用这个向量来从立方关心图中采样:

OpenGL ES教程——立方体贴图

咱们依据调查方向向量I和物体的法向量N,来核算反射向量R。咱们能够运用GLSL内建的reflect函数来核算这个反射向量。终究的R向量将会作为索引/采样立方关心图的方向向量,返回环境的颜色值。终究的成果是物体看起来反射了天空盒。

I怎样得到呢?能够依据camera方位和当时元素方位核算出来。

#version 300 es
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aNormal;
out vec3 normal;
out vec3 position;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main() {
    normal = mat3(transpose(inverse(model))) * aNormal;
    position = vec3(model * vec4(aPos, 1.0));
    gl_Position = projection * view * model * vec4(aPos, 1.0);
}
#version 300 es
precision mediump float;
out vec4 outColor;
in vec3 normal;
in vec3 position;
uniform samplerCube skybox;
uniform vec3 cameraPos;
void main() {
    vec3 I = normalize(position - cameraPos);
    vec3 R = reflect(I, normalize(normal));
    outColor = vec4(texture(skybox, R).rgb, 1.0);
}

盒子运用如上glsl代码后,再更新下坐标,由于需求法向量了,就能看见本文开篇时的作用了。