目录

OpenGL和Imgui整合入门3

之前我们通过OpenGL绘制简单模型都是通过手动输入顶点数据,然后分配VAO,VBO进行绑定绘制,如果我们想要更模块化的实现这一个绑定过程,并实现从OBJ文件读取定点信息,我们则需要自己定义Mesh类和Model类

(常规的VAO,VBO绑定流程)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
float quadVertices[] = { // vertex attributes for a quad that fills the entire screen in Normalized Device Coordinates.
        // positions   // texCoords
        -1.0f,  1.0f,  0.0f, 1.0f,
        -1.0f, -1.0f,  0.0f, 0.0f,
         1.0, -1.0f,  1.0f, 0.0f,

        -1.0f,  1.0f,  0.0f, 1.0f,
         1.0f, -1.0f,  1.0f, 0.0f,
         1.0f,  1.0f,  1.0f, 1.0f
};

unsigned int quadVAO, quadVBO;
    glGenVertexArrays(1, &quadVAO);
    glGenBuffers(1, &quadVBO);
    glBindVertexArray(quadVAO);
    glBindBuffer(GL_ARRAY_BUFFER, quadVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), &quadVertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));

Mesh类

一个简单的Mesh类需要包含mesh的定点信息还有他所绑定的VAO,VBO

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class Mesh {
public:
    // mesh Data
    vector<Vertex>       vertex;
    unsigned int VAO;

    Mesh() = default;
    // constructor
    Mesh(vector<Vertex> vertices)
    {
        this->vertex = vertices;
        // now that we have all the required data, set the vertex buffers and its attribute pointers.
        setupMesh();
    }

private:
    // render data 
    unsigned int VBO;
};

其次我们要在Mesh类构造的时候设置其顶点信息,setupMesh的函数实现如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void setupMesh()
    {
        // create buffers/arrays
        glGenVertexArrays(1, &VAO);
        glGenBuffers(1, &VBO);

        glBindVertexArray(VAO);
        // load data into vertex buffers
        glBindBuffer(GL_ARRAY_BUFFER, VBO);
        // A great thing about structs is that their memory layout is sequential for all its items.
        // The effect is that we can simply pass a pointer to the struct and it translates perfectly to a glm::vec3/2 array which
        // again translates to 3/2 floats which translates to a byte array.
        glBufferData(GL_ARRAY_BUFFER, vertex.size() * sizeof(Vertex), &vertex[0], GL_STATIC_DRAW);       
        // set the vertex attribute pointers
        // vertex Positions
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)0);
        // vertex normals
        glEnableVertexAttribArray(1);
        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Normal));
        // vertex texture coords
        glEnableVertexAttribArray(2);
        glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, TexCoords));        
    }

最后,我们把绘制模型的步骤封装成一个可供外部调用的函数Draw(当然你也可以把Texture封装进Mesh类,然后再Draw函数内调用,这里只讲了简单的实现思路)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void Draw(Shader& shader)
    {
        // draw mesh
        glBindVertexArray(VAO);
        glDrawArrays(GL_TRIANGLES,0, vertex.size());
        glBindVertexArray(0);

        // always good practice to set everything back to defaults once configured.
        glActiveTexture(GL_TEXTURE0);
        
    }

Model类

为了从obj文件读取顶点信息,存入Mesh类,我们需要再定义一个Model类,Model类包含了模型的顶点信息和模型的位置信息

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
struct Transform
{
    glm::vec3 Position;
    glm::vec3 Scale;
    float Rotation[3] = {0};
    Transform()
    {
        Position = glm::vec3(0);
        Scale = glm::vec3(1);
    }
};

class Model
{
public:
    Mesh* mesh;
    Transform transform = Transform();

    Model(const char* model_path)
    {
        loadOBJ(model_path, buffer);
        mesh = new Mesh(buffer);
    }

    glm::mat4 GetModelMatrix()
    {
        glm::mat4 model = glm::mat4(1);
        model = glm::translate(model,transform.Position);
        model = glm::scale(model, transform.Scale);

        model = glm::rotate(model, glm::radians(transform.Rotation[0]), glm::vec3(1, 0, 0));
        model = glm::rotate(model, glm::radians(transform.Rotation[1]), glm::vec3(0, 1, 0));
        model = glm::rotate(model, glm::radians(transform.Rotation[2]), glm::vec3(0, 0, 1));

        return model;
    }

private:
    std::vector<Vertex> buffer;
};

loadOBJ将从obj文件读取顶点信息并存入缓存buffer,GetModelMatrix则是获取模型的模型空间矩阵,所有模型空间的变换结果将会通过这个函数计算

下面是读取模型信息的过程,obj文件的信息可以通过txt打开,打开后会发现,顶点信息存储在v开头的行,法线信息存储在vn开头的行,uv信息则存储在vt开头的行,最后f是面信息,我们通过obj文件的构成规则进行解析就行:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
 bool loadOBJ(
        const char* path,
        std::vector<Vertex>& vertices)
    {
        printf("Loading OBJ file %s...\n", path);

        std::vector<unsigned int> vertexIndices, uvIndices, normalIndices;
        std::vector<glm::vec3> temp_vertices;
        std::vector<glm::vec2> temp_uvs;
        std::vector<glm::vec3> temp_normals;

        FILE* file = fopen(path, "r");
        if (file == NULL)
        {
            printf("Impossible to open the file ! Are you in the right path ? See Tutorial 1 for details\n");
            getchar();
            return false;
        }

        while (1)
        {

            char lineHeader[128];
            // read the first word of the line
            int res = fscanf(file, "%s", lineHeader);
            if (res == EOF)
                break; // EOF = End Of File. Quit the loop.

            // else : parse lineHeader

            if (strcmp(lineHeader, "v") == 0)
            {
                glm::vec3 vertex;
                fscanf(file, "%f %f %f\n", &vertex.x, &vertex.y, &vertex.z);
                temp_vertices.push_back(vertex);
            }
            else if (strcmp(lineHeader, "vt") == 0)
            {
                glm::vec2 uv;
                fscanf(file, "%f %f\n", &uv.x, &uv.y);
                uv.y = -uv.y; // Invert V coordinate since we will only use DDS texture, which are inverted. Remove if you want to use TGA or BMP loaders.
                temp_uvs.push_back(uv);
            }
            else if (strcmp(lineHeader, "vn") == 0)
            {
                glm::vec3 normal;
                fscanf(file, "%f %f %f\n", &normal.x, &normal.y, &normal.z);
                temp_normals.push_back(normal);
            }
            else if (strcmp(lineHeader, "f") == 0)
            {
                std::string vertex1, vertex2, vertex3;
                unsigned int vertexIndex[3], uvIndex[3], normalIndex[3];
                int matches = fscanf(file, "%d/%d/%d %d/%d/%d %d/%d/%d\n", &vertexIndex[0], &uvIndex[0], &normalIndex[0], &vertexIndex[1], &uvIndex[1], &normalIndex[1], &vertexIndex[2], &uvIndex[2], &normalIndex[2]);
                if (matches != 9)
                {
                    printf("File can't be read by our simple parser :-( Try exporting with other options\n");
                    fclose(file);
                    return false;
                }
                vertexIndices.push_back(vertexIndex[0]);
                vertexIndices.push_back(vertexIndex[1]);
                vertexIndices.push_back(vertexIndex[2]);
                uvIndices.push_back(uvIndex[0]);
                uvIndices.push_back(uvIndex[1]);
                uvIndices.push_back(uvIndex[2]);
                normalIndices.push_back(normalIndex[0]);
                normalIndices.push_back(normalIndex[1]);
                normalIndices.push_back(normalIndex[2]);
            }
            else
            {
                // Probably a comment, eat up the rest of the line
                char stupidBuffer[1000];
                fgets(stupidBuffer, 1000, file);
            }
        }

        // For each vertex of each triangle
        for (unsigned int i = 0; i < vertexIndices.size(); i++)
        {

            // Get the indices of its attributes
            unsigned int vertexIndex = vertexIndices[i];
            unsigned int uvIndex = uvIndices[i];
            unsigned int normalIndex = normalIndices[i];

            // Get the attributes thanks to the index
            glm::vec3 vertex = temp_vertices[vertexIndex - 1];
            glm::vec2 uv = temp_uvs[uvIndex - 1];
            glm::vec3 normal = temp_normals[normalIndex - 1];

            Vertex temp(vertex, normal, uv);

            // Put the attributes in buffers
            vertices.push_back(temp);
        }
        fclose(file);
        std::cout << "Load  successful" << endl;
        return true;
    }

实现了这两个类后,我们直接在main函数中调用即可

调用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Shader ourShader("resources/shader/shader.vs", "resources/shader/testShader.fs");
Model nanosuit_model("resources/models/nanosuit/nanosuit.obj");
...
int main()
{
    ...
    while(!glfwWindowShouldClose(window))
    {
        ...
        ourShader.setMat4("model", Rendering::model->GetModelMatrix());
        ourShader.setMat4("projection", projection);
        ourShader.setMat4("view", view);
        ourShader.setVec3("viewPos", InputManager::camera->Position);
        nanosuit_model.mesh->Draw(ourShader);
     }
}

Shader类Camera类的实现可参考learnOpenGL网站,InputManager是管理窗口输入的头文件,该头文件定义了一个Camera指针,表示当前窗口控制的主摄像机,并封装了所有输入回调函数。

如果不出错的话,最终即可正确绘制(此处模型的texture是在main函数里绑定的)

https://cdn.jsdelivr.net/gh/xinqinew/pic@main/img/v2-515621d9c4ad90a581c0b9ac9122e892_b.jpg