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

通过使用Assimp可以加载和不同的模型到程序中,但是载入后它们都被存储为Assimp的数据结构。最终仍要将这些数据转化为OpenGL能够理解的格式才能渲染物体。网格(Mesh)代表的是单个的可绘制实体,先定义一个网格类。

一个网格至少需要一系列的顶点,每个顶点包含一个位置向量、一个法向量和一个纹理坐标向量。一个网格还应该包含用于索引绘制的索引以及纹理形式的材质数据(漫反射/镜面光贴图)。

将所有需要的向量存储到结构体Vertex中,用它来索引每个顶点的属性:

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

除Vertex结构体外还需要将纹理数据整理到一个Texture结构体中:

struct Texture {
    unsigned int id;
    std::string type;
    std::string path;
};

结构体内存储了纹理的ID以及它的类型(漫反射贴图/镜面光贴图)。

网格类的结构:

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

    void Draw(Shader shader) {
    }
private:
    unsigned vbo, ebo;

    void SetupMesh() {
    }
};

在构造函数中将所有数据赋予网格,在SetupMesh()函数中初始化缓冲并最终使用Draw()函数绘制网格。将着色器传入网格类中可以使得在绘制之前设置一些uniform变量。

构造函数:

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 SetupMesh() {
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), &vertices[0], GL_STATIC_DRAW);

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

    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)0);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, normal));
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, tex_coords));
    glEnableVertexAttribArray(2);
    glBindVertexArray(0);
}

成员变量都是同一类型的情况下C++的结构体内存布局是连续的,如果将结构体作为一个数据数组使用它将会以顺序排列结构体变量,这将会直接转换为在数组缓冲中所需要的float(实际上是字节)数组。所以可以直接传入Vertex结构体的指针作为缓冲数据:

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

结构体还有它的预处理指令offset(s, m),其中第一个参数是一个结构体,第二个参数是结构体中成员变量的名字,它会返回变量据结构体头部的字节偏移量(Byte Offset):

glBindVertexArray(vao);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, normal));
glEnableVertexAttribArray(1);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, tex_coords));
glEnableVertexAttribArray(2);
glBindVertexArray(0);

最后是渲染的Draw()函数。在渲染网格之前需要先绑定相应的纹理,设定一个命名标准:漫反射纹理被命名玩儿u、镜面光纹理被命名为specular_texture_n,其中n的范围是$1$到纹理采样器最大允许的数字。

根据这个命名标准可以在着色器中定义任意需要数量的纹理采样器。

最终的渲染函数Draw()代码:

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(("material." + 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);
}

所以网格类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) {
        this->vertices = vertices;
        this->indices = indices;
        this->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(("material." + 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 vbo, ebo;

    void SetupMesh() {
        glGenBuffers(1, &vbo);
        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), &vertices[0], GL_STATIC_DRAW);

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

        glGenVertexArrays(1, &vao);
        glBindVertexArray(vao);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)0);
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, normal));
        glEnableVertexAttribArray(1);
        glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, tex_coords));
        glEnableVertexAttribArray(2);
        glBindVertexArray(0);
    }
};
Last modification:March 22nd, 2020 at 08:34 pm
如果觉得我的文章对你有用,请随意赞赏