导入模型

1、网格


#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <vector>using namespace std;// GL Includes
#include <GL/glew.h> // Contains all the necessery OpenGL includes
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>struct Vertex {// Positionglm::vec3 Position;// Normalglm::vec3 Normal;// TexCoordsglm::vec2 TexCoords;};struct Texture {GLuint id;string type;aiString path;};class Mesh {public:/*  Mesh Data  */vector<Vertex> vertices;vector<GLuint> indices;vector<Texture> textures;/*  Functions  */// ConstructorMesh(vector<Vertex> vertices, vector<GLuint> indices, vector<Texture> textures){this->vertices = vertices;this->indices = indices;this->textures = textures;// Now that we have all the required data, set the vertex buffers and its attribute pointers.this->setupMesh();}// Render the meshvoid Draw(Shader shader){// Bind appropriate texturesGLuint diffuseNr = 1;GLuint specularNr = 1;for (GLuint i = 0; i < this->textures.size(); i++){glActiveTexture(GL_TEXTURE0 + i); // Active proper texture unit before binding// Retrieve texture number (the N in diffuse_textureN)stringstream ss;string number;string name = this->textures[i].type;if (name == "texture_diffuse")ss << diffuseNr++; // Transfer GLuint to streamelse if (name == "texture_specular")ss << specularNr++; // Transfer GLuint to streamnumber = ss.str();// Now set the sampler to the correct texture unitglUniform1i(glGetUniformLocation(shader.Program, (name + number).c_str()), i);// And finally bind the textureglBindTexture(GL_TEXTURE_2D, this->textures[i].id);}// Also set each mesh's shininess property to a default value (if you want you could extend this to another mesh property and possibly change this value)glUniform1f(glGetUniformLocation(shader.Program, "material.shininess"), 16.0f);// Draw meshglBindVertexArray(this->VAO);glDrawElements(GL_TRIANGLES, this->indices.size(), GL_UNSIGNED_INT, 0);glBindVertexArray(0);// Always good practice to set everything back to defaults once configured.for (GLuint i = 0; i < this->textures.size(); i++){glActiveTexture(GL_TEXTURE0 + i);glBindTexture(GL_TEXTURE_2D, 0);}}private:/*  Render data  */GLuint VAO, VBO, EBO;/*  Functions    */// Initializes all the buffer objects/arraysvoid setupMesh(){// Create buffers/arraysglGenVertexArrays(1, &this->VAO);glGenBuffers(1, &this->VBO);glGenBuffers(1, &this->EBO);glBindVertexArray(this->VAO);// Load data into vertex buffersglBindBuffer(GL_ARRAY_BUFFER, this->VBO);// A great thing about structs is that their memory layout is sequential for all its items.// The effect is that we can simply pass a pointer to the struct and it translates perfectly to a glm::vec3/2 array which// again translates to 3/2 floats which translates to a byte array.glBufferData(GL_ARRAY_BUFFER, this->vertices.size() * sizeof(Vertex), &this->vertices[0], GL_STATIC_DRAW);glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->EBO);glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->indices.size() * sizeof(GLuint), &this->indices[0], GL_STATIC_DRAW);// Set the vertex attribute pointers// Vertex PositionsglEnableVertexAttribArray(0);glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)0);// Vertex NormalsglEnableVertexAttribArray(1);glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)offsetof(Vertex, Normal));// Vertex Texture CoordsglEnableVertexAttribArray(2);glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)offsetof(Vertex, TexCoords));glBindVertexArray(0);}};

2、加载模型

网格(Mesh)代表的是单个的可绘制实体,我们现在先来定义一个我们自己的网格类。

我们会使用Assimp来加载模型,并将它转换(Translate)至多个Mesh对象。

我会先把Model类的结构给你:

class Model
{public:/*  函数   */Model(char *path){loadModel(path);}void Draw(Shader shader);   private:/*  模型数据  */vector<Mesh> meshes;string directory;/*  函数   */void loadModel(string path);void processNode(aiNode *node, const aiScene *scene);Mesh processMesh(aiMesh *mesh, const aiScene *scene);vector<Texture> loadMaterialTextures(aiMaterial *mat, aiTextureType type, string typeName);
};

Model类包含了一个Mesh对象的vector(译注:这里指的是C++中的vector模板类,之后遇到均不译),构造器需要我们给它一个文件路径。在构造器中,它会直接通过loadModel来加载文件。

这个模型被输出为一个.obj文件以及一个.mtl文件,.mtl文件包含了模型的漫反射、镜面光和法线贴图(这个会在后面学习到),你可以在这里下载到(稍微修改之后的)模型,

声明一个Model对象,将模型的文件位置传入。接下来模型应该会自动加载并(如果没有错误的话)在渲染循环中使用它的Draw函数来绘制物体,这样就可以了。不再需要缓冲分配、属性指针和渲染指令,只需要一行代码就可以了。接下来如果你创建一系列着色器,其中片段着色器仅仅输出物体的漫反射纹理颜色,最终的结果看上去会是这样的:

Shader ourShader("1.model_loading.vs", "1.model_loading.fs");// load models// -----------Model ourModel(FileSystem::getPath("resources/objects/nanosuit/nanosuit.obj"));

这次我们将会加载Crytek的游戏孤岛危机(Crysis)中的原版纳米装(Nanosuit)。这个模型被输出为一个.obj文件以及一个.mtl文件,.mtl文件包含了模型的漫反射、镜面光和法线贴图(这个会在后面学习到),,你可以在这里下载到(稍微修改之后的)模型,

现在在代码中,声明一个Model对象,将模型的文件位置传入。接下来模型应该会自动加载并(如果没有错误的话)在渲染循环中使用它的Draw函数来绘制物体,这样就可以了。不再需要缓冲分配、属性指针和渲染指令,只需要一行代码就可以了。接下来如果你创建一系列着色器,其中片段着色器仅仅输出物体的漫反射纹理颜色,最终的结果看上去会是这样的:

​
// Setup and compile our shadersShader shader("../res/model/model_loading.vs", "../res/model/model_loading.frag");// Load modelsModel ourModel((GLchar *)"../res/nanosuit/nanosuit.obj");​

3、导入模型

shader.Use();   // <-- Don't forget this one!// Transformation matricesglm::mat4 projection = glm::perspective(camera.GetZoom(), (float)screenWidth / (float)screenHeight, 0.1f, 100.0f);glm::mat4 view = camera.GetViewMatrix();glUniformMatrix4fv(glGetUniformLocation(shader.Program, "projection"), 1, GL_FALSE, glm::value_ptr(projection));glUniformMatrix4fv(glGetUniformLocation(shader.Program, "view"), 1, GL_FALSE, glm::value_ptr(view));// Draw the loaded modelglm::mat4 model;model = glm::translate(model, glm::vec3(0.0f, -1.75f, 0.0f)); // Translate it down a bit so it's at the center of the scenemodel = glm::scale(model, glm::vec3(0.2f, 0.2f, 0.2f)); // It's a bit too big for our scene, so scale it downglUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));ourModel.Draw(shader);

着色器

#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 normal;
layout (location = 2) in vec2 texCoords;out vec2 TexCoords;uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;void main()
{gl_Position = projection * view * model * vec4(position, 1.0f);TexCoords = texCoords;}
#version 330 core
out vec4 color;in vec2 TexCoords;uniform sampler2D texture_diffuse1;void main()
{    color = vec4(texture(texture_diffuse1, TexCoords));
}

立方体贴图

立方体贴图就是一个包含了6个2D纹理的纹理,每个2D纹理都组成了立方体的一个面:一个有纹理的立方体。你可能会奇怪,这样一个立方体有什么用途呢?为什么要把6张纹理合并到一张纹理中,而不是直接使用6个单独的纹理呢?立方体贴图有一个非常有用的特性,它可以通过一个方向向量来进行索引/采样。假设我们有一个1x1x1的单位立方体,方向向量的原点位于它的中心。使用一个橘黄色的方向向量来从立方体贴图上采样一个纹理值会像是这样:

使用立方体的实际位置向量来对立方体贴图进行采样了。接下来,我们可以将所有顶点的纹理坐标当做是立方体的顶点位置。最终得到的结果就是可以访问立方体贴图上正确面(Face)纹理的一个纹理坐标。

1、创建立方体贴图

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

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

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

由于我们有6个面,OpenGL给我们提供了6个特殊的纹理目标,专门对应立方体贴图的一个面。

纹理目标 方位
GL_TEXTURE_CUBE_MAP_POSITIVE_X
GL_TEXTURE_CUBE_MAP_NEGATIVE_X
GL_TEXTURE_CUBE_MAP_POSITIVE_Y
GL_TEXTURE_CUBE_MAP_NEGATIVE_Y
GL_TEXTURE_CUBE_MAP_POSITIVE_Z
GL_TEXTURE_CUBE_MAP_NEGATIVE_Z

和OpenGL的很多枚举(Enum)一样,它们背后的int值是线性递增的,所以如果我们有一个纹理位置的数组或者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_TEXTURE_WRAP_R吓到,它仅仅是为纹理的R坐标设置了环绕方式,它对应的是纹理的第三个维度(和位置的z一样)。我们将环绕方式设置为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);
}

天空盒

1、提取纹理

因为天空盒本身就是一个立方体贴图,加载天空盒和之前加载立方体贴图时并没有什么不同。为了加载天空盒,我们将使用下面的函数,它接受一个包含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;
}

函数本身应该很熟悉了。它基本就是上一部分中立方体贴图的代码,只不过合并到了一个便于管理的函数中。

2、加载纹理

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

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

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

3、天空盒着色器

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

用于贴图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);
}

片段着色器非常直观。我们将顶点属性的位置向量作为纹理的方向向量,并使用它从立方体贴图中采样纹理值。

4、渲染

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

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

环境映射

我们现在将整个环境映射到了一个纹理对象上了,能利用这个信息的不仅仅只有天空盒。通过使用环境的立方体贴图,我们可以给物体反射和折射的属性。这样使用环境立方体贴图的技术叫做环境映射(Environment Mapping),其中最流行的两个是反射(Reflection)和折射(Refraction)。

1、因为我们使用了法线,你还需要更新一下顶点数据,并更新属性指针。

2、还要记得去设置cameraPos这个uniform。

3、着色器

#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 normal;out vec3 Normal;
out vec3 Position;uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;void main()
{gl_Position = projection * view * model * vec4(position, 1.0f);Normal = mat3(transpose(inverse(model))) * normal;Position = vec3(model * vec4(position, 1.0f));
}
#version 330 core  in vec3 Normal;
in vec3 Position;
out vec4 color;uniform vec3 cameraPos;
uniform samplerCube skybox;void main()
{float ratio = 1.00 / 1.52;vec3 I = normalize(Position - cameraPos);vec3 R = refract(I, normalize(Normal), ratio);color = texture(skybox, R);
}

4、我们在渲染箱子之前先绑定立方体贴图纹理:

glActiveTexture(GL_TEXTURE0);glUniform1i(glGetUniformLocation(shadercub.Program, "skybox"), 0);glBindTexture(GL_TEXTURE_CUBE_MAP, textureCubemap);glBindVertexArray(cubeVAO);//glBindTexture(GL_TEXTURE_CUBE_MAP, textureCubemap);glDrawArrays(GL_TRIANGLES, 0, 36);glBindTexture(GL_TEXTURE_2D, 0);glBindVertexArray(0);

参考文献:

https://learnopengl-cn.github.io/04%20Advanced%20OpenGL/06%20Cubemaps/

https://learnopengl-cn.github.io/03%20Model%20Loading/03%20Model/

南邮|计算机图形学——导入模型、添加天空盒相关推荐

  1. OpenGL南邮计算机图形学实验报告四——用键盘控制物体

    OpenGL南邮计算机图形学实验报告四--用键盘控制物体 计算机图形学的新题目要求 OpenGL配置参考: 南邮老前辈wonz哥的OpenGL配置(Shader.h始终不用改).SOIL2 环境配置. ...

  2. OpenGL南邮计算机图形学实验报告二——两个纹理的渐变变换和移动

    OpenGL南邮计算机图形学实验报告二--两个纹理的渐变变换和移动 计算机图形学的新题目要求 OpenGL配置参考: 南邮老前辈wonz哥的OpenGL配置(Shader.h始终不用改).SOIL2 ...

  3. OpenGL南邮计算机图形学实验报告三——实现类似地月系统的两物体环绕移动

    OpenGL南邮计算机图形学实验报告三--实现类似地月系统的两物体环绕移动 计算机图形学的新题目要求 OpenGL配置参考: 南邮老前辈wonz哥的OpenGL配置(Shader.h始终不用改).SO ...

  4. [NJUPT | 2021-2022-1] 南邮计算机图形学课程代码与笔记

    NJUPT-CG-OpenGL 由于疫情,[2021-2022-1] 的计算机图形学课程(课程代号:B0301312C )采取了线上教学模式. 鉴于这门课程的内容有一定难度,我已将课程回放保存并上传 ...

  5. 南邮 | 计算机图形学大作业:Skybox + Shadow volume

    计算机图形学期末大作业:实现 Skybox 天空盒,以及 Shadow volume 阴影体. 写在前面 本人才疏学浅,水平有限,只实现了 Skybox ,Shadow volume 没有完全实现(我 ...

  6. 南邮计算机图形学水不水,南邮计算机图形学实验报告(修正版)….doc

    实 验 报 告 实验名称指导教师实验类型综合实验学时2实验时间一.实验目的和要求 能够灵活的运用OpenGL图形API函数,基于C++程序语言,. 设计增加键盘及鼠标输入的互动,实现三维物体交互式运动 ...

  7. 南邮计算机学院哪个研究生导师项目比较多,南邮自杀研究生曾抱怨“导师不让毕业” 导师被停职...

    1月25日上午9点,南京市新模范马路66号,南京邮电大学综合科研大楼,计算机学院研三的学生蒋华文从9楼一坠而下,结束了自己25岁的生命. 澎湃新闻(www.thepaper.cn)采访获悉,就在自杀前 ...

  8. 南邮计算机实验报告合集【非常全】

    南邮计算机实验报告合集 GitHub上自取,可以借鉴,请勿直接抄袭 南京邮电大学数据结构实验,南京邮电大学离散数学实验,南京邮电大学操作系统实验,南京邮电大学电工电子基础实验B实验,南京邮电大学汇编语 ...

  9. 南邮计算机与科学排名,南邮计算机全国排名

    技校网专门为您推荐的类似问题答案 问题1: 在南邮通达学计算机还好就业啊 好不好看自己啊如果只是满足于学校安排的课程,那毕业了你就完蛋了大一适应一下环境,把英语学好,4级过了从大二开始就应该自己找个方 ...

最新文章

  1. 入门架构——单机高性能
  2. node.js linux shell,bash – Node.js Shell脚本和参数
  3. COGS 930. [河南省队2012] 找第k小的数 主席树
  4. [LeetCode Online Judge]系列-求二维平面内在一条直线上的最大点数
  5. android 遍历所有view,Android 算法:遍历ViewGroup找出所有子View
  6. leetcode - 983. 最低票价
  7. 如何断开所有SQL Server所有的连接
  8. 【OpenCV入门指南】第七篇 线段检测与圆检测
  9. eclipse语言包安装太慢,或者卡住不动的解决方法
  10. 5.FlashFXP连接失败(连接已拒绝)及530 permission denied(以root用户连接已被客户端关闭)
  11. Termux安装SSH,下载linux系统
  12. UPnP 体系架构和基本原理 —— UPnP的描述文件
  13. python语音转文字软件_免费的语音转文字电脑版软件-批量语音转文字小工具(不限时长)下载V1.1完全免费版-西西软件下载...
  14. Typora基本技巧
  15. 遭遇XP-664129A8.EXE
  16. YUV_420_888数据裁剪
  17. 杜佑夸高颎,NB人夸NB人
  18. Python 自动化测试怎么做?
  19. 由legacy+MBR改为UEFI+GPT引导方式
  20. 牛客练习赛51 C 勾股定理

热门文章

  1. 服务器和PC性能差距,服务器主机和PC的差距在哪里?
  2. 2021-06-21解决列表查询很慢的优化SQL定位查询慢原因优化
  3. 新版本es映射报错问题
  4. 是谁在偷窥我们的网络隐私?
  5. null == undefined ?
  6. 00014__strok、strtok_r和strtok_s
  7. 收割 offer 之前需要知道的
  8. 工厂模式——猫粮厂的演进
  9. android截图分辨率,高分辨率屏幕截图的Android(High resolution screen shot in An
  10. 孕妇php是什么意思,关于孕妇