参考:

https://learnopenglcn.github.io/04%20Advanced%20OpenGL/06%20Cubemaps/

立方体贴图就是一个包含了6个2D纹理的纹理,每个2D纹理都组成了立方体的一个面:一个有纹理的立方体

立方体贴图有一个非常有用的特性,它可以通过一个方向向量来进行索引/采样。假设我们有一个1x1x1的单位立方体,方向向量的原点位于它的中心。使用一个橘黄色的方向向量来从立方体贴图上采样一个纹理值会像是这样

如果我们假设将这样的立方体贴图应用到一个立方体上,采样立方体贴图所使用的方向向量将和立方体(插值的)顶点位置非常相像。
这样子,只要立方体的中心位于原点,我们就能使用立方体的实际位置向量来对立方体贴图进行采样了。
接下来,我们可以将所有顶点的纹理坐标当做是立方体的顶点位置。最终得到的结果就是可以访问立方体贴图上正确面(Face)纹理的一个纹理坐标。

创建立方体贴图

如果想创建一个立方体贴图的话,我们需要生成一个纹理,并将其绑定到纹理目标上,之后再做其它的纹理操作。这次要绑定到GL_TEXTURE_CUBE_MAP

unsigned int textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);

因为立方体贴图包含有6个纹理,每个面一个,我们需要调用glTexImage2D函数6次,参数和之前教程中很类似。
但这一次我们将纹理目标(target)参数设置为立方体贴图的一个特定的面,告诉OpenGL我们在对立方体贴图的哪一个面创建纹理。这就意味着我们需要对立方体贴图的每一个面都调用一次glTexImage2D

如果我们有一个纹理位置的数组或者vector,我们就可以从GL_TEXTURE_CUBE_MAP_POSITIVE_X开始遍历它们,在每个迭代中对枚举值加1,遍历了整个纹理目标

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,它包含了立方体贴图所需的所有纹理路径,并以表中的顺序排列。
这将为当前绑定的立方体贴图中的每个面生成一个纹理。

立方体贴图和其它纹理没什么不同,我们也需要设定它的环绕和过滤方式

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_CLAMP_TO_EDGE,这是因为正好处于两个面之间的纹理坐标可能不能击中一个面
通过使用GL_CLAMP_TO_EDGE,OpenGL将在我们对两个面之间采样的时候,永远返回它们的边界值。

在绘制使用立方体贴图的物体之前,我们要先激活对应的纹理单元,并绑定立方体贴图,这和普通的2D纹理没什么区别。

在片段着色器中,使用了一个不同类型的采样器,samplerCube,将使用texture函数使用它进行采样,但这次将使用一个vec3的方向向量而不是vec2。使用立方体贴图的片段着色器会像是这样的:

in vec3 textureDir; // 代表3D纹理坐标的方向向量
uniform samplerCube cubemap; // 立方体贴图的纹理采样器void main()
{             FragColor = texture(cubemap, textureDir);
}

天空盒

天空盒是一个包含了整个场景的(大)立方体,它包含周围环境的6个图像,让玩家以为他处在一个比实际大得多的环境当中

下面这张截图中展示的是星空的天空盒,它来自于『上古卷轴3』

天空盒图像通常有以下的形式:

如果你将这六个面折成一个立方体,你就会得到一个完全贴图的立方体,模拟一个巨大的场景。
下载地址

加载天空盒

天空盒本身就是一个立方体贴图,加载天空盒和之前加载立方体贴图时并没有什么不同,为了加载天空盒,我们将使用下面的函数,它接受一个包含6个纹理路径的vector:

unsigned int loadCubemap(vector<std::string> faces)
{unsigned int textureID;glGenTextures(1, &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(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: " << 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);return textureID;
}

在调用这个函数之前,我们需要将合适的纹理路径按照立方体贴图枚举指定的顺序加载到一个vector中:

vector<std::string> faces
{"right.jpg","left.jpg","top.jpg","bottom.jpg","front.jpg","back.jpg"
};
unsigned int cubemapTexture = loadCubemap(faces);

现在我们就将这个天空盒加载为一个立方体贴图了,它的id是cubemapTexture。
我们可以将它绑定到一个立方体中,替换掉用了很长时间的难看的纯色背景

显示天空盒

由于天空盒是绘制在一个立方体上的,和其它物体一样,我们需要另一个VAO、VBO以及新的一组顶点

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
};

用于贴图3D立方体的立方体贴图可以使用立方体的位置作为纹理坐标来采样
当立方体处于原点(0, 0, 0)时,它的每一个位置向量都是从原点出发的方向向量
这个方向向量正是获取立方体上特定位置的纹理值所需要的。正是因为这个,我们只需要提供位置向量而不用纹理坐标了。

要渲染天空盒的话,我们需要一组新的着色器,它们都不是很复杂。因为我们只有一个顶点属性,顶点着色器非常简单:

#version 330 core
layout (location = 0) in vec3 aPos;out vec3 TexCoords;uniform mat4 projection;
uniform mat4 view;void main()
{TexCoords = aPos;gl_Position = projection * view * vec4(aPos, 1.0);
}

我们将输入的位置向量作为输出给片段着色器的纹理坐标。片段着色器会将它作为输入来采样samplerCube

#version 330 core
out vec4 FragColor;in vec3 TexCoords;uniform samplerCube skybox;void main()
{    FragColor = texture(skybox, TexCoords);
}

渲染天空盒现在就非常简单了,我们只需要绑定立方体贴图纹理,skybox采样器就会自动填充上天空盒立方体贴图了。
绘制天空盒时,我们需要将它变为场景中的第一个渲染的物体,并且禁用深度写入。这样子天空盒就会永远被绘制在其它物体的背后了。

glDepthMask(GL_FALSE);
skyboxShader.use();
// ... 设置观察和投影矩阵
glBindVertexArray(skyboxVAO);
glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture);
glDrawArrays(GL_TRIANGLES, 0, 36);
glDepthMask(GL_TRUE);
// ... 绘制剩下的场景

如果你运行一下的话你就会发现出现了一些问题。我们希望天空盒是以玩家为中心的,这样不论玩家移动了多远,天空盒都不会变近,让玩家产生周围环境非常大的印象。然而,当前的观察矩阵会旋转、缩放和位移来变换天空盒的所有位置,所以当玩家移动的时候,立方体贴图也会移动
我们可以将观察矩阵转换为3x3矩阵(移除位移),再将其转换回4x4矩阵,来达到类似的效果

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

这将移除任何的位移,但保留旋转变换,让玩家仍然能够环顾场景

优化

目前我们是首先渲染天空盒,之后再渲染场景中的其它物体。这样子能够工作,但不是非常高效。
如果我们先渲染天空盒,我们就会对屏幕上的每一个像素运行一遍片段着色器,即便只有一小部分的天空盒最终是可见的。可以使用提前深度测试(Early Depth Testing)轻松丢弃掉的片段能够节省我们很多宝贵的带宽

所以,我们将会最后渲染天空盒,以获得轻微的性能提升。这样子的话,深度缓冲就会填充满所有物体的深度值了,我们只需要在提前深度测试通过的地方渲染天空盒的片段就可以了,很大程度上减少了片段着色器的调用

天空盒只是一个1x1x1的立方体,它很可能会不通过大部分的深度测试,导致渲染失败。不用深度测试来进行渲染不是解决方案,因为天空盒将会复写场景中的其它物体。我们需要欺骗深度缓冲,让它认为天空盒有着最大的深度值1.0,只要它前面有一个物体,深度测试就会失败

透视除法是在顶点着色器运行之后执行的,将gl_Position的xyz坐标除以w分量。
从深度测试小节中知道,相除结果的z分量等于顶点的深度值。使用这些信息,我们可以将输出位置的z分量等于它的w分量,让z分量永远等于1.0,这样子的话,当透视除法执行之后,z分量会变为w / w = 1.0

void main()
{TexCoords = aPos;vec4 pos = projection * view * vec4(aPos, 1.0);gl_Position = pos.xyww;
}

我们还要改变一下深度函数,将它从默认的GL_LESS改为GL_LEQUAL
深度缓冲将会填充上天空盒的1.0值,所以我们需要保证天空盒在值小于或等于深度缓冲而不是小于时通过深度测试

效果如下:

优化后的代码如下:

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include "shader.h"
#include <iostream>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include "Camera.h"
#include "Model.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void processInput(GLFWwindow *window);
unsigned int loadTexture(const char *path);
unsigned int loadCubemap(vector<std::string> faces);const unsigned int SCR_WIDTH = 1280;
const unsigned int SCR_HEIGHT = 720;Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
float lastX = SCR_WIDTH / 2.0f;
float lastY = SCR_HEIGHT / 2.0f;
bool firstMouse = true;float deltaTime = 0.0f;
float lastFrame = 0.0f;glm::vec3 lightPos(1.2f, 1.0f, 2.0f);
int main(void)
{//初始化glfw并创建窗口//-------------------glfwInit();glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);GLFWwindow * window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "window", NULL, NULL);if (window == NULL){std::cout << "Failed to create GLFW window" << std::endl;glfwTerminate();return -1;}glfwMakeContextCurrent(window);glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);glfwSetCursorPosCallback(window, mouse_callback);glfwSetScrollCallback(window, scroll_callback);glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);//初始化GLAD//--------if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){std::cout << "Failed to initialize GLAD" << std::endl;return -1;}glEnable(GL_DEPTH_TEST);//创建着色器//---------Shader shader("D:\\OpenGL\\Project1\\lightshader.vs", "D:\\OpenGL\\Project1\\lightshader.fs");Shader skyboxShader("D:\\OpenGL\\Project1\\4.1.texture.vs", "D:\\OpenGL\\Project1\\4.1.texture.fs");float cubeVertices[] = {// Back face-0.5f, -0.5f, -0.5f,  0.0f, 0.0f, // Bottom-left0.5f,  0.5f, -0.5f,  1.0f, 1.0f, // top-right0.5f, -0.5f, -0.5f,  1.0f, 0.0f, // bottom-right         0.5f,  0.5f, -0.5f,  1.0f, 1.0f, // top-right-0.5f, -0.5f, -0.5f,  0.0f, 0.0f, // bottom-left-0.5f,  0.5f, -0.5f,  0.0f, 1.0f, // top-left// Front face-0.5f, -0.5f,  0.5f,  0.0f, 0.0f, // bottom-left0.5f, -0.5f,  0.5f,  1.0f, 0.0f, // bottom-right0.5f,  0.5f,  0.5f,  1.0f, 1.0f, // top-right0.5f,  0.5f,  0.5f,  1.0f, 1.0f, // top-right-0.5f,  0.5f,  0.5f,  0.0f, 1.0f, // top-left-0.5f, -0.5f,  0.5f,  0.0f, 0.0f, // bottom-left// Left face-0.5f,  0.5f,  0.5f,  1.0f, 0.0f, // top-right-0.5f,  0.5f, -0.5f,  1.0f, 1.0f, // top-left-0.5f, -0.5f, -0.5f,  0.0f, 1.0f, // bottom-left-0.5f, -0.5f, -0.5f,  0.0f, 1.0f, // bottom-left-0.5f, -0.5f,  0.5f,  0.0f, 0.0f, // bottom-right-0.5f,  0.5f,  0.5f,  1.0f, 0.0f, // top-right// Right face0.5f,  0.5f,  0.5f,  1.0f, 0.0f, // top-left0.5f, -0.5f, -0.5f,  0.0f, 1.0f, // bottom-right0.5f,  0.5f, -0.5f,  1.0f, 1.0f, // top-right         0.5f, -0.5f, -0.5f,  0.0f, 1.0f, // bottom-right0.5f,  0.5f,  0.5f,  1.0f, 0.0f, // top-left0.5f, -0.5f,  0.5f,  0.0f, 0.0f, // bottom-left     // Bottom face-0.5f, -0.5f, -0.5f,  0.0f, 1.0f, // top-right0.5f, -0.5f, -0.5f,  1.0f, 1.0f, // top-left0.5f, -0.5f,  0.5f,  1.0f, 0.0f, // bottom-left0.5f, -0.5f,  0.5f,  1.0f, 0.0f, // bottom-left-0.5f, -0.5f,  0.5f,  0.0f, 0.0f, // bottom-right-0.5f, -0.5f, -0.5f,  0.0f, 1.0f, // top-right// Top face-0.5f,  0.5f, -0.5f,  0.0f, 1.0f, // top-left0.5f,  0.5f,  0.5f,  1.0f, 0.0f, // bottom-right0.5f,  0.5f, -0.5f,  1.0f, 1.0f, // top-right     0.5f,  0.5f,  0.5f,  1.0f, 0.0f, // bottom-right-0.5f,  0.5f, -0.5f,  0.0f, 1.0f, // top-left-0.5f,  0.5f,  0.5f,  0.0f, 0.0f  // bottom-left        };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};unsigned int cubeVAO, cubeVBO;glGenVertexArrays(1, &cubeVAO);glGenBuffers(1, &cubeVBO);glBindVertexArray(cubeVAO);glBindBuffer(GL_ARRAY_BUFFER, cubeVBO);glBufferData(GL_ARRAY_BUFFER, sizeof(cubeVertices), &cubeVertices, GL_STATIC_DRAW);glEnableVertexAttribArray(0);glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);glEnableVertexAttribArray(1);glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));// skybox VAOunsigned 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);// load textures// -------------unsigned int cubeTexture = loadTexture("D:/OpenGL/LearnOpenGL-master/resources/textures/container.jpg");vector<std::string> faces{"D:/OpenGL/skybox/right.jpg","D:/OpenGL/skybox/left.jpg","D:/OpenGL/skybox/top.jpg","D:/OpenGL/skybox/bottom.jpg","D:/OpenGL/skybox/front.jpg","D:/OpenGL/skybox/back.jpg"};unsigned int cubemapTexture = loadCubemap(faces);shader.use();shader.setInt("texture1", 0);skyboxShader.use();skyboxShader.setInt("skybox", 0);while (!glfwWindowShouldClose(window)){float currentFrame = glfwGetTime();deltaTime = currentFrame - lastFrame;lastFrame = currentFrame;processInput(window);glClearColor(0.1f, 0.1f, 0.1f, 1.0f);glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);shader.use();glm::mat4 model = glm::mat4(1.0f);glm::mat4 view = camera.GetViewMatrix();glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);shader.setMat4("model", model);shader.setMat4("view", view);shader.setMat4("projection", projection);//立方体glBindVertexArray(cubeVAO);glActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_2D, cubeTexture);glDrawArrays(GL_TRIANGLES, 0, 36);glBindVertexArray(0);//天空盒glDepthFunc(GL_LEQUAL);  // 改变深度函数,当值等于深度缓冲区的内容时,深度测试通过skyboxShader.use();view = glm::mat4(glm::mat3(camera.GetViewMatrix())); // 从视图矩阵中删除平移skyboxShader.setMat4("view", view);skyboxShader.setMat4("projection", projection);glBindVertexArray(skyboxVAO);glActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture);glDrawArrays(GL_TRIANGLES, 0, 36);glBindVertexArray(0);glDepthFunc(GL_LESS);glfwSwapBuffers(window);glfwPollEvents();}glDeleteVertexArrays(1, &cubeVAO);glDeleteVertexArrays(1, &skyboxVAO);glDeleteBuffers(1, &cubeVBO);glDeleteBuffers(1, &skyboxVAO);glfwTerminate();return 0;}
void processInput(GLFWwindow *window)
{if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)glfwSetWindowShouldClose(window, true);if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)camera.ProcessKeyboard(FORWARD, deltaTime);if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)camera.ProcessKeyboard(BACKWARD, deltaTime);if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)camera.ProcessKeyboard(LEFT, deltaTime);if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)camera.ProcessKeyboard(RIGHT, deltaTime);if (glfwGetKey(window, GLFW_KEY_E) == GLFW_PRESS)camera.ProcessKeyboard(UP, deltaTime);if (glfwGetKey(window, GLFW_KEY_Q) == GLFW_PRESS)camera.ProcessKeyboard(DOWN, deltaTime);
}void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{glViewport(0, 0, width, height);
}
void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{if (firstMouse){lastX = xpos;lastY = ypos;firstMouse = false;}float xoffset = xpos - lastX;float yoffset = lastY - ypos; // reversed since y-coordinates go from bottom to toplastX = xpos;lastY = ypos;camera.ProcessMouseMovement(xoffset, yoffset);
}
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{camera.ProcessMouseScroll(yoffset);
}
unsigned int loadTexture(char const * path)
{unsigned int textureID;glGenTextures(1, &textureID);int width, height, nrComponents;unsigned char *data = stbi_load(path, &width, &height, &nrComponents, 0);if (data){GLenum format;if (nrComponents == 1)format = GL_RED;else if (nrComponents == 3)format = GL_RGB;else if (nrComponents == 4)format = GL_RGBA;glBindTexture(GL_TEXTURE_2D, textureID);glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);glGenerateMipmap(GL_TEXTURE_2D);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, format == GL_RGBA ? GL_CLAMP_TO_EDGE : GL_REPEAT); // for this tutorial: use GL_CLAMP_TO_EDGE to prevent semi-transparent borders. Due to interpolation it takes texels from next repeat glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, format == GL_RGBA ? GL_CLAMP_TO_EDGE : GL_REPEAT);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);stbi_image_free(data);}else{std::cout << "Texture failed to load at path: " << path << std::endl;stbi_image_free(data);}return textureID;
}
unsigned int loadCubemap(vector<std::string> faces)
{unsigned int textureID;glGenTextures(1, &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(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: " << 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);return textureID;
}

反射

反射这个属性表现为物体反射它周围环境,即根据观察者的视角,物体的颜色或多或少等于它的环境。镜子就是一个反射性物体:它会根据观察者的视角反射它周围的环境


我们根据观察方向向量I¯和物体的法向量N¯,来计算反射向量R
可以使用GLSL内建的reflect函数来计算这个反射向量。最终的R¯向量将会作为索引/采样立方体贴图的方向向量,返回环境的颜色值。最终的结果是物体看起来反射了天空盒

改变箱子的片段着色器,让箱子有反射性:

#version 330 core
out vec4 FragColor;in vec3 Normal;
in vec3 Position;uniform vec3 cameraPos;
uniform samplerCube skybox;void main()
{             vec3 I = normalize(Position - cameraPos);vec3 R = reflect(I, normalize(Normal));FragColor = vec4(texture(skybox, R).rgb, 1.0);
}

先计算了观察/摄像机方向向量I,并使用它来计算反射向量R
之后我们将使用R来从天空盒立方体贴图中采样

现在又有了片段的插值Normal和Position变量,所以我们需要更新一下顶点着色器

#version 330 core
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);
}

使用了一个法向量,所以我们将使用法线矩阵(Normal Matrix)来变换它们。Position输出向量是一个世界空间的位置向量。顶点着色器的这个Position输出将用来在片段着色器内计算观察方向向量
使用了法线,你还需要更新一下顶点数据,并更新属性指针

float vertices[] = {-0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f, 0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f, 0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f, -0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f, -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f, -0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,-0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,-0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,-0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,-0.5f,  0.5f, -0.5f, -1.0f,  0.0f,  0.0f,-0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,-0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,-0.5f, -0.5f,  0.5f, -1.0f,  0.0f,  0.0f,-0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,0.5f,  0.5f, -0.5f,  1.0f,  0.0f,  0.0f,0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,0.5f, -0.5f,  0.5f,  1.0f,  0.0f,  0.0f,0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,-0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,-0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,-0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,-0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,-0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,-0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f
};

还要设置cameraPos的uniform
接下来,我们在渲染箱子之前先绑定立方体贴图纹理

glBindVertexArray(cubeVAO);
glBindTexture(GL_TEXTURE_CUBE_MAP, skyboxTexture);
glDrawArrays(GL_TRIANGLES, 0, 36);

效果如下

折射

环境映射的另一种形式是折射,它和反射很相似。折射是光线由于传播介质的改变而产生的方向变化。
折射是通过斯涅尔定律(Snell’s Law)来描述的,使用环境贴图的话看起来像是这样:

有一个观察向量I,一个法向量N,而这次是折射向量R
可以看到,观察向量的方向轻微弯曲了。弯折后的向量R将会用来从立方体贴图中采样

折射可以使用GLSL的内建refract函数来轻松实现,它需要一个法向量、一个观察方向和两个材质之间的折射率

折射率决定了材质中光线弯曲的程度,每个材质都有自己的折射率。一些最常见的折射率可以在下表中找到


我们使用这些折射率来计算光传播的两种材质间的比值。在我们的例子中,光线/视线从空气进入玻璃(如果我们假设箱子是玻璃制的),所以比值为1.001.52=0.658
修改片元着色器:

void main()
{             float ratio = 1.00 / 1.52;vec3 I = normalize(Position - cameraPos);vec3 R = refract(I, normalize(Normal), ratio);FragColor = vec4(texture(skybox, R).rgb, 1.0);
}

可以想象出有了光照、反射、折射和顶点移动的正确组合,你可以创建出非常漂亮的水。
如果要想获得物理上精确的结果,我们还需要在光线离开物体的时候再次折射

OpenGL进阶之立方体贴图相关推荐

  1. 【OpenGL ES】立方体贴图(6张图)

    1 前言 本文通过一个立方体贴图的例子,讲解三维纹理贴图的应用,案例中使用 6 张不同的图片给立方体贴图,图片如下: 本文涉及到的知识点主要包含:三维绘图.MVP 矩阵变换.纹理贴图,读者如果对 Op ...

  2. OpenGL 核心技术之立方体贴图

    笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人;已出版书籍:<手把手教你架构3D游戏引擎>电子工业出版社和<Unity3D ...

  3. OPenGL笔记--给立方体贴图(纹理)

    文章目录 一.前置知识 二.效果展示. 三.完整代码 附.给立方体每个面渲染不同的纹理 一.前置知识 经过前面的学习,我们已经知道了立方体怎么创建了,接下来学习怎么给立方体贴图: 为了将纹理正确的映射 ...

  4. OpenGL Cube Map立方体贴图的实例

    OpenGL Shadow Mapping阴影贴图 先上图,再解答. 完整主要的源代码 源代码剖析 先上图,再解答. 完整主要的源代码 #include "vermilion.h" ...

  5. 使用OpenGL 立方体贴图

    openGL系列文章目录 文章目录 openGL系列文章目录 前言 一.OpenGL 立方体贴图 二.使用步骤 1.代码 2.着色器程序 运行结果 注意 源码下载 参考 前言 对于室外3D 场景,通常 ...

  6. 第三十七章 立方体贴图总结

    立方体贴图:将多个纹理组合起来映射到一张纹理上的一种纹理类型. 一个立方体贴图时包含了6个2D纹理的纹理,每个2D纹理都组成了立方体的一个面,相当于是一个有纹理的立方体. 创建立方体贴图: 首先需要生 ...

  7. OpenGL cubemap 立方体贴图实例

    OpenGL cubemap 立方体贴图 先上图,再解答. 完整主要的源代码 源代码剖析 先上图,再解答. 完整主要的源代码 #include <glad/glad.h> #include ...

  8. OpenGL ES 3. 天空盒 立方体贴图

    大家好,接下来将为大家介绍OpenGL ES 3. 天空盒 立方体贴图. OpenGL ES 立方体贴图本质上还是纹理映射,是一种 3D 纹理映射.立方体贴图所使的纹理称为立方图纹理,它是由 6 个单 ...

  9. OpenGL 入门 17:立方体贴图

    立方体贴图(Cube Map) 立方体贴图是由"上下左右前后"6个2D纹理合并成的一张纹理.与2D纹理使用(u,v)坐标采样不同的是,立方体纹理使用一个方向向量进行采样. 1. 方 ...

最新文章

  1. vue父子组件传值,sync语法糖
  2. Python学习 - 之 数据封装和私有属性
  3. SwiftUI之深入解析布局如何自定义AlignmentGuides
  4. 怎样获取当前页面值php,想要得到当前页面的所有url参数信息怎么用PHP来实现?...
  5. LVM( Logical Volume Manager )配置案例(on RHEL4)
  6. 三次握手的本质_关于TCP三次握手,这是我见过最好的解读了,通俗易懂
  7. Java基础学习总结(47)——JAVA输入输出流再回忆
  8. 别把可视化不当事,看完大屏模板,Excel和PPT直言比不过
  9. 计算机视觉基础:自适应阈值分割(Computer Vision Fundamentals: Adaptive Threshold Segmentation)
  10. coderforces 731c
  11. python2和python3中的unicode
  12. 【场景化集成方案】如何让企业快速集成钉钉各种能力
  13. i5处理器学计算机怎么设置,处理器怎么超频 酷睿i3/i5/i7系列CPU超频详细教程 (全文)...
  14. [附源码]java毕业设计政府公用车辆管理系统
  15. 【阶段总结】大四上学期总结
  16. php+矩阵,PHP实现简单矩阵算法
  17. 清华大学计算机综合基础真题,【盛世清北】2021清华大学912计算机专业基础综合考研真题-清华考...
  18. 分子内电荷转移有哪些最新发表的毕业论文呢?
  19. 制造业要用什么项目管理软件?
  20. 神策数据微信小程序 SDK 功能介绍 | 数据采集

热门文章

  1. 使用Managed DirectX编写游戏(-)
  2. 蛮荒搜神记服务器在维护,宏伟神话故事落幕 《蛮荒搜神记》宣布停止运营
  3. java-net-php-python-java“校园易购”网站开发计算机毕业设计程序
  4. 3dmax脚本自动化切割模型的解决方案
  5. HTTP 错误 401.0 - Unauthorized 的解决方案
  6. ExtScreen,为智能电视和VR设备打造的快应用引擎
  7. 【HBuilderX】wap2app项目如何获取iOS广告标识idfa?
  8. 嵌入式开发环境搭建 与 系统移植
  9. 编程零基础,如何19周掌握深度学习?
  10. weta工作室 Linux,新西兰Weta工作室(转)