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

2023-07-29 12:00:33

前言

搞定了天空盒,才算是真正完成了場景的搭建,以後再要進行什麼樣的圖學測試,都可以在這個場景下進行。比如後面的反射、折射就是這樣的例子。

寫完這篇,我決定暫時結束這個系列。主要是因為我太懶了,居然拖拖拉拉拖了兩年。其實可以探索的內容還有很多,比如陰影啊、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郵箱:[email protected]