OpenGL加载模型之 模型

运行效果

在这里插入图片描述

其他内容

知识1
知识2
知识3

源代码

 顶点着色器:

#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

in vec2 TexCoords;

out vec4 color;

uniform sampler2D texture_diffuse1;

void main()
{    
    color = vec4(texture(texture_diffuse1, TexCoords));
}

 网格:

#pragma once
// Std. Includes
#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 {
    // Position
    glm::vec3 Position;
    // Normal
    glm::vec3 Normal;
    // TexCoords
    glm::vec2 TexCoords;
};

// 纹理结构体(包含纹理id、纹理贴图类型、)
struct Texture {
    GLuint id;
    string type;
    aiString path;
};

// 网格类
class Mesh {
public:
    /*  网格存储的数据:一系列 顶点、渲染索引顶点、纹理  */
    vector<Vertex> vertices;
    vector<GLuint> indices;
    vector<Texture> textures;

    /*  网格的构造函数,传入网格所有数据 并调用网格设置函数  */
    Mesh(vector<Vertex> vertices, vector<GLuint> indices, vector<Texture> textures)
    {
        this->vertices = vertices;
        this->indices = indices;
        this->textures = textures;

        // 设置网格的缓冲区
        this->setupMesh();
    }

    // 依据传入的着色器程序渲染网格
    void Draw(Shader shader)
    {
        // 记录对应纹理类型的数目(因为在着色器中命名的采样器也是按照类型分类的)
        GLuint diffuseNr = 1;
        GLuint specularNr = 1;
        // 激活纹理单元、设置着色器中采样器的位置值、绑定纹理到对应纹理类型上(作用:将纹理赋值给着色器的采样器)
        for (GLuint i = 0; i < this->textures.size(); i++)
        {
            // 激活对应着色器上的对应纹理单元
            glActiveTexture(GL_TEXTURE0 + i); 
            
            // 使用stringstram方便从GL_Luint和string之间的转换
            stringstream ss;
            string number;
            // 获得纹理的类型
            string name = this->textures[i].type;
            // 根据纹理类型得到并记录 对应纹理类型的数目
            if (name == "texture_diffuse")
                ss << diffuseNr++;  // 属于漫反射贴图
            else if (name == "texture_specular")
                ss << specularNr++; // 属于高光贴图
            number = ss.str();

            // 设置着色器中采样器的位置值为i,其中(name+number).c_str() 返回 "texture_diffuseN"或"texture_specular"
            glUniform1i(glGetUniformLocation(shader.Program, (name + number).c_str()), i);
            // 绑定当前迭代的纹理 到对应纹理类型上,将纹理赋值给着色器中的采样器
            glBindTexture(GL_TEXTURE_2D, this->textures[i].id);
        }

        // 设置着色器中材质的高光发光值
        glUniform1f(glGetUniformLocation(shader.Program, "material.shininess"), 16.0f);

        // 绑定网格的VAO
        glBindVertexArray(this->VAO);
        // 根据网格的索引数据进行网格的渲染
        glDrawElements(GL_TRIANGLES, this->indices.size(), GL_UNSIGNED_INT, 0);
        // 解绑VAO
        glBindVertexArray(0);

        // 一旦渲染完(使用完纹理单元,就要将激活的纹理单元恢复原状,即解绑纹理)
        for (GLuint i = 0; i < this->textures.size(); i++)
        {
            glActiveTexture(GL_TEXTURE0 + i);
            glBindTexture(GL_TEXTURE_2D, 0);
        }
    }

private:
    /*  缓冲数据:VAO VBO EBO  */
    GLuint VAO, VBO, EBO;

    /*  设置网格的缓冲数据,即设置VAO VBO EBO,并且将顶点和索引数据传入显存   */
    void setupMesh()
    {
        // 申请缓冲区对象的引用
        glGenVertexArrays(1, &this->VAO);
        glGenBuffers(1, &this->VBO);
        glGenBuffers(1, &this->EBO);

        // 将对象绑定到对应缓冲区类型上
        glBindVertexArray(this->VAO);
        glBindBuffer(GL_ARRAY_BUFFER, this->VBO);
        
        // 将网格的顶点数据传入VBO中
        glBufferData(GL_ARRAY_BUFFER, this->vertices.size() * sizeof(Vertex), &this->vertices[0], GL_STATIC_DRAW);

        // 绑定EBO并且将网格的索引顶点数据传入EBO中
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->EBO);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->indices.size() * sizeof(GLuint), &this->indices[0], GL_STATIC_DRAW);

        // 设置顶点数据的解析方式(每次读取一个Vertex结构体即三个GL_FLOAT值)
        // 解析顶点位置数据
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)0);
        // 解析顶点法向量数据
        glEnableVertexAttribArray(1);
        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)offsetof(Vertex, Normal));
        // 解析顶点纹理坐标数据
        glEnableVertexAttribArray(2);
        glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)offsetof(Vertex, TexCoords));

        // 设置完网格解绑VAO
        glBindVertexArray(0);
    }
};



 模型:

#pragma once
// Std. Includes
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <map>
#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>
#include <SOIL.h>
#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/postprocess.h>

#include "Mesh.h"
#include "Shader.h"

// 定义所有加载过的纹理
GLint TextureFromFile(const char* path, string directory);

class Model
{
public:
    /*  构造函数(参数:模型路径)   */
    Model(const char* path)
    {
        this->loadModel(path);
    }

    // 使用着色器渲染模型(本质是对模型中各个网格分别调用渲染)
    void Draw(Shader shader)
    {
        for (GLuint i = 0; i < this->meshes.size(); i++)
            this->meshes[i].Draw(shader);
    }

private:
    /*  模型数据:网格数组、模型所在的目录地址、已加载纹理数据 */
    vector<Mesh> meshes;
    string directory;
    vector<Texture> textures_loaded;

    /*  加载模型(参数:模型路径)   */
    void loadModel(string path)
    {
        // 使用Assimp读入模型文件,返回scene对象
        Assimp::Importer importer;
        const aiScene* scene = importer.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs);// 开启图元类型全为三角形、纹理翻转
        // 检查文件是否成功读入并返回正确的scene对象
        if (!scene || scene->mFlags == AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) // if is Not Zero
        {
            cout << "ERROR::ASSIMP:: " << importer.GetErrorString() << endl;
            return;
        }
        // 计算模型的目录地址(从模型地址的第0个字符到最后一个"/"字符 之前)
        this->directory = path.substr(0, path.find_last_of('\\'));

        // 将根节点及其所有子节点的网格加入模型的网格数组
        this->processNode(scene->mRootNode, scene);
    }

    // 传入根节点node,将node根节点及其所有子节点的网格加入模型网格数组中
    void processNode(aiNode* node, const aiScene* scene)
    {
        // mNumMeshes代表根节点的网格数组的长度,迭代网格数组
        for (GLuint i = 0; i < node->mNumMeshes; i++)
        {
            // 根节点网格数组中第i个网格在scene网格数组中的索引为:node->mMeshes[i]
            // 从scene网格数组scene->mMeshes中获得迭代的i个网格的实际数据
            aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];
            // 将迭代网格的实际数据读入到模型中
            this->meshes.push_back(this->processMesh(mesh, scene));
        }
        // 遍历根节点的每一个子节点,读取其 和其子节点的网格数据
        for (GLuint i = 0; i < node->mNumChildren; i++)
        {
            this->processNode(node->mChildren[i], scene);
        }

    }

    // 读取Assimp类型的网格数据,返回Mesh类型的网格数据
    Mesh processMesh(aiMesh* mesh, const aiScene* scene)
    {
        // 定义返回网格数据的属性
        vector<Vertex> vertices;
        vector<GLuint> indices;
        vector<Texture> textures;

        // 读取网格数据中的顶点位置(mNumVertices表示mesh网格中的顶点数目)
        for (GLuint i = 0; i < mesh->mNumVertices; i++)
        {
            // 定义一个顶点类型变量
            Vertex vertex;
            // 定义一个中间值
            glm::vec3 vector; 

            // mVertices存储网格的顶点位置,将网格顶点位置存储到顶点类型变量中
            // glm::vec3和Assimp中向量不是很兼容,所以不能直接赋值,需要创建glm::vec3向量分别对xyz赋值
            vector.x = mesh->mVertices[i].x;
            vector.y = mesh->mVertices[i].y;
            vector.z = mesh->mVertices[i].z;
            // 赋值完glm::vec3向量然后再赋值给顶点的位置
            vertex.Position = vector;

            // 同样使用中间值记录网格顶点的法向量
            vector.x = mesh->mNormals[i].x;
            vector.y = mesh->mNormals[i].y;
            vector.z = mesh->mNormals[i].z;
            // 赋值给顶点的法向量
            vertex.Normal = vector;

            // 检查网格是否具有纹理
            if (mesh->mTextureCoords[0])
            {
                // 定义中间值记录纹理坐标
                glm::vec2 vec;
                // 一个纹理可能具有多个纹理坐标,在此我们仅读取第0个纹理 即只读取一个纹理
                vec.x = mesh->mTextureCoords[0][i].x;
                vec.y = mesh->mTextureCoords[0][i].y;
                // 将中间值赋值给顶点的纹理坐标
                vertex.TexCoords = vec;
            }
            else
                // 如果纹理不存在就赋值为0.0(需要申请内存并且初始化)
                vertex.TexCoords = glm::vec2(0.0f, 0.0f);
            // 将顶点数据插入顶点数组
            vertices.push_back(vertex);
        }

        // 读取网格的索引
        for (GLuint i = 0; i < mesh->mNumFaces; i++)
        {
            // 获取迭代的面
            aiFace face = mesh->mFaces[i];
            // 先获取一个个需要渲染的面即图元,然后记录每一个图元的顶点索引
            for (GLuint j = 0; j < face.mNumIndices; j++)
                indices.push_back(face.mIndices[j]);
        }

        // 获取网格的纹理(如果纹理存在)
        if (mesh->mMaterialIndex >= 0)
        {
            aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex];

            // 1. 获得网格的漫反射贴图纹理(记录于定义的纹理数组中)
            vector<Texture> diffuseMaps = this->loadMaterialTextures(material, aiTextureType_DIFFUSE, "texture_diffuse");
            textures.insert(textures.end(), diffuseMaps.begin(), diffuseMaps.end());
            // 2. 获得网格的镜面反射贴图纹理(记录于定义的纹理数组中)
            vector<Texture> specularMaps = this->loadMaterialTextures(material, aiTextureType_SPECULAR, "texture_specular");
            textures.insert(textures.end(), specularMaps.begin(), specularMaps.end());
        }

        // 返回Mesh类型的网格,使用读取的顶点数据、索引数据、纹理数据构造
        return Mesh(vertices, indices, textures);
    }

    // 加载纹理
vector<Texture> loadMaterialTextures(aiMaterial* mat, aiTextureType type, string typeName)
    {
        vector<Texture> textures;
        for(GLuint i = 0; i < mat->GetTextureCount(type); i++)
        {
            aiString str;
            mat->GetTexture(type, i, &str);
            // Check if texture was loaded before and if so, continue to next iteration: skip loading a new texture
            GLboolean skip = false;
            for(GLuint j = 0; j < textures_loaded.size(); j++)
            {
                if(std::strcmp(textures_loaded[j].path.C_Str(), str.C_Str()) == 0)
                {
                    textures.push_back(textures_loaded[j]);
                    skip = true; // A texture with the same filepath has already been loaded, continue to next one. (optimization)
                    break;
                }
            }
            if(!skip)
            {   // If texture hasn't been loaded already, load it
                Texture texture;
                texture.id = TextureFromFile(str.C_Str(), this->directory);
                texture.type = typeName;
                texture.path = str;
                textures.push_back(texture);
                this->textures_loaded.push_back(texture);  // Store it as texture loaded for entire model, to ensure we won't unnecesery load duplicate textures.
            }
        }
        return textures;
    }
};


// 读取纹理文件
GLint TextureFromFile(const char* path, string directory)
{
    //Generate texture ID and load texture data 
    string filename = string(path);
    filename = directory + '\\' + filename;
    GLuint textureID;
    glGenTextures(1, &textureID);
    int width, height;
    unsigned char* image = SOIL_load_image(filename.c_str(), &width, &height, 0, SOIL_LOAD_RGB);
    // Assign texture to ID
    glBindTexture(GL_TEXTURE_2D, textureID);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
    glGenerateMipmap(GL_TEXTURE_2D);

    // Parameters
    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);
    glBindTexture(GL_TEXTURE_2D, 0);
    SOIL_free_image_data(image);
    return textureID;
}

 主程序:

// Std. Includes
#include <string>

// GLEW
#define GLEW_STATIC
#include <GL/glew.h>

// GLFW
#include <GLFW/glfw3.h>

// GL includes
#include "Shader.h"
#include "Camera.h"
#include "Model.h"

// GLM Mathemtics
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

// Other Libs
#include <SOIL.h>

// Properties
GLuint screenWidth = 800, screenHeight = 600;

// Function prototypes
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void Do_Movement();

// Camera
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
bool keys[1024];
GLfloat lastX = 400, lastY = 300;
bool firstMouse = true;

GLfloat deltaTime = 0.0f;
GLfloat lastFrame = 0.0f;

// The MAIN function, from here we start our application and run our Game loop
int main()
{
    // Init GLFW
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

    GLFWwindow* window = glfwCreateWindow(screenWidth, screenHeight, "LearnOpenGL", nullptr, nullptr); // Windowed
    glfwMakeContextCurrent(window);

    // Set the required callback functions
    glfwSetKeyCallback(window, key_callback);
    glfwSetCursorPosCallback(window, mouse_callback);
    glfwSetScrollCallback(window, scroll_callback);

    // Options
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

    // Initialize GLEW to setup the OpenGL Function pointers
    glewExperimental = GL_TRUE;
    glewInit();

    // Define the viewport dimensions
    glViewport(0, 0, screenWidth, screenHeight);

    // Setup some OpenGL options
    glEnable(GL_DEPTH_TEST);

    // 构造着色器程序
    Shader shader("C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\vertexShader.txt", "C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\fragmentShader.txt");

    // 构造模型
    Model ourModel("D:\\Download\\nanosuit\\nanosuit.obj");

    // Draw in wireframe
    //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

    // Game loop
    while (!glfwWindowShouldClose(window))
    {
        // Set frame time
        GLfloat currentFrame = glfwGetTime();
        deltaTime = currentFrame - lastFrame;
        lastFrame = currentFrame;

        // Check and call events
        glfwPollEvents();
        Do_Movement();

        // Clear the colorbuffer
        glClearColor(0.05f, 0.05f, 0.05f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // 使用着色器程序
        shader.Use(); 
        
        // 计算各种变换矩阵
        glm::mat4 projection = glm::perspective(camera.Zoom, (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));

        // 传入着色器变换矩阵
        glm::mat4 model = glm::mat4(1.0f);
        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 scene
        model = glm::scale(model, glm::vec3(0.2f, 0.2f, 0.2f));	// It's a bit too big for our scene, so scale it down
        glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
        
        // 使用着色器渲染模型
        ourModel.Draw(shader);

        // Swap the buffers
        glfwSwapBuffers(window);
    }

    glfwTerminate();
    return 0;
}

#pragma region "User input"

// Moves/alters the camera positions based on user input
void Do_Movement()
{
    // Camera controls
    if (keys[GLFW_KEY_W])
        camera.ProcessKeyboard(FORWARD, deltaTime);
    if (keys[GLFW_KEY_S])
        camera.ProcessKeyboard(BACKWARD, deltaTime);
    if (keys[GLFW_KEY_A])
        camera.ProcessKeyboard(LEFT, deltaTime);
    if (keys[GLFW_KEY_D])
        camera.ProcessKeyboard(RIGHT, deltaTime);
}

// Is called whenever a key is pressed/released via GLFW
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
    if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
        glfwSetWindowShouldClose(window, GL_TRUE);

    if (action == GLFW_PRESS)
        keys[key] = true;
    else if (action == GLFW_RELEASE)
        keys[key] = false;
}

void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
    if (firstMouse)
    {
        lastX = xpos;
        lastY = ypos;
        firstMouse = false;
    }

    GLfloat xoffset = xpos - lastX;
    GLfloat yoffset = lastY - ypos;

    lastX = xpos;
    lastY = ypos;

    camera.ProcessMouseMovement(xoffset, yoffset);
}

void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
    camera.ProcessMouseScroll(yoffset);
}

#pragma endregion
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

仰望—星空

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值