上接:https://blog.csdn.net/weixin_44506615/article/details/150935025?spm=1001.2014.3001.5501
完整代碼:https://gitee.com/Duo1J/learn-open-gl | https://github.com/Duo1J/LearnOpenGL
一、立方體貼圖 (Cubemap)
立方體貼圖就是一個包含了6張2D紋理的紋理,不同于2D紋理使用UV來采樣,我們使用一個方向向量來對Cubemap進行采樣,如下圖所示 (圖片來自于LearnOpenGL)
創建Cubemap
unsigned int cubemapID;
glGenTextures(1, &cubemapID);
glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapID);
要綁定紋理數據,之前我們使用 glTexImage2D
,對于Cubemap我們則需要調用6次
// faces: Cubemap紋理路徑數組
for (unsigned int i = 0; i < faces.size(); i++)
{const char* path = faces[i].c_str();unsigned char* data = stbi_load(path, &width, &height, &channel, 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);std::cout << "Load cubmap(" << GL_TEXTURE_CUBE_MAP_POSITIVE_X + i << "): " << path << " success, with: " << width << " height: " << height << " channel: " << channel << " ID: " << textureID << std::endl;}else{std::cout << "[Error] Failed to load cubemap: " << path << std::endl;}
}
這里第一位參數我們在2D紋理中傳入的是 GL_TEXTURE_2D
對于Cubemap我們則需要通過這個參數來表明它是哪一個面的紋理,如下表所示
目標 | 方向 | 枚舉值 |
---|---|---|
GL_TEXTURE_CUBE_MAP_POSITIVE_X | 右 | 0x8515 |
GL_TEXTURE_CUBE_MAP_NEGATIVE_X | 左 | 0x8516 |
GL_TEXTURE_CUBE_MAP_POSITIVE_Y | 上 | 0x8517 |
GL_TEXTURE_CUBE_MAP_NEGATIVE_Y | 下 | 0x8518 |
GL_TEXTURE_CUBE_MAP_POSITIVE_Z | 后 | 0x8519 |
GL_TEXTURE_CUBE_MAP_NEGATIVE_Z | 前 | 0x851A |
由于枚舉值連續,所以我們在設置 faces
的時候邊可以按 右左上下前后 的順序傳入
接下來我們來包裝一個立方體紋理類來管理Cubemap
TextureCube.h 新建
#pragma once#include <iostream>
#include <vector>#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include "stb_image.h"class TextureCube
{
public:explicit TextureCube(std::vector<std::string> faces);/*** 獲取紋理ID*/unsigned int GetTextureID();private:/*** 紋理ID*/unsigned int textureID = 0;/*** 六面路徑* 順序 Right - Left - Top - Bottom - Front - Back*/std::vector<std::string> faces;/*** 加載*/void LoadCubemap();
};
TextureCube.cpp 新建
#include "TextureCube.h"TextureCube::TextureCube(std::vector<std::string> faces)
{this->faces = faces;LoadCubemap();
}void TextureCube::LoadCubemap()
{glGenTextures(1, &textureID);glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);stbi_set_flip_vertically_on_load(false);int width, height, channel;for (unsigned int i = 0; i < faces.size(); i++){const char* path = faces[i].c_str();unsigned char* data = stbi_load(path, &width, &height, &channel, 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);std::cout << "Load cubmap(" << GL_TEXTURE_CUBE_MAP_POSITIVE_X + i << "): " << path << " success, with: " << width << " height: " << height << " channel: " << channel << " ID: " << textureID << std::endl;}else{std::cout << "[Error] Failed to load cubemap: " << path << std::endl;}}stbi_set_flip_vertically_on_load(true);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);
}unsigned int TextureCube::GetTextureID()
{return textureID;
}
二、天空盒
接下來我們應用Cubemap來創建一個天空盒,天空盒就是一個包圍場景的大立方體
首先我們可以在這里或是頂部git倉庫中的 Resource/skybox
目錄中獲取到天空盒資源
接著定義天空盒Cube的頂點數據以及天空盒紋理的路徑
Main.cpp
// 天空盒紋理
std::vector<std::string> skyboxFaces
{"F:/Scripts/Cpp/LearnOpenGL/learn-open-gl/Resource/skybox/T_Right.jpg","F:/Scripts/Cpp/LearnOpenGL/learn-open-gl/Resource/skybox/T_Left.jpg","F:/Scripts/Cpp/LearnOpenGL/learn-open-gl/Resource/skybox/T_Top.jpg","F:/Scripts/Cpp/LearnOpenGL/learn-open-gl/Resource/skybox/T_Bottom.jpg","F:/Scripts/Cpp/LearnOpenGL/learn-open-gl/Resource/skybox/T_Front.jpg","F:/Scripts/Cpp/LearnOpenGL/learn-open-gl/Resource/skybox/T_Back.jpg"
};// 天空盒Cube頂點
float skyboxVertices[] = {// 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
};
然后編寫天空盒的頂點和片段著色器
SkyboxVertex.glsl 新建
這里我們使用了pos.xyww
來作為頂點著色器的輸出,之后在透視除法時,z分量會w / w = 1
,表示深度值為1,以便后續利用深度緩沖優化天空盒的繪制 (除此之外我們可以把天空盒放在第一個繪制,并禁用深度寫入來達到一樣的效果,但是性能會劣于以上方法)
#version 330 corelayout (location = 0) in vec3 aPos;out vec3 Direction;uniform mat4 projection;
uniform mat4 view;void main()
{Direction = aPos;vec4 pos = projection * view * vec4(aPos, 1.0);gl_Position = pos.xyww;
}
SkyboxFragment.glsl 新建
直接用方向向量對天空盒Cubemap進行采樣
#version 330 coreout vec4 FragColor;in vec3 Direction;uniform samplerCube skybox;void main()
{FragColor = texture(skybox, Direction);
}
接著準備繪制天空盒
Main.cpp
// 天空盒緩沖
unsigned int skyboxVAO, skyboxVBO;
glGenVertexArrays(1, &skyboxVAO);
glGenBuffers(1, &skyboxVBO);
glBindVertexArray(skyboxVAO);
glBindBuffer(GL_ARRAY_BUFFER, skyboxVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(skyboxVertices), &skyboxVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);// 天空盒Cubemap
TextureCube skyboxCubemap(skyboxFaces);// 天空盒Shader
Shader skyboxShader("Shader/SkyboxVertex.glsl", "Shader/SkyboxFragment.glsl");// 主循環
// 繪制不透明物體// 繪制天空盒
// 注意要設置為小于等于,因為天空盒深度為1
glDepthFunc(GL_LEQUAL);
skyboxShader.Use();
// 天空盒是固定的,去掉位移
skyboxShader.SetMat4("view", glm::mat4(glm::mat3(view)));
skyboxShader.SetMat4("projection", projection);
skyboxShader.SetInt("skybox", 0);
glBindVertexArray(skyboxVAO);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_CUBE_MAP, skyboxCubemap.GetTextureID());
glDrawArrays(GL_TRIANGLES, 0, 36);
glBindVertexArray(0);
glDepthFunc(GL_LESS);// 繪制半透明物體
// 屏幕后處理
編譯運行,順利的話可以看見以下圖像
三、環境映射
我們將周圍的環境映射到了Cubemap上,除了天空盒還可以做更多需要依賴環境信息的消息,例如反射 (Reflection)和折射 (Refraction)
反射 (Reflect)
我們可以通過計算 反射后的方向R 來對Cubemap進行采樣,從而獲得物體表面的反射顏色,如下圖所示 (圖片來自于LearnOpenGL)
首先編寫Shader,我們復用并修改背包的頂點著色器,并新增反射專用的片段著色器
Main.cpp
Shader reflectShader("Shader/VertexShader.glsl", "Shader/ReflectFragment.glsl");
VertexShader.glsl
#version 330 corelayout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;out vec3 FragPos;
out vec3 Normal;
out vec2 TexCoords;
// 新增頂點世界空間位置
out vec3 Position;uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;void main()
{gl_Position = projection * view * model * vec4(aPos, 1.0);FragPos = vec3(model * vec4(aPos, 1.0));Normal = mat3(transpose(inverse(model))) * aNormal;TexCoords = aTexCoords;// model乘以aPos得到世界空間位置Position = vec3(model * vec4(aPos, 1.0));
}
ReflectFragment.glsl 新增
#version 330 coreout vec4 FragColor;in vec3 Normal;
in vec3 Position;uniform vec3 cameraPos;
uniform samplerCube skybox;void main()
{vec3 I = normalize(Position - cameraPos);// 計算視線反射后的向量Rvec3 R = reflect(I, normalize(Normal));// 采樣skyboxFragColor = vec4(texture(skybox, R).rgb, 1.0);
}
接下來使用新創建的著色器進行繪制
// 繪制背包之后// 反射
modelMatrix = glm::translate(modelMatrix, glm::vec3(-5.0f, 0.0f, 0.0f));
reflectShader.Use();
reflectShader.SetMat4("view", view);
reflectShader.SetMat4("projection", projection);
reflectShader.SetMat4("model", modelMatrix);
reflectShader.SetVec3("cameraPos", camera.transform.position);
reflectShader.SetInt("skybox", 0);
glBindTexture(GL_TEXTURE_CUBE_MAP, skyboxCubemap.GetTextureID());
model.Draw(shader);
編譯運行,順利的話可以看見如下圖像,一個鏡子一樣的背包
折射 (Refract)
和反射的實現方式類似,我們可以通過計算視線折射后的方向來對Cubemap進行采樣,如下圖所示 (圖片來自于LearnOpenGL)
計算折射同樣可以使用GLSL提供的 refract
函數來實現,最后一位參數我們需要傳入折射率,以下是一些常見材質的折射率
材質 | 折射率 |
---|---|
空氣 | 1.00 |
水 | 1.33 |
冰 | 1.309 |
玻璃 | 1.52 |
鉆石 | 2.42 |
這里我們使用玻璃的折射率來渲染一個玻璃背包,只考慮從空氣進入玻璃發生的折射,那么比值 ratio = 1.00 / 1.52 = 0.658
RefractFragment.glsl 新建
#version 330 coreout vec4 FragColor;in vec3 Normal;
in vec3 Position;uniform vec3 cameraPos;
uniform samplerCube skybox;void main()
{float ratio = 1 / 1.52;vec3 I = normalize(Position - cameraPos);vec3 R = refract(I, normalize(Normal), ratio);FragColor = vec4(texture(skybox, R).rgb, 1.0);
}
Main.cpp
// 折射
Shader refractShader("Shader/VertexShader.glsl", "Shader/RefractFragment.glsl");// 繪制背包之后// 折射
modelMatrix = glm::translate(modelMatrix, glm::vec3(-5.0f, 0.0f, 0.0f));
refractShader.Use();
refractShader.SetMat4("view", view);
refractShader.SetMat4("projection", projection);
refractShader.SetMat4("model", modelMatrix);
refractShader.SetVec3("cameraPos", camera.transform.position);
refractShader.SetInt("skybox", 0);
model.Draw(shader);
編譯運行,順利的話可以看見以下圖像,一個玻璃背包
完整代碼可在頂部git倉庫中找到
下接:https://blog.csdn.net/weixin_44506615/article/details/151043459?spm=1001.2014.3001.5502