模型

  • 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;    //IDstd::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){// 绑定贴图IDunsigned 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命名空间中ImporterAssimp::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列表 / vectoraiMesh* 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;}elsevertex.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 mapsvector<Texture> diffuseMaps = loadMaterialTextures(material, aiTextureType_DIFFUSE, "texture_diffuse");textures.insert(textures.end(), diffuseMaps.begin(), diffuseMaps.end());// 2. specular mapsvector<Texture> specularMaps = loadMaterialTextures(material, aiTextureType_SPECULAR, "texture_specular");textures.insert(textures.end(), specularMaps.begin(), specularMaps.end());// 3. normal mapsstd::vector<Texture> normalMaps = loadMaterialTextures(material, aiTextureType_HEIGHT, "texture_normal");textures.insert(textures.end(), normalMaps.begin(), normalMaps.end());// 4. height mapsstd::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)加载一个纹理并返回该纹理的IDtexture.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()
{//初始化GLFWglfwInit();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的函数前需要初始化gladif (!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);
}
  1. 首先将模型路径传入,构造model,利用assimp库加载模型
  2. 将场景scene下结点,递归遍历,将其中的网格全部转化成自己封装的网格Mesh,插入到vector容器中
  3. 转化的过程中 包含 顶点数据 位置 法线 切线 UV 设置索引 要处理材质
  4. 随后加载纹理 获取到顶点数组 索引数组 纹理数组
  5. 设置网格初始化各种缓冲等,最后给到着色器去绘画

效果

《OpenGL 模型》 渲染出帅气的暗影战士相关推荐

  1. OpenGL模型加载和渲染

    OpenGL模型加载和渲染 先上图,再解答. 完整主要的源代码 源代码剖析 先上图,再解答. 完整主要的源代码 #include <stdio.h> #include "GL/g ...

  2. OpenGL使用纯VBO方式渲染出三角形,非VAO

    OpenGL使用纯VBO方式渲染出三角形,要设置兼容格式GLFW_OPENGL_COMPAT_PROFILE. // 必须使用VAO渲染     //glfwWindowHint(GLFW_OPENG ...

  3. 使用VMD中的Tachyon渲染出透明逼真的水盒子效果

    1.概述 在本教程中将重点向你展示如何通过VMD中的Tachyon制作一个高质量的可视化图像.本文假设你对vmd有一定了解(最基本的打开载入分子即可).关于vmd制作图像的快速入门中文教程,本例所使用 ...

  4. Android OpenGL+Camera2渲染(3) —— 大眼,贴纸功能实现

    Android OpenGL+Camera2渲染(1) -- OpenGL简单介绍 Android OpenGL+Camera2渲染(2) -- OpenGL实现Camera2图像预览 Android ...

  5. html3d模型渲染,【SVG】纯clip-path打造的3D模型渲染器

    几天之前, 一个species-in-pieces的网站把我震到了(如下图), 出于一个优秀前端的敏锐嗅觉和原始本能, 我立刻祭出了看家法宝--Chrome开发者工具开始偷窥这个网站. 简单推敲之后, ...

  6. Android硬件加速(二)-RenderThread与OpenGL GPU渲染

    Android4.0之后,系统默认开启硬件加速来渲染视图,之前,理解Android硬件加速的小白文简单的讲述了硬件加速的简单模型,不过主要针对前半阶段,并没怎么说是如何使用OpenGL.GPU处理数据 ...

  7. OpenGL 重复渲染

    OpenGL重复渲染 OpenGL重复渲染简介 源代码剖析 主要源代码 OpenGL重复渲染简介 想象一下,你想渲染一个巨大的军队移动的场景.你有一个士兵模型,你想渲染几千名士兵.第一种方法 - 正面 ...

  8. OpenGL HDR渲染

    OpenGL HDR渲染 HDR渲染简介 浮点帧缓冲 色调映射 HDR渲染简介 一般来说,当存储在帧缓冲(Framebuffer)中时,亮度和颜色的值是默认被限制在0.0到1.0之间的.这个看起来无辜 ...

  9. Qt移动应用开发(八):实现跨平台的QML和OpenGL混合渲染

    Qt移动应用开发(八):实现跨平台的QML和OpenGL混合渲染 上一篇文章讲到了利用C++这个桥梁,我们实现了QML和Java的交互.Qt 5大力推崇的QML/JS开发,让轻量.高速开发的QML/J ...

最新文章

  1. mysql中engine=innodb和engine=myisam的区别详解
  2. MFC显示JPG、JIF图片
  3. ASP.NET跨页面传值(二)
  4. 单片机小白学步系列(二)爱上单片机的一万个理由
  5. 硬核干货!大学老师2019必备工作神器汇总(附下载链接)
  6. java分布式(java入门)
  7. JAVA通过调用数据库函数调用存储过程
  8. Long Short-Term Memory Over Tree Structures
  9. Lc165-版本号比较
  10. rabbitmq4-工作队列及公平分发模式
  11. 嵌入式Littlevgl之linux移植
  12. 实现自定义Sql 注入器
  13. verilog编程,可能你一直在错误地使用计数器cnt
  14. 域名解析中TTL是什么意思?
  15. errMsg: “getUserProfile:fail can only be invoked by user TAP gesture.
  16. freeswitch拨打分机号源代码跟踪
  17. 2022-03-03 北京 计算机知识。字符编码,ppt
  18. 【Matlab】彻底清除persistent变量
  19. dns服务器地址显示fec0,DNS服务器地址为fec0
  20. 电子地图如何制作简介

热门文章

  1. 吃鸡决赛圈直播却卡屏的我心好痛,立马找来开发刚了一波 1
  2. 浏览器兼容模式的快捷方法
  3. 数据结构之线性结构与非线性结构
  4. 仿微信查找聊天记录自动搜索关键字
  5. php swoole yaf,swoole+yaf 实现定时执行任务
  6. Spring Boot返回中文变成问号,全局异常中文返回问号
  7. 再来聊聊SEO和SEM的区别,数字营销必备
  8. 代码重构新手教程:如何将烂代码变成好代码?
  9. Transfer Learning from Speaker Verification toMultispeaker Text-To-Speech Synthesis复制他的声音MockingBird
  10. 华为云 云速邮箱教学第一步