此篇博文为个人OpenGL学习笔记,内容参考LearnOpenGL CNGLFW官方文档OpenGL 4 Reference Pages、OpenGL编程指南(原书第9版)

创建另一个类来完整地表示一个模型,或者说是包含多个网格,甚至是多个物体的模型。一个包含木质阳台、塔楼、甚至游泳池的房子仍会被加载为一个模型。使用Assimp加载模型并把它转换(Translate)至多个Mesh对象。

Model类的结构:

class Model {
public:
    std::vector<Mesh> meshes;
    std::string directory;

    Model(const std::string& path) {
        LoadModel(path);
    }

    void Draw(Shader shader) {
    }
private:
    void LoadModel(const std::string& path) {
    }

    void ProcessNode(aiNode* node, const aiScene* scene) {
    }

    Mesh ProcessMesh(aiMesh* mesh, const aiScene* scene) {
    }
    
    std::vector<Texture> LoadMaterialTextures(aiMaterial* material, aiTextureType type, std::string type_name) {
    }
};

Model类包含了一个Mesh对象的std::vector,构造函数接收一个文件路径参数。在构造函数中通过LoadMoel()函数加载文件。私有函数将会处理Assimp导入过程中的一部分。

渲染函数Draw()遍历所有网格并调用它们各自的渲染函数Draw()

void Draw(Shader shader) {
    for (Mesh mesh : meshes) {
        mesh.Draw(shader);
    }
}

导入模型首先调用的函数是LoadModel()。在LoadModel()中使用Assimp加载模型到Assimp的scene的数据结构中,这是Assimp数据结构的根对象。有这个场景之后就能访问到加载后的模型中所有所需的数据。

Assimp抽象掉了加载不同文件格式的所有技术细节:

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

首先声明Assimp命名空间内的一个Importer,之后调用其ReadFile()函数。此函数第一个参数为文件路径,第二个参数是一些后期处理(Post-processing)的选项。可以通过第二个参数设定一些选项来强制Assimp对导入的数据做一些额外的计算或操作。设定aiProcess_Triangulate告诉Assimp如果模型不是(全部由)三角形组成则需要将所有图元形状变换为三角形。设定aiProcess_FlipUVs将在处理的时候翻转y周的纹理坐标。还有一些其它的选项:

  • aiProcess_GenNormals:如果模型不包含法向量就为每个顶点创建法线。
  • aiProcess_SplitLargeMeshes:将比较大的网格分割成更小的子网格,若渲染有最大顶点数限制则会使用。
  • aiProcess_OptimizeMeshes:将多个小网格拼接为一个大的网格,减少绘制调用从而进行优化。

完整的LoadModel()函数:

void LoadModel(const std::string& path) {
    Assimp::Importer importer;
    const aiScene* scene = importer.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs);

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

    ProcessNode(scene->mRootNode, scene);
}

加载模型后再检查场景和其根节点不为空并检查它的一个标记判断返回的数据是否完整。若有错误则会报告并返回。

若没有错吴则会处理场景中的所有节点,将根节点传入递归函数ProcessNode()

Assimp的结构中每个节点包含一系列的网格索引,每个索引指向场景中特定的网格。获取网格索引,获取每个网格,处理每个网格并对每个节点的子节点重复此过程:

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);
    }
}

首先遍历每个节点的网格索引并索引场景的mMeshes数组来获取对应的网格。返回的网格会传递到ProcessMesh()函数中,函数回返回一个Mesh对象,把它储存在类型为meshesstd::vector容器内。

把一个aiMesh对象转化为网格对象Mesh需要访问网格的相关属性并将它们进行储存,ProcessMesh()函数结构:

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;
        //...
        vertices.push_back(vertex);
    }
    //...
    if (mesh->mMaterialIndex >= 0) {
        //...
    }
    return Mesh(vertices, indices, textures);
}

处理网格的过程由三个部分组成:获取所有的顶点数据,获取它们的网格索引,获取相关的材质数据。处理后的数据储存在三个std::vector中,使用它们构建一个Mesh对象并将对象返回。

获取顶点的数据就是遍历网格中所有的顶点(使用mesh->mNumVertices)并填充Vertex结构体,最后把相关Vertex结构体加到vertices中:

for (unsigned int i = 0; i < mesh->mNumVertices; ++i) {
    Vertex vertex;
    vertex.position = glm::vec3(mesh->mVertices[i].x, mesh->mVertices[i].y, mesh->mVertices[i].z);
    vertex.normal = glm::vec3(mesh->mNormals[i].x, mesh->mNormals[i].y, mesh->mNormals[i].z);
    if (mesh->mTextureCoords[0]) {
        vertex.tex_coords = glm::vec2(mesh->mTextureCoords[0][i].x, mesh->mTextureCoords[0][i].y);
    }
    else {
        vertex.tex_coords = glm::vec2(0.0f, 0.0f);
    }
    vertices.push_back(vertex);
}

Assimp的接口定义了每个网格都有一个面(Face)数组,每个面代表了一个图元,由于使用了aiProcess_Triangulate选项所以图元总是三角形。一个面包含了多个索引被定义在每个图元中。遍历所有的面并储存面的索引到indices中:

for (unsigned int i = 0; i < mesh->mNumFaces; ++i) {
    aiFace face = mesh->mFaces[i];
    for (unsigned int j = 0; j < face.mNumIndices; ++i) {
        indices.push_back(face.mIndices[j]);
    }
}

喝节点一样一个网格只包含了一个指向材质对象的索引,网格真正的材质存储在索引场景的mMaterials数组中,网格材质索引位于它的mMaterialIndex属性中:

if (mesh->mMaterialIndex >= 0) {
    aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex];
    std::vector<Texture> diffuse_maps = LoadMaterialTextures(material, aiTextureType_DIFFUSE, "diffuse_texture");
    textures.insert(textures.end(), diffuse_maps.begin(), diffuse_maps.end());
    std::vector<Texture> specular_maps = LoadMaterialTextures(material, aiTextureType_SPECULAR, "specular_texture");
    textures.insert(textures.end(), specular_maps.begin(), specular_maps.end());
}

首先从场景的mMaterials数组中获取aiMaterial对象,接着加载网格的漫反射和/或镜面光贴图。一个材质对象的内部对每种纹理类型都存储了一个纹理位置数组。不同纹理类型都以aiTextureType_为前缀,使用LoadMaterialTextures()工具函数从材质中获取纹理,并把其返回的std::vector追加到纹理textures之后。

LoadMaterialTextures()函数遍历给定纹理类型的所有纹理位置,获取纹理的文件位置并加载、生成纹理,最后将信息存储在一个Texture结构体中并返回:

std::vector<Texture> LoadMaterialTextures(aiMaterial* material, aiTextureType type, std::string type_name) {
    std::vector<Texture> textures;
    for (unsigned int i = 0; i < material->GetTextureCount(type); ++i) {
        aiString str;
        material->GetTexture(type, i, &str);
        Texture texture;
        texture.id = LoadTexture(str.C_Str(), directory);
        texture.type = type_name;
        texture.path = str.C_Str();
        textures.push_back(texture);
    }
    return textures;
}

首先通过GetTextureCount()函数获取存在材质中指定纹理类型的纹理的数量并遍历每个纹理,使用GetTexture()函数获取纹理文件位置并存储在aiString中。使用LoadTexture()工具函数加载(使用stb_image.h)纹理并返回纹理ID。

目前model.h的完整代码:

#pragma once

#include <glad/glad.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#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 <vector>
#include <map>

unsigned int LoadTexture(const char* path, const std::string& directory) {
    std::string filename = std::string(path);
    filename = directory + '/' + filename;

    unsigned int texture_id;
    glGenTextures(1, &texture_id);

    int width, height, channels;
    unsigned char* data = stbi_load(path, &width, &height, &channels, 0);
    if (data) {
        GLenum format;
        if (channels == 1) {
            format = GL_RED;
        }
        else if (channels == 3) {
            format = GL_RGB;
        }
        else if (channels == 4) {
            format = GL_RGBA;
        }

        glBindTexture(GL_TEXTURE_2D, texture_id);
        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_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_REPEAT);
    }
    else {
        std::cout << "Failed to load texture at path: " << path << std::endl;
    }
    stbi_image_free(data);
    return texture_id;
}

class Model {
public:
    std::vector<Mesh> meshes;
    std::string directory;

    Model(const std::string& path) {
        LoadModel(path);
    }

    void Draw(Shader shader) {
        for (Mesh mesh : meshes) {
            mesh.Draw(shader);
        }
    }
private:
    void LoadModel(const std::string& path) {
        Assimp::Importer importer;
        const aiScene* scene = importer.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs);

        if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) {
            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]];
            meshes.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;
            vertex.position = glm::vec3(mesh->mVertices[i].x, mesh->mVertices[i].y, mesh->mVertices[i].z);
            vertex.normal = glm::vec3(mesh->mNormals[i].x, mesh->mNormals[i].y, mesh->mNormals[i].z);
            if (mesh->mTextureCoords[0]) {
                vertex.tex_coords = glm::vec2(mesh->mTextureCoords[0][i].x, mesh->mTextureCoords[0][i].y);
            }
            else {
                vertex.tex_coords = 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; ++i) {
                indices.push_back(face.mIndices[j]);
            }
        }
        if (mesh->mMaterialIndex >= 0) {
            aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex];
            std::vector<Texture> diffuse_maps = LoadMaterialTextures(material, aiTextureType_DIFFUSE, "diffuse_texture");
            textures.insert(textures.end(), diffuse_maps.begin(), diffuse_maps.end());
            std::vector<Texture> specular_maps = LoadMaterialTextures(material, aiTextureType_SPECULAR, "specular_texture");
            textures.insert(textures.end(), specular_maps.begin(), specular_maps.end());
        }
        return Mesh(vertices, indices, textures);
    }
    
    std::vector<Texture> LoadMaterialTextures(aiMaterial* material, aiTextureType type, std::string type_name) {
        std::vector<Texture> textures;
        for (unsigned int i = 0; i < material->GetTextureCount(type); ++i) {
            aiString str;
            material->GetTexture(type, i, &str);
            Texture texture;
            texture.id = LoadTexture(str.C_Str(), directory);
            texture.type = type_name;
            texture.path = str.C_Str();
            textures.push_back(texture);
        }
        return textures;
    }
};

但是这样的Model还可以再继续进行优化。大多数场景都会在多个网格中重复使用部分纹理。比如一个房子墙壁有花岗岩的纹理,这个纹理同样可以被应用到地板、天花板、楼梯、桌子甚至井上。加载纹理开销并不大但重复加载相同的纹理仍然会造成模型加载实现的性能瓶颈。

优化的办法就是把所有加载过的纹理全局存储,每加载一个纹理首先在全局存储纹理容器中判断是否被加载过,若其被加载过则直接是用,否则再加载对应纹理:

std::vector<Texture> textures_loaded;

...

std::vector<Texture> LoadMaterialTextures(aiMaterial* material, aiTextureType type, std::string type_name) {
    std::vector<Texture> textures;
    for (unsigned int i = 0; i < material->GetTextureCount(type); ++i) {
        aiString str;
        material->GetTexture(type, i, &str);
        bool skip = false;
        for (Texture texture : textures_loaded) {
            if (std::strcmp(texture.path.data(), str.C_Str()) == 0) {
                textures.push_back(texture);
                skip = true;
                break;
            }
        }
        if (!skip) {
            Texture texture;
            texture.id = LoadTexture(str.C_Str(), directory);
            texture.type = type_name;
            texture.path = str.C_Str();
            textures.push_back(texture);
            textures_loaded.push_back(texture);
        }
    }
    return textures;
}

导入Crytek的游戏孤岛危机(Crysis)中的原版纳米装(Nanosuit)。模型包含一个.obj文件和一个.mtl文件,.mtl文件包含模型的漫反射、镜面光和法线贴图。模型下载链接

shader.vs:

#version 460 core
layout (location = 0) in vec3 v_pos;
layout (location = 1) in vec3 v_normal;
layout (location = 2) in vec2 v_tex_coords;

out vec2 f_tex_coords;

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

void main() {
    f_tex_coords = v_tex_coords;
    gl_Position = projection * view * model * vec4(v_pos, 1.0f);
}

shader.fs:

#version 460 core
out vec4 color;

in vec2 f_tex_coords;

uniform sampler2D diffuse_texture_1;

void main() {
    color = texture(diffuse_texture_1, f_tex_coords);
}

mesh.h:

#pragma once

#include <glad/glad.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "shader.h"
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <vector>

struct Vertex {
    glm::vec3 position;
    glm::vec3 normal;
    glm::vec2 tex_coords;
};

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) {
        vertices = _vertices;
        indices = _indices;
        textures = _textures;

        SetupMesh();
    }

    void Draw(Shader shader) {
        unsigned int diffuse_ptr = 1;
        unsigned int specular_ptr = 1;
        for (unsigned int i = 0; i < textures.size(); ++i) {
            glActiveTexture(GL_TEXTURE0 + i);
            std::string number;
            std::string name = textures[i].type;
            if (name == "diffuse_texture") {
                number = std::to_string(diffuse_ptr++);
            }
            else if (name == "specular_texture") {
                number = std::to_string(specular_ptr++);
            }
            shader.SetInt((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);

        glActiveTexture(GL_TEXTURE0);
    }
private:
    unsigned 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, tex_coords));
        glBindVertexArray(0);
    }
};

model.h:

#pragma once

#include <glad/glad.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#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 <vector>
#include <map>

unsigned int LoadTexture(const char* path, const std::string& directory) {
    std::string filename = std::string(path);
    filename = directory + "/" + filename;

    unsigned int texture_id;
    glGenTextures(1, &texture_id);

    int width, height, channels;
    unsigned char* data = stbi_load(filename.c_str(), &width, &height, &channels, 0);
    if (data) {
        GLenum format;
        if (channels == 1) {
            format = GL_RED;
        }
        else if (channels == 3) {
            format = GL_RGB;
        }
        else if (channels == 4) {
            format = GL_RGBA;
        }

        glBindTexture(GL_TEXTURE_2D, texture_id);
        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_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_REPEAT);
    }
    else {
        std::cout << "Failed to load texture at path: " << path << std::endl;
    }
    stbi_image_free(data);
    return texture_id;
}

class Model {
public:
    std::vector<Texture> textures_loaded;
    std::vector<Mesh> meshes;
    std::string directory;

    Model(const std::string& path) {
        LoadModel(path);
    }

    void Draw(Shader shader) {
        for (unsigned int i = 0; i < meshes.size(); ++i) {
            meshes[i].Draw(shader);
        }
    }
private:
    void LoadModel(const std::string& path) {
        Assimp::Importer importer;
        const aiScene* scene = importer.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs);

        if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) {
            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]];
            meshes.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;
            vertex.position = glm::vec3(mesh->mVertices[i].x, mesh->mVertices[i].y, mesh->mVertices[i].z);
            vertex.normal = glm::vec3(mesh->mNormals[i].x, mesh->mNormals[i].y, mesh->mNormals[i].z);
            if (mesh->mTextureCoords[0]) {
                vertex.tex_coords = glm::vec2(mesh->mTextureCoords[0][i].x, mesh->mTextureCoords[0][i].y);
            }
            else {
                vertex.tex_coords = 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]);
            }
        }

        aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex];

        std::vector<Texture> diffuse_maps = LoadMaterialTextures(material, aiTextureType_DIFFUSE, "diffuse_texture");
        textures.insert(textures.end(), diffuse_maps.begin(), diffuse_maps.end());
        std::vector<Texture> specular_maps = LoadMaterialTextures(material, aiTextureType_SPECULAR, "specular_texture");
        textures.insert(textures.end(), specular_maps.begin(), specular_maps.end());

        return Mesh(vertices, indices, textures);
    }
    
    std::vector<Texture> LoadMaterialTextures(aiMaterial* material, aiTextureType type, std::string type_name) {
        std::vector<Texture> textures;
        for (unsigned int i = 0; i < material->GetTextureCount(type); ++i) {
            aiString str;
            material->GetTexture(type, i, &str);
            bool skip = false;
            for (unsigned int j = 0; j < textures_loaded.size(); ++j) {
                if (std::strcmp(textures_loaded[j].path.data(), str.C_Str()) == 0) {
                    textures.push_back(textures_loaded[j]);
                    skip = true;
                    break;
                }
            }
            if (skip == false) {
                Texture texture;
                texture.id = LoadTexture(str.C_Str(), this->directory);
                texture.type = type_name;
                texture.path = str.C_Str();
                textures.push_back(texture);
                textures_loaded.push_back(texture);
            }
        }
        return textures;
    }
};

main.cpp:

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <iostream>
#include "shader.h"
#include "camera.h"
#include "model.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

const unsigned int window_width = 800;
const unsigned int window_height = 600;

Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
float mouse_last_x = window_width / 2.0f;
float mouse_last_y = window_height / 2.0f;
bool first_mouse = true;

float delta_time = 0.0f;
float last_frame = 0.0f;

void FramebufferSizeCallback(GLFWwindow* window, int width, int height) {
    glViewport(0, 0, width, height);
}

void ProcessInput(GLFWwindow* window) {
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
        glfwSetWindowShouldClose(window, true);
    }
    if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) {
        camera.ProcessKeyboard(FORWARD, delta_time);
    }
    if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) {
        camera.ProcessKeyboard(BACKWARD, delta_time);
    }
    if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) {
        camera.ProcessKeyboard(LEFT, delta_time);
    }
    if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) {
        camera.ProcessKeyboard(RIGHT, delta_time);
    }
}

void MouseCallback(GLFWwindow* window, double x_pos, double y_pos) {
    if (first_mouse) {
        mouse_last_x = x_pos;
        mouse_last_y = y_pos;
        first_mouse = false;
    }
    float x_offset = x_pos - mouse_last_x;
    float y_offset = mouse_last_y - y_pos;
    mouse_last_x = x_pos;
    mouse_last_y = y_pos;
    camera.ProcessMouseMovement(x_offset, y_offset);
}

void ScrollCallback(GLFWwindow* window, double x_offset, double y_offset) {
    camera.ProcessMouseScroll(y_offset);
}

int main() {
    if (!glfwInit()) {
        std::cout << "Failed to initialize GLFW" << std::endl;
        return -1;
    }
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    GLFWwindow* window = glfwCreateWindow(window_width, window_height, "Hello OpenGL", NULL, NULL);
    if (window == NULL) {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, FramebufferSizeCallback);
    glfwSetCursorPosCallback(window, MouseCallback);
    glfwSetScrollCallback(window, ScrollCallback);
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }
    glEnable(GL_DEPTH_TEST);

    Shader shader("shader.vs", "shader.fs");
    Model model("src/obj/nanosuit/nanosuit.obj");

    while (!glfwWindowShouldClose(window)) {
        float current_frame = glfwGetTime();
        delta_time = current_frame - last_frame;
        last_frame = current_frame;

        ProcessInput(window);

        glClearColor(0.05f, 0.05f, 0.05f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        shader.Use();
        glm::mat4 projection_matrix = glm::perspective(glm::radians(camera.fov), (float)window_width / (float)window_height, 0.1f, 100.0f);
        glm::mat4 view_matrix = camera.GetViewMatrix();
        shader.SetMat4("projection", projection_matrix);
        shader.SetMat4("view", view_matrix);
        glm::mat4 model_matrix = glm::mat4(1.0f);
        model_matrix = glm::translate(model_matrix, glm::vec3(0.0f, -1.75f, 0.0f));
        model_matrix = glm::scale(model_matrix, glm::vec3(0.2f, 0.2f, 0.2f));
        shader.SetMat4("model", model_matrix);

        model.Draw(shader);

        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glfwTerminate();
    return 0;
}

运行效果:

运行效果

同样可以在场景中添加几处点光源。

shader.vs:

#version 460 core
layout (location = 0) in vec3 v_pos;
layout (location = 1) in vec3 v_normal;
layout (location = 2) in vec2 v_tex_coords;

out vec3 f_pos;
out vec3 f_normal;
out vec2 f_tex_coords;

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

void main() {
    f_pos = vec3(model * vec4(v_pos, 1.0));
    f_normal = mat3(transpose(inverse(model))) * v_normal;
    f_tex_coords = v_tex_coords;
    gl_Position = projection * view * model * vec4(v_pos, 1.0f);
}

shader.fs:

#version 460 core
out vec4 color;

in vec3 f_pos;
in vec3 f_normal;
in vec2 f_tex_coords;

struct Material {
    sampler2D diffuse;
    sampler2D specular;
    float shininess;
};

uniform Material materials_1;

struct PointLight {
    vec3 position;

    float constant;
    float linear;
    float quadratic;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};

#define POINT_LIGHTS_NUMBER 4
uniform PointLight point_lights[POINT_LIGHTS_NUMBER];

vec3 CalcPointLight(PointLight light, vec3 normal, vec3 f_pos, vec3 view_dir) {
    vec3 light_dir = normalize(light.position - f_pos);

    float diff = max(dot(normal, light_dir), 0.0f);

    vec3 reflect_dir = reflect(-light_dir, normal);
    float spec = pow(max(dot(view_dir, reflect_dir), 0.0f), materials_1.shininess);

    float distance = length(light.position - f_pos);
    float attenuation = 1.0f / (light.constant + light.linear * distance + light.quadratic * (distance * distance));

    vec3 ambient = light.ambient * vec3(texture(materials_1.diffuse, f_tex_coords));
    vec3 diffuse = light.diffuse * diff * vec3(texture(materials_1.diffuse, f_tex_coords));
    vec3 specular = light.specular * spec * vec3(texture(materials_1.specular, f_tex_coords));

    ambient *= attenuation;
    diffuse *= attenuation;
    specular *= attenuation;

    return (ambient + diffuse + specular);
}

uniform vec3 view_pos;

void main() {
    vec3 norm = normalize(f_normal);
    vec3 view_dir = normalize(view_pos - f_pos);

    vec3 result = vec3(0.0f);
    for (int i = 0; i < POINT_LIGHTS_NUMBER; ++i) {
        result += CalcPointLight(point_lights[i], norm, f_pos, view_dir);
    }

    color = vec4(result, 1.0f);
}

main.cpp:

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <iostream>
#include "shader.h"
#include "camera.h"
#include "model.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

const unsigned int window_width = 800;
const unsigned int window_height = 600;

Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
float mouse_last_x = window_width / 2.0f;
float mouse_last_y = window_height / 2.0f;
bool first_mouse = true;

float delta_time = 0.0f;
float last_frame = 0.0f;

void FramebufferSizeCallback(GLFWwindow* window, int width, int height) {
    glViewport(0, 0, width, height);
}

void ProcessInput(GLFWwindow* window) {
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
        glfwSetWindowShouldClose(window, true);
    }
    if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) {
        camera.ProcessKeyboard(FORWARD, delta_time);
    }
    if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) {
        camera.ProcessKeyboard(BACKWARD, delta_time);
    }
    if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) {
        camera.ProcessKeyboard(LEFT, delta_time);
    }
    if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) {
        camera.ProcessKeyboard(RIGHT, delta_time);
    }
}

void MouseCallback(GLFWwindow* window, double x_pos, double y_pos) {
    if (first_mouse) {
        mouse_last_x = x_pos;
        mouse_last_y = y_pos;
        first_mouse = false;
    }
    float x_offset = x_pos - mouse_last_x;
    float y_offset = mouse_last_y - y_pos;
    mouse_last_x = x_pos;
    mouse_last_y = y_pos;
    camera.ProcessMouseMovement(x_offset, y_offset);
}

void ScrollCallback(GLFWwindow* window, double x_offset, double y_offset) {
    camera.ProcessMouseScroll(y_offset);
}

int main() {
    if (!glfwInit()) {
        std::cout << "Failed to initialize GLFW" << std::endl;
        return -1;
    }
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    GLFWwindow* window = glfwCreateWindow(window_width, window_height, "Hello OpenGL", NULL, NULL);
    if (window == NULL) {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, FramebufferSizeCallback);
    glfwSetCursorPosCallback(window, MouseCallback);
    glfwSetScrollCallback(window, ScrollCallback);
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }
    glEnable(GL_DEPTH_TEST);

    Shader shader("shader.vs", "shader.fs");
    Shader lamp_shader("lamp_shader.vs", "lamp_shader.fs");

    Model model("src/obj/nanosuit/nanosuit.obj");

    float vertices[] = {
        -0.5f, -0.5f, -0.5f,
        0.5f, -0.5f, -0.5f,
        0.5f,  0.5f, -0.5f,
        0.5f,  0.5f, -0.5f,
        -0.5f,  0.5f, -0.5f,
        -0.5f, -0.5f, -0.5f,
        -0.5f, -0.5f,  0.5f,
        0.5f, -0.5f,  0.5f,
        0.5f,  0.5f,  0.5f,
        0.5f,  0.5f,  0.5f,
        -0.5f,  0.5f,  0.5f,
        -0.5f, -0.5f,  0.5f,
        -0.5f,  0.5f,  0.5f,
        -0.5f,  0.5f, -0.5f,
        -0.5f, -0.5f, -0.5f,
        -0.5f, -0.5f, -0.5f,
        -0.5f, -0.5f,  0.5f,
        -0.5f,  0.5f,  0.5f,
        0.5f,  0.5f,  0.5f,
        0.5f,  0.5f, -0.5f,
        0.5f, -0.5f, -0.5f,
        0.5f, -0.5f, -0.5f,
        0.5f, -0.5f,  0.5f,
        0.5f,  0.5f,  0.5f,
        -0.5f, -0.5f, -0.5f,
        0.5f, -0.5f, -0.5f,
        0.5f, -0.5f,  0.5f,
        0.5f, -0.5f,  0.5f,
        -0.5f, -0.5f,  0.5f,
        -0.5f, -0.5f, -0.5f,
        -0.5f,  0.5f, -0.5f,
        0.5f,  0.5f, -0.5f,
        0.5f,  0.5f,  0.5f,
        0.5f,  0.5f,  0.5f,
        -0.5f,  0.5f,  0.5f,
        -0.5f,  0.5f, -0.5f,
    };

    glm::vec3 lamp_positions[] = {
        glm::vec3(0.7f, 0.2f, 2.0f),
        glm::vec3(1.0f, -1.0f, -2.0f),
        glm::vec3(-2.0f, 2.0f, -2.0f),
        glm::vec3(0.0f, 0.0f, -2.0f)
    };

    unsigned int vbo, lamp_vao;
    glGenVertexArrays(1, &lamp_vao);
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    glBindVertexArray(lamp_vao);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);

    while (!glfwWindowShouldClose(window)) {
        float current_frame = glfwGetTime();
        delta_time = current_frame - last_frame;
        last_frame = current_frame;

        ProcessInput(window);

        glClearColor(0.05f, 0.05f, 0.05f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        shader.Use();
        shader.SetVec3("view_pos", camera.position);
        shader.SetFloat("materials_1.shininess", 32.0f);

        shader.SetVec3("point_lights[0].position", lamp_positions[0]);
        shader.SetVec3("point_lights[0].ambient", 0.05f, 0.05f, 0.05f);
        shader.SetVec3("point_lights[0].diffuse", 0.8f, 0.8f, 0.8f);
        shader.SetVec3("point_lights[0].specular", 1.0f, 1.0f, 1.0f);
        shader.SetFloat("point_lights[0].constant", 1.0f);
        shader.SetFloat("point_lights[0].linear", 0.045f);
        shader.SetFloat("point_lights[0].quadratic", 0.0075f);

        shader.SetVec3("point_lights[1].position", lamp_positions[1]);
        shader.SetVec3("point_lights[1].ambient", 0.05f, 0.05f, 0.05f);
        shader.SetVec3("point_lights[1].diffuse", 0.8f, 0.8f, 0.8f);
        shader.SetVec3("point_lights[1].specular", 1.0f, 1.0f, 1.0f);
        shader.SetFloat("point_lights[1].constant", 1.0f);
        shader.SetFloat("point_lights[1].linear", 0.045f);
        shader.SetFloat("point_lights[1].quadratic", 0.0075f);

        shader.SetVec3("point_lights[2].position", lamp_positions[2]);
        shader.SetVec3("point_lights[2].ambient", 0.05f, 0.05f, 0.05f);
        shader.SetVec3("point_lights[2].diffuse", 0.8f, 0.8f, 0.8f);
        shader.SetVec3("point_lights[2].specular", 1.0f, 1.0f, 1.0f);
        shader.SetFloat("point_lights[2].constant", 1.0f);
        shader.SetFloat("point_lights[2].linear", 0.045f);
        shader.SetFloat("point_lights[2].quadratic", 0.0075f);

        shader.SetVec3("point_lights[3].position", lamp_positions[3]);
        shader.SetVec3("point_lights[3].ambient", 0.05f, 0.05f, 0.05f);
        shader.SetVec3("point_lights[3].diffuse", 0.8f, 0.8f, 0.8f);
        shader.SetVec3("point_lights[3].specular", 1.0f, 1.0f, 1.0f);
        shader.SetFloat("point_lights[3].constant", 1.0f);
        shader.SetFloat("point_lights[3].linear", 0.045f);
        shader.SetFloat("point_lights[3].quadratic", 0.0075f);

        glm::mat4 projection_matrix = glm::perspective(glm::radians(camera.fov), (float)window_width / (float)window_height, 0.1f, 100.0f);
        glm::mat4 view_matrix = camera.GetViewMatrix();
        shader.SetMat4("projection", projection_matrix);
        shader.SetMat4("view", view_matrix);
        glm::mat4 model_matrix = glm::mat4(1.0f);
        model_matrix = glm::translate(model_matrix, glm::vec3(0.0f, -1.75f, 0.0f));
        model_matrix = glm::scale(model_matrix, glm::vec3(0.2f, 0.2f, 0.2f));
        shader.SetMat4("model", model_matrix);

        model.Draw(shader);

        lamp_shader.Use();
        lamp_shader.SetMat4("projection", projection_matrix);
        lamp_shader.SetMat4("view", view_matrix);
        glBindVertexArray(lamp_vao);
        for (unsigned int i = 0; i < 4; ++i) {
            model_matrix = glm::mat4(1.0f);
            model_matrix = glm::translate(model_matrix, lamp_positions[i]);
            model_matrix = glm::scale(model_matrix, glm::vec3(0.2f));
            lamp_shader.SetMat4("model", model_matrix);

            glDrawArrays(GL_TRIANGLES, 0, 36);
        }

        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glfwTerminate();
    return 0;
}

运行效果:

运行效果

Last modification:March 23rd, 2020 at 01:38 pm
如果觉得我的文章对你有用,请随意赞赏