模型
- Assimp
- 流程
- 网格
- 模型
- 效果
Assimp
3D建模工具,可以让艺术家创建复杂的形状,Assimp库用于加载,如加载obj格式的文件到我们的程序之中,下载CMAKE用于构建该库(会有很多问题),不过!我已经为大家整理好了,大家加入到自己的ide中,设置好链接,头文件加入就好,文件我放在这里,直接用。
我的VX:18268044262 或者 私信我CSDN 我发你
流程
- 所有的场景/模型数据都包含在Scene对象
- Scene下的mMeshes数组储存了真正的Mesh对象,节点中的mMeshes数组保存的只是场景中网格数组的索引
- 一个Mesh对象本身包含了渲染所需要的所有相关数据,像是顶点位置、法向量、纹理坐标、面(Face)和物体的材质
- Face代表的是物体的渲染图元(Primitive)(三角形、方形、点)
- 最后,一个网格也包含了一个Material对象,它包含了一些函数能让我们获取物体的材质属性,比如说颜色和纹理贴图(比如漫反射和镜面光贴图
网格
理解模型构建的基础单位 是网格,就可以了,相当于积木块一样,组成一个大玩具,我们先需要定义一个OpenGL的网格类,来接收通过Assimp解析后的数据(网格(Mesh)代表的是单个的可绘制实体)
话不多说,直接上代码,看看网格类
Mesh.h
// _MESH_H
//定义网格所需要的基本属性 顶点数据 纹理数据
//顶点数据结构体
struct Vertex {
// 顶点数据
glm::vec3 Position;
// 法线数据
glm::vec3 Normal;
// 坐标
glm::vec2 TexCoords;
// 切线
glm::vec3 Tangent;
// 副切线
glm::vec3 Bitangent;
};
//纹理属性结构体
struct Texture {
unsigned int id; //ID
std::string type; //类型/名字
std::string path; //路径
};
//网格类
class Mesh {
public:
/* 网格数据 */
std::vector<Vertex> vertices;
std::vector<unsigned int> indices;
std::vector<Texture> textures;
unsigned int VAO;
/* 函数 */
// 构造函数 初始化 拷贝构造函数
Mesh(std::vector<Vertex> vertices, std::vector<unsigned int> indices, std::vector<Texture> textures)
{
this->vertices = vertices;
this->indices = indices;
this->textures = textures;
// 设置网格
setupMesh();
}
// 绘制网格
void Draw(Shader shader)
{
// 绑定贴图ID
unsigned int diffuseNr = 1;
unsigned int specularNr = 1;
unsigned int normalNr = 1;
unsigned int heightNr = 1;
//遍历结贴图构体中贴图的数量
for (unsigned int i = 0; i < textures.size(); i++)
{
glActiveTexture(GL_TEXTURE0 + i); //在绑定纹理之前先激活纹理单元
std::string number; //各种类型贴图的数量 与Shader里面定义贴图名字匹配
std::string name = textures[i].type; //判断贴图结构体数据贴图的名字 是否为 diffuse Speular.. 如果是相应的加上1 为了匹配Shader里面定义贴图名字
if (name == "texture_diffuse")
number = std::to_string(diffuseNr++);
else if (name == "texture_specular")
number = std::to_string(specularNr++);
else if (name == "texture_normal")
number = std::to_string(normalNr++);
else if (name == "texture_height")
number = std::to_string(heightNr++);
// 赋值材质里的贴图uniform
//为遍历出的每个贴图分配纹理单元
glUniform1i(glGetUniformLocation(shader.ID, (name + number).c_str()), i);
// 绑定贴图采样
glBindTexture(GL_TEXTURE_2D, textures[i].id);
}
// 绘制网格
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
// always good practice to set everything back to defaults once configured.
glActiveTexture(GL_TEXTURE0);
}
private:
/* 渲染数据 */
unsigned int VBO, EBO;
/* 函数 */
// 初始化各种缓冲
void setupMesh()
{
// 创建 VBO 顶点缓冲对象 VAO顶点数组对象 EBO索引缓冲对象
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
//绑定VAO,VBO与EBO对象
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
// 复制顶点数据到缓冲内存中
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), &vertices[0], GL_STATIC_DRAW);
// 复制顶点索引到缓冲内存中
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), &indices[0], GL_STATIC_DRAW);
//链接顶点属性,设置顶点属性指针
//顶点位置 0 vec3
//属性位置值为0的顶点属性
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)0);
//顶点法线坐标 1 vec3
//属性位置值为1的顶点属性
//预处理指令offsetof(s, m),它的第一个参数是一个结构体,第二个参数是这个结构体中变量的名字。这个宏会返回那个变量距结构体头部的字节偏移量(Byte Offset)
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Normal));
//顶点UV坐标 2 vec2
//属性位置值为2的顶点属性
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, TexCoords));
//顶点切线坐标 3 vec3
//属性位置值为3的顶点属性
glEnableVertexAttribArray(3);
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Tangent));
//顶点副切线坐标 4 vec3
//属性位置值为4的顶点属性
glEnableVertexAttribArray(4);
glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Bitangent));
glBindVertexArray(0);
}
};
模型
创建另一个类来完整地表示一个模型,或者说是包含多个网格,甚至是多个物体的模型
不多解释,直接上代码
model.h
#ifndef MODEL_H
#define MODEL_H
#include <glad/glad.h>
#include <glad/glad.h>
#include <glm-0.9.9.8/glm-0.9.9.8/glm/glm.hpp>
#include <glm-0.9.9.8/glm-0.9.9.8/glm/gtc/matrix_transform.hpp>
#include <glm-0.9.9.8/glm-0.9.9.8/glm/gtc/type_ptr.hpp>
//#define STB_IMAGE_IMPLEMENTATION //通过定义STB_IMAGE_IMPLEMENTATION,预处理器会修改头文件,让其只包含相关的函数定义源码,等于是将这个头文件变为一个 .cpp 文件了
//#include "stb_image.h" //图片处理
#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/postprocess.h>
#include "Mesh.h" //基础的网格
#include "shader.h" //基础的渲染着色器
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <map>
#include <vector>
using namespace std;
unsigned int TextureFromFile(const char *path, const string &directory, bool gamma = false);
class Model
{
public:
/* 模型数据 */
vector<Texture> textures_loaded; //贴图对象数组 将所有加载过的纹理储存在一个vector中
vector<Mesh> meshes; //网格对象数组
string directory; //目录
bool gammaCorrection;
/* 函数 */
// 构造器 参数模型的路径
Model(string const &path, bool gamma = false) : gammaCorrection(gamma)
{
//导入模型的路径
loadModel(path);
}
// 绘制模型里的多个网格
void Draw(Shader shader)
{
//std::cout << meshes.size() << std::endl;
for (unsigned int i = 0; i < meshes.size(); i++)
meshes[i].Draw(shader);
}
private:
/* 函数 */
// 从构造器中直接调用loadModel函数加载模型路径,用Assimp来加载模型至Assimp的一个叫做scene的数据结构中
//这是Assimp数据接口的根对象。一旦我们有了这个场景对象,我们就能访问到加载后的模型中所有所需的数据了。
void loadModel(string const &path)
{
// 读取模型路径 读取函数在Assimp命名空间中Importer
Assimp::Importer importer;
//第一个参数一个文件路径
//第二个参数是一些后期处理(Post-processing)的选项
//除了加载文件之外,Assimp允许我们设定一些选项来强制它对导入的数据做一些额外的计算或操作。
//通过设定aiProcess_Triangulate,告诉Assimp,如果模型不是(全部)由三角形组成,它需要将模型所有的图元形状变换为三角形。
//aiProcess_FlipUVs将在处理的时候翻转y轴的纹理坐标(在OpenGL中大部分的图像的y轴都是反的,所以这个后期处理选项将会修复这个)
//aiProcess_GenNormals:如果模型不包含法向量的话,就为每个顶点创建法线。
//aiProcess_SplitLargeMeshes:将比较大的网格分割成更小的子网格,如果你的渲染有最大顶点数限制,只能渲染较小的网格,那么它会非常有用。
//aiProcess_OptimizeMeshes:和上个选项相反,它会将多个小网格拼接为一个大的网格,减少绘制调用从而进行优化。
const aiScene* scene = importer.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs | aiProcess_CalcTangentSpace);
// 检查场景和其根节点不为null,并且检查了它的一个标记(Flag),来查看返回的数据是不是不完整的
if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode)
{
//通过导入器的GetErrorString函数来报告错误并返回
cout << "ERROR::ASSIMP:: " << importer.GetErrorString() << endl;
return;
}
// 获取文件路径的目录路径
directory = path.substr(0, path.find_last_of('/'));
// 将第一个节点(根节点)传入了递归的processNode函数
//因为每个节点(可能)包含有多个子节点,首先处理参数中的节点,再继续处理该节点所有的子节点,以此类推。
processNode(scene->mRootNode, scene);
}
// 递归函数
//处理自身所有节点下所有网格
//在Assimp的结构中,每个节点包含了一系列的网格索引,每个索引指向场景对象中的那个特定网格。
//我们接下来就想去获取这些网格索引,获取每个网格,处理每个网格,接着对每个节点的子节点重复这一过程
//当一个节点不再有任何子节点之后,这个函数将会停止执行。
void processNode(aiNode *node, const aiScene *scene)
{
// 处理节点所有的网格(如果有的话)
for (unsigned int i = 0; i < node->mNumMeshes; i++)
{
//检查每个节点的网格索引,并索引场景的mMeshes数组来获取对应的网格。
//返回的网格将会传递到processMesh函数中,它会返回一个Mesh对象,我们可以将它存储在meshes列表 / vector
aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];
//在插入meshes前要转化自己封装的类
meshes.push_back(processMesh(mesh, scene));
}
// 接下来对它的子节点重复这一过程
for (unsigned int i = 0; i < node->mNumChildren; i++)
{
processNode(node->mChildren[i], scene);
}
}
//将Assimp的数据解析到Mesh类
Mesh processMesh(aiMesh *mesh, const aiScene *scene)
{
// 声明要填充的数据
vector<Vertex> vertices;
vector<unsigned int> indices;
vector<Texture> textures;
// 获取所有的顶点数据
//遍历网格中的所有顶点(使用mesh->mNumVertices来获取)
for (unsigned int i = 0; i < mesh->mNumVertices; i++)
{
Vertex vertex;
//储存顶点数据
glm::vec3 vector;
// 位置
vector.x = mesh->mVertices[i].x;
vector.y = mesh->mVertices[i].y;
vector.z = mesh->mVertices[i].z;
vertex.Position = vector;
// 法线
vector.x = mesh->mNormals[i].x;
vector.y = mesh->mNormals[i].y;
vector.z = mesh->mNormals[i].z;
vertex.Normal = vector;
// UV
//Assimp允许一个模型在一个顶点上有最多8个不同的纹理坐标,这里不会用到那么多,只关心第一组纹理坐标
if (mesh->mTextureCoords[0]) // 不是所有的Mesh 都有UV坐标 进行判断
{
glm::vec2 vec;
vec.x = mesh->mTextureCoords[0][i].x;
vec.y = mesh->mTextureCoords[0][i].y;
vertex.TexCoords = vec;
}
else
vertex.TexCoords = glm::vec2(0.0f, 0.0f);
// 切线
vector.x = mesh->mTangents[i].x;
vector.y = mesh->mTangents[i].y;
vector.z = mesh->mTangents[i].z;
vertex.Tangent = vector;
// 副切线
vector.x = mesh->mBitangents[i].x;
vector.y = mesh->mBitangents[i].y;
vector.z = mesh->mBitangents[i].z;
vertex.Bitangent = vector;
vertices.push_back(vertex);
}
//设置索引
//Assimp的接口定义了每个网格都有一个面(Face)数组,每个面代表了一个图元
//在我们的例子中(由于使用了aiProcess_Triangulate选项)它总是三角形。
//一个面包含了多个索引,它们定义了在每个图元中,我们应该绘制哪个顶点,并以什么顺序绘制。
//所以如果我们遍历了所有的面,并储存了面的索引到indices这个vector中就可以了。
for (unsigned int i = 0; i < mesh->mNumFaces; i++)
{
aiFace face = mesh->mFaces[i];
for (unsigned int j = 0; j < face.mNumIndices; j++)
indices.push_back(face.mIndices[j]);
}
// 处理材质
//一个网格只包含了一个指向材质对象的索引。如果想要获取网格真正的材质,我们还需要索引场景的mMaterials数组
//网格材质索引位于它的mMaterialIndex属性中;
//检测一个网格是否包含有材质
if (mesh->mMaterialIndex >= 0)
{
aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex];
//一个材质对象的内部对每种纹理类型都存储了一个纹理位置数组
//不同的纹理类型都以aiTextureType_为前缀。
//使用loadMaterialTextures的工具函数来从材质中获取纹理。这个函数将会返回一个Texture结构体的vector
//我们将在模型的textures vector的尾部之后存储它。
// 1. diffuse maps
vector<Texture> diffuseMaps = loadMaterialTextures(material, aiTextureType_DIFFUSE, "texture_diffuse");
textures.insert(textures.end(), diffuseMaps.begin(), diffuseMaps.end());
// 2. specular maps
vector<Texture> specularMaps = loadMaterialTextures(material, aiTextureType_SPECULAR, "texture_specular");
textures.insert(textures.end(), specularMaps.begin(), specularMaps.end());
// 3. normal maps
std::vector<Texture> normalMaps = loadMaterialTextures(material, aiTextureType_HEIGHT, "texture_normal");
textures.insert(textures.end(), normalMaps.begin(), normalMaps.end());
// 4. height maps
std::vector<Texture> heightMaps = loadMaterialTextures(material, aiTextureType_AMBIENT, "texture_height");
textures.insert(textures.end(), heightMaps.begin(), heightMaps.end());
}
// 返回Mesh的所需数据
return Mesh(vertices, indices, textures);
}
//loadMaterialTextures函数遍历了给定纹理类型的所有纹理位置,获取了纹理的文件位置,并加载并生成纹理,将信息储存在了一个textures结构体中
vector<Texture> loadMaterialTextures(aiMaterial *mat, aiTextureType type, string typeName)
{
vector<Texture> textures;
//GetTextureCount函数检查储存在材质中纹理的数量
for (unsigned int i = 0; i < mat->GetTextureCount(type); i++)
{
aiString str;
//GetTexture获取每个纹理的文件位置,它会将结果储存在一个aiString中
mat->GetTexture(type, i, &str);
// 检查它有没有被加载过
bool skip = false;
for (unsigned int j = 0; j < textures_loaded.size(); j++)
{
//将纹理的路径与储存在textures_loaded这个vector中的所有纹理进行比较,看看当前纹理的路径是否与其中的一个相同。
//如果是的话,则跳过纹理加载/生成的部分,直接使用定位到的纹理结构体为网格的纹理。
if (std::strcmp(textures_loaded[j].path.data(), str.C_Str()) == 0)
{
textures.push_back(textures_loaded[j]);
skip = true;
break;
}
}
if (!skip)
{ // 如果纹理还没有被加载,则加载它
Texture texture;
//TextureFromFile的工具函数,它将会(用stb_image.h)加载一个纹理并返回该纹理的ID
texture.id = TextureFromFile(str.C_Str(), this->directory);
texture.type = typeName;
texture.path = str.C_Str();
textures.push_back(texture);
// 添加到已加载的纹理中
textures_loaded.push_back(texture); // store it as texture loaded for entire model, to ensure we won't unnecesery load duplicate textures.
}
}
return textures;
}
};
//导入贴图函数
unsigned int TextureFromFile(const char *path, const string &directory, bool gamma)
{
string filename = string(path);
//导入与模型相同路径下的贴图
filename = directory + '/' + filename;
unsigned int textureID;
glGenTextures(1, &textureID);
int width, height, nrComponents;
unsigned char *data = stbi_load(filename.c_str(), &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, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, 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;
}
#endif
main.cpp
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <KHR/khrplatform.h>
#include <iostream>
#include <string>
#include <chrono>
#include <thread>
#include <glm-0.9.9.8/glm-0.9.9.8/glm/glm.hpp>
#include <glm-0.9.9.8/glm-0.9.9.8/glm/gtc/matrix_transform.hpp>
#include <glm-0.9.9.8/glm-0.9.9.8/glm/gtc/type_ptr.hpp>
#define STB_IMAGE_IMPLEMENTATION
#include "shader.h"
#include "stb_image.h"
#include "camera.h"
#include "model.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);
//设置窗口的宽和高
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
//初始化相机的位置
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;
//主函数
int main()
{
//初始化GLFW
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); //主版本号
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); //次版本号
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); //适配IOS苹果
#endif
//创建窗口并设置其大小,名称,与检测是否创建成功
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
//创建完毕之后,需要让当前窗口的环境在当前线程上成为当前环境,就是接下来的画图都会画在我们刚刚创建的窗口上
glfwMakeContextCurrent(window);
//告诉GLFW我们希望每当窗口调整大小的时候调用这个函数
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
//监听鼠标移动事件 回调鼠标响应函数
glfwSetCursorPosCallback(window, mouse_callback);
监听鼠标滚轮的移动
glfwSetScrollCallback(window, scroll_callback);
//隐藏鼠标的光标 光标会一直停留在窗口中
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
//glad寻找opengl的函数地址,调用opengl的函数前需要初始化glad
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
//启用深度测试,默认是关闭的
glEnable(GL_DEPTH_TEST);
//构建和编译Shader 读取着色器路径
Shader ourShader("nanosuitVS.txt", "nanosuitFS.txt");
//导入模型
Model ourModel(("../resources/meshs/tree/12150_Christmas_Tree_V2_L2.mtl"));
//glViewport(0, 0, 4000, 3000);
// 绘制线框
//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
//渲染循环
while (!glfwWindowShouldClose(window))
{
//计算上下两帧的渲染时间差
float currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
//响应键盘输入
processInput(window);
//设置清除颜色
glClearColor(0.05f, 0.05f, 0.05f, 1.0f);
//清除当前窗口,把颜色设置为清除颜色
//清除深度信息
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//激活链接程序,激活着色器,开始渲染
ourShader.use();
// 视口,投影矩阵
glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
glm::mat4 view = camera.GetViewMatrix();
ourShader.setMat4("projection", projection);
ourShader.setMat4("view", view);
// 模型空间--世界空间矩阵
glm::mat4 model = glm::mat4(1.0f);
model = glm::translate(model, glm::vec3(0.0f, -1.75f, 0.0f));
model = glm::scale(model, glm::vec3(0.2f, 0.2f, 0.2f));
ourShader.setMat4("model", model);
ourModel.Draw(ourShader);
//交换颜色缓冲
glfwSwapBuffers(window);
//处理事件
glfwPollEvents();
}
//释放前面所申请的内存
glfwTerminate();
return 0;
}
//响应键盘输入事件
void processInput(GLFWwindow *window)
{
//ESC 退出窗口
//glfwGetKey()用来判断一个键是否按下。第一个参数是GLFW窗口句柄,第二个参数是一个GLFW常量,代表一个键。
//GLFW_KEY_ESCAPE表示Esc键。如果Esc键按下了,glfwGetKey将返回GLFW_PRESS(值为1),否则返回GLFW_RELEASE(值为0)。
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
//WASD移动摄像机
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);
}
// 当用户改变窗口的大小的时候,视口也应该被调整。
//对窗口注册一个回调函数(Callback Function),它会在每次窗口大小被调整的时候被调用
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
//OpenGL渲染窗口的尺寸大小
//glViewport函数前两个参数控制窗口左下角的位置。第三个和第四个参数控制渲染窗口的宽度和高度(像素)
glViewport(0, 0, width, height);
}
//响应鼠标事件 创建鼠标的回调函数
//xpos,ypos鼠标的位置
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; // 这里如果不反过来 实际操作过程中向上移动摄像机 物体也会跟着向上移动 实际应该向下
lastX = xpos;
lastY = ypos;
camera.ProcessMouseMovement(xoffset, yoffset);
}
//响应鼠标滚轮
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
camera.ProcessMouseScroll(yoffset);
}
- 首先将模型路径传入,构造model,利用assimp库加载模型
- 将场景scene下结点,递归遍历,将其中的网格全部转化成自己封装的网格Mesh,插入到vector容器中
- 转化的过程中 包含 顶点数据 位置 法线 切线 UV 设置索引 要处理材质
- 随后加载纹理 获取到顶点数组 索引数组 纹理数组
- 设置网格初始化各种缓冲等,最后给到着色器去绘画