4Usage of index buffer & why to use it

glDrawArrays(GL_TRIANGLES, 0, 6); This function draws triangles in the order of vertices. So to draw a rectangle, you need to draw two triangles, and each triangle needs to provide three vertices, then in There are 6 vertex data in the positon array, and each vertex occupies two float numbers (if it is two-dimensional).

This leads to redundancy, so the index buffer is used:

 float positions[] = {<!-- --> /*Redundant points, so index buffer is needed*/
        -0.5f, -0.5f,// 0
        0.5f, -0.5f,// 1
        0.5f, 0.5f, // 2
        -0.5f, 0.5f, // 3
    };

    unsigned int indices[] = {<!-- -->
        0, 1, 2,
        2, 3, 0
    };

The coordinates of four points are recorded in positions, so indices can directly record the serial numbers of the drawn points.

Corresponding usage:

 unsigned int ibo;
    glGenBuffers(1, & amp;ibo);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * sizeof(unsigned int), indices, GL_STATIC_DRAW);
//......

glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);

Total code:

#include 
#include 

#include 
#include 
#include 
#include 

struct ShaderProgramSource
{
    std::string VertexSource;
    std::string FragmentSource;
};

static ShaderProgramSource ParseShader(const std::string & amp; filepath) {
    std::ifstream stream(filepath);

    /*You asked a good question. Let's analyze from a grammatical perspective why enum class is called "enumeration type with scope":

- Ordinary enum definition is:

  enum EnumName {
   value1,
   value2
  }

- Enumeration values are not scoped, and the value name can be used directly

- And the enum class definition is:

  enum class EnumName {
   value1,
   value2
  }

- The class keyword is used here

- According to the C++ standard, the class keyword will generate a new scope for the enumeration type

- The enum value name will be placed in this new scope

- So to use enumeration value names, you need to add the scope operator::

 Such as EnumName::value1

- This isolates possible duplicate names in other scopes

- and prevent enumeration value names from colliding with other names

Therefore, judging from the scope generated by the class keyword in the enum class syntax:

- It generates a separate namespace for enumeration type value names

- This gives rise to the semantics of "scoped"

I hope this analysis can help you understand the syntax mechanism of enum class!*/
    enum class ShaderType { /* Enumeration type with scope, not a class*/
        NONE = -1, VERTEX = 0, FRAGMENT = 1
    };

    std::string line;
    std::stringstream ss[2];
    ShaderType type = ShaderType::NONE;
    while (getline(stream, line)) {
        if (line.find("#shader") != std::string::npos) { /* Found*/
            if (line.find("vertex") != std::string::npos) {
                // set mode to vertex
                type = ShaderType::VERTEX;
            }
            else if (line.find("fragment") != std::string::npos) {
                // set mode to fragment
                type = ShaderType::FRAGMENT;
            }
        }
        else {
            ss[(int)type] << line << '\\
';
        }
    }

    return { ss[0].str(), ss[1].str() };
}
 

/*For convenience, write it as a function*/
static unsigned int CompileShader(unsigned int type, const std::string & amp; source) {
    unsigned int id = glCreateShader(type);/*vertex or fragment */
    const char* src = source.c_str(); /*or write & amp;source[0]*/
    glShaderSource(id, 1, & amp;src, nullptr);
    glCompileShader(id);

    int result;
    glGetShaderiv(id, GL_COMPILE_STATUS, & amp;result);
    if (result == GL_FALSE) {
        int length;
        glGetShaderiv(id, GL_INFO_LOG_LENGTH, & amp;length);
        // char message[length]; /*Here you will find that stack allocation cannot be done because of the variable length, but you still have to do it*/
        char* message = (char*)alloca(length * sizeof(char));
        glGetShaderInfoLog(id, length, & length, message);
        std::cout << "Failed to compile " <<
            (type == GL_VERTEX_SHADER ? "vertex":"fragment" )<< "shader! Please locate this line" << std::endl;
        std::cout << message << std::endl;
        glDeleteShader(id);
        return 0;
    }

    return id;
}

/* Use static because you don’t want it to leak to other translation units?
Using string is not the best choice, but it is relatively safe. Int type - the unique identifier of the shader, an ID*/
static unsigned int CreateShader(const std::string & amp; vertexShader, const std::string & amp; fragmentShader) {
    /*Use unsigned because this is the parameter it accepts,
    Or you can use GLuint, but the author doesn't like this because it uses multiple image api*/
    unsigned int program = glCreateProgram();
    unsigned int vs = CompileShader(GL_VERTEX_SHADER, vertexShader);
    unsigned int fs = CompileShader(GL_FRAGMENT_SHADER, fragmentShader);

    glAttachShader(program, vs);
    glAttachShader(program, fs);
    glLinkProgram(program);
    glValidateProgram(program);

    glDeleteShader(vs);
    glDeleteShader(fs);

    return program;
}

int main(void)
{
    GLFWwindow* window;

    /* Initialize the library */
    if (!glfwInit())
        return -1;

    //if (glewInit() != GLEW_OK)/*glew document, an error will be reported here, because context is needed, and the context is behind*/
    // std::cout << "ERROR!-1" << std::endl;

    /* Create a windowed mode window and its OpenGL context */
    window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL);
    if (!window)
    {
        glfwTerminate();
        return -1;
    }

    /* Make the window's context current */
    glfwMakeContextCurrent(window);

    if (glewInit() != GLEW_OK)/*No error will be reported here*/
        std::cout << "ERROR!-2" << std::endl;

    std::cout << glGetString(GL_VERSION) << std::endl;

    float positions[] = {<!-- --> /*Redundant points, so index buffer is needed*/
        -0.5f, -0.5f,// 0
        0.5f, -0.5f,// 1
        0.5f, 0.5f, // 2
        -0.5f, 0.5f, // 3
    };

    unsigned int indices[] = {<!-- -->
        0, 1, 2,
        2, 3, 0
    };

    /*
    This code creates and initializes the Vertex Buffer Object (VBO for short).

VBO is a very important concept in OpenGL and is used to render vertex data efficiently.

What this code does is:

glGenBuffers generates a new VBO, and the ID is saved in the buffer variable.

glBindBuffer binds this VBO to the GL_ARRAY_BUFFER target.

glBufferData fills the actual vertex data into the bound VBO.

Go through these three steps:

We get a VBO object that can store vertex data

Subsequent draw calls only need to specify this VBO to load vertex data.

The tutorial emphasizes VBO because:

It is more efficient than sending the vertices directly

Draw calls no longer need to send the same vertices repeatedly every frame

Improve rendering performance

So to summarize, VBO can efficiently draw complex vertex data to the graphics card, which is an important concept of OpenGL.



glGenBuffers(1, & amp;buffer);
The function of glGenBuffers is to generate the ID number of the VBO object.

The first parameter 1 indicates the number of VBOs to be generated. Only 1 is generated here.

The second parameter &buffer is used to return the generated VBO ID number.

glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBindBuffer is used to bind the VBO object to the specified target.

The first parameter GL_ARRAY_BUFFER indicates that the target to be bound is the vertex attribute array buffer.

GL_ARRAY_BUFFER specifies that vertex attribute data such as position, color, etc. will be saved.

The second parameter buffer is the VBO ID generated by glGenBuffers previously.

So to summarize:

glGenBuffers generates 1 VBO object and gets the ID number

glBindBuffer binds this VBO to the attribute buffer target as the storage object for subsequent vertex data.




The function of glBufferData is to fill the actual vertex data into the previously bound VBO object.

Parameter Description:

GL_ARRAY_BUFFER: Specifies that the operation target is the vertex attribute buffer (consistent with glBindBuffer)

6 * sizeof(float): data size, here the positions array has 6 float numbers

positions: array pointer, providing the actual data source

GL_STATIC_DRAW: Data usage pattern

GL_STATIC_DRAW: Data will not or rarely change
GL_DYNAMIC_DRAW: Data may be modified
GL_STREAM_DRAW: The data will change every time it is drawn.
Its function is:

Allocate memory of the specified size to the currently bound VBO object

Copy the contents of the positions array to the VBO object memory

In GL_STATIC_DRAW mode, the graphics card knows how to optimally allocate memory

In this way, the vertex data in the positions array is uploaded to the VBO object in the GPU.

OpenGL then reads the vertex data through the VBO object for drawing.

*/
    unsigned int buffer;
    glGenBuffers(1, & amp;buffer);
    glBindBuffer(GL_ARRAY_BUFFER, buffer);
    glBufferData(GL_ARRAY_BUFFER, 6 * 2 * sizeof(float), positions, GL_STATIC_DRAW);

    glEnableVertexAttribArray(0);
    /*index-There is only one attribute, fill in 0
    size-two numbers represent a point, fill in 2
    stripe - number of bytes between vertices
    pointer-offset




    Okay, let's use an example to explain the meaning of the parameters of glVertexAttribPointer:

Suppose we have a VBO that stores three three-dimensional vertex data. Each vertex consists of (x, y, z), and each element type is float.

Then the data is arranged in the VBO as follows:

VBO address | data
0|x1
4 | y1\
8|z1
12|x2
16|y2
20|z2
24|x3
28|y3
32|z3

Now we need to tell OpenGL how to parse this data:

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 12, 0);

- 0: The attribute is location data
- 3: Each position consists of 3 floats, (x, y, z)
- GL_FLOAT: The data type is float
- 12: The interval from the current attribute to the next attribute, that is, a vertex requires 12 bytes
- 0: The starting position of this attribute is the beginning of the VBO

This way OpenGL knows:

- Read 3 floats from the VBO starting address as the position of the first vertex
- Read another 3 floats at an offset of 12 bytes to the next vertex

The last parameter 0 tells OpenGL what the starting reading offset of the attribute is.





    
    Okay, let’s use an example to illustrate this situation in detail:

Suppose we have a VBO to store vertex data. Each vertex contains two attributes: position and color.

The data is arranged inside the VBO as:

position x | position y | position z | color r | color g | color b

So for the first vertex, its layout within the VBO is:

VBO address | data
0 | position x\
4 | position y
8 | position z
12 | color r
16 | colorg
20 | color b

At this point, we set the pointers of the position attribute and color attribute:

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 24, 0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 24, 12);

can be seen:

- The position attribute is read starting from byte 0
- Color attributes are read starting from 12 bytes (to make room for position data)

This is why the offset of the position attribute cannot be written as 0, and a non-zero offset needs to be specified to make room for the color attribute storage space.

In this way, the two separate but co-located VBO data can be correctly parsed. */
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, 0);/* (const void)*/

    unsigned int ibo;
    glGenBuffers(1, & amp;ibo);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * sizeof(unsigned int), indices, GL_STATIC_DRAW);

   
    //Test ShaderProgramSource
    ShaderProgramSource source = ParseShader("res/shaders/Basic.shader");

    unsigned int shader = CreateShader(source.VertexSource, source.FragmentSource);
    glUseProgram(shader);

    /* Loop until the user closes the window */
    while (!glfwWindowShouldClose(window))
    {
        /* Render here */
        glClear(GL_COLOR_BUFFER_BIT);

        // glDrawArrays(GL_TRIANGLES, 0, 6);
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);

    /* glBegin(GL_TRIANGLES);
        glVertex2f(-0.5f, 0.5f);
        glVertex2f(0.0f, 0.0f);
        glVertex2f(0.5f, 0.5f);
        glEnd();*/

        /* Swap front and back buffers */
        glfwSwapBuffers(window);

        /* Poll for and process events */
        glfwPollEvents();
    }

    glDeleteProgram(shader);

    glfwTerminate();
    return 0;
}