欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

老子不信我学不会OpenGL系列!003 绘图的操作!

程序员文章站 2023-12-25 08:37:57
...

关于渲染管线,这个博主讲的比我好多了,具体参看下面的文章:

【《Real-Time Rendering 3rd》 提炼总结】(二) 第二章 · 图形渲染管线 The Graphics Rendering Pipeline

【《Real-Time Rendering 3rd》 提炼总结】(三) 第三章 · GPU渲染管线与可编程着色器 The Graphics Processing Unit

——————分割线——————

OpenGL的所有东西都是3D的。而我们的显示器是2D的(……废话)。所以很大一部分工作就是将数据从3D转成2D。OpenGL里干这个事的,叫做graphics pipeline(中文应该是 渲染管线)。graphics pipeline又可以分为两大部分:3D→2D,2D→像素色块。

具体来说,graphics pipeline是分了好几步来完成这件事的,第一步接收了很多三维空间中的点(点里面不止有坐标,还会有其他的数据,比如颜色之类的),之后每一步的输出,都会成为下一步的输入,直到最后一步执行完输出一个位图。这里每一步都相当的独立,都是一个独立的函数,这些函数被显卡执行(而不是CPU),被称为shader。而我们可以对其中一些步骤,编写自己的函数,实现更精细的控制。(另外一些不让修改的步骤,就是OpenGL要求厂商为你提供的服务。我个人认为,理论上你可以完全不管他是怎么实现的,只要记住功能就行了,但是……所有的书上可都讲了这块……【待更正】)

下图是graphics pipeline中各个步骤的一个示意图(源自我看的那个英文教程),蓝色部分是可以被我们控制的部分。

老子不信我学不会OpenGL系列!003 绘图的操作!


基本名词:

vertex(顶点):一个点,以及这个点上所包含的信息。逻辑上就是(位置,信息)这种东西。

vertex attribute:(位置,信息)里包含了很多的vertex attributes,如:位置,颜色等。

Vertex Data:vertex的集合,graphics pipeline接收的就是这个东西。

primitive(图元):用来告诉OpenGL如何利用vertex画图。你是想把vertex画成散点、折线、三角形?这种信息叫primitive。(大概这么理解,primitive也可以理解成一个图形,反正我是没理解……)

渲染管线的过程:

【待补,先自己去网上搜搜吧,我真的不是很了解,这里有一篇 ,我随便找的】


1.构造原始数据:

(内存)写出坐标,用float型的数组:

	float vertices[] = {
		-0.5f, -0.5f, 0.0f,
		0.5f, -0.5f, 0.0f,
		0.0f,  0.5f, 0.0f
	};

(显卡)告诉显卡内存有一片空间(buffer)需要用:(这些空间都有唯一的ID,所以通过ID来管理这些空间)

unsigned int VBO;
glGenBuffers(1, &VBO); 

glGenBuffers( 个数,ID地址 ):要产生几个buffer,用来存储ID的地方。

(显卡)为这片空间指定数据类型:

glBindBuffer(GL_ARRAY_BUFFER, VBO);  

glBindBuffer( 类型,ID)

(显卡)将数据赋给这个buffer:

glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

glBufferData( 类型,数据的字节数,数据,存取方法 ):存取方法由:这个数据是否要经常发生改变,而决定。

  • GL_STATIC_DRAW:这个数据压根就不需要改,或者基本不变(比如一个形状不会改变的石头)
  • GL_DYNAMIC_DRAW:可能会经常改变。
  • GL_STREAM_DRAW:时时刻刻都在变……。

【在后面会解释什么是VertexAttribPointer,先跳过去,待会回来再看这里】关于GL_ARRAY_BUFFER这类东西,可以理解成是一个显存对外的接口(一条可以走的路,其他地方都进不去,无法访问)。每种类型只有一个接口。如果你想访问显存,要么走这个接口(直接将VBO绑定到GL_ARRAY_BUFFER上),要么自己进去,为特定的区域(一个特定的VBO)修一条路,即设置一个VertexAttribPointer(你先得进去,就是在修路的时候(即,配置VertexAttribPointer的时候),必须要绑定VBO),修好之后(配置好之后)就可以解绑了。

如果想解绑的话,用0绑定就可以了,如下:(其他的绑定也是一样)

	glBindBuffer(GL_ARRAY_BUFFER, 0);


2.写Vertex Shader程序代码:

做了3件事:1.告诉编译器OpenGL的版本,2.从显存(应该是显存吧)中导入数据,3.为这个着色器的输出变量(一个预制变量 gl_Position)赋值。

//版本声明
#version 330 core
//vertex attributes 即 输入
layout (location = 0) in vec3 aPos;

void main()
{
    // 输出,一个 vec4 类型的预制变量 gl_Position
    gl_Position = vec4(aPos.x,aPos.y,aPos.z,1.0);
}

上面是GLSL,语言形式很类似于C。在输入的地方,要确定这个属性用用哪个指针读入,就是 location = 0 的作用。至于具体的从内存的什么位置读入,怎么读入,就是那个指针的事情了,下面会说。详细内容会在下一节讲。


3.编译Vertex Shader:

有3件事要做:1.创建shader对象,同样用ID来管理,2.将上面写的代码,赋给这个shader对象,3.编译这个shader对象

	GLuint vshader;
	vshader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vshader, 1, &vsl, NULL);
	glCompileShader(vshader);

这玩意应该相当于中间文件,最后是可以删掉的(一般会删掉,节省空间嘛)


4.写Fragment Shader代码:

差不多,详细内容后面会说。

#version 330 core
out vec4 FragColor;

void main()
{
    FragColor = vec4(1.0f,0.5f,0.2f,1.0f);
}

5.编译Fragment Shader:

与VertexShader几乎完全一样,只是改变了一下类型:

	GLuint fshader;
	fshader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fshader, 1, &fsl, NULL);
	glCompileShader(fshader);


6.链接各个shader,并**,并删除中间文件:

3件事:1.创建一个shader program对象,2.将编译好的vshader,fshader都加进去,3对shader program进行链接。

	GLuint shadProg;
	shadProg = glCreateProgram();
	glAttachShader(shadProg,vshader);
	glAttachShader(shadProg,fshader);
	glLinkProgram(shadProg);

shader program应该就是类似于.exe执行文件,可以直接用的。不过用的时候要**一下(就像你要双击.exe一样)。当我们**了这个shader program的时候,graphics pipeline就会用这个来画所有的图像。同样当你想换的时候,就**另一个就好了。下面是如何**shader program:

	glUseProgram(shadProg);

在链接完之后,就可以删掉了~,下面的代码是删除中间文件:

	glDeleteShader(vshader);
	glDeleteShader(fshader);


7.配置vertex attribute格式:

到现在为止,graphics pipeline已经配置好了。显卡部分,唯一有问题的就是,graphics pipeline还不知道vertex attribute的格式(哪里放的是坐标,哪里放的是颜色),就是整个 graphics pipeline的输入部分。我们通过一个指针告诉它这部分信息,同样,这个指针也需要**。

	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);

这个指针是指向GL_ARRAY_BUFFER 的。

glVertexAttribPointer( 指针序号,数据维度,数据类型,正规化,移动步长,起点 ):

  • 指针序号:就是location那里对应的序号,相当于一个ID。
  • 数据维度:这里位置数据,是vec3,所以就是3……
  • 数据类型:每一维的数据是什么类型的,我们是用的float数组,所以就是GL_FLOAT。
  • 正规化:是否希望将数据映射到[0,1](对于负数是[-1,1]【待确认】)之间。
  • 移动步长(stride):下一个点的同一个属性在多少个字节之后。
  • 起点(offset):第一点的这个属性在哪里。


8.建立VAO:

首先你得知道VAO,vertex array object是个啥:

老子不信我学不会OpenGL系列!003 绘图的操作!

一个VAO可以将这些保存下来:

  • 对于0号,1号,...指针的配置,即glVertexAttribPointer()这个函数的设置。
  • 这些指针的**状态,即glEndableVertexAttribArray()。
  • 这些指针所指向的VBO。

老子不信我学不会OpenGL系列!003 绘图的操作!

(图片来自这里

实际上,可以把一个VAO对应成一个可以传给graphics pipeline的物体。它实际上比VBO多了配置格式的部分。VBO只是提供了数据,而VAO不止提供数据,还告诉graphics pipeline,这组数据中,每6个是一个点,其中前三个是位置,后三个是颜色。

你要做5件事:1.建立VAO(还是用ID管理),2.绑定VAO,配置VAO:3.绑定他的VBO,4.配置他的指针,5.**他的指针。

	GLuint VAO;
	glGenVertexArrays(1, &VAO);

	glBindVertexArray(VAO);
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);

9.画图~~~

啊,终于可以画图了~~~,这部分就贼轻松了~~~

3件事:1.启用你想用的shader program,2.绑定你想画的VAO,3.画图~~~

		glUseProgram(shadProg);
		glBindVertexArray(VAO);
		glDrawArrays(GL_TRIANGLES, 0, 3);

glDrawArrays( 图元,从哪个点开始画,要画几个点) :【待补】这个函数应该是在对图元装配那一步进行设置。


啊~终于搞定了~

到这里终于画出来了第一个三角形,真的刺激,待会就继续进入下一章,讲GLSL。

下面是源代码,可以参考一下。

//  1.头文件:
#include <glad/glad.h>
#include <GLFW/glfw3.h>

#include <iostream>

//  5.OpenGL与窗口(main之前):
void CBK_framebuffer_size(GLFWwindow* window, int w, int h)
{
	glViewport(0, 0, w, h);
}

void processInput(GLFWwindow* window)
{
	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
		glfwSetWindowShouldClose(window, true);
}

int main()
{
	//  2.在创建窗口之前……:
	glfwInit();

	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

	//  3.创建窗口:
	GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
	if (window == NULL)
	{
		std::cout << "Failed to create GLFW window" << std::endl;
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);

	//  4.在OpenGL之前……:
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
	{
		std::cout << "Failed to initialize GLAD" << std::endl;
		return -1;
	}

	//  5.OpenGL与窗口:
	glViewport(0, 0, 800, 600);
	glfwSetFramebufferSizeCallback(window, CBK_framebuffer_size);

	//  OpenGL的配置:
	glClearColor(0.2f, 0.3f, 0.3f, 1.0f);//设置背景色

	//  绘图需要用的数据:
	float vertices[] = {
		-0.5f, -0.5f, 0.0f,
		0.5f, -0.5f, 0.0f,
		0.0f,  0.5f, 0.0f
	};
	GLuint VBO;
	glGenBuffers(1, &VBO);
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
	
	GLuint VAO;
	glGenVertexArrays(1, &VAO);
	glBindVertexArray(VAO);
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);//可以解绑,不会影响结果,因为VAO主要是有指针决定的,他“存储”的VBO,是由指针来直接访问的【待更正】。
	glBindVertexArray(0);//可以解绑……反正待会还要再绑定上。

	//  Shader:
	const char *vsl = "#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";
	const char *fsl = "#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";

	GLuint vshader;
	vshader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vshader, 1, &vsl, NULL);
	glCompileShader(vshader);

	GLuint fshader;
	fshader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fshader, 1, &fsl, NULL);
	glCompileShader(fshader);

	GLuint shadProg;
	shadProg = glCreateProgram();
	glAttachShader(shadProg,vshader);
	glAttachShader(shadProg,fshader);
	glLinkProgram(shadProg);
	
	glDeleteShader(vshader);
	glDeleteShader(fshader);

	//  6.Render Loop:
	while (!glfwWindowShouldClose(window))
	{
		processInput(window);

		glClear(GL_COLOR_BUFFER_BIT);//绘制背景色
		//  这下面就可以写绘图的代码了
		glUseProgram(shadProg);
		glBindVertexArray(VAO);
		glDrawArrays(GL_TRIANGLES, 0, 3);
		//  这上面是绘图的代码
		glfwSwapBuffers(window);
		glfwPollEvents();
	}

	//  7.程序结束之前:
	glfwTerminate();

	return 0;
}

下面是运行结果(控制台就不截图了,反正是空的):

老子不信我学不会OpenGL系列!003 绘图的操作!

相关标签: OpenGL

上一篇:

下一篇: