【OpenGL】OpenGL绘图的一点理解

来源:互联网 发布:为linux添加引导 编辑:程序博客网 时间:2024/05/18 01:08

主要是在这篇文章的基础上理解的,唉,这东西真麻烦,肖姐姐基本没讲OpenGL的具体工作,其实现在关于OpenGL有了很多新技术,但是按肖姐姐给的库,很多还都是很早的,已经是弃用或者不推荐使用的库,虽然可以说原理都一样,但是……为了实现作业,乱七八糟的搜资料,很多时间都是浪费在这上面,而且各种实现,比如在这个网上它应该是使用了最新的OpenGL3.3+版本,就和之前的不太一样,真是缭乱在风雨中啊,好吧,吐槽完毕……

我就按它源代码的顺序记下自己现在的理解,因为不记下来我自己又会凌乱的。首先声明,这篇文章并没有从头讲怎么使用OpenGL,只是讲了一些对于不懂的地方的理解,如果要具体学习,还请移步该网址。

1.初始化工作。这里都是一些固定的初始化,就不讲了,具体的我还不是很清楚。比较重要的是这里启用了深度测试:

// Enable depth testglEnable(GL_DEPTH_TEST);// Accept fragment if it closer to the camera than the former oneglDepthFunc(GL_LESS); 

2.创建一个顶点数组对象,并将它设为当前对象(细节暂不深入)。这里其实只是做了一个初始化的工作,后面在绘图工作中并没有用到这个数据:

GLuint VertexArrayID;// 该顶点数组对象的IDglGenVertexArrays(1, &VertexArrayID);// 真正创建一个数组,返回值传给VertexArrayIDglBindVertexArray(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 unitsglm::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 Spaceglm::vec3(0,0,0), // and looks at the originglm::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 * positiongl_Position =  MVP * vec4(vertexPosition_modelspace,1);// The color of each vertex will be interpolated// to produce the color of each fragmentfragmentColor = 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 verticescolor = 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;// 生成一个缓冲区,并将返回的标识符传给vertexbufferglGenBuffers(1, &vertexbuffer);// 讲vertexbuffer设置为当前的数组缓冲对象,也就是告诉OpenGL,好了,我下面都是对这个缓冲区进行操作的glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);// 将之前定义的数据传给OpenGLglBufferData(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。

                // 使用我们的shaderglUseProgram(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 : colorsglEnableVertexAttribArray(1);glBindBuffer(GL_ARRAY_BUFFER, colorbuffer);glVertexAttribPointer(1,                                // attribute. No particular reason for 1, but must match the layout in the shader.3,                                // sizeGL_FLOAT,                         // typeGL_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 trianglesglDisableVertexAttribArray(0);glDisableVertexAttribArray(1);// Swap buffersglfwSwapBuffers();



思考:
如果要绘制两个图形呢?比如一个正方体和一个三角形。我试了下,简单的话就是直接在定义顶点数据时再加上3个点就行了。但这样是不太科学的,扩展性也不好,又试了下定义两个顶点数据对象(见第2步),然后分别绑定各自的缓冲区,最后在主循环中通过glBindVertexArray(VertexArrayID);来进行切换,也是可以的。

事实上,我们应当使用后面一种方法,也就是通过切换不同的VBO来绘制不同的图形。


原创粉丝点击