LearnOpenGL-模型

模型

通过assimp加载模型并转换为多个上一节创建的Mesh对象。

首先构建Model类

class Model 
{
    public:
        Model(char *path)
        {
            loadModel(path);
        }
        void Draw(Shader shader);   
    private:
        /*  模型数据  */
        vector<Texture> textures_loaded;
        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);
};

在构造器中加载模型,在私有函数中处理assimp导入模型的这一过程。同时我们还会将已加载的纹理路径存储起来,以便重复使用。

Draw函数通过调用Mesh中的绘制函数实现。

void Draw(Shader shader)
{
    for(unsigned int i = 0; i < meshes.size(); i++)
        meshes[i].Draw(shader);
}

导入3D模型到OpenGL

添加assimp头文件

#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/postprocess.h>

首先需要实现的是loadModel(path)函数,模型通过assimp会加载到一个scene对象中,而有了这个对象,我们就能够拿到模型中的所有数据了。

Assimp很棒的一点在于,它抽象掉了加载不同文件格式的所有技术细节,只需要一行代码就能完成所有的工作:

Assimp::Importer importer;
const aiScene *scene = importer.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs);

这两步就能实现加载,在ReadFile函数中,还可以进行一些限制以及计算等,通过设定aiProcess_Triangulate,我们告诉Assimp,如果模型不是(全部)由三角形组成,它需要将模型所有的图元形状变换为三角形。aiProcess_FlipUVs将在处理的时候翻转y轴的纹理坐标(在OpenGL中大部分的图像的y轴都是反的,所以这个后期处理选项将会修复这个)。

加载模型

void loadModel(string path)
{
    Assimp::Importer import;
    const aiScene *scene = import.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs);

    if(!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) 
    {
        cout << "ERROR::ASSIMP::" << import.GetErrorString() << endl;
        return;
    }
    directory = path.substr(0, path.find_last_of('/'));

    processNode(scene->mRootNode, scene);
}

我们加载了模型之后,我们会检查场景和其根节点不为null,并且检查了它的一个标记(Flag),来查看返回的数据是不是不完整的。如果遇到了任何错误,我们都会通过导入器的GetErrorString函数来报告错误并返回。我们也获取了文件路径的目录路径。

如果没有发生错误,则处理场景中的所有节点。而首先我们需要处理根节点mRootNode,根节点又(可能)包含多个子节点,处理完根节点后再处理其子节点,所以这是一个递归的结构。在处理节点时我们定义一个递归函数,并使用不同的参数(节点)调用自身,当所有节点被处理完后退出。

节点处理:

void processNode(aiNode *node, const aiScene *scene)
{
    // 处理节点所有的网格(如果有的话)
    for(unsigned int i = 0; i < node->mNumMeshes; i++)
    {
        aiMesh *mesh = scene->mMeshes[node->mMeshes[i]]; 
        meshes.push_back(processMesh(mesh, scene));         
    }
    // 接下来对它的子节点重复这一过程
    for(unsigned int i = 0; i < node->mNumChildren; i++)
    {
        processNode(node->mChildren[i], scene);
    }
}

上半部分是对该节点的处理,获取该节点的所有网格,并通过该节点存储的索引,在scene中找到真正的网格数据。然后对返回的网格进行处理,处理完毕后存储到meshes容器中。

下半部分是对该节点的递归,获取该节点的所有子节点,并调用自身函数递归,当不再有任何子节点时,函数执行完毕。

网格处理:

Mesh processMesh(aiMesh* mesh,const aiScene* scene)
    {
        std::vector<Vertex> vertices;
        std::vector<unsigned int> indices;
        std::vector<Texture> textures;

        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;

            if (mesh->HasNormals())
            {
                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;
                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);

            vertices.push_back(vertex);

        };
        //处理索引
        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]);
            }
        }
        //处理材质
        if (mesh->mMaterialIndex >= 0)
        {
            aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex];
            std::vector<Texture> diffuseMap = loadMaterialTexture(material,aiTextureType_DIFFUSE, "texture_diffuse");
            std::vector<Texture> reflectionMap = loadMaterialTexture(material, aiTextureType_SPECULAR, "texture_reflection");
            //在textures容器末尾处插入diffuseMap容器中的所有元素。
            textures.insert(textures.end(), diffuseMap.begin(), diffuseMap.end());
            textures.insert(textures.end(), reflectionMap.begin(), reflectionMap.end());
        }
        return Mesh(vertices, indices, textures);
    }

在这一处理过程中,我们会将真实的顶点、法线、纹理数据存储到Vertex类型的容器vertices中。通

过遍历图元,来获取索引缓冲,并存储到容器indices中。在材质处理中先按照类型加载纹理,再按照类型在容器texture中先插入漫反射纹理,再尾插镜面反射纹理。最后将这三个容器返回。

加载纹理函数:

std::vector<Texture> loadMaterialTexture(aiMaterial* mat, aiTextureType type, const std::string& typeName)
    {
        std::vector<Texture> textures;

        for (unsigned int i = 0;i < mat->GetTextureCount(type);i++)
        {
            aiString textureFilePosition;
            mat->GetTexture(type, i, &textureFilePosition);
            bool skip = false;
            for (unsigned int j = 0;j < textures_loaded.size();j++)
            {
                if (std::strcmp(textures_loaded[j].path.data(), textureFilePosition.C_Str()) == 0)
                {
                    textures.push_back(textures_loaded[j]);    
                    skip = true;
                    break;
                }
            }
            if (!skip)
            {
                Texture texture;
                texture.id = TextureFormFile(textureFilePosition.C_Str(), this->directory);
                texture.type = typeName;
                texture.path = textureFilePosition.C_Str();
                textures.push_back(texture);
                textures_loaded.push_back(texture);
            }
            return textures;
        }
    }

通过GetTextureCount 获取纹理的数量,并传入一个类型。

接下来定义一个纹理所在textureFilePosition,通过GetTexture函数获取纹理并存入,在设置一个开关,当textures_loaded容器中已经存在加载过得纹理路径与需要加载的纹理路径相同时,就不再重新加载,直接使用已加载的纹理。若未加载过,使用另外一个叫做TextureFromFile的工具函数,它将会(用stb_image.h)加载一个纹理并返回该纹理的ID。

利用stb库加载纹理。

这一部分与之前的纹理加载基本一致。

unsigned int TextureFormFile(const char* path,const std::string& directory)
    {
        std::string fileName = std::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;
    }

效果图:

加入光照

当加入光照和镜面光反射贴图后,效果明显提升。

代码:

mesh.h:

#include <glad/glad.h>

#include "vendor/glm/glm/glm.hpp"
#include "vendor/glm/glm/gtc/matrix_transform.hpp"

#include "Shader.h"

#include <string>
#include <vector>

struct Vertex
{
    glm::vec3 Position;
    glm::vec3 Normal;
    glm::vec2 TexCoords;

};

struct Texture
{
    unsigned int 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)
    {
        unsigned int diffuseNr = 1;
        unsigned int reflectionNr = 1;
        for (unsigned int i = 0; i < textures.size(); i++)
        {
            glActiveTexture(GL_TEXTURE0 + i);
            std::string name = textures[i].type;
            std::string number;
            if (name == "texture_diffuse")
            {
                number = std::to_string(diffuseNr++);
            }
            else if (name == "texture_reflection")
            {
                number = std::to_string(reflectionNr++);
            }

            shader.setInt(("materials." + name + number).c_str(), i);
            glBindTexture(GL_TEXTURE_2D, textures[i].id);
        }
        glActiveTexture(GL_TEXTURE0);

        glBindVertexArray(VAO);
        glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0);
        glBindVertexArray(0);
    }

private:
    unsigned int VBO, EBO;

    void setupMesh()
    {
        glGenVertexArrays(1, &VAO);
        glGenBuffers(1, &VBO);
        glGenBuffers(1, &EBO);

        glBindVertexArray(VAO);
        glBindBuffer(GL_ARRAY_BUFFER, VBO);

        glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), &vertices[0], GL_STATIC_DRAW);

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), &indices[0], GL_STATIC_DRAW);

        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)0);
        glEnableVertexAttribArray(1);
        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Normal));
        glEnableVertexAttribArray(2);
        glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, TexCoords));


        glBindVertexArray(0);
    }
};

Model.h:

#include <glad/glad.h> 

#include "vendor/glm/glm/glm.hpp"
#include "vendor/glm/glm/gtc/matrix_transform.hpp"
#include "vendor/stb_image/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>


class Model
{
public:
    std::vector<Texture> textures_loaded;
    std::vector<Mesh> meshs;
    std::string directory;
    bool gammaCorrection;

    Model(std::string const &path ,bool gamma =false) : gammaCorrection(gamma)
    {
        loadModel(path);
    }

    void Draw(Shader &shader)
    {
        for (unsigned int i = 0;i < meshs.size();i++)
            meshs[i].Draw(shader);
    }
private:
    void loadModel(std::string const &path)
    {
        Assimp::Importer importer;
        const aiScene* scene = importer.ReadFile(path, aiProcess_Triangulate | aiProcess_GenSmoothNormals | aiProcess_FlipUVs | aiProcess_CalcTangentSpace);
        if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) // if is Not Zero
        {
            std::cout << "ERROR::ASSIMP:: " << importer.GetErrorString() << std::endl;
            return;
        }

        directory = path.substr(0, path.find_last_of("/"));//逆序查找字符,返回字符位置,并复制为子字符串,也就是路径所在文件夹。
        processNode(scene->mRootNode,scene);
    }

    void processNode(aiNode* node,const aiScene* scene)
    {
        for (unsigned int i = 0;i < node->mNumMeshes;i++)
        {
            aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];
            meshs.push_back(processMesh(mesh, scene));
        }

        for (unsigned int i = 0;i < node->mNumChildren;i++)
        {
            processNode(node->mChildren[i], scene);
        }
    }

    Mesh processMesh(aiMesh* mesh,const aiScene* scene)
    {
        std::vector<Vertex> vertices;
        std::vector<unsigned int> indices;
        std::vector<Texture> textures;

        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;

            if (mesh->HasNormals())
            {
                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;
                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);

            vertices.push_back(vertex);

        };
        //处理索引
        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]);
            }
        }
        //处理材质
        if (mesh->mMaterialIndex >= 0)
        {
            aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex];
            std::vector<Texture> diffuseMap = loadMaterialTexture(material,aiTextureType_DIFFUSE, "texture_diffuse");
            std::vector<Texture> reflectionMap = loadMaterialTexture(material, aiTextureType_SPECULAR, "texture_reflection");
            //在textures容器末尾处插入diffuseMap容器中的所有元素。
            textures.insert(textures.end(), diffuseMap.begin(), diffuseMap.end());
            textures.insert(textures.end(), reflectionMap.begin(), reflectionMap.end());
        }
        return Mesh(vertices, indices, textures);
    }

    std::vector<Texture> loadMaterialTexture(aiMaterial* mat, aiTextureType type, const std::string& typeName)
    {
        std::vector<Texture> textures;

        for (unsigned int i = 0;i < mat->GetTextureCount(type);i++)
        {
            aiString textureFilePosition;
            mat->GetTexture(type, i, &textureFilePosition);
            bool skip = false;
            for (unsigned int j = 0;j < textures_loaded.size();j++)
            {
                if (std::strcmp(textures_loaded[j].path.data(), textureFilePosition.C_Str()) == 0)
                {
                    textures.push_back(textures_loaded[j]);    
                    skip = true;
                    break;
                }
            }
            if (!skip)
            {
                Texture texture;
                texture.id = TextureFormFile(textureFilePosition.C_Str(), this->directory);
                texture.type = typeName;
                texture.path = textureFilePosition.C_Str();
                textures.push_back(texture);
                textures_loaded.push_back(texture);
            }
            return textures;
        }
    }

    unsigned int TextureFormFile(const char* path,const std::string& directory)
    {
        std::string fileName = std::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;
    }

};

顶点着色器:

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;

out vec3 Normal;
out vec3 FragPos;
out vec2 TexCoords;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
    FragPos = vec3(model * vec4(aPos,1.0f));
    Normal = aNormal;

    gl_Position = projection * view * model * vec4(aPos, 1.0);
    TexCoords = aTexCoords;
}

片段着色器:

#version 330 core
out vec4 FragColor;

in vec2 TexCoords;
in vec3 Normal;
in vec3 FragPos;

struct Material
{
    sampler2D texture_diffuse1;
    sampler2D texture_diffuse2;
    //sampler2D texture_diffuse3;
    sampler2D texture_reflection1;
    sampler2D texture_reflection2;
    float shininess;
};

struct DirLight
{
    vec3 direction;

    vec3 ambient;
    vec3 diffuse;
    vec3 reflection;
};

uniform vec3 viewPos;
uniform DirLight dirLight;
uniform Material materials;

vec3 CalcDirLight(DirLight light,vec3 normal,vec3 viewDir)
{
    vec3 lightDir = normalize(-light.direction);

    float diff = max(dot(lightDir,normal),0.0);

    vec3 reflectDir = reflect(-lightDir,normal);
    float ref = pow(max(dot(viewDir,reflectDir),0.0),materials.shininess);

    vec3 ambient = light.ambient * vec3(texture(materials.texture_diffuse1,TexCoords));
         ambient += light.ambient * vec3(texture(materials.texture_diffuse2,TexCoords));
    vec3 diffuse = light.diffuse * diff * vec3(texture(materials.texture_diffuse1,TexCoords));
         diffuse += light.ambient * vec3(texture(materials.texture_diffuse2,TexCoords));
    vec3 reflection = light.reflection * ref * vec3(texture(materials.texture_reflection1,TexCoords));
         reflection += light.reflection * ref * vec3(texture(materials.texture_reflection2,TexCoords));
    return (ambient + diffuse + reflection);
}

void main()
{
    vec3 norm = normalize(Normal);
    vec3 viewDir = normalize(viewPos - FragPos);

    //定向光计算
    vec3 result = CalcDirLight(dirLight,norm,viewDir);

    FragColor = vec4(result,1.0);
}

application.cpp中的游戏循环

    while (!glfwWindowShouldClose(window))
    {
        // per-frame time logic
        float currentFrame = static_cast<float>(glfwGetTime());
        deltaTime = currentFrame - lastFrame;
        lastFrame = currentFrame;

        // input
        processInput(window);

        // render
        // ------
        glClearColor(0.1f, 0.1f, 0.1f, 0.1f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        // activate shader
        ourShader.use();

        ourShader.setVec3("viewPos", camera.Position);
        ourShader.setFloat("materials.shininess", 32.0f);

        // directional light
        ourShader.setVec3("dirLight.direction", 0.2f, -1.0f, -0.3f);
        ourShader.setVec3("dirLight.ambient", 0.1f, 0.1f, 0.1f);
        ourShader.setVec3("dirLight.diffuse", 0.3f, 0.3f, 0.3f);
        ourShader.setVec3("dirLight.reflection", 0.5f, 0.5f, 0.5f);
        // view/projection transformations
        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);

        // render the loaded model
        glm::mat4 model = glm::mat4(1.0f);
        model = glm::translate(model, glm::vec3(0.0f, 0.0f, 0.0f)); // translate it down so it's at the center of the scene
        model = glm::scale(model, glm::vec3(1.0f, 1.0f, 1.0f));    // it's a bit too big for our scene, so scale it down
        ourShader.setMat4("model", model);
        ourModel.Draw(ourShader);

        // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
        // -------------------------------------------------------------------------------
        glfwSwapBuffers(window);
        glfwPollEvents();
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值