【OpenGL】OpenGL绘图的一点理解

主要是在这篇文章的基础上理解的,唉,这东西真麻烦,肖姐姐基本没讲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来绘制不同的图形。


原文地址:https://www.cnblogs.com/xiaowangba/p/6314732.html