Read "computer graphics programming (using OpenGL and C + +)" 4

When drawing an object, its vertex data needs to be sent to the vertex shader. Usually, the vertex data is put into a buffer on the C + + side, and the buffer is associated with the vertex attributes declared in the shader. The steps are as follows:

The steps that are only done once are generally placed in init().

1. Create a buffer.

2. Copy vertex data into the buffer.

If it's an animated scene, you have to do it every frame, usually in display().

1. Enables buffers that contain vertex data.

2. Associate this buffer with a vertex attribute.

3. Enable this vertex attribute.

4. Use glDrawArrays() to draw objects.

In OpenGL, the buffer is contained in the vertex buffer object (VBO), which is usually created uniformly at the beginning of the program.

When glDrawArrays() is executed, the data in the buffer begins to flow, starting from the beginning of the buffer and flowing through the vertex shader in order. The vertex shader executes once for each vertex. Vertices in 3D space need three values, so vertex attributes in shaders often receive these three values as vec3 type. Then, for each set of these three values in the buffer, the shader is called. Specify GL_ In triangles, rasterization is done triangle by triangle. The call to glDrawArrays() usually precedes other commands that adjust the rendering settings of this model.

There is also a related structure in OpenGL, called vertex array object (VAO), which creates at least one VAO.

The following two OpenGL commands create VAO and VBO respectively and return their integer IDs. The two commands each have two parameters: 1. How many IDs to create. 2. An array used to hold the returned ID.

glGenVertexArrays(GLsizei n, GLuint* arrays); 
glGenBuffers(GLsizei n, GLuint* buffers);

The glBindVertexArray() command marks the specified VAO as "active", so that the generated buffer will be associated with the VAO.

glBindVertexArray(GLuint array)

Each buffer needs to have a corresponding vertex attribute variable declared in the vertex shader. Vertex attributes are usually the first variables declared in the shader. We can declare this in vertex shader:

layout (location = 0) in vec3 position;

The keyword "in" means input, indicating that the vertex attribute will receive a value from the buffer.

"vec3" means that the shader will catch three floating-point values for each call, representing x, y and z respectively, which form a vertex data.

Variable name "position".

"layout (location = 0)" is called "layout modifier", which is how we associate vertex attributes with specific buffers. The identification number of this vertex attribute is 0.

Now let's draw a cube.

To render a scene so that it looks 3D, you need to build an appropriate transformation matrix. And apply them to each vertex of the model. Applying the required matrix operations in vertex shaders is the most efficient, and it is customary to send these matrices from C++/OpenGL applications to unified variables in shaders.

The keyword "uniform" declares a uniform variable in the shader.

uniform mat4 mv_matrix;
uniform mat4 proj_matrix;

The keyword "mat4" indicates that these are 4 × 4 matrix. Because 3D transformation is 4 × 4, so mat4 is a commonly used data type in GLSL shader unification.

Entities defined by vertices are converted to clips before the clip shader rasterizes. The rasterization process linearly interpolates vertex attribute values so that the displayed pixels can seamlessly connect the modeled surface.

The unified variable is similar to an initialized constant and remains unchanged in every vertex shader call (that is, every vertex sent from the buffer). The unified variable itself is not interpolated; No matter how many vertices there are, it always contains the same value.

Seeing vertex attributes declared as "in" in the vertex shader indicates that they receive values from the buffer. Vertex attributes can also be declared "out" instead, and they send the value to the next stage in the pipeline. OpenGL has a built-in vec4 variable named gl_Position is an "out" variable declared for the vertex position. The transformed vertices are automatically output to the raster shader, and finally the corresponding pixel positions are sent to the clip shader.

You can build three matrices and send them to a unified variable.

The model matrix represents the position and orientation of the object in the world coordinate space. If the model moves, the matrix needs to be rebuilt continuously.

The view matrix moves and rotates the model in the world to simulate the effect of the camera in the desired position. Because the camera can move, it also needs to be created once per frame. Build according to the required camera position and orientation. Combine the model and view matrix into a single "MV" matrix.

The perspective matrix is a transformation that provides a 3D effect based on the desired cone of view. It only needs to be created once. It needs to use the width and height of the screen window (and the required viewing cone parameters), which is usually unchanged unless the window is resized.

Send MV and projection matrix to the corresponding shader unified variable.

main.cpp

#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"
#include "glm/gtc/type_ptr.hpp"
#include "Utils.h"

using namespace std;

#define numVAOs 1
#define numVBOs 2

float cameraX, cameraY, cameraZ;
float cubeLocX, cubeLocY, cubeLocZ;
GLuint renderingProgram;
GLuint vao[numVAOs]; // OpenGL These values are required to be specified as an array
GLuint vbo[numVBOs];

// to display()use
GLuint mvLoc, projLoc;
int width, height;
float aspect;
glm::mat4 pMat, vMat, mMat, mvMat;

void setupVertices(void)
{
    float vertexPositions[108] = {
        -1.0f,  1.0f, -1.0f, -1.0f, -1.0f, -1.0f,  1.0f, -1.0f, -1.0f,
         1.0f, -1.0f, -1.0f,  1.0f,  1.0f, -1.0f, -1.0f,  1.0f, -1.0f,
         1.0f, -1.0f, -1.0f,  1.0f, -1.0f,  1.0f,  1.0f,  1.0f, -1.0f,
         1.0f, -1.0f,  1.0f,  1.0f,  1.0f,  1.0f,  1.0f,  1.0f, -1.0f,
         1.0f, -1.0f,  1.0f, -1.0f, -1.0f,  1.0f,  1.0f,  1.0f,  1.0f,
        -1.0f, -1.0f,  1.0f, -1.0f,  1.0f,  1.0f,  1.0f,  1.0f,  1.0f,
        -1.0f, -1.0f,  1.0f, -1.0f, -1.0f, -1.0f, -1.0f,  1.0f,  1.0f,
        -1.0f, -1.0f, -1.0f, -1.0f,  1.0f, -1.0f, -1.0f,  1.0f,  1.0f,
        -1.0f, -1.0f,  1.0f,  1.0f, -1.0f,  1.0f,  1.0f, -1.0f, -1.0f,
         1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f,  1.0f,
        -1.0f,  1.0f, -1.0f,  1.0f,  1.0f, -1.0f,  1.0f,  1.0f,  1.0f,
         1.0f,  1.0f,  1.0f, -1.0f,  1.0f,  1.0f, -1.0f,  1.0f, -1.0f,
    };

    glGenVertexArrays(1, vao); // Create a vao,And returns its integer type ID Save into array vao in
    glBindVertexArray(vao[0]); // activation vao
    glGenBuffers(numVBOs, vbo);// Create two vbo,And return their integer type ID Save into array vbo in

    glBindBuffer(GL_ARRAY_BUFFER, vbo[0]); // activation vbo Buffer 0
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertexPositions), vertexPositions, GL_STATIC_DRAW); // Copy the array containing vertex data into the active buffer (here is the 0th) VBO)
}

void init(GLFWwindow* window) {
    renderingProgram = Utils::createShaderProgram("vertShader.glsl", "fragShader.glsl");
    cameraX = 0.0f;  cameraY = 0.0f;    cameraZ = 8.0f;
    cubeLocX = 0.0f; cubeLocY = -2.0f; cubeLocZ = 0.0f; // along Y Move axis down to show Perspective
    setupVertices();
}

void display(GLFWwindow* window, double currentTime)
{
    glClear(GL_DEPTH_BUFFER_BIT);
    glUseProgram(renderingProgram);

    // obtain MV Reference of unified variables of matrix and projection matrix
    mvLoc = glGetUniformLocation(renderingProgram, "mv_matrix"); 
    projLoc = glGetUniformLocation(renderingProgram, "proj_matrix");

    // Construct perspective matrix
    glfwGetFramebufferSize(window, &width, &height);
    aspect = (float)width / (float)height;
    pMat = glm::perspective(1.0472f, aspect, 0.1f, 1000.0f); // 1.0472 radians = 60 degrees

    // Build view matrix, model matrix and view-Model matrix
    vMat = glm::translate(glm::mat4(1.0f), glm::vec3(-cameraX, -cameraY, -cameraZ));
    mMat = glm::translate(glm::mat4(1.0f), glm::vec3(cubeLocX, cubeLocY, cubeLocZ));
    mvMat = vMat * mMat;

    // The perspective matrix and MV The matrix is copied to the corresponding unified variable
    glUniformMatrix4fv(mvLoc, 1, GL_FALSE, glm::value_ptr(mvMat)); // GLM function call value_ptr()Returns a reference to matrix data
    glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(pMat));

    // take VBO Wire to the corresponding vertex attribute in the vertex shader
    glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);                  // Mark buffer 0 as active
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);  // Associate attribute 0 to buffer
    glEnableVertexAttribArray(0);                           // Enable the 0th vertex attribute

    // adjustment OpenGL Settings, drawing models
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);
    glDrawArrays(GL_TRIANGLES, 0, 36);                      // Execute the statement, 0 VBO The data in will be transferred to the owner of location 0 layout Modifier's vertex attribute. This sends the cube's vertex data to the shader.

}

int main(void)
{
    GLFWwindow* window;

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

    /* Create a windowed mode window and its OpenGL context */
    window = glfwCreateWindow(400, 300, "Hello World", NULL, NULL); //Unused parameters are used to allow full screen display and resource sharing
    if (!window)
    {
        glfwTerminate();
        return -1;
    }

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

    GLenum err = glewInit();
    if (err != GLEW_OK)
    {
        std::cout << "Error: " << glewGetErrorString(err) << std::endl;
    }
    glfwSwapInterval(1); // The interactive buffer interval is set to 1, that is, it is updated every frame. The switching interval represents the number of frames waiting before switching the buffer, which is usually called Vsync Vertical synchronization
    init(window);

    /* Loop until the user closes the window */
    while (!glfwWindowShouldClose(window))
    {
        display(window, glfwGetTime()); // glfwGetTime()return GLFW Elapsed time after initialization

        /* Swap front and back buffers */
        glfwSwapBuffers(window); // GLFW Two buffers are used by default

        /* Poll for and process events */
        glfwPollEvents(); // Handle window related events (such as key press events)
    }

    glfwDestroyWindow(window);
    glfwTerminate();
    return 0;
}

Vertex shader: vertshader glsl

#version 460
layout (location = 0) in vec3 position;
uniform mat4 mv_matrix;
uniform mat4 proj_matrix;
void main(void)
{ 
   gl_Position = proj_matrix * mv_matrix *vec4(position, 1.0);
}

Clip shader: fragshader glsl

#version 460
out vec4 color;
uniform mat4 mv_matrix;
uniform mat4 proj_matrix;
void main(void)
{ 
   color = vec4(1.0, 0.0, 0.0, 1.0);
}

The results are as follows:

 

Here, two vbos are established, but only one is used to load the cube vertices into the 0th VBO buffer.

The init() function also gives the position of the cube and camera in the world.

Construct a transformation matrix in the form of GLM call to the translate() function: specify the transformation value in the form of unit matrix and vector. Many GLM transformation operations use this method.

vMat = glm::translate(glm::mat4(1.0f), glm::vec3(-cameraX, -cameraY, -cameraZ));

Note that shaders all contain the same uniform variable declaration block. This is not always necessary, but it is usually a good practice to include the same unified variable declaration block in all shaders in a particular renderer.

Keywords: C++ OpenGL

Added by fansa on Sat, 29 Jan 2022 21:23:33 +0200