preface
New knowledge is learned step by step, from the basic to the complex. OpenGL ES has introduced the concept of OpenGL ES. This article starts with the official start of the OpenGL ES rendering series first stop — drawing triangles. Triangle drawing does not involve complex matrix transformation and texture sampling. When rendering OpenGL ES context is not using the native GLSurfaceView, but their reference GLSurfaceView process in NDK layer to achieve a set of EGL rendering context logic, the advantage is that they more clearly understand the implementation of OpenGL ES context switch logic. Interested can refer to the article OpenGL ES upgrade play strange GLSurfaceView source analysis analysis and implementation.
Renderer glsurfaceView. Renderer glsurfaceView. Renderer glsurfaceView. Renderer glsurfaceView. Renderer
- onSurfaceCreated(ANativeWindow *nativeWindow)
- onSurfaceChanged(ANativeWindow *nativeWindow, int format, int width, int height)
- Draw () // Render onDrawFrame(gl: GL10?)
OpenGL ES Rendering principles
OpenGL rendering principle involves computer graphics knowledge, interested students can study.
Review of OpenGL ES concepts
shader
Shaders are small programs that run on a GPU and can handle vertex and slice dependent calculations. We only care about the vertex shader and the fragment shader code, and the shader language is GLSL. The main uses are vertex shaders and chip shaders
Introduction of GLSL
GLSL is a high-level graphical programming language, derived from C language, at the same time it provides a richer native type for image processing, such as vector, matrix and so on.
OpenGLES contains the following features:
- GLSL is a high-level procedural language (not object-oriented);
- The basic syntax of GLSL is basically the same as that of C/C++;
- Perfect support vector and matrix operations.
- To manage input and output types through type qualifier operations
- GLSL provides a large number of built-in functions to provide rich extensions.
- In short, OpenGL ES coloring language is a kind of easy to implement, powerful, easy to use, and can be highly parallel processing, excellent performance of the high-level graphics programming language.
For more details on GLSL syntax, see GLSL in OpenGL
Vertex Array Object buffer
VAO for short, VAO is a reference to VBO that holds all vertex attributes and does not itself store any vertex information.
Vertex Buffer Object
VBO for short, VBO opens a memory space in GPU memory for storing various vertex attribute information, including vertex coordinates, vertex normal vector, vertex color data, etc. The advantage is that the vertex attribute information can be read directly from video memory during rendering, instead of being passed in from the CPU, which is more efficient.
Element Buffer Object
EBO for short, EBO is to create a memory space in GPU memory for storing all vertex location index indices.
Draw the way
OpenGL ES supports three types of drawing modes, including points, line segments, and triangles. Each type contains one or more drawing modes. The specific drawing modes are as follows:
- Point drawing method: GL_POINTS;
- Line segment drawing methods: GL_LINES, GL_LINE_STRIP, GL_LINE_LOOP;
- Triangles can be drawn using GL_TRIANGLES, GL_TRIANGLE_STRIP, and GL_TRIANGLE_F
How to implement OpenGL ES rendering
To review how to implement the OpenGL ES rendering process, there are several steps to achieve rendering:
- Load the vertex shader and slice shader code
- Compile the shader code
- Create program build link compiled shader
- Load vertex coordinates and color coordinates or texture coordinates
- Draw the corresponding graph according to the drawing method
Draw a triangle
To start our rendering process, we will create a TriangleFilter object, which is the same as glSurfaceView.renderer. My own Demo uses VBO and EBO to store vertex coordinate data and index coordinate data respectively. For the concepts of VBO and EBO, refer to the advanced concepts of OpenGL ES concepts. This article describes how to use VBO and EBO.
Initialize vertex coordinates
DEMO uses VBO to store vertex coordinate data and color data.
GLfloat *vertex_color_coords = new GLfloat[] {
// Positions // Colors
0.0 f.0.5 f.0.0 f.1.0 f.0.0 f.0.0 f.1.0 f.// Top Right
0.5 f.0.5 f.0.0 f.0.0 f.1.0 f.0.0 f.1.0 f.// Bottom Right
0.5 f.0.5 f.0.0 f.0.0 f.0.0 f.1.0 f.1.0 f.// Bottom Left
};
Copy the code
Initialize the index coordinates
DEMO uses EBO to store index data.
GLshort *vertex_indexs = new GLshort[] {0.1.2};
Copy the code
Initialize the shader code
Vertex shader code in DEMO:
const char *vShaderStr =
"#version 300 es \n"
"layout(location = 0) in vec3 aPosition; \n"
"layout(location = 1) in vec4 aColor; \n"
"out vec4 vColor; \n"
"void main() \n"
"{ \n"
"Gl_Position = VEC4 (aPosition, 1.0); \n"
" vColor = aColor; \n"
"} \n";
Copy the code
Slice shader code in DEMO:
const char *fShaderStr =
"#version 300 es \n"
"precision mediump float; \n"
"in vec4 vColor; \n"
"out vec4 fragColor; \n"
"void main() \n"
"{ \n"
" fragColor = vColor; \n"
"} \n";
Copy the code
Version 300 ES in the above code is due to OpenGL ES 3.0 corresponding to GLSL version 3.0
Compile and link shaders
Since the shader code is running on the GPU code, so we can’t use it directly, so how can we use it? We need to compile the Shader code and link it to the program instance. It can be understood that the program is a pointer to the featured program. When the program is linked to the Shader program, we can operate with the program.
Compiler shader
Compiles for different shader types and returns the shader’s special pointer, which is considered usable only if it is greater than 0.
GLuint vertexShader = compileShader(GL_VERTEX_SHADER, vertexShaderSource);
GLuint fragmentShader = compileShader(GL_FRAGMENT_SHADER, fragmentShaderSource);
Copy the code
The vertex shader uses type GL_VERTEX_SHADER and the fragment shader uses type GL_FRAGMENT_SHADER. Compile shader code as follows:
GLuint GLShaderUtil::compileShader(GLenum type, const char *shaderSource) {
DLOGD(GLShaderUtil_TAG, "~~~compileShader~~~\n");
GLuint shader = glCreateShader(type);
checkGlError("glCreateShader");
if (shader == 0) {
DLOGE(GLShaderUtil_TAG, "Could not create new shader.\n");
}
glShaderSource(shader, 1, &shaderSource, NULL);
checkGlError("glShaderSource");
glCompileShader(shader);
checkGlError("glCompileShader");
GLint compiled = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
if(! compiled) { GLint infoLen =0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
if (infoLen) {
char *buf = (char *) malloc(sizeof(char) * infoLen);
if (buf) {
glGetShaderInfoLog(shader, infoLen, NULL, buf);
DFLOGE(GLShaderUtil_TAG, "Could not compile shader %d:\n%s\n", shader, buf);
}
free(buf);
}
glDeleteShader(shader);
shader = 0;
} else {
DFLOGD(GLShaderUtil_TAG, "Create type = 0x%x, shader = %d Success! \n", type, shader);
}
return shader;
}
Copy the code
OpenGL is only a standard API, and the implementation is implemented by various manufacturers. Therefore, when we locate problems, we find that we cannot debug into the GPU. Of course, some tools can, such as RenderDoc (I don’t use this tool, but I know it exists. If you are interested, you can study it and let me know. One might ask how do we locate problems without the debug tool? That’s what I’m going to tell you. I use glGetError and glGetShaderInfoLog to get the error codes and Shader error logs.
The following code detects errors after executing an OpenGL API. The returned error code can be found in the
header file. You can refer to the error code to locate the problem. Review your own implementation if it does. (Maybe you think it is nonsense, when you compare the normal code review several times naturally understand)
static void checkGlError(const char* op) {
GLint error;
for (error = glGetError(a); error; error =glGetError()) {
if (DebugEnable && GL_ERROR_DEBUG) {
DFLOGE(GL_ERROR_TAG, "error::after %s() glError (0x%x)\n", op, error); }}}Copy the code
Link shader
The next step is to create program and link shader programs. Program only makes sense if it is greater than 0.
GLuint program = linkProgram(vertexShader, fragmentShader);
Copy the code
The link shader code is as follows:
GLuint GLShaderUtil::linkProgram(GLuint vertexShader, GLuint fragmentShader) {
DLOGD(GLShaderUtil_TAG, "~~~linkProgram~~~\n");
GLuint program = glCreateProgram(a);if (program) {
glAttachShader(program, vertexShader);
checkGlError("glAttachShader");
glAttachShader(program, fragmentShader);
checkGlError("glAttachShader");
glLinkProgram(program);
checkGlError("glLinkProgram");
glDetachShader(program, vertexShader);
glDetachShader(program, fragmentShader);
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
GLint linkStatus = 0;
glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
if(! linkStatus) { GLint infoLen =0;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLen);
if (infoLen) {
char *buf = (char *) malloc(sizeof(char) * infoLen);
if (buf) {
glGetProgramInfoLog(program, infoLen, NULL, buf);
DFLOGE(GLShaderUtil_TAG, "Could not link program %d:\n%s\n", program, buf);
}
free(buf);
}
glDeleteProgram(program);
program = 0; }}else {
DLOGE(GLShaderUtil_TAG, "Could not create program.\n");
}
return program;
}
Copy the code
Load vertex coordinates and color data
This Demo uses VAO, VBO to store vertex coordinate information and color data. VAO and VBO initialization:
//generate vao vbo
glGenVertexArrays(1, &vao); // Generate a VAO object
checkGlError("glGenVertexArrays");
glGenVertexArrays(1, &vbo); // Generate VBO objects
checkGlError("glGenVertexArrays")
Copy the code
Load vertex data and color data to VBO:
// Bind VAO
glBindVertexArray(vao); // Bind VAO objects
checkGlError("glBindVertexArray");
glBindBuffer(GL_ARRAY_BUFFER, vbo);// Bind VBO objects
checkGlError("glBindBuffer vbo");
glBufferData(GL_ARRAY_BUFFER, VERTEX_COLOR_COORDS_LENGTH * BYTES_PER_FLOAT, vertex_color_coords, GL_STATIC_DRAW);// Fill the memory managed by the buffer object, allocate a block of video memory, and store vertex_COLOR_coords in it
checkGlError("glBufferData vbo");
// Position attribute
glEnableVertexAttribArray(0);// Enable object location = 0 in Shader
checkGlError("glEnableVertexAttribArray position");
glVertexAttribPointer(0, COORDS_PER_VERTEX, GL_FLOAT, false, STRIDE_PRE_COORD * BYTES_PER_FLOAT,(const void *) nullptr); // Specify the data format and location of the vertex property array
checkGlError("glVertexAttribPointer position");
// Color attribute
glEnableVertexAttribArray(1);// Enable object location = 1 in Shader
checkGlError("glEnableVertexAttribArray color");
glVertexAttribPointer(1, COORDS_PER_COLOR, GL_FLOAT, false, STRIDE_PRE_COORD * BYTES_PER_FLOAT,(const void *) (COORDS_PER_VERTEX * BYTES_PER_FLOAT));// Specify the data format and location of the color array
checkGlError("glVertexAttribPointer color");
Copy the code
Loading index data
This DEMO uses EBO to store index data.
//generate ebo
glGenVertexArrays(1, &ebo); // Generate an EBO object
checkGlError("glGenVertexArrays");
Copy the code
Load index data to EBO:
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
checkGlError("glBindBuffer ebo");
glBufferData(GL_ELEMENT_ARRAY_BUFFER, VERTEX_INDICES_LENGTH * BYTES_PER_SHORT, vertex_indexs, GL_STATIC_DRAW);
checkGlError("glBufferData ebo");
Copy the code
draw
OpenGL ES provides two apis for drawing, glDrawArrays and glDrawElements. The difference between the two is that glDrawElements can be rendered according to the specified vertex index. GlDrawElements are rendered here.
/ / use the EBO
glDrawElements(GL_TRIANGLES, VERTEX_INDICES_LENGTH, GL_UNSIGNED_SHORT, (const void *) nullptr);
// Do not use EBO
glDrawElements(GL_TRIANGLES, VERTEX_INDICES_LENGTH, GL_UNSIGNED_SHORT, vertex_indexs);
Copy the code
As you can see, the data passed in using EBO is different from the data passed in without EBO: the pointer offset is passed in using EBO, and the pointer is passed in without EBO.
rendering
Source code portal
See the article GLSL in OpenGL