preface

In the last article, the OpenGL environment has been built, so this article is to formally use OpenGL to draw a square from scratch, so that we are familiar with the basic process of Drawing OpenGL.

1. Initialize the library and set related parameters

In the main function of main. CPP, complete the initialization of the GLUT and GLEW libraries, basic Settings, and the registration of some callback methods

int main(int argc,char *argv[])
{
    // Set the current working directory for MAC OS X
    /* 'GLTools' function' glSetWorkingDrectory 'sets the current working directory. In fact, this is not necessary in Windows because the working directory is the same directory as the program executable by default. But on Mac OS X, this program changes the current working folder to the '/Resource' folder in the application bundle. The 'GLUT' priority setting does this automatically, but the method is more secure. * /
    gltSetWorkingDirectory(argv[0]);
      
    // Initialize GLUT library. This function just legends command arguments and initializes GLUT library
    glutInit(&argc, argv);
    
    /* Initialize the double buffer window, where GLUT_DOUBLE, GLUT_RGBA, GLUT_DEPTH, GLUT_STENCIL indicate the double buffer window, RGBA color mode, depth test, and template buffer, respectively. Double-cached Windows, where a drawing command is actually executed off-screen from the cache and then quickly converted to a window view, are often used to generate animation effects; --GLUT_DEPTH ': flag allocates a depth cache as part of the display, so we can perform a depth test; GLUT_STENCIL: Make sure we also have a template cache available. * /
    glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGBA|GLUT_DEPTH|GLUT_STENCIL);
    
    //GLUT window size, window title
    glutInitWindowSize(800.600);
    glutCreateWindow("Square");
    
    /* GLUT internally runs a local message loop that intercepts the appropriate message. Then call the callback function that we registered at different times. We registered two callback functions: 1) a callback for window resizing and 2) a callback that contains OpenGL rendering */
    // Register the remodeling function
    glutReshapeFunc(changeSize);
    // Register the display function
    glutDisplayFunc(RenderScene);
    
    /* Initialize a GLEW library to ensure that the OpenGL API is fully available to the program. Before attempting to do any rendering, check to make sure there are no problems with driver initialization */
    GLenum status = glewInit();
    if(GLEW_OK ! = status) {printf("GLEW Error:%s\n",glewGetErrorString(status));
        return 1;     
    }
    
    // Set up our render environment
    setupRC();
    // Run a loop similar to iOS runloop
    glutMainLoop();

    return  0;
}
Copy the code

Most of the above code is basically the same across multiple projects, because OpenGL functions are procedural-oriented functions similar to C, so there is a fixed flow. The only functions we need to manipulate here are changeSize, RenderScene, and setupRC().

2. Set the changeSize

This is a window change function, also known as the reshape function. Simply put, after we draw a pattern, we stretch the window with the mouse, and the pattern is distorted. This function is used to adapt the window, that is, the pattern does not deform after the window is stretched.

/* Receives new width & height when the window size changes. * /
void changeSize(int w,int h)
{
    /* x and y parameters represent the coordinates of the lower left corner of the view in the window, while width and height are represented by pixels. Usually x and y are 0 */
    glViewport(0.0, w, h);
    
}
Copy the code

3. SetupRC sets up the rendering environment

Here we copy the vertex data to batch container and set the drawing method GL_TRIANGLE_FAN.

// Define a shader manager
GLShaderManager shaderManager;

// A simple batch container is a simple container class for GLTools.
GLBatch triangleBatch;

/ / blockSize side length
GLfloat blockSize = 0.2 f;

// 4 points of the square
GLfloat vVerts[] = {
        -blockSize,-blockSize,0.0 f,
        blockSize,-blockSize,0.0 f,
        blockSize,blockSize,0.0 f,
        -blockSize,blockSize,0.0 f
};

void setupRC(a)
{
    // Set the clear screen color (background color)
    glClearColor(0.1 f.0.4 f.0.7 f.1);
  
    // No rendering can be done in the OpenGL core framework without shaders. Initialize a render manager.
    shaderManager.InitializeStockShaders();

    //GL_TRIANGLE_FAN, 4 vertices
    triangleBatch.Begin(GL_TRIANGLE_FAN, 4);
    triangleBatch.CopyVertexData3f(vVerts);
    triangleBatch.End();    
}
Copy the code

GLBatch is a basic batch class that can be used to draw various graphs. GL_TRIANGLE_FAN is a drawing type of primitives. There are ten primitives in OpenGL, and each model can be composed of these ten primitives.

4. RenderScene square

This function clears the cache, sets the storage shader type and line color, submits the storage shader, and swaps the buffers.

void RenderScene(void)
{
    //1. Clear a specific cache or group of caches
    /* A buffer is a block of storage space containing image information. The red, green, blue, and alpha components are often referenced together as color caches or pixel caches. More than one buffer in OpenGL (color cache, depth cache, and template cache) clears the cache to preset values: GL_COLOR_BUFFER_BIT: indicates the currently active buffer for color writing GL_DEPTH_BUFFER_BIT: indicates the depth buffer GL_STENCIL_BUFFER_BIT: indicates the template buffer */
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
    //2. Set a set of floating-point numbers to represent red
    GLfloat vRed[] = {1.0.0.0.0.0.1.0 f};
  // Passes to the storage shader, the GLT_SHADER_IDENTITY shader, which simply renders the geometry on the screen using the specified color and default Cartesian coordinates
    shaderManager.UseStockShader(GLT_SHADER_IDENTITY,vRed);
    // Submit the shader
    triangleBatch.Draw();
    // When we first set up the openGL window, we specified a double buffer rendering environment. This means that the rendering will be done in the background buffer and then swapped out to the foreground. This prevents the viewer from seeing the rendering process that might be accompanied by flicker from animation frame to animation frame. The buffer switching platform will proceed in a platform-specific manner.
    // Render the background buffer and swap it to the foreground when finished
    glutSwapBuffers();
    
}
Copy the code

One question that might arise here is, why clear the cache? OpenGL is a state machine, it can record its own state (such as the current use of color, whether to enable depth testing, whether to enable the mixing function, etc.) detailed explanation in OpenGL entry (a) — OpenGL professional term parsing

The GLT_SHADER_IDENTITY storage shader used is a wrapped shader that just needs to be called, we just need to call it without knowing its internal logic. So if you use a storage shader, you don’t have to customize the shader and compile links.

GlutSwapBuffers are the buffers that will be exchanged between the screen buffer and the off-screen buffer

Summary and case address

The whole process looks relatively simple, but the API seems strange, interested students can quickly start to try! Click to see the full Demo address

Extension: How do I use the arrow keys to move this square around the screen?

Ideas:

  1. Determine the coordinates of the four vertices of the square
  2. glutSpecialFunc(SpecialKeys);Registers a special key response function.(This function is called when the user uses a special key.)
  3. Set the moving step size, set the relative moving vertex to coordinate D (-blocksize,blockSize), and then judge the upper, lower, left and right keys to calculate the moving distance
  4. Update vertex coordinates (ABCD position) according to coordinate D
void SpecialKeys(int key, int x, int y){
    / / step length
    GLfloat stepSize = 0.05 f;
    
    / / coordinates of D
    GLfloat blockX = vVerts[0];
    GLfloat blockY = vVerts[10];

    if (key == GLUT_KEY_UP) {
        
        blockY += stepSize;
    }
    
    if (key == GLUT_KEY_DOWN) {
        
        blockY -= stepSize;
    }
    
    if (key == GLUT_KEY_LEFT) {
        blockX -= stepSize;
    }
    
    if (key == GLUT_KEY_RIGHT) {
        blockX += stepSize;
    }
    // Update ABCD coordinates
    // Recalculate vertex positions
    vVerts[0] = blockX;
    vVerts[1] = blockY - blockSize*2;
    
    printf("(%f,%f)\n",vVerts[0],vVerts[1]);
    
    
    vVerts[3] = blockX + blockSize*2;
    vVerts[4] = blockY - blockSize*2;
    printf("(%f,%f)\n",vVerts[3],vVerts[4]);
    
    vVerts[6] = blockX + blockSize*2;
    vVerts[7] = blockY;
    printf("(%f,%f)\n",vVerts[6],vVerts[7]);
    
    vVerts[9] = blockX;
    vVerts[10] = blockY;
    printf("(%f,%f)\n",vVerts[9],vVerts[10]);
    
    triangleBatch.CopyVertexData3f(vVerts);
    
    glutPostRedisplay();
}
Copy the code

Boundary processing

The square movement has been controlled by the arrow keys. During the test, we found that the square would move out of the screen, so we still need to solve the boundary problem.

// Handle all four boundaries
    
    // When the square moves past the leftmost point
    if (blockX < 1.0 f) {
        blockX = 1.0 f;
    }
    
    // When the square moves to the right
    //1.0 - blockSize * 2 = Total side length - Square side length = position of leftmost point
    if (blockX > (1.0 - blockSize * 2)) {
        blockX = 1.0 f - blockSize * 2;
    }
    
    // When the square moves to the bottom
    //-1.0 - blockSize * 2 = Y (negative axis boundary) - square side length = the position of the lowest point
    if (blockY < 1.0 f + blockSize * 2 ) {
        
        blockY = 1.0 f + blockSize * 2;
    }
    
    // When the square moves to the top
    if (blockY > 1.0 f) {
        
        blockY = 1.0 f;
        
    }
Copy the code

So we’re looking at the maximum and minimum coordinates of X and Y as we move to the upper, lower, left, and right boundaries of the square ABCD, D.

Vertices overload problem

Above is changing the position of the graph by updating the vertex coordinates. What if the graph is a polygon? And it’s a polygon with hundreds or thousands of vertices, so is this scheme still appropriate? Is it too complicated to update so many locations at a time? This is where the translation matrix comes in, see below.

OpenGL introduction (a) — OpenGL professional noun analysis

Introduction to OpenGL (2) — Build an OpenGL Mac environment

OpenGL Start (3) — Quickly draw a square

OpenGL introduction (4) – rendering process analysis

OpenGL introduction (five) – metagraph drawing actual combat

OpenGL Introduction (six) – Matrix basic change actual combat

OpenGL introduction (seven) – hidden surface elimination details

OpenGL Start (eight) – Texture coordinate parsing