Glew, GLFW and GLM introduction
Now that you have the project, let's start by introducing the Open Source library for the project and why it's needed.
The OpenGL Extension Wrangler (Glew) is used to access OpenGL 3.2 API functions. Unfortunately, you can't simply use # include to access the OpenGL interface unless you want to use an older version of OpenGL. In modern OpenGL, API functions are determined at runtime (run time), not the compilation period (compile time). Glew can load OpenGL APIs at run time.
GLFW allows us to create windows across platforms and accept mouse keyboard messages. OpenGL does not process these window creation and input, so we need to do it ourselves. I chose GLFW because it was small and easy to understand.
OpenGL Mathematics (GLM) is a mathematical library that handles almost everything from vectors to matrices. Older versions of OpenGL provide functions like Glrotate, Gltranslate, and Glscale, and in modern OpenGL, these functions are no longer present, and we need to handle all the mathematical operations ourselves. GLM can help with many vector and matrix operations in subsequent tutorials.
In all the tutorials in this series, we've also written a small library TDOGL to reuse C + + code. This tutorial will contain Tdogl::shader and TDOGL::P rogram is used to load, compile, and link shaders.
What is shaders?
Shaders is a very important concept in modern OpenGL. The application is inseparable from it, and unless you understand it, the code doesn't make any sense.
Shaders is a GLSL applet that runs on the GPU rather than the CPU. They are written in OpenGL Shading Language (GLSL) languages and look like C or C + +, but they are a different language. Using shader is like writing a normal program: write code, compile, and finally link together to generate the final program.
Shaders is not a very good name, because it is not just coloring. Just remember that they are written in a different language, running on the graphics card on the small program on the line.
In older versions of OpenGL, shaders is optional. In modern OpenGL, shaders is necessary to be able to display objects on the screen.
For possible close-up understanding of shaders and graphics rendering pipelines, I recommend Durian software related articles the graphics Pipeline chapter.
What did the shaders actually do? It depends on what kind of shader.
Vertex Shaders
Vertex shader is mainly used to transform points (x, y, z coordinates) into different points. Vertices are just a point in the geometry, a point called Vectex, and multiple points called vertices (pronounced Ver-tuh-seez). In this tutorial, our triangles need three vertices (vertices) to make up.
The GLSL code for Vertex shader is as follows:
123456 |
#version 150 in vec3 vert; void main() { // does not alter the vertices at all gl_Position = vec4(vert, 1); } |
The first line of #version 150 tells OpenGL that this shader uses GLSL version 1.50.
The second row in vec3 Vert; Tell shader to need that vertex as input and put the variable vert.
The third line defines the function main, which is the shader run entry. This looks like C, but GLSL does not need to take any arguments, and does not return void.
Row Four gl_position = Vec4 (vert, 1), outputs the input vertices directly, and the variable gl_position is the OpenGL-defined global variable used to store the output of vertex shader. All vertex shaders need to be assigned a value of gl_position.
Gl_position is 4D coordinates (VEC4), but Vert is 3D coordinates (VEC3), so we need to convert vert to 4D coordinates vec4 (vert, 1). The second parameter 1 is assigned to the four-dimensional coordinates. We'll learn more about 4D coordinates in the next tutorial. But now, as long as we know that the coordinates of the four dimensions are 1, I can ignore it and treat it as a 3D coordinate.
Vertex shader didn't do anything in this article, and later we'll modify it to deal with animations, cameras and other things.
Fragment Shaders
The main function of Fragment shader is to calculate the color of each pixel point that needs to be drawn.
A "fragment" is basically a pixel, so you can think of the fragment shader (fragment shader) as the pixel shader (pixel shader). Each fragment in this article is a pixel, but this is not always the case. You can change one of the OpenGL settings to get a smaller fragment than the pixel, and then we'll talk about that later in the article.
The fragment shader code used in this article is as follows:
123456 |
#version 150 out vec4 finalColor; void main() { //set every drawn pixel to white finalColor = vec4(1.0, 1.0, 1.0, 1.0); } |
Again, the first line of #version 150 tells OpenGL that this shader is using GLSL 1.50.
The second line is Finalcolor = VEC4 (1.0, 1.0, 1.0, 1.0), and the output variable is set to white. VEC4 (1.0, 1.0, 1.0, 1.0) creates an RGBA color, and both red and green blue and Alpha are set to the maximum value, which is white.
Now, you can use shader to draw pure white in OpenGL. In the following articles, we will also add different colors and textures. The map is the image on your 3D model.
Compiling and linking shaders
In C + +, you need to compile your. cpp files and then link them together to form the final program. This is also true of OpenGL's shaders.
In this article, two reusable classes are used to handle the compilation and linking of shaders: Tdogl::shader and TDOGL::P rogram. There are not many of these two class codes, and with detailed comments, I suggest you read the source code and link to how OpenGL works.
What are Vbo and Vao?
When shaders is running on the GPU and other code is running on the CPU, you need a way to pass the data from the CPU to the GPU. In this article, we transmit a triangle of three vertex data, but in larger projects 3D models will have thousands of vertices, colors, mapping coordinates and other things.
That's why we need vertex Buffer Objects (VBOs) and vertex Array Objects (Vaos). Vbo and Vao are used to pass data from a C + + program to shaders for rendering.
In older versions of OpenGL, each frame of data was sent to the GPU through the Glvertex,gltexcoord and Glnormal functions. In modern OpenGL, all data must be sent to the video card before rendering via VBO. When you need to render some data, set Vao to describe what VBO data is pushed to the shader variable.
Vertex Buffer Objects (VBOs)
In the first step we need to upload three vertices of the triangle from memory to the video. That's what VBO should do. Vbo is actually the "buffer (buffers)" of the video memory-a sequence of byte regions containing various binary data. You can upload 3D coordinates, colors, and even your favorite music and poetry. Vbo doesn't care what this data is, because it simply replicates the memory.
Vertex Array Objects (Vaos)
The second step is to render the triangles in the shaders with VBO data. Keep in mind that Vbo is just a piece of data and it doesn't know the type of data. And tell OpenGL what type of data this buffer is in, and that's Vao tube.
The Vao Vbo and shader variables are connected. It describes the type of data that VBO contains, and the shader variable to which to pass the data. Of all the inaccuracies in OpenGL's technical terminology, "Vertex Array Object" is the worst of all, because it simply doesn't explain what Vao should do.
You look back at the vertex shader of this article (in front of the article) and you can see that we have only one input variable vert. In this article, we use Vao to illustrate "Hi,opengl, where the VBO has three vertices, I want you to vertex shader, send three vertex data to the vert variable. ”
In the following article, we will use Vao to say "Hi,opengl, here Vbo has the vertex, color, mapping coordinates, I want you in shader, send vertex data to vert variable, hair color data to vertcolor variable, Send map coordinates to the Verttexcoord variable. ”
Reminders for users who are using the previous OpenGL version:
If you used VBO in an older version of OpenGL but didn't use Vao, you might not agree with Vao's description. You'll argue that vertex properties can connect Vbo and shaders with Glvertexattribpointer instead of Vao. It depends on whether you think the vertex attribute should be Vao "built-in (inside)" (I think so), or whether they are a global state of Vao external. The 3.2 kernel and the AIT driver I used, VAO is not optional-no Vao package Glenablevertexattribarray, Glvertexattribpointer and gldrawarrays will cause gl_invalid_ Operation error. That's why I think the vertex attribute should be built into the VAO, not the global state. The 3.2 kernel manual also says Vao is a must, but I've only heard that ATI drivers can throw errors. The following describes the reference from the OpenGL 3.2 kernel manual
All data definitions related to vertex processing should be encapsulated in Vao. The general Vao boundary contains all commands that change the state of the vertex array, such as Vertexattribpointer and Enablevertexattribarray, and all commands that are drawn using the vertex array, For example, Drawarrays and drawelements; All commands for querying the vertex array status (see Chapter 6th).
Anyway, I also know why some people think that vertex attributes should be placed outside the Vao. Glvertexattribpointer appears earlier than Vao, during which the vertex attribute is always considered to be a global state. You should be able to see that Vao is an effective way to change the global state. I'm more inclined to think so: if you didn't create VAO, OpenGL passed a default global Vao. So when you use Glvertexattribpointer, you're still modifying the vertex properties within Vao, but now from the default Vao to the Vao you created yourself.
Here are more discussions: http://www.opengl.org/discussion_boards/showthread.php/174577-Questions-on-VAOs
Code explanation
Finally! The theory is done, and we start coding. OpenGL is not particularly friendly to beginners, but if you understand the concepts described earlier (SHADERS,VBO,VAO) then you have no problem.
Open Main.cpp, we start with the main () function.
First, we initialize the GLFW:
1234 |
glfwSetErrorCallback(OnError); if (!glfwInit()) throw std::runtime_error( "glfwInit failed" ); glfwSetErrorCallback(OnError)这一行告诉GLFW当错误发生时调用OnError函数。OnError函数会抛一个包含错误信息的异常,我们能从中发现哪里出错了。 |
Then we create a window with GLFW.
12345678 |
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
gWindow = glfwCreateWindow((int)SCREEN_SIZE.x, (int)SCREEN_SIZE.y,
"OpenGL Tutorial"
, NULL, NULL);
if
(!gWindow)
throw
std::runtime_error(
"glfwCreateWindow failed. Can your hardware handle OpenGL 3.2?"
);
|
The window contains a forward-compatible OpenGL 3.2 kernel context. If Glfwcreatewindow fails, you should lower the OpenGL version.
To create the last step of the window, we should set a "current" OpenGL context to the window just created:
1 |
glfwMakeContextCurrent(gWindow); |
Regardless of which OpenGL function we invoke, it will affect the current context. We'll just use a context, so when we're done, we'll leave it alone. Theoretically, we can have multiple windows, and each window can have its own context.
Now that our window has OpenGL context variables, we need to initialize Glew to access the OpenGL interface.
123 |
glewExperimental = GL_TRUE; //stops glew crashing on OSX :-/ if (glewInit() != GLEW_OK) throw std::runtime_error( "glewInit failed" ); |
Here the Glew and OpenGL kernel a little bit of a problem, set Glewexperimental can be repaired, but hope that the future will never happen.
We can also use Glew to confirm the existence of version 3.2 again:
12 |
if (!GLEW_VERSION_3_2) throw std::runtime_error( "OpenGL 3.2 API is not available." ); |
In the Loadshaders function, we use the Tdogl::shader and TDOGL provided in this tutorial::P rogram Two classes compile and link vertex shader and fragment shader.
1234 |
std::vector shaders; shaders.push_back(tdogl::Shader::shaderFromFile(ResourcePath( "vertex-shader.txt" ), GL_VERTEX_SHADER)); shaders.push_back(tdogl::Shader::shaderFromFile(ResourcePath( "fragment-shader.txt" ), GL_FRAGMENT_SHADER)); gProgram = new tdogl::Program(shaders); |
In the Loadtriangle function, we created a Vao and a vbo. This is the first step to create and bind a new Vao:
12 |
glGenVertexArrays(1, &gVAO); glBindVertexArray(gVAO); |
Then we create and bind the new VBO:
12 |
glGenBuffers(1, &gVBO); glBindBuffer(GL_ARRAY_BUFFER, gVBO); |
Next, we upload some data into the VBO. The data is three vertices, each of which contains three glfloat.
1234567 |
glfloat vertexdata[] = { // x y z       0.0f, 0.8f, 0.0f,      - 0.8f,-0.8f, 0.0f,       0.8f,-0.8f, 0.0f, glbufferdata (gl_array_buffer, sizeof (vertexData), vertexdata, gl_static_ DRAW); |
Now that the buffer contains the three vertices of the triangle, it is time to start setting up the Vao. First, we should enable the vert variable in the shader program. These variables can be turned on or off, which is off by default, so we need to turn it on. The vert variable is a "property variable (attribute variable)", which is why there is a "Attrib" in the OpenGL function name. We can see more types in the subsequent articles.
1 |
glEnableVertexAttribArray(gProgram->attrib( "vert" )); |
The most complex part of the Vao setting is the next function: Glvertexattribpointer. Let's call the function first, and then explain.
1 |
glVertexAttribPointer(gProgram->attrib( "vert" ), 3, GL_FLOAT, GL_FALSE, 0, NULL); |
The first parameter, Gprogram->attrib ("vert"), is the Shder variable that needs to upload the data. In this example, we need to send the data to the Vertshader variable.
The second argument, 3, indicates that each vertex requires three digits.
The third parameter, Gl_float, shows that three digits are of type glfloat. This is important because the data size of the gldouble type is different from it.
The fourth parameter, Gl_false, shows that we do not need to "normalized" the floating-point number, and if we use normalization, then this value will be limited to a minimum of 0, and a maximum of 1. We don't need to limit our vertices, so this parameter is false.
The fifth parameter, 0, can be used when there is an interval between vertices, and the parameter is set to 0, which means there is no interval between the data.
The sixth parameter, NULL, can be specified if our data is not started from the head of the buffer. Setting this parameter to NULL indicates that our data starts with the first byte of Vbo.
Now that both VBO and Vao are set up, we need to unbind them to prevent them from accidentally being changed.
12 |
glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); |
Here, Shader,vbo and Vao are all ready. We can start drawing in the render function.
First, let's clear the screen and make it pure black:
12 |
glClearColor(0, 0, 0, 1); // black glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
Then tell OpenGL we're going to start using Vao and shader:
12 |
glUseProgram(gProgram->object()); glBindVertexArray(gVAO); |
Finally, we draw a triangle:
1 |
glDrawArrays(GL_TRIANGLES, 0, 3); |
Calling the Gldrawarrays function shows that we need to draw a triangle, starting with the No. 0 vertex, with 3 vertices being sent to shader. OpenGL determines where to get vertices from within the current Vao range.
The vertices are removed from the VBO and sent to the vertex shader. Then each pixel within the triangle is sent to fragment shader. The fragment shader then turns each pixel into white. Cheer!
Now that the drawing is finished, we need to unbind shader and Vao for security reasons:
12 |
glBindVertexArray(0); glUseProgram(0); |
The last thing we need to do is switch frame buffering before we see the triangle:
1 |
glfwSwapBuffers(gWindow); |
Before the frame buffer is swapped, we draw an invisible off-screen (off-screen) framebuffer. When we call Glfwswapbuffers, the off-screen buffer becomes a screen buffer, so we can see the content on the window.
Further reading
In subsequent articles, we'll map the triangles. After that, you'll learn a little bit of matrix transformation, and you can use the vertex shader to achieve 3D cube rotation.
After that, we started to create a 3D scene and submit multiple objects.
More modern OpenGL data
Unfortunately, I had to skip a lot of things to prevent this tutorial from being too lengthy. There are plenty of good modern OpenGL data to satisfy your thirst for knowledge:
An intro to modern OpenGL by Joe Groff of Durian Software
Learning modern 3D Graphics programming by Jason L. McKesson
A collection of simple single file OpenGL examples by Jakob Progsch
OpenGL Step by Step by Etay Meiri
All on OpenGL ES 2.x by Diney Bomfim
The OpenGL progamming book on Wikibooks
Tutorials on the OpenGL wiki
OpenGL 4 Tutorials by Donald Urquhart (swiftless)
Open.gl by Alexander Overvoorde
Openglbook.com by Eddy Luten
The official OpenGL SDK documentation
Compatibility tables for OpenGL, OpenGL ES, GLSL, and GLSL es by Sugih Jamin
1.openGL Exploration