Linux 下的 OpenGL 之路(九):天空盒、反射和折射

发布时间 2023-07-29 11:03:11作者: 京山游侠

前言

搞定了天空盒,才算是真正完成了场景的搭建,以后再要进行什么样的图形学测试,都可以在这个场景下进行。比如后面的反射、折射就是这样的例子。

写完这篇,我决定暂时结束这个系列。主要是因为我太懒了,居然拖拖拉拉拖了两年。其实可以探索的内容还有很多,比如阴影啊、HDR啊、辉光啊、基于物理的渲染啊什么的,内容还挺多,我想的是等以后我把我的代码搞得更完善一点之后,可能重写这一个系列。

也有可能不重写这个系列了,直接进军 Vulkan。

天空盒的技术其实挺简单

在没有做天空盒的时候,觉得天空盒很神秘。做了天空盒之后,发现天空盒的技术其实挺简单,就是利用了立方体贴图而已。

先说模型,就是绘制六个面,组成一个盒子而已,唯一不同的,可能就是这个盒子的面都是向内的。至于盒子的大小,其实不重要,后来我算是想明白了,反正应用纹理之后,我们看到的都是一个封闭的世界背景,那么把这个背景放在坐标为1的位置,还是放在远处,其实没什么区别。唯一需要注意的是,需要在 Shader 里面修改它的深度值,让它绘制在所有物体的后面,这样,其实是给了我们一个天空盒在无穷远处的假象。

再说立方体贴图,其实就是 6 张图片,使用相应的 API 载入而已。

最后说一说在场景中漫游的问题。通过前面的描述,可以看出盒子的位置一定在原点,盒子的大小不重要,所以 Model Matrix 就没有必要要了。但是我们还是需要在场景中转动,所以 View Matrix 还是要考虑,但是又要和前面的 View Matrix 不同,也就是我们只考虑摄像机的转动,而不用考虑摄像机的移动。所以这个要单独处理。

下面直接贴代码:

#ifndef __SKYBOX_H__
#define __SKYBOX_H__

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <vector>
#include <string>
#include <string.h>
#include <GL/glew.h>
#include <iostream>
#include "model.hpp"

class SkyBox
{
protected:
    GLuint VAO, VBO, textureID;

public:
    GLuint getTextureID(){
        return textureID;
    }

    void loadSkyBox(std::string directory)
    {
        std::cout << "Directory name: " << directory << std::endl;
        float vertex_data[] = {
            // positions
            -1.0f, 1.0f, -1.0f,
            -1.0f, -1.0f, -1.0f,
            1.0f, -1.0f, -1.0f,
            1.0f, -1.0f, -1.0f,
            1.0f, 1.0f, -1.0f,
            -1.0f, 1.0f, -1.0f,

            -1.0f, -1.0f, 1.0f,
            -1.0f, -1.0f, -1.0f,
            -1.0f, 1.0f, -1.0f,
            -1.0f, 1.0f, -1.0f,
            -1.0f, 1.0f, 1.0f,
            -1.0f, -1.0f, 1.0f,

            1.0f, -1.0f, -1.0f,
            1.0f, -1.0f, 1.0f,
            1.0f, 1.0f, 1.0f,
            1.0f, 1.0f, 1.0f,
            1.0f, 1.0f, -1.0f,
            1.0f, -1.0f, -1.0f,

            -1.0f, -1.0f, 1.0f,
            -1.0f, 1.0f, 1.0f,
            1.0f, 1.0f, 1.0f,
            1.0f, 1.0f, 1.0f,
            1.0f, -1.0f, 1.0f,
            -1.0f, -1.0f, 1.0f,

            -1.0f, 1.0f, -1.0f,
            1.0f, 1.0f, -1.0f,
            1.0f, 1.0f, 1.0f,
            1.0f, 1.0f, 1.0f,
            -1.0f, 1.0f, 1.0f,
            -1.0f, 1.0f, -1.0f,

            -1.0f, -1.0f, -1.0f,
            -1.0f, -1.0f, 1.0f,
            1.0f, -1.0f, -1.0f,
            1.0f, -1.0f, -1.0f,
            -1.0f, -1.0f, 1.0f,
            1.0f, -1.0f, 1.0f};

        GLuint vertex_num = 36;

        glCreateVertexArrays(1, &VAO);
        glBindVertexArray(VAO);
        glCreateBuffers(1, &VBO);
        glBindBuffer(GL_ARRAY_BUFFER, VBO);
        glNamedBufferStorage(VBO, sizeof(vertex_data), vertex_data, 0);
        std::cout << "sizeof vertex_data:" << sizeof(vertex_data) << std::endl;
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void *)0);

        std::vector<std::string> faces{
            "right.jpg",
            "left.jpg",
            "top.jpg",
            "bottom.jpg",
            "front.jpg",
            "back.jpg"
        };

    glGenTextures(1, &textureID);
    glActiveTexture(GL_TEXTURE0 + textureID);
    glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);

    int width, height, nrChannels;
    for (unsigned int i = 0; i < faces.size(); i++)
    {
        unsigned char *data = stbi_load((directory + "/" + faces[i]).c_str(), &width, &height, &nrChannels, 0);
        if (data)
        {
            glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 
                         0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data
            );
            stbi_image_free(data);
        }
        else
        {
            std::cout << "Cubemap texture failed to load at path: " << directory + "/" + faces[i] << std::endl;
            stbi_image_free(data);
        }
    }
    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);
    }

    void render()
    {
        glBindVertexArray(VAO);
        glDrawArrays(GL_TRIANGLES, 0, 36);
    }
};

#endif

主文件的代码我就不贴了。下面来看看顶点着色器的代码和片段着色器的代码。

#version 330 core
layout (location = 0) in vec3 aPos;

out vec3 TexCoords;

uniform mat4 projection_matrix;
uniform mat4 view_matrix;

void main()
{
    TexCoords = aPos;
    vec4 tempPosition = projection_matrix * view_matrix * vec4(aPos, 1.0);
    gl_Position = tempPosition.xyww;
}
#version 330 core
out vec4 FragColor;

in vec3 TexCoords;

uniform samplerCube skybox;

void main()
{    
    FragColor = texture(skybox, TexCoords);
}

关于反射和折射

关于反射和折射的计算,大家都懂,主要是涉及到视线方向和法线方向。当我知道了 GLSL 里面本来就有 reflect 函数和 refract 函数之后,我就觉得,这 TM 也实在是太简单了。

多的话不说,直接上图吧。

嗯,还是有点意思的。

总结

这个系列暂时就到这里结束吧,虽然我还有一点意犹未尽。让图形学的东西通过自己的代码在自己的屏幕上显示出来,还是挺激动人心的。以后我可能会将这个系列重整,也可能会添加一些东西,但是那就不知道是什么时候的事情了,可能要等我把手上的事情忙完,也可能那时候我又换了新电脑,换了新环境。

如果下次再重整这个系列,我可能会先解决在 OpenGL 里面显示文字的功能,然后显示个帧率看看,这样,可以让自己对各种图形学的算法和实现的效率有一个体验,也是不错的。

哦,对了,还有 VS Code 不得不提,真的是挺好用的。

版权申明

该随笔由京山游侠在2023年07月29日发布于博客园,引用请注明出处,转载或出版请联系博主。QQ邮箱:1841079@qq.com