OpenGL学习之可编程绘制管线

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_18517165/article/details/79144844

写在前面

 笔者为了做一些计算流体力学的东西,在看了很多论文后开始着手编写程序。原本是打算借用GitHub上面的项目框架,自己再往里添加自己的东西。但是,为了发文章以及在老师和学长的建议下,还是准备自己完成整个工程。于是乎,有了现在的再学OpenGL!!!

 早在2017年6月,为了早点融入新的环境开始了自己新的学习之旅。第一个就是OpenGL!并没有太深入的学习,也就是网上找了一些简单的教程,跟着教程简单的学习一下,运行一下程序。感受了一下OpenGL,知道了“哦,原来是这么回事!”。到底怎么回事?

 OpenGL和很多一些开源库(工具)不一样,OpenGL函数没有明确的是输入输出以及作用对象。就拿OpenCV来说,cvtColor函数如下:

cvtColor(Mat src,Mat dst,Size size);
有着明确的输入兼作用对象src,输出dst,卷积核大小size。很明确,很明了。只要会点C/C++的人都会使用这个函数!但是,OpenGL就很不一样了,比如:
 glMatrixMode (GL_PROJECTION ); 
 
 glLoadIdentity (); 
新手一看就懵了,这是干啥啊?连个输入和输出都没有啊?OpenGL代码看多会发现:很多函数是在对某个或某些“源”进行操作,而且这个或这些“源”是某个或某些公用的东东。所以,无需像OpenCV那样需要指定每次操作的对象。比如,在上面其实就是对Projection矩阵的操作,不是没有操作对象。只是这个矩阵是个公用的,函数可以“看到”。后来知道了,OpenGL是一个状态机。在OpenGL里,可以理解为全局变量。为什么要使用状态机好像与由于OpenGL内核是由C写的有关,具体就不在这里讨论了。

1 为什么要再学?

 这两天准备写自己的框架,就按照学长推荐的一个链接(https://learnopengl.com/)学习。看了不到10分钟,就发现不对劲了。glCreateShader、glShaderSource、glCompileShader还有VBO、VAO都是什么鬼?我的glEnable(),glColor(),glVertex(),glEnable()呢?我以前恐怕是学的假OpenGL。
 和学长的交流中,发现这就是传说中的固定管线可编程管线的区别。固定管线是一套固定的流程和函数,开发者可以“傻瓜”式的使用这些函数完成绘制和渲染,根本不需要掌握GPU的相关知识。但是,由于固定管线很多都是固定的,最终效果也就差强人意。好在OpenGL ES 2.0以后就开始有了可编程管线。

2 可编程绘制管线

 可编程绘制管线的流程大概如下图所示,要注意蓝色部分代表的是我们可以注入自定义的着色器的部分。图形渲染管线可以被划分为几个阶段,每个阶段将会把前一个阶段的输出作为输入。所有这些阶段都是高度专门化的(它们都有一个特定的函数),并且很容易并行执行。正是由于它们具有并行执行的特性,当今大多数显卡都有成千上万的小处理核心,它们在GPU上为每一个(渲染管线)阶段运行各自的小程序,从而在图形渲染管线中快速处理你的数据。这些小程序叫做着色器(Shader)。

 图形渲染管线的第一个部分是顶点着色器(Vertex Shader),它把一个单独的顶点作为输入。顶点着色器主要的目的是把3D坐标转为另一种3D坐标(后面会解释),同时顶点着色器允许我们对顶点属性进行一些基本处理。
 图元装配(Primitive Assembly)阶段将顶点着色器输出的所有顶点作为输入(如果是GL_POINTS,那么就是一个顶点),并所有的点装配成指定图元的形状;本节例子中是一个三角形。
 图元装配阶段的输出会传递给几何着色器(Geometry Shader)。几何着色器把图元形式的一系列顶点的集合作为输入,它可以通过产生新顶点构造出新的(或是其它的)图元来生成其他形状。例子中,它生成了另一个三角形。
 几何着色器的输出会被传入光栅化阶段(Rasterization Stage),这里它会把图元映射为最终屏幕上相应的像素,生成供片段着色器(Fragment Shader)使用的片段(Fragment)。在片段着色器运行之前会执行裁切(Clipping)。裁切会丢弃超出你的视图以外的所有像素,用来提升执行效率。
 片段着色器的主要目的是计算一个像素的最终颜色,这也是所有OpenGL高级效果产生的地方。通常,片段着色器包含3D场景的数据(比如光照、阴影、光的颜色等等),这些数据可以被用来计算最终像素的颜色。
 在所有对应颜色值确定以后,最终的对象将会被传到最后一个阶段,我们叫做Alpha测试和混合(Blending)阶段。这个阶段检测片段的对应的深度(和模板(Stencil))值,用它们来判断这个像素是其它物体的前面还是后面,决定是否应该丢弃。这个阶段也会检查alpha值(alpha值定义了一个物体的透明度)并对物体进行混合(Blend)。所以,即使在片段着色器中计算出来了一个像素输出的颜色,在渲染多个三角形的时候最后的像素颜色也可能完全不同。

3 第一个可编程OpenGL程序

 需要配置GLFW和glad。具体环境配置和代码解读会在下篇博客里详细说明。
#include <iostream>
#include <glad/glad.h>
#include <GLFW/glfw3.h>

unsigned int shaderProgram;
GLFWwindow* window;
float vertices[] = {
	-0.5f, -0.5f, 0.0f,
	0.5f, -0.5f, 0.0f,
	0.0f,  0.5f, 0.0f
};
unsigned int VAO,VBO;
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
	glViewport(0, 0, width, height);
}
void processInput(GLFWwindow *window)
{
	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
		glfwSetWindowShouldClose(window, true);
}
void vertexAndFragmentShader()
{
	/*Vertex Shader*/
	const char *vertexShaderSource = "#version 330 core\n"
		"layout (location = 0) in vec3 aPos;\n"
		"void main()\n"
		"{\n"
		"   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
		"}\0";
	/*Compile Shader*/
	unsigned int vertexShader;
	vertexShader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
	glCompileShader(vertexShader);

	/*fragment shader*/
	const char *fragmentShaderSource = "#version 330 core\n"
		"out vec4 FragColor;\n"
		"void main()\n"
		"{\n"
		"   FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
		"}\n\0";
	/*Compile Shader*/
	unsigned int fragmentShader;
	fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
	glCompileShader(fragmentShader);

	int  success;
	char infoLog[512];
	glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
	if (!success)
	{
		glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
		std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
	}

	/*Shader Program Object*/
	shaderProgram = glCreateProgram();
	glAttachShader(shaderProgram, vertexShader);
	glAttachShader(shaderProgram, fragmentShader);
	glLinkProgram(shaderProgram);

	glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
	if (!success) {
		glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
		std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
	}

	glDeleteShader(vertexShader);
	glDeleteShader(fragmentShader);
}
void initWork()
{
	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	//glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
	window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
	if (window == NULL)
	{
		std::cout << "Failed to create GLFW window" << std::endl;
		glfwTerminate();
		//return -1;
	}
	glfwMakeContextCurrent(window);
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
	{
		std::cout << "Failed to initialize GLAD" << std::endl;
		//return -1;
	}
	glViewport(0, 0, 800, 600);
	/* call this function on every window resize for resizing viewport*/
	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
}
void linkingVertexAttributes()
{
	glGenVertexArrays(1, &VAO);
	glBindVertexArray(VAO);

	glGenBuffers(1, &VBO);
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
	//attrib location ,attrib size,type,normalize,stride,offset
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);//attrib location

	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindVertexArray(0);
}
/**************          MAIN          ******************/
int main()
{
	initWork();
	/*shader*/
	vertexAndFragmentShader();
	linkingVertexAttributes();
	
	// uncomment this call to draw in wireframe polygons.
	//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	while (!glfwWindowShouldClose(window))
	{
		/*keyborad input*/
		processInput(window);

		/*render*/
		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT);

		glUseProgram(shaderProgram);
		glBindVertexArray(VAO);
		glDrawArrays(GL_TRIANGLES, 0, 3);
		
		/*glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.*/
		glfwSwapBuffers(window);
		glfwPollEvents();
	}
	glDeleteVertexArrays(1, &VAO);
	glDeleteBuffers(1, &VBO);
	glfwTerminate();
	return 0;
}
运行结果:



展开阅读全文

没有更多推荐了,返回首页