主要是在这篇文章的基础上理解的,唉,这东西真麻烦,肖姐姐基本没讲OpenGL的具体工作,其实现在关于OpenGL有了很多新技术,但是按肖姐姐给的库,很多还都是很早的,已经是弃用或者不推荐使用的库,虽然可以说原理都一样,但是……为了实现作业,乱七八糟的搜资料,很多时间都是浪费在这上面,而且各种实现,比如在这个网上它应该是使用了最新的OpenGL3.3+版本,就和之前的不太一样,真是缭乱在风雨中啊,好吧,吐槽完毕……
我就按它源代码的顺序记下自己现在的理解,因为不记下来我自己又会凌乱的。首先声明,这篇文章并没有从头讲怎么使用OpenGL,只是讲了一些对于不懂的地方的理解,如果要具体学习,还请移步该网址。
1.初始化工作。这里都是一些固定的初始化,就不讲了,具体的我还不是很清楚。比较重要的是这里启用了深度测试:
// Enable depth test
glEnable(GL_DEPTH_TEST);
// Accept fragment if it closer to the camera than the former one
glDepthFunc(GL_LESS);
2.创建一个顶点数组对象,并将它设为当前对象(细节暂不深入)。这里其实只是做了一个初始化的工作,后面在绘图工作中并没有用到这个数据:
GLuint VertexArrayID; // 该顶点数组对象的ID
glGenVertexArrays(1, &VertexArrayID); // 真正创建一个数组,返回值传给VertexArrayID
glBindVertexArray(VertexArrayID); // 设置为当前对象
当窗口创建成功后(即OpenGL上下文创建后),马上做这一步工作;必须在任何其他OpenGL调用前完成。
我的理解是,这里是把顶点数据进行分组。比如这个数组里的顶点用来绘一个正方体,那个数组用来绘一个三角形。对于每一个这样的顶点数组对象,想要把它下面的数据绘在屏幕上都需要经过下面的几个步骤。
最后的这句话将告诉下面创建的缓冲区啊什么的,都是这个数组对象的。
3. 载入外部shader。
// 从外部shader中,创建并编译我们的GLSL(OpenGL Shader Language)
GLuint programID = LoadShaders( "TransformVertexShader.vertexshader", "ColorFragmentShader.fragmentshader" );
// 得到vertaxshader中的“MVP” uniform的句柄
GLuint MatrixID = glGetUniformLocation(programID, "MVP");
// 透视矩阵 : 45?Field of View, 4:3 ratio, display range : 0.1 unit <-> 100 units
glm::mat4 Projection = glm::perspective(45.0f, 4.0f / 3.0f, 0.1f, 100.0f);
// 视图矩阵
glm::mat4 View = glm::lookAt(
glm::vec3(4,3,-3), // Camera is at (4,3,-3), in World Space
glm::vec3(0,0,0), // and looks at the origin
glm::vec3(0,1,0) // Head is up (set to 0,-1,0 to look upside-down)
);
// 模型矩阵 : an identity matrix (model will be at the origin)
glm::mat4 Model = glm::mat4(1.0f);
// 我们的 ModelViewProjection : multiplication of our 3 matrices,在主循环中将会传递给上述的MVP句柄,从而改变视角
glm::mat4 MVP = Projection * View * Model; // Remember, matrix multiplication is the other way around
下面就是两个shader的内容。
TransformVertexShader.vertexshader:
#version 330 core
// 输入的顶点数据,这里是和下面将要讲的顶点属性一一对应,下面这些值实际上是靠我们将要定义在内存中的数据(如位置,颜色)来初始化的.
layout(location = 0) in vec3 vertexPosition_modelspace;
layout(location = 1) in vec3 vertexColor;
// 输出数据: will be interpolated for each fragment.
out vec3 fragmentColor;
// Values that stay constant for the whole mesh.
uniform mat4 MVP;
void main(){
// Output position of the vertex, in clip space : MVP * position
gl_Position = MVP * vec4(vertexPosition_modelspace,1);
// The color of each vertex will be interpolated
// to produce the color of each fragment
fragmentColor = vertexColor;
}
ColorFragmentShader.fragmentshader:
#version 330 core
// 从上面vertaxshader中传入的数据,经过了插值后的值
in vec3 fragmentColor;
// 输出的数据,这里我没有懂,好像OpenGL直接就使用这个值用于绘制颜色了
out vec3 color;
void main(){
// Output color = 我们在vertex shader中定义的颜色
// interpolated between all 3 surrounding vertices
color = fragmentColor;
}
4.现在需要告诉OpenGL有哪些内容,换句话就是说,应该往屏幕上画些什么。首先我们要在内存中定义这些顶点:
// 包含了3个向量的数组,表示3个顶点
static const GLfloat g_vertex_buffer_data[] = {
-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
};
这里只是在内存里定义了所有点的位置信息,但是还没有跟OpenGL联系起来。所以接下来就要把这个三角形传给OpenGL。我们通过创建一个缓冲区完成:
// 用于标识缓冲区
GLuint vertexbuffer;
// 生成一个缓冲区,并将返回的标识符传给vertexbuffer
glGenBuffers(1, &vertexbuffer);
// 讲vertexbuffer设置为当前的数组缓冲对象,也就是告诉OpenGL,好了,我下面都是对这个缓冲区进行操作的
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
// 将之前定义的数据传给OpenGL
glBufferData(GL_ARRAY_BUFFER, sizeof(g_vertex_buffer_data), g_vertex_buffer_data, GL_STATIC_DRAW);
这只要做一次。它所做的实际上是告诉OpenGL,标识符为vertexbuffer的缓冲区里放了哪些数据,以后你画的时候就要根据这些数据进行绘图。我们可以定义多个缓冲区
因此颜色缓冲区的操作类似。
// One color for each vertex. They were generated randomly.
static const GLfloat g_color_buffer_data[] = {
0.583f, 0.771f, 0.014f,
0.609f, 0.115f, 0.436f,
0.327f, 0.483f, 0.844f,
0.822f, 0.569f, 0.201f,
0.435f, 0.602f, 0.223f,
0.310f, 0.747f, 0.185f,
0.597f, 0.770f, 0.761f,
0.559f, 0.436f, 0.730f,
0.359f, 0.583f, 0.152f,
0.483f, 0.596f, 0.789f,
0.559f, 0.861f, 0.639f,
0.195f, 0.548f, 0.859f,
0.014f, 0.184f, 0.576f,
0.771f, 0.328f, 0.970f,
0.406f, 0.615f, 0.116f,
0.676f, 0.977f, 0.133f,
0.971f, 0.572f, 0.833f,
0.140f, 0.616f, 0.489f,
0.997f, 0.513f, 0.064f,
0.945f, 0.719f, 0.592f,
0.543f, 0.021f, 0.978f,
0.279f, 0.317f, 0.505f,
0.167f, 0.620f, 0.077f,
0.347f, 0.857f, 0.137f,
0.055f, 0.953f, 0.042f,
0.714f, 0.505f, 0.345f,
0.783f, 0.290f, 0.734f,
0.722f, 0.645f, 0.174f,
0.302f, 0.455f, 0.848f,
0.225f, 0.587f, 0.040f,
0.517f, 0.713f, 0.338f,
0.053f, 0.959f, 0.120f,
0.393f, 0.621f, 0.362f,
0.673f, 0.211f, 0.457f,
0.820f, 0.883f, 0.371f,
0.982f, 0.099f, 0.879f
};
GLuint colorbuffer;
glGenBuffers(1, &colorbuffer);
glBindBuffer(GL_ARRAY_BUFFER, colorbuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(g_color_buffer_data), g_color_buffer_data, GL_STATIC_DRAW);
总结一下,对于顶点位置信息和顶点颜色信息,实际上都是 在内存中定义数据——为其生成一个缓冲区——用glBindBuffer将刚刚生成的缓冲区设置为当前值——使用定义好的数据填充缓冲区。这样就完成了所有需要的缓冲区的创建工作。
5.准备工作都已经做好了,现在进入我们的主循环中。
在经过了一些屏幕清除工作后,我们要告诉OpenGL,我们要使用自己的shader。
// 使用我们的shader
glUseProgram(programID);
// 把我们的变换矩阵传送给当前绑定的shader
// 你应该还记得MatrixID是我们得到shader中名字为"MVP" uniform的句柄
glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &MVP[0][0]);
接下来就是使用缓冲区来绘图了。我们需要把缓冲区中的数据和shader中的各个值进行对应。
// 1rst attribute buffer : vertices 开启了某一个顶点属性后,在渲染时就会使用这个属性。这句话的位置是无所谓的,只要在渲染前开启就可以了。
glEnableVertexAttribArray(0);
// 这里再次调用了glBindBuffer函数,上一次调用(见上)是为了给这个缓冲区填充数据,现在是为了使用vertexbuffer缓冲区
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
// 这里按我现在的理解是,是将这个缓冲区和属性0绑定了。也就是说,这个缓冲区里的数据用来指定每个点属性0的值
glVertexAttribPointer(
0, // 属性 0。没有特别的原因必须使用0,但是一定要与shader中的布局匹配
3, // 大小
GL_FLOAT, // 类型
GL_FALSE, // 是否正则化
0, // 步幅
(void*)0 // 数组缓冲区偏移
);
如果还是不懂,往上翻看到vertaxshader,有这样一行代码:
layout(location = 0) in vec3 vertexPosition_modelspace;
实际上就是把vertexbuffer里的数据作为属性0传给了shader,shader中这个值被赋给了名字为vertexPosition_modelspace的变量,并用于gl_Position的赋值,gl_Position是内定的变量,就是点的位置。
颜色数据的对应也是类似的。
// 2nd attribute buffer : colors
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, colorbuffer);
glVertexAttribPointer(
1, // attribute. No particular reason for 1, but must match the layout in the shader.
3, // size
GL_FLOAT, // type
GL_FALSE, // normalized?
0, // stride
(void*)0 // array buffer offset
);
6. 呼呼,终于做完了。下面就使用shader把图形画在屏幕上!还要记得把之前用到的属性再次disable掉。
// Draw the triangle !
glDrawArrays(GL_TRIANGLES, 0, 12*3); // 12*3 indices starting at 0 -> 12 triangles
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
// Swap buffers
glfwSwapBuffers();
思考:
如果要绘制两个图形呢?比如一个正方体和一个三角形。我试了下,简单的话就是直接在定义顶点数据时再加上3个点就行了。但这样是不太科学的,扩展性也不好,又试了下定义两个顶点数据对象(见第2步),然后分别绑定各自的缓冲区,最后在主循环中通过glBindVertexArray(VertexArrayID);来进行切换,也是可以的。
事实上,我们应当使用后面一种方法,也就是通过切换不同的VBO来绘制不同的图形。