目录

  • 写在前面
  • 天空盒简介
  • 创建立方体贴图
  • 渲染一个立方体
  • 立方体贴图着色器
  • 开始绘制天空盒
  • 完整代码
    • 着色器
    • c++

写在前面

上一篇博客回顾:OpenGL学习(九)阴影映射(shadowMapping)

在昨天我们实现了非常简单的阴影映射特效,今天来更新立方体贴图的内容。这部分的内容相当简单,前提是要对 OpenGL 及其绘制过程有一个基本的理解。

相信我,仅 10 分钟足够你完成一个立方体贴图,并且用于天空盒的渲染。

天空盒简介

注意到我们之前的代码都是利用:

glClearColor(1.0, 1.0, 1.0, 1.0);   // 背景颜色

来填充一个纯色来作为背景图像。

事实上在现代计算机游戏中,天空盒是一个常见的填充背景的手段,并且往往能够起到好的效果。

通过立方体贴图我们得以实现天空盒的绘制。立方体贴图顾名思义就是将一个立方体的 6 个面贴上对应的纹理,然后用这个立方体将相机包裹住:

这样相机视线的背景就永远是立方体上的花样纹理,而不是纯白或者纯黑的 glClearColor 。

回想起小时候玩的大富翁的纸质骰子,我们用 6 张图片就可以将立方体变成一个被风景包围的立方体:

立方体贴图和普通 2D 贴图一样,只是在查询的时候,我们以三维坐标去查询,而不是普通纹理的二维坐标。我们通过 glsl 中的 samplerCube 类型的采样器,就可以访问到立方体贴图:

uniform samplerCube skybox;...color = textureCube(skybox, texcoord);

其中纹理坐标我们直接 利用立方体的坐标 即可完成查询。因为 glsl 的采样器会自动根据我们提供的方向向量,来返回视线触碰到的立方体贴图的颜色。

创建立方体贴图

创建一个立方体贴图也十分简单。我们直接循环进行 6 张 2D 贴图的创建即可,值得注意的是使用

GL_TEXTURE_CUBE_MAP_POSITIVE_X + 偏移量

来指定当前生成的是第几张贴图,在最后我们返回当前创建的纹理对象的索引。下面给出创建立方体贴图的函数:

GLuint loadCubemap(std::vector<const GLchar*> faces)
{GLuint textureID;glGenTextures(1, &textureID);glActiveTexture(GL_TEXTURE0);int width, height;unsigned char* image;glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);for (GLuint i = 0; i < faces.size(); i++){image = SOIL_load_image(faces[i], &width, &height, 0, SOIL_LOAD_RGB);glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0,GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);}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);glBindTexture(GL_TEXTURE_CUBE_MAP, 0);return textureID;
}

然后我们可以通过:

std::vector<const GLchar*> faces;
faces.push_back("skybox/right.jpg");
faces.push_back("skybox/left.jpg");
faces.push_back("skybox/top.jpg");
faces.push_back("skybox/bottom.jpg");
faces.push_back("skybox/back.jpg");
faces.push_back("skybox/front.jpg");
skyboxTexture = loadCubemap(faces);

来指定 6 张贴图的路径,并且调用 loadCubemap 进行创建。

渲染一个立方体

在获取了立方体贴图的纹理对象之后,我们还需要向场景中添加一个立方体,同时将纹理贴上去。立方体的添加也十分简单,我们指定 8 个顶点,然后指定 36 个三角面片索引即可。

注意因为我们直接使用立方体的坐标作为纹理采样的坐标(毕竟逻辑上也是直接获取对应点上的像素值),我们无需传递纹理坐标和法线等顶点属性

Model skybox;   // 渲染一个立方体用于立方体贴图绘制天空盒...// 生成一个立方体做天空盒的 “画布”
Mesh cube;
cube.vertexPosition = { // 立方体的 8 个顶点glm::vec3(-1, -1, -1),glm::vec3(1, -1, -1),glm::vec3(-1, 1, -1),glm::vec3(1, 1, -1),glm::vec3(-1, -1, 1),glm::vec3(1, -1, 1),glm::vec3(-1, 1, 1),glm::vec3(1, 1, 1)
};
cube.index = {0,3,1,0,2,3,1,5,4,1,4,0,4,2,0,4,6,2,5,6,4,5,7,6,2,6,7,2,7,3,1,7,5,1,3,7};
cube.bindData();
skybox.meshes.push_back(cube);

立方体贴图着色器

因为立方体贴图直接利用立方体的坐标作为方向向量进行纹理采样,而无需纹理坐标,于是我们的着色器发生了一些变换。我们最好利用一组新的着色器来管理这些特殊情况。我们创建 skybox 系列着色器。

其中顶点着色器仍然负责完成 mvp 变换,值得注意的是,我们直接将变换后的坐标作为采样的方向向量,传递到片元着色器中:

#version 330 core// 顶点着色器输入
layout (location = 0) in vec3 vPosition;out vec3 texcoord;uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;void main()
{gl_Position = projection * view * model * vec4(vPosition, 1.0);  texcoord = vPosition;   // 坐标作为cubeMap采样坐标
}

片元着色器则更为简单,我们利用传入的 cubmap 采样器,和从顶点着色器中获取的 “纹理坐标” 进行立方体贴图的采样,并且输出最终的结果:

#version 330 corein vec3 texcoord;
out vec4 fColor;uniform samplerCube skybox;void main()
{fColor = textureCube(skybox, texcoord);
}

与此同时,在 c++ 中别忘记创建我们的着色器对象:

GLuint skyboxProgram;   // 天空盒绘制...skyboxProgram = getShaderProgram("shaders/skybox.fsh", "shaders/skybox.vsh");

开始绘制天空盒

我们正式开始绘制天空盒。这一部分我们放到 display 也就是每一帧的回调函数中进行。首先我们使用着色器,并且做一些清空窗口等杂活:

// 绘制天空盒
glUseProgram(skyboxProgram);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glViewport(0, 0, windowWidth, windowHeight);

注:
我是在正常渲染之前进行天空盒的绘制,所以必须提前 glClear
而后续的正常渲染则不需要调用 glClear 了
因为渲染的像素是覆盖在天空盒之上的

然后我们传送相机的变换矩阵,同时传送我们天空盒的立方体贴图纹理,最后调用 draw call:

// 传视图,投影矩阵
glUniformMatrix4fv(glGetUniformLocation(skyboxProgram, "view"), 1, GL_FALSE, glm::value_ptr(camera.getViewMatrix()));
glUniformMatrix4fv(glGetUniformLocation(skyboxProgram, "projection"), 1, GL_FALSE, glm::value_ptr(camera.getProjectionMatrix()));// 传cubemap纹理
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_CUBE_MAP, skyboxTexture);
glUniform1i(glGetUniformLocation(skyboxProgram, "skybox"), 1);// 立方体永远跟随相机
skybox.translate = camera.position;glDepthMask(GL_FALSE);
skybox.draw(skyboxProgram);
glDepthMask(GL_TRUE);

值得注意的是,立方体必须时刻跟随相机
因为我们的立方体默认在 (0,0,0) 位置,但是相机发生移动之后,立方体就无法包裹住相机了
就会出现。。。唔 单独的一个立方体的情况,如下图:

好,如果一切顺利,那么我们会得到一个十分逼真的效果:


这下牛大了,这不把之前的白色背景干的碎碎的

完整代码

着色器

  • debug 和 shadow 和正常渲染的着色器见:上一篇博客
  • 天空盒着色器见:上文 立方体贴图着色器部分

c++

// std c++
#include <iostream>
#include <string>
#include <fstream>
#include <vector>
#include <map>
#include <sstream>
#include <iostream>// glew glut
#include <GL/glew.h>
#include <GL/freeglut.h>// glm
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>// SOIL
#include <SOIL2/SOIL2.h>// assimp
#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/postprocess.h>// --------------------- end of include --------------------- //class Mesh
{public:// OpenGL 对象GLuint vao, vbo, ebo;GLuint diffuseTexture;  // 漫反射纹理// 顶点属性std::vector<glm::vec3> vertexPosition;std::vector<glm::vec2> vertexTexcoord;std::vector<glm::vec3> vertexNormal;// glDrawElements 函数的绘制索引std::vector<int> index;Mesh() {}void bindData(){// 创建顶点数组对象glGenVertexArrays(1, &vao); // 分配1个顶点数组对象glBindVertexArray(vao);     // 绑定顶点数组对象// 创建并初始化顶点缓存对象 这里填NULL 先不传数据glGenBuffers(1, &vbo);glBindBuffer(GL_ARRAY_BUFFER, vbo);glBufferData(GL_ARRAY_BUFFER,vertexPosition.size() * sizeof(glm::vec3) +vertexTexcoord.size() * sizeof(glm::vec2) +vertexNormal.size() * sizeof(glm::vec3),NULL, GL_STATIC_DRAW);// 传位置GLuint offset_position = 0;GLuint size_position = vertexPosition.size() * sizeof(glm::vec3);glBufferSubData(GL_ARRAY_BUFFER, offset_position, size_position, vertexPosition.data());glEnableVertexAttribArray(0);   // 着色器中 (layout = 0) 表示顶点位置glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (GLvoid*)(offset_position));// 传纹理坐标GLuint offset_texcoord = size_position;GLuint size_texcoord = vertexTexcoord.size() * sizeof(glm::vec2);glBufferSubData(GL_ARRAY_BUFFER, offset_texcoord, size_texcoord, vertexTexcoord.data());glEnableVertexAttribArray(1);   // 着色器中 (layout = 1) 表示纹理坐标glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, (GLvoid*)(offset_texcoord));// 传法线GLuint offset_normal = size_position + size_texcoord;GLuint size_normal = vertexNormal.size() * sizeof(glm::vec3);glBufferSubData(GL_ARRAY_BUFFER, offset_normal, size_normal, vertexNormal.data());glEnableVertexAttribArray(2);   // 着色器中 (layout = 2) 表示法线glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 0, (GLvoid*)(offset_normal));// 传索引到 eboglGenBuffers(1, &ebo);glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);glBufferData(GL_ELEMENT_ARRAY_BUFFER, index.size() * sizeof(GLuint), index.data(), GL_STATIC_DRAW);glBindVertexArray(0);}void draw(GLuint program){glBindVertexArray(vao);// 传纹理glActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_2D, diffuseTexture);glUniform1i(glGetUniformLocation(program, "texture"), 0);// 绘制glDrawElements(GL_TRIANGLES, this->index.size(), GL_UNSIGNED_INT, 0);glBindVertexArray(0);}
};class Model
{public:std::vector<Mesh> meshes;std::map<std::string, GLuint> textureMap;glm::vec3 translate = glm::vec3(0, 0, 0), rotate = glm::vec3(0, 0, 0), scale = glm::vec3(1, 1, 1);Model() {}void load(std::string filepath){Assimp::Importer import;const aiScene* scene = import.ReadFile(filepath, aiProcess_Triangulate | aiProcess_FlipUVs);// 异常处理if (!scene || scene->mFlags == AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode){std::cout << "读取模型出现错误: " << import.GetErrorString() << std::endl;exit(-1);}// 模型文件相对路径std::string rootPath = filepath.substr(0, filepath.find_last_of('/'));// 循环生成 meshfor (int i = 0; i < scene->mNumMeshes; i++){// 引用当前meshmeshes.push_back(Mesh());Mesh& mesh = meshes.back();// 获取 assimp 的读取到的 aimesh 对象aiMesh* aimesh = scene->mMeshes[i];// 我们将数据传递给我们自定义的meshfor (int j = 0; j < aimesh->mNumVertices; j++){// 顶点glm::vec3 vvv;vvv.x = aimesh->mVertices[j].x;vvv.y = aimesh->mVertices[j].y;vvv.z = aimesh->mVertices[j].z;mesh.vertexPosition.push_back(vvv);// 法线vvv.x = aimesh->mNormals[j].x;vvv.y = aimesh->mNormals[j].y;vvv.z = aimesh->mNormals[j].z;mesh.vertexNormal.push_back(vvv);// 纹理坐标: 如果存在则加入。assimp 默认可以有多个纹理坐标 我们取第一个(0)即可glm::vec2 vv(0, 0);if (aimesh->mTextureCoords[0]){vv.x = aimesh->mTextureCoords[0][j].x;vv.y = aimesh->mTextureCoords[0][j].y;}mesh.vertexTexcoord.push_back(vv);}// 如果有材质,那么传递材质if (aimesh->mMaterialIndex >= 0){// 获取当前 aimesh 的材质对象aiMaterial* material = scene->mMaterials[aimesh->mMaterialIndex];// 获取 diffuse 贴图文件路径名称 我们只取1张贴图 故填 0 即可aiString aistr;material->GetTexture(aiTextureType_DIFFUSE, 0, &aistr);std::string texpath = aistr.C_Str();texpath = rootPath + '/' + texpath;   // 取相对路径// 如果没生成过纹理,那么生成它if (textureMap.find(texpath) == textureMap.end()){// 生成纹理GLuint tex;glGenTextures(1, &tex);glBindTexture(GL_TEXTURE_2D, tex);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);int textureWidth, textureHeight;unsigned char* image = SOIL_load_image(texpath.c_str(), &textureWidth, &textureHeight, 0, SOIL_LOAD_RGB);glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, textureWidth, textureHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, image);   // 生成纹理delete[] image;textureMap[texpath] = tex;}// 传递纹理mesh.diffuseTexture = textureMap[texpath];}// 传递面片索引for (GLuint j = 0; j < aimesh->mNumFaces; j++){aiFace face = aimesh->mFaces[j];for (GLuint k = 0; k < face.mNumIndices; k++){mesh.index.push_back(face.mIndices[k]);}}mesh.bindData();}}void draw(GLuint program){// 传模型矩阵glm::mat4 unit(    // 单位矩阵glm::vec4(1, 0, 0, 0),glm::vec4(0, 1, 0, 0),glm::vec4(0, 0, 1, 0),glm::vec4(0, 0, 0, 1));glm::mat4 scale = glm::scale(unit, this->scale);glm::mat4 translate = glm::translate(unit, this->translate);glm::mat4 rotate = unit;    // 旋转rotate = glm::rotate(rotate, glm::radians(this->rotate.x), glm::vec3(1, 0, 0));rotate = glm::rotate(rotate, glm::radians(this->rotate.y), glm::vec3(0, 1, 0));rotate = glm::rotate(rotate, glm::radians(this->rotate.z), glm::vec3(0, 0, 1));// 模型变换矩阵glm::mat4 model = translate * rotate * scale;GLuint mlocation = glGetUniformLocation(program, "model");    // 名为model的uniform变量的位置索引glUniformMatrix4fv(mlocation, 1, GL_FALSE, glm::value_ptr(model));   // 列优先矩阵for (int i = 0; i < meshes.size(); i++){meshes[i].draw(program);}}
};class Camera
{public:// 相机参数glm::vec3 position = glm::vec3(0, 0, 0);    // 位置glm::vec3 direction = glm::vec3(0, 0, -1);  // 视线方向glm::vec3 up = glm::vec3(0, 1, 0);          // 上向量,固定(0,1,0)不变float pitch = 0.0f, roll = 0.0f, yaw = 0.0f;    // 欧拉角float fovy = 70.0f, aspect = 1.0, zNear = 0.01, zFar = 100; // 透视投影参数float left = -1.0, right = 1.0, top = 1.0, bottom = -1.0; // 正交投影参数Camera() {}// 视图变换矩阵glm::mat4 getViewMatrix(bool useEulerAngle = true){if (useEulerAngle)  // 使用欧拉角更新相机朝向{direction.x = cos(glm::radians(pitch)) * sin(glm::radians(yaw));direction.y = sin(glm::radians(pitch));direction.z = -cos(glm::radians(pitch)) * cos(glm::radians(yaw)); // 相机看向z轴负方向}return glm::lookAt(position, position + direction, up);}// 投影矩阵glm::mat4 getProjectionMatrix(bool usePerspective = true){if (usePerspective) // 透视投影{return glm::perspective(glm::radians(fovy), aspect, zNear, zFar);}return glm::ortho(left, right, bottom, top, zNear, zFar);}
};// ---------------------------- end of class definition ---------------------------- //// 模型
std::vector<Model> models;  // 场景
Model screen;   // 渲染一个四方形做屏幕
Model skybox;   // 渲染一个立方体用于立方体贴图绘制天空盒// 着色器程序对象
GLuint program;
GLuint debugProgram;    // 调试用
GLuint shadowProgram;   // 绘制阴影的着色器程序对象
GLuint skyboxProgram;   // 天空盒绘制// 纹理
GLuint skyboxTexture;   // 天空盒
GLuint shadowTexture;   // 阴影纹理// 相机
Camera camera;          // 正常渲染
Camera shadowCamera;    // 从光源方向渲染// 光源与阴影参数
int shadowMapResolution = 1024;             // 阴影贴图分辨率
GLuint shadowMapFBO;                        // 从光源方向进行渲染的帧缓冲// glut与交互相关变量
int windowWidth = 512;  // 窗口宽
int windowHeight = 512; // 窗口高
bool keyboardState[1024];   // 键盘状态数组 keyboardState[x]==true 表示按下x键// --------------- end of global variable definition --------------- //// 读取文件并且返回一个长字符串表示文件内容
std::string readShaderFile(std::string filepath)
{std::string res, line;std::ifstream fin(filepath);if (!fin.is_open()){std::cout << "文件 " << filepath << " 打开失败" << std::endl;exit(-1);}while (std::getline(fin, line)){res += line + '\n';}fin.close();return res;
}// 获取着色器对象
GLuint getShaderProgram(std::string fshader, std::string vshader)
{// 读取shader源文件std::string vSource = readShaderFile(vshader);std::string fSource = readShaderFile(fshader);const char* vpointer = vSource.c_str();const char* fpointer = fSource.c_str();// 容错GLint success;GLchar infoLog[512];// 创建并编译顶点着色器GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);glShaderSource(vertexShader, 1, (const GLchar**)(&vpointer), NULL);glCompileShader(vertexShader);glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);   // 错误检测if (!success){glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);std::cout << "顶点着色器编译错误\n" << infoLog << std::endl;exit(-1);}// 创建并且编译片段着色器GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);glShaderSource(fragmentShader, 1, (const GLchar**)(&fpointer), NULL);glCompileShader(fragmentShader);glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);   // 错误检测if (!success){glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);std::cout << "片段着色器编译错误\n" << infoLog << std::endl;exit(-1);}// 链接两个着色器到program对象GLuint shaderProgram = glCreateProgram();glAttachShader(shaderProgram, vertexShader);glAttachShader(shaderProgram, fragmentShader);glLinkProgram(shaderProgram);// 删除着色器对象glDeleteShader(vertexShader);glDeleteShader(fragmentShader);return shaderProgram;
}// 鼠标滚轮函数
void mouseWheel(int wheel, int direction, int x, int y)
{// zFar += 1 * direction * 0.1;glutPostRedisplay();    // 重绘
}// 鼠标运动函数
void mouse(int x, int y)
{// 调整旋转camera.yaw += 35 * (x - float(windowWidth) / 2.0) / windowWidth;camera.yaw = glm::mod(camera.yaw + 180.0f, 360.0f) - 180.0f;    // 取模范围 -180 ~ 180camera.pitch += -35 * (y - float(windowHeight) / 2.0) / windowHeight;camera.pitch = glm::clamp(camera.pitch, -89.0f, 89.0f);glutWarpPointer(windowWidth / 2.0, windowHeight / 2.0);glutPostRedisplay();    // 重绘
}// 键盘回调函数
void keyboardDown(unsigned char key, int x, int y)
{keyboardState[key] = true;
}
void keyboardDownSpecial(int key, int x, int y)
{keyboardState[key] = true;
}
void keyboardUp(unsigned char key, int x, int y)
{keyboardState[key] = false;
}
void keyboardUpSpecial(int key, int x, int y)
{keyboardState[key] = false;
}
// 根据键盘状态判断移动
void move()
{float cameraSpeed = 0.035f;// 相机控制if (keyboardState['w']) camera.position += cameraSpeed * camera.direction;if (keyboardState['s']) camera.position -= cameraSpeed * camera.direction;if (keyboardState['a']) camera.position -= cameraSpeed * glm::normalize(glm::cross(camera.direction, camera.up));if (keyboardState['d']) camera.position += cameraSpeed * glm::normalize(glm::cross(camera.direction, camera.up));if (keyboardState[GLUT_KEY_CTRL_L]) camera.position.y -= cameraSpeed;if (keyboardState[' ']) camera.position.y += cameraSpeed;// 光源位置控制if (keyboardState['i']) shadowCamera.position.x += cameraSpeed;if (keyboardState['I']) shadowCamera.position.x -= cameraSpeed;if (keyboardState['o']) shadowCamera.position.y += cameraSpeed;if (keyboardState['O']) shadowCamera.position.y -= cameraSpeed;glutPostRedisplay();    // 重绘
}GLuint loadCubemap(std::vector<const GLchar*> faces)
{GLuint textureID;glGenTextures(1, &textureID);glActiveTexture(GL_TEXTURE0);int width, height;unsigned char* image;glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);for (GLuint i = 0; i < faces.size(); i++){image = SOIL_load_image(faces[i], &width, &height, 0, SOIL_LOAD_RGB);glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0,GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);}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);glBindTexture(GL_TEXTURE_CUBE_MAP, 0);return textureID;
}// 初始化
void init()
{// 生成着色器程序对象program = getShaderProgram("shaders/fshader.fsh", "shaders/vshader.vsh");shadowProgram = getShaderProgram("shaders/shadow.fsh", "shaders/shadow.vsh");debugProgram = getShaderProgram("shaders/debug.fsh", "shaders/debug.vsh");skyboxProgram = getShaderProgram("shaders/skybox.fsh", "shaders/skybox.vsh");// ------------------------------------------------------------------------ // // 读取 obj 模型Model tree1 = Model();tree1.translate = glm::vec3(2.5, 0, 2);tree1.scale = glm::vec3(0.0025, 0.0025, 0.0025);tree1.load("models/tree/tree02.obj");models.push_back(tree1);Model tree2 = Model();tree2.translate = glm::vec3(10, 0, 7);tree2.scale = glm::vec3(0.0015, 0.0015, 0.0015);tree2.load("models/tree/tree02.obj");models.push_back(tree2);Model plane = Model();plane.translate = glm::vec3(0, -1.1, 0);plane.scale = glm::vec3(10, 10, 10);plane.rotate = glm::vec3(0, 0, 0);plane.load("models/plane/plane.obj");models.push_back(plane);// 光源位置标志物Model vlight = Model();vlight.translate = glm::vec3(1, 0, -1);vlight.rotate = glm::vec3(-90, 0, 0);vlight.scale = glm::vec3(0.03, 0.03, 0.03);vlight.load("models/duck/12248_Bird_v1_L2.obj");models.push_back(vlight);// ------------------------------------------------------------------------ // // 生成一个四方形做荧幕 -- 用以显示纹理中的数据Mesh msquare;msquare.vertexPosition = { glm::vec3(-1, -1, 0), glm::vec3(1, -1, 0), glm::vec3(-1, 1, 0), glm::vec3(1, 1, 0) };msquare.vertexTexcoord = { glm::vec2(0, 0), glm::vec2(1, 0), glm::vec2(0, 1), glm::vec2(1, 1) };msquare.index = { 0,1,2,2,1,3 };msquare.bindData();screen.meshes.push_back(msquare);// ------------------------------------------------------------------------ //// 生成一个立方体做天空盒的 “画布”Mesh cube;cube.vertexPosition = { // 立方体的 8 个顶点glm::vec3(-1, -1, -1),glm::vec3(1, -1, -1),glm::vec3(-1, 1, -1),glm::vec3(1, 1, -1),glm::vec3(-1, -1, 1),glm::vec3(1, -1, 1),glm::vec3(-1, 1, 1),glm::vec3(1, 1, 1)};cube.index = {0,3,1,0,2,3,1,5,4,1,4,0,4,2,0,4,6,2,5,6,4,5,7,6,2,6,7,2,7,3,1,7,5,1,3,7};cube.bindData();skybox.meshes.push_back(cube);// 加载立方体贴图std::vector<const GLchar*> faces;faces.push_back("skybox/right.jpg");faces.push_back("skybox/left.jpg");faces.push_back("skybox/top.jpg");faces.push_back("skybox/bottom.jpg");faces.push_back("skybox/back.jpg");faces.push_back("skybox/front.jpg");/*faces.push_back("skybox/DOOM16RT.png");faces.push_back("skybox/DOOM16LF.png");faces.push_back("skybox/DOOM16UP.png");faces.push_back("skybox/DOOM16DN.png");faces.push_back("skybox/DOOM16FT.png");faces.push_back("skybox/DOOM16BK.png");*/skyboxTexture = loadCubemap(faces);// ------------------------------------------------------------------------ // // 正交投影参数配置 -- 视界体范围 -- 调整到场景一般大小即可shadowCamera.left = -20;shadowCamera.right = 20;shadowCamera.bottom = -20;shadowCamera.top = 20;shadowCamera.position = glm::vec3(0, 4, 15);// 创建shadow帧缓冲glGenFramebuffers(1, &shadowMapFBO);// 创建阴影纹理glGenTextures(1, &shadowTexture);glBindTexture(GL_TEXTURE_2D, shadowTexture);glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, shadowMapResolution, shadowMapResolution, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);// 将阴影纹理绑定到 shadowMapFBO 帧缓冲glBindFramebuffer(GL_FRAMEBUFFER, shadowMapFBO);glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, shadowTexture, 0);glDrawBuffer(GL_NONE);glReadBuffer(GL_NONE);glBindFramebuffer(GL_FRAMEBUFFER, 0);// ------------------------------------------------------------------------ // glEnable(GL_DEPTH_TEST);  // 开启深度测试glClearColor(1.0, 1.0, 1.0, 1.0);   // 背景颜色
}// 显示回调函数
void display()
{move(); // 移动控制 -- 控制相机位置// 最后一个物体作为光源位置的标志物models.back().translate = shadowCamera.position + glm::vec3(0, 0, 2);// ------------------------------------------------------------------------ // // 从光源方向进行渲染glUseProgram(shadowProgram);glBindFramebuffer(GL_FRAMEBUFFER, shadowMapFBO);glClear(GL_DEPTH_BUFFER_BIT);glViewport(0, 0, shadowMapResolution, shadowMapResolution);// 光源看向世界坐标原点shadowCamera.direction = glm::normalize(glm::vec3(0, 0, 0) - shadowCamera.position);// 传视图矩阵glUniformMatrix4fv(glGetUniformLocation(shadowProgram, "view"), 1, GL_FALSE, glm::value_ptr(shadowCamera.getViewMatrix(false)));// 传投影矩阵glUniformMatrix4fv(glGetUniformLocation(shadowProgram, "projection"), 1, GL_FALSE, glm::value_ptr(shadowCamera.getProjectionMatrix(false)));// 从光源方向进行绘制for (auto m : models){m.draw(shadowProgram);}// ------------------------------------------------------------------------ // // 绘制天空盒glUseProgram(skyboxProgram);glBindFramebuffer(GL_FRAMEBUFFER, 0);glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);glViewport(0, 0, windowWidth, windowHeight);// 传视图,投影矩阵glUniformMatrix4fv(glGetUniformLocation(skyboxProgram, "view"), 1, GL_FALSE, glm::value_ptr(camera.getViewMatrix()));glUniformMatrix4fv(glGetUniformLocation(skyboxProgram, "projection"), 1, GL_FALSE, glm::value_ptr(camera.getProjectionMatrix()));// 传cubemap纹理glActiveTexture(GL_TEXTURE1);glBindTexture(GL_TEXTURE_CUBE_MAP, skyboxTexture);glUniform1i(glGetUniformLocation(skyboxProgram, "skybox"), 1);// 立方体永远跟随相机skybox.translate = camera.position;glDepthMask(GL_FALSE);skybox.draw(skyboxProgram);glDepthMask(GL_TRUE);// ------------------------------------------------------------------------ // // 正常滴渲染glUseProgram(program);// 传视图矩阵glUniformMatrix4fv(glGetUniformLocation(program, "view"), 1, GL_FALSE, glm::value_ptr(camera.getViewMatrix()));// 传投影矩阵glUniformMatrix4fv(glGetUniformLocation(program, "projection"), 1, GL_FALSE, glm::value_ptr(camera.getProjectionMatrix()));/*// debug用 -- 从光源方向渲染场景 -- 测试正交投影是否正确glUniformMatrix4fv(glGetUniformLocation(program, "view"), 1, GL_FALSE, glm::value_ptr(shadowCamera.getViewMatrix(false)));glUniformMatrix4fv(glGetUniformLocation(program, "projection"), 1, GL_FALSE, glm::value_ptr(shadowCamera.getProjectionMatrix(false)));*/// 传递矩阵: 转换到光源坐标的变换矩阵glm::mat4 shadowVP = shadowCamera.getProjectionMatrix(false) * shadowCamera.getViewMatrix(false);glUniformMatrix4fv(glGetUniformLocation(program, "shadowVP"), 1, GL_FALSE, glm::value_ptr(shadowVP));// 传深度纹理glActiveTexture(GL_TEXTURE1);glBindTexture(GL_TEXTURE_2D, shadowTexture);glUniform1i(glGetUniformLocation(program, "shadowtex"), 1);// 传递光源位置glUniform3fv(glGetUniformLocation(program, "lightPos"), 1, glm::value_ptr(shadowCamera.position));// 传递相机位置glUniform3fv(glGetUniformLocation(program, "cameraPos"), 1, glm::value_ptr(camera.position));// 正常绘制for (auto m : models){m.draw(program);}// ------------------------------------------------------------------------ // // debug着色器输出一个四方形以显示深度纹理中的数据glDisable(GL_DEPTH_TEST);   // 需要取消深度测试以保证其覆盖在原画面上glUseProgram(debugProgram);glViewport(0, 0, windowWidth / 3, windowHeight / 3);// 传深度纹理glActiveTexture(GL_TEXTURE1);GLuint test = models.back().textureMap.begin()->second;glBindTexture(GL_TEXTURE_2D, shadowTexture);glUniform1i(glGetUniformLocation(debugProgram, "shadowtex"), 1);// 绘制screen.draw(debugProgram);glEnable(GL_DEPTH_TEST);// ------------------------------------------------------------------------ // glutSwapBuffers();                  // 交换缓冲区
}// -------------------------------- main -------------------------------- //int main(int argc, char** argv)
{glutInit(&argc, argv);              // glut初始化glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH);glutInitWindowSize(windowWidth, windowHeight);// 窗口大小glutCreateWindow("9 - skybox"); // 创建OpenGL上下文#ifdef __APPLE__
#elseglewInit();
#endifinit();// 绑定鼠标移动函数 -- //glutMotionFunc(mouse);  // 左键按下并且移动glutPassiveMotionFunc(mouse);   // 鼠标直接移动//glutMouseWheelFunc(mouseWheel); // 滚轮缩放// 绑定键盘函数glutKeyboardFunc(keyboardDown);glutSpecialFunc(keyboardDownSpecial);glutKeyboardUpFunc(keyboardUp);glutSpecialUpFunc(keyboardUpSpecial);glutDisplayFunc(display);           // 设置显示回调函数 -- 每帧执行glutMainLoop();                     // 进入主循环return 0;
}

OpenGL学习(十)天空盒相关推荐

  1. OpenGL学习十九:纹理过滤

    当物体放大缩小时导致投影在上面的纹理也随着变化,OpenGL为了 优化其细节使其效果更好,因此可以采用纹理过滤 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MA ...

  2. OpenGL入门学习(十五)

    OpenGL入门学习[十五] 这次讲的所有内容都装在一个立方体中,呵呵. 呵呵,绘制一个立方体,简单呀,我们学了第一课第二课,早就会了. 先别着急,立方体是很简单,但是这里只是拿立方体做一个例子,来说 ...

  3. Android OpenGL ES 学习(十) – GLSurfaceView 源码解析GL线程以及自定义 EGL

    OpenGL 学习教程 Android OpenGL ES 学习(一) – 基本概念 Android OpenGL ES 学习(二) – 图形渲染管线和GLSL Android OpenGL ES 学 ...

  4. Android OpenGL ES 学习(十二) - MediaCodec + OpenGL 解析H264视频+滤镜

    OpenGL 学习教程 Android OpenGL ES 学习(一) – 基本概念 Android OpenGL ES 学习(二) – 图形渲染管线和GLSL Android OpenGL ES 学 ...

  5. 【我的OpenGL学习进阶之旅】【持续更新】关于学习OpenGL的一些资料

    目录 一.相关书籍 OpenGL 方面 C方面 NDK 线性代数 二.相关博客 2.0 一些比较官方的链接 2.1 OpenGL着色器语言相关 2.2 [[yfan]](https://segment ...

  6. NeHe OpenGL第十九课:粒子系统

    NeHe OpenGL第十九课:粒子系统 粒子系统: 你是否希望创建爆炸,喷泉,流星之类的效果.这一课将告诉你如何创建一个简单的例子系统,并用它来创建一种喷射的效果. 欢迎来到第十九课.你已经学习了很 ...

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

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

  8. OpenGL(十四)——Qt OpenGL纹理

    OpenGL(十四)--Qt OpenGL纹理 一.纹理 终于写到纹理的部分了: 纹理(Texture)的本质是一个2D图片(1D和3D),或者叫图形数据.只是在OpenGL中专业术语中称其为纹理. ...

  9. 【OpenGL学习笔记⑧】——键盘控制正方体+光源【冯氏光照模型 光照原理 环境光照+漫反射光照+镜面光照】

    ✅ 重点参考了 LearnOpenGL CN 的内容,但大部分知识内容,小编已作改写,以方便读者理解. 文章目录 零. 成果预览图 一. 光照原理与投光物的配置 1.1 光照原理 1.2 投光物 二. ...

最新文章

  1. linux 进入单用户模式修改root密码
  2. 甩锅!偷懒!PUA!转嫁压力!铲除异己!压榨下属!这就是职场leader真面目!...
  3. 如何提高 Xcode 的编译速度
  4. 每日一皮:据说现在小孩从小容易生病、体质不如从前是因为少了这个运动......
  5. 如何在mac上面看充电器的瓦数!
  6. linux 好用的命令积累
  7. Oracle统计信息的导出、导入
  8. 数据库:SQLServer分页查询整理
  9. rufus中gpt和mrb磁盘_计算机关于磁盘的大杂烩
  10. 如何用深度学习 AI 美颜实现天天 P 图疯狂变脸算法? | 技术头条
  11. 拓端tecdat|Python用PyMC3实现贝叶斯线性回归模型
  12. C/C++[codeup 2025]比较字符串
  13. linux 内核 解压出错,imx258 内核解压失败(已解决)
  14. 美化滚动条jquery.nicescroll.js
  15. 11.3 帧中继基本配置
  16. Linux之无人值守安装系统
  17. Zemax基础知识7--衍射知识(一)
  18. 《思维导图与识字教学》理论在教学实践中的应用
  19. 域名被墙的解决方法是什么?
  20. 【TOUGH2】系列建模方法及在CO2地质封存、水文地球化学、地热、地下水污染等领域中的实践技术

热门文章

  1. 博通Broadcom SDK源码学习与开发9——Interface接口管理
  2. 机器视觉技术的发展动态
  3. 大闹天竺里的机器人_数字看清王宝强《大闹天竺》里的植入
  4. 蓝懿ios技术交流和心得分享 16.1.30
  5. (十六)【模电】(放大电路中的反馈)反馈的概念及判断
  6. CHIL-SQL-DEFAULT 约束
  7. 利用 OpenGL ES 给视频播放器和相机做个字符画滤镜
  8. 导入/导出dBase
  9. python中的鸭子模型
  10. ChatGPT 以及相关开源项目体验