LearnOpenGL Notes - Getting Started 04 Hello, Triangle

Series Article Directory



foreword

This article is more difficult, and the learning curve suddenly becomes steeper. But it doesn't matter, I will explain my understanding from the perspective of a beginner to help you learn VAO, VBO, EBO, Shader and other concepts. First of all, the full text is still summarized in the form of a list of knowledge points.

hello triangle

  • Remember three words first
    • Vertex Array Object: Vertex Array Object, VAO
    • Vertex buffer object: Vertex Buffer Object, VBO
    • Element Buffer Object: Element Buffer Object, EBO or Index Buffer Object, Index Buffer Object, IBO
  • Everything in OpenGL is in 3D space, while the screen is 2D. The process of converting 3D coordinates to 2D coordinates is performed by OpenGL's graphics rendering pipeline
  • The graphics rendering pipeline is divided into two main parts
    • Convert 3D coordinates to 2D coordinates
    • 2D coordinates converted to actual colored pixels
  • The graphics rendering pipeline can be divided into several stages, each stage will take the output of the previous stage as input. These stages are easily executed in parallel. The core of the graphics card runs small programs at each stage, these small programs are called shaders (Shader)
  • Shaders in some stages can be written by developers. OpenGL shaders are written in the OpenGL Shading Language (GLSL)
  • The figure below is an abstract representation of each stage of the rendering pipeline. The blue part can enter a custom shader
    + insert image description here
  • The first part of the pipeline is the Vertex Shader, which takes a vertex as input. The role is to convert 3D coordinates into another 3D coordinates (explained later), and the vertex shader allows us to perform some basic processing on vertex attributes.
  • The primitive assembly (Primitive Assembly) stage takes as input all the vertices output by the vertex shader (if it is GL_POINTS, then it is a vertex), and assembles all points into the shape of the specified primitive; in this section, it is a triangle.
  • The output of the primitive assembly stage is passed to the Geometry Shader. A geometry shader takes as input a collection of vertices in the form of primitives, and it can generate other shapes by constructing new (or other) primitives by generating new vertices. In the example, it generates another triangle.
  • The output of the geometry shader will be passed to the rasterization stage (Rasterization Stage), where it will map the primitive to the corresponding pixel on the final screen, and generate a fragment (Fragment) for the fragment shader (Fragment Shader). Clipping is performed before the fragment shader runs. Cropping discards all pixels outside your view to improve performance.
  • The main purpose of the fragment shader is to calculate the final color of a pixel, and this is where all the advanced OpenGL effects come from. Typically, a fragment shader contains 3D scene data (such as lighting, shadows, light colors, etc.), which can be used to calculate the final pixel color.
  • The final object will be passed to the final stage, which we call Alpha testing and blending (Blending) stage. This stage detects the corresponding depth (and template (Stencil)) values ​​​​of the fragment (described later), and uses them to determine whether this pixel is in front of or behind other objects, and decides whether it should be discarded. This stage also checks the alpha value (the alpha value defines the transparency of an object) and blends the object (Blend). So, even if the output color of a pixel is calculated in the fragment shader, the final pixel color may be completely different when rendering multiple triangles.
  • In modern OpenGL we have to define at least one vertex shader and one fragment shader.

vertex input

  • Drawing graphics requires vertices. In OpenGL, vertices use 3D coordinates, namely x, y and z, and the range is between [-1, 1]. We call this range Normalized Coordinates . Points outside the range will not be displayed.
float vertices[] = {
    
    
    -0.5f, -0.5f, 0.0f,
     0.5f, -0.5f, 0.0f,
     0.0f,  0.5f, 0.0f
};
  • insert image description here
  • Normalized device coordinates are converted to screen space coordinates through glViewportthe function to change the viewport.
  • verticesThe points in are now in memory (your code memory), we need to send them to the vertex shader. The vertex shader allocates a space in video memory to store these points. At the same time you have to tell OpenGL how to interpret the data.
  • Vertex Buffer Objects (VBO) are responsible for managing this video memory.
unsigned int VBO;
glGenBuffers(1, &VBO);
  • glGenBuffersCreate the VBO using
  • glBindBuffer(GL_ARRAY_BUFFER, VBO); Bind this VBO to a vertex buffer object in the OpenGL Context.
  • glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);Copy data from memory to video memory
    • GL_STATIC_DRAW : The data will not or will hardly change.
    • GL_DYNAMIC_DRAW: The data will be changed a lot.
    • GL_STREAM_DRAW : The data changes each time it is drawn.

Vertex Shader (Vertex Shader)

  • Modern OpenGL requires us to set up at least one vertex and one fragment shader. Write vertex shaders using GLSL (OpenGL Shading Language).
#version 330 core
layout (location = 0) in vec3 aPos;

void main()
{
    
    
    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}
  • #version 330 coreDeclare version and core schema. In OpenGL 3.3 and higher, the GLSL version number matches the OpenGL version.
  • inDeclare Input Vertex Attribute
  • vec3Vector type, vec.x, vec.y, vec.z get different components
  • gl_PositionIs the output of the Vertex Shader. We have to assign position data to it. it is a vec4type

Compile the shader

const char *vertexShaderSource = R"(
    #version 330
    layout (location = 0) in vec3 aPos;
    layout (location = 1) in vec3 bPos;
    void main()
    {
        gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
    }
)";

auto vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if (!success) {
    
    
    glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
    std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n"
              << infoLog << std::endl;
    return -1;
}
  • glCreateShader(GL_VERTEX_SHADER)Create a vertex shader whose type is GL_VERTEX_SHADER to indicate its type.
  • glShaderSource(vertexShader, 1,&vertexShaderSource, NULL);Sets the source code of the shader object.
  • glCompileShader(vertexShader);Compile the shader.
  • glGetShaderivCheck if the compilation is successful

Fragment Shader

  • What the fragment shader does is calculate the final color output of the pixel
#version 330 core
out vec4 FragColor;

void main()
{
    
    
    FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
} 
  • Fragment shaders only need to output one variable. The variable is defined using outthe keyword .
  • Compiling a fragment shader is similar to compiling a vertex shader
unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);

shader program

  • Shader Program Object (Shader Program Object) is the merged and finally linked version of multiple shaders.
  • We must link (Link) multiple compiled shaders into a shader program object and activate it when rendering.
unsigned int shaderProgram;
shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success) {
    
    
    glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog)
    std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n
              << infoLog << std::endl;
    return -1;
}
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);

glUseProgram(shaderProgram);
  • glCreateProgramCreate a shader program object
  • glAttachShaderAttaching vertex shaders and fragment shaders to program objects (modification of program object attributes)
  • glLinkProgram(shaderProgram);link them.
  • glUseProgram(shaderProgram);Bind the program object to the OpenGL Context and activate it.
  • glDeleteShader, Link remember to delete the shader objects in the future, we don't need them anymore.

link vertex attributes

  • Earlier, we fed a bunch of data to the vertex shader via VBO. Allows us to specify any input in the form of vertex attributes.
  • This is very flexible, but also means that we have to manually specify which part of the input data corresponds to which vertex attribute of the vertex shader.
    insert image description here
  • glVertexAttribPointerTell OpenGL how to interpret the data in the VBO
  • glEnableVertexAttribArrayEnables vertex attributes; vertex attributes are disabled by default.

Vertex Array Object (VAO)

  • OpenGL's core mode requires us to use VAOs so it knows what to do with our vertex input. If we fail to bind the VAO, OpenGL will refuse to draw anything.
  • A vertex array object stores the following:
    • Calls to glEnableVertexAttribArray and glDisableVertexAttribArray.
    • Vertex attribute configuration set by glVertexAttribPointer.
    • Call the vertex buffer object associated with the vertex attribute through glVertexAttribPointer.
  • glGenVertexArraysCreate VAOs
  • Use VAO and VBO to draw the graphics process, as follows:
// ..:: 初始化代码(只运行一次 (除非你的物体频繁改变)) :: ..
// 1. 绑定VAO
glBindVertexArray(VAO);
// 2. 把顶点数组复制到缓冲中供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. 设置顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

[...]

// ..:: 绘制代码(渲染循环中) :: ..
// 4. 绘制物体
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
someOpenGLFunctionThatDrawsOurTriangle();

triangle!

glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
  • The glDrawArrays function, which uses the currently active shader, the previously defined vertex attribute configuration, and the vertex data of the VBO (indirectly bound through the VAO) to draw the primitive

element buffer object

  • Drawing a rectangle requires 4 vertices, but glDrawArrays can only draw points, lines, and triangles. Can't draw a rectangle. Fortunately, you can draw two triangles for this purpose. The vertices are as follows:
float vertices[] = {
    
    
    // 第一个三角形
    0.5f, 0.5f, 0.0f,   // 右上角
    0.5f, -0.5f, 0.0f,  // 右下角
    -0.5f, 0.5f, 0.0f,  // 左上角
    // 第二个三角形
    0.5f, -0.5f, 0.0f,  // 右下角
    -0.5f, -0.5f, 0.0f, // 左下角
    -0.5f, 0.5f, 0.0f   // 左上角
};
  • Specify the bottom right and top left twice! A rectangle has only 4 vertices instead of 6, which incurs 50% additional overhead. A better solution is to just store the distinct vertices, and set the order in which they are drawn. In this way, we only need to store 4 vertices to draw a rectangle. EBO does just that.
float vertices[] = {
    
    
    0.5f, 0.5f, 0.0f,   // 右上角
    0.5f, -0.5f, 0.0f,  // 右下角
    -0.5f, -0.5f, 0.0f, // 左下角
    -0.5f, 0.5f, 0.0f   // 左上角
};

unsigned int indices[] = {
    
    
    // 注意索引从0开始! 
    // 此例的索引(0,1,2,3)就是顶点数组vertices的下标,
    // 这样可以由下标代表顶点组合成矩形

    0, 1, 3, // 第一个三角形
    1, 2, 3  // 第二个三角形
};
unsigned int EBO;
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
  • glDrawElementsDraw using the index in the currently bound index buffer object.

Guess you like

Origin blog.csdn.net/weiwei9363/article/details/128958736