OpenGL (I) OpenGL Pipeline and Programmable Pipeline Flow

Keywords: Attribute shell Mobile

Since OpenGL is a pure rendering core, to write OpenGL programs, you need to build a shell program first. There are different implementations of shell programs under different platforms. This series of articles are all carried out on win32 platform. Specific implementation, the Internet can find many, so this is not the focus of this article. This article mainly discusses the construction of OpenGL pipeline itself, specifically, the construction of programmable pipeline.

Main process

The general process is as follows:

  1. Pull up the shell program in the main function and initialize some default parameters. For example, double buffering, 32-bit color, etc.
  2. Create the GL context (HGLRC).
  3. Loading & Setting OpenGL Related Parameters
  4. Drawing in Frame Loop

As for fixed and programmable pipelines, the main difference is in parts 3 and 4. Fixed pipeline is to write the drawing method to the code, and programmable pipeline can be understood as scripting the rendering logic. However, the data part rendered by the two methods is still provided by the compiled program.

Specifically, through a grammar definition, a text file is instantiated into memory, and dynamically compiled, mounted and linked to GPU program at run time to complete the replacement of rendering algorithm. Correspondingly, since all data is stored in memory, when drawing, it needs to be transferred to the GPU, which is the glBuffer. Buffers on the CPU will submit data from the buffer to the GPU in some time periods.

Realization

Generally speaking, the programmable pipeline generates a drawing program dynamically. In each GPU core, the drawing command is used to draw data to him. Look at the code snippets in two parts:

The first is to create a GPU drawing program.

GLuint CreateGPUProgram(const char* vsShaderPath,const char* fsShaderPath)
{
    GLuint vsShader = glCreateShader(GL_VERTEX_SHADER);
    GLuint fsShader = glCreateShader(GL_FRAGMENT_SHADER);

    //Load
    const char* vsCode = LoadFileContent(vsShaderPath);
    const char* fsCode = LoadFileContent(fsShaderPath);

    //Compile
    glShaderSource(vsShader,1,&vsCode,nullptr);
    glShaderSource(fsShader,1,&fsCode,nullptr);
    glCompileShader(vsShader);
    glCompileShader(fsShader);

    //Attach
    GLuint program = glCreateProgram();
    glAttachShader(program,vsShader);
    glAttachShader(program,fsShader);

    //Link
    glLinkProgram(program);

    //Clear
    glDetachShader(program,vsShader);
    glDetachShader(program,fsShader);
    glDeleteShader(vsShader);
    glDeleteShader(fsShader);

    return program;
}

When the GPU program is created, you can write a dead loop in the main thread and then call the following drawing command:

struct Vertex
{
    float pos[3];
    float color[3];
};

void VFRender()
{
    glUseProgram(s_program);
    glUniformMatrix4fv(s_shaderData.MLocation,1,GL_FALSE,identify);
    glUniformMatrix4fv(s_shaderData.VLocation,1,GL_FALSE,identify);
    glUniformMatrix4fv(s_shaderData.PLocation,1,GL_FALSE,glm::value_ptr(s_shaderData.projection));

    glBindBuffer(GL_ARRAY_BUFFER,s_shaderData.vbo);
    glEnableVertexAttribArray(s_shaderData.posLocation);
    glVertexAttribPointer(s_shaderData.posLocation,3,GL_FLOAT,GL_FALSE,sizeof(Vertex),nullptr); 
    glEnableVertexAttribArray(s_shaderData.colorLoacation);
    glVertexAttribPointer(s_shaderData.colorLoacation,3,GL_FLOAT,GL_FALSE,sizeof(Vertex),(void*)(sizeof(float)*3)); 

    glDrawArrays(GL_TRIANGLES,0,3);
    glBindBuffer(GL_ARRAY_BUFFER,0);
    glUseProgram(0);
}

Among them, the vertex data drawn is s_shaderData.vbo, and these VBO (Vertex Buffer Object) data are usually ready before drawing. I will write an article about the principle of VBO later.

GL2.0 restriction

Since most mobile phones in the market are still OpenGL 2.0, this paper summarizes the points needing attention in 2.0:

  • For the worst hardware supporting GL2.0, only eight Vector4 inputs are supported. That is, the vec4 input for attribute should not exceed 8. Uni is not subject to this restriction.
  • Matrix will occupy 4 vec4, so attribute s should not be used to modify mat4, and uniform should be used.
  • Variables passed by VertexShader and FragmentShader are marked with varying.
  • In VS, gl_Position must be assigned a value, otherwise it cannot be drawn.

GLSL example

The simplest shader can be written as follows:

VertexShader

attribute vec3 pos;
attribute vec4 color;

uniform mat4 M;
uniform mat4 V;
uniform mat4 P;

varying vec4 V_Color;
void main()
{
    V_Color = color;
    gl_Position = P*V*M*vec4(pos,1.0);
}

FragmentShader

varying vec4 V_Color;

void main(){
    gl_FragColor = V_Color;
}

Simplified, VertexShader processes 3d rendering information and outputs 2d rendering information. FragmentShader processes 2D information in color.

summary

This is the process of building OpenGL pipeline. It should be noted that some variables need to be passed from C++ programs to OpenGL. This is because viewport programs often provide some way to dynamically change these values. So it's more flexible to import shader from the outside. If these values can be declared directly in the shader during the testing phase, the trouble of passing values across programs is eliminated.

Pay attention to my Wechat Public Number and get more quality content

Posted by plinares on Sun, 14 Apr 2019 23:00:32 -0700