Opengl modern design, model import mesh class

Disclaimer: This article is a blogger original article, shall not be reproduced without the bloggers allowed. https://blog.csdn.net/leon_zeng0/article/details/88885307

In this article the main reference  https://learnopengl.com/   and  https://learnopengl-cn.github.io/  learn from.

Use Assimp, we can load different models into the program, but we want to save all the data, we need a reasonable data structure. This article describes the necessary data such a class, the class grid, saving a single entity can be drawn. The whole model is composed of a plurality of mesh, or grid vector composition model we need to show.

First let us look at the knowledge we've learned so far, think about what a grid requires a minimum of data. A grid required a number of vertices, each comprising a position vector, a normal vector and a texture coordinate vector. This should also contain a grid index for indexing the drawing, and the texture data in the form of material (diffuse reflection / specular map).

Since the minimum requirements that we have a grid class, we can define a vertex in OpenGL:

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

We will all need to store vectors called Vertex structure, we can use it to index each vertex attribute.

In addition to the Vertex structure, we also need to sort out the texture data to a Texture structure.

struct Texture {
    unsigned int id;
    string type;
};

We stored texture id and its type, such as a diffuse texture map or specular light.

Know and realize the vertex textures, we can begin to define the grid structure of the class:

class Mesh {
    public:
        /*  网格数据  */
        vector<Vertex> vertices;
        vector<unsigned int> indices;
        vector<Texture> textures;
        /*  函数  */
        Mesh(vector<Vertex> vertices, vector<unsigned int> indices, vector<Texture> textures);
        void Draw(Shader shader);
    private:
        /*  渲染数据  */
        unsigned int VAO, VBO, EBO;
        /*  函数  */
        void setupMesh();
};  

You can see that this class is not complicated. In the constructor, we will be given all the necessary data grid, we initialize buffer in setupMesh function, and end-use Draw function to draw the grid. Note that we will be introduced to Draw a shader function, the transmission grid shader class allows us to set some uniform before drawing (such as a link to a texture sampler unit).

Content builder is very easy to understand. We only need to use the class constructor parameter set of public variable on it. We also call the constructor function setupMesh:

Mesh(vector<Vertex> vertices, vector<unsigned int> indices, vector<Texture> textures)
{
    this->vertices = vertices;
    this->indices = indices;
    this->textures = textures;

    setupMesh();
}

There is nothing to say. We next discuss setupMesh function.

initialization

Thanks to the constructor, we now have a large column grid data for rendering. Before we must configure the correct buffer, and the layout of the vertex shader is defined by the vertex pointer attributes. Now you should be very familiar with these concepts, but this time we will change a little bit, using the vertex data structure in:

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

And you think the code should have no different, but with the help Vertex structure, we use some tips.

C ++ structure has a great feature thereof is continuous memory layout (Sequential). That is, if we structure used as a data array, it will be in order of the variable structure, which we will be directly converted to float in the array buffer required (actually bytes) array . For example, if we have a Vertex structure after the filling, then its memory layout will be equal to:

Vertex vertex;
vertex.Position  = glm::vec3(0.2f, 0.4f, 0.6f);
vertex.Normal    = glm::vec3(0.0f, 1.0f, 0.0f);
vertex.TexCoords = glm::vec2(1.0f, 0.0f);
// = [0.2f, 0.4f, 0.6f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f];

Thanks to this useful properties, we are able to directly pass a pointer to structure large Vertex column as a data buffer, which will be perfect conversion parameters can be used to glBufferData:

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

NATURAL sizeofarithmetic calculation may be used up its byte size of the structure. This should be a 32-byte (eight per float * 4 bytes).

Another good use of the structure is its preprocessing directive offsetof(s, m), its first parameter is a structure, the second parameter is the name of the variable in the structure. The macro returns the offset variable byte (Byte Offset) from the structure of the head. This offset parameter may be used in just the definition of the function glVertexAttribPointer:

glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Normal)); 

Offsetof offset is now used to define, where byte offset vector which will set the offset method France vector structure, i.e. three float, i.e. 12 bytes. Note that we also will step size parameter set to Vertex structure.

The use of such a structure not only provides more readable code also allows us to easily expand the structure. If we want to add another vertex attributes, we only need to add it to the structure on it. Because of its flexibility, rendering code will not be destroyed.

Render

We need to define Mesh class last function, its Draw function. Before actually rendering the grid, we need first before calling the function glDrawElements bind the corresponding texture. However, this is actually somewhat difficult, we do not know the start grid (if any) the number of texture, texture is what type. So how do we set the texture sampler unit and then in the shader?

To solve this problem, we need to set a naming standard: each is named diffuse texture texture_diffuseN, each of the specular light texture should be named texture_specularN, wherein the Nrange is 1 to texture sampler maximum allowed number. For example, we have three diffuse texture, texture two specular light to a certain grid, their texture sampler should then be invoked:

uniform sampler2D texture_diffuse1;
uniform sampler2D texture_diffuse2;
uniform sampler2D texture_diffuse3;
uniform sampler2D texture_specular1;
uniform sampler2D texture_specular2;

According to this standard, we can define any desired number of texture samplers in the shader, if you really contains a grid (so much) texture, we can know what their names Yes. According to this standard, we can handle any number in a grid textures, developers can freely choose the number you want to use, he only needs to define the correct sampler on it (although less defined, then a bit wasted binding and uniform call).

Like this question there are many different solutions. If you do not like this solution, you may want to own one of your own solution.

The final rendering code like this:

void Draw(Shader shader) 
{
    unsigned int diffuseNr = 1;
    unsigned int specularNr = 1;
    for(unsigned int i = 0; i < textures.size(); i++)
    {
        glActiveTexture(GL_TEXTURE0 + i); // 在绑定之前激活相应的纹理单元
        // 获取纹理序号(diffuse_textureN 中的 N)
        string number;
        string name = textures[i].type;
        if(name == "texture_diffuse")
            number = std::to_string(diffuseNr++);
        else if(name == "texture_specular")
            number = std::to_string(specularNr++);

        shader.setFloat(("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);
}

We first compute the texture for each component type N-, and splicing it to the texture type string, to obtain the corresponding uniform name. Next we find the corresponding sampler, its position value is set to the currently active texture unit, and texture binding. This is why we need the shader Draw function. We will also "material."add to the final uniform names, because we want the texture stored in a structure in the material (which may have different in each implementation).

Note that we will diffuse reflection and specular light counter into the counter stringstreamwhen they are incremental. In C ++, this increment operation: variable++will return the variable itself, then again incremented, and ++variableis the first increase, then the return value. In our example, it is first inserted into the original counter value stringstream, then incrementing after it for the next cycle of use.

You can here find the complete source code for the class Mesh

With this grid class, we can on this basis, structure 3d model and rendering opengl display.

Guess you like

Origin blog.csdn.net/leon_zeng0/article/details/88885307