入门OpenGL渲染

先简单了解OpenGL渲染的基本原理,可以参考:LearnOpenGL CN
以下先对其作主要的摘录。

在OpenGL中,任何事物都在3D空间中,而屏幕和窗口却是2D像素数组,这导致OpenGL的大部分工作都是关于把3D坐标转变为2D像素。3D坐标转为2D坐标的处理过程是由OpenGL的图形渲染管线(Graphics Pipeline)管理的。

图形渲染管线可以被划分为两个主要部分:第一部分把3D坐标转换为2D坐标,第二部分是把2D坐标转变为实际的有颜色的像素。

图形渲染管线可以被划分为几个阶段,每个阶段将会把前一个阶段的输出作为输入。它们具有并行执行的特性,当今大多数显卡都有成千上万的小处理核心,它们在GPU上为每一个(渲染管线)阶段运行各自的小程序,从而在图形渲染管线中快速处理你的数据。这些小程序叫做着色器(Shader)。OpenGL着色器是用OpenGL着色器语言(GLSL)写成的。

入门OpenGL渲染_第1张图片

注:蓝色部分代表的是我们可以注入自定义的着色器的部分

如上图所示,图形渲染管线包含了多个部分。首先,我们以数组的形式传递3个3D坐标作为图形渲染管线的输入,用来表示一个三角形,这个数组叫做顶点数据;顶点数据是一系列顶点的集合。一个顶点是一个3D坐标的数据的集合。而顶点数据是用顶点属性表示的,它可以包含任何我们想用的数据(包括3D位置和一些颜色值)。

图形渲染管线的第一个部分是顶点着色器(Vertex Shader),它把一个单独的顶点作为输入。顶点着色器主要的目的是把3D坐标转为另一种3D坐标。

图元装配(Primitive Assembly)阶段将顶点着色器输出的所有顶点作为输入,并把所有的点装配成指定图元的形状。

图元装配阶段的输出会传递给几何着色器(Geometry Shader)。几何着色器把图元形式的一系列顶点的集合作为输入,它可以通过产生新顶点构造出新的图元来生成其他形状。

几何着色器的输出会被传入光栅化阶段(Rasterization Stage),这里它会把图元映射为最终屏幕上相应的像素,生成供片段着色器使用的片段(OpenGL中的一个片段是OpenGL渲染一个像素所需的所有数据)。在片段着色器运行之前会执行裁切。裁切会丢弃超出你的视图以外的所有像素,用来提升执行效率。
片段着色器的主要目的是计算一个像素的最终颜色。

Alpha测试和混合阶段用来检测片段的对应的深度值,用来判断这个像素是其它物体的前面还是后面,决定是否应该丢弃。

对于大多数场合,我们只需要配置顶点和片段着色器就行了。

顶点输入

OpenGL仅当3D坐标在3个轴(x、y和z)上都为-1.0到1.0的范围内时才处理它。
通常深度可以理解为z坐标,它代表一个像素在空间中和你的距离。
定义好顶点数据以后,我们就把它作为输入发送给图形渲染管线的第一个处理阶段:顶点着色器。它会在GPU上创建内存用于储存顶点数据,还要配置OpenGL如何解释这些内存,我们通过顶点缓冲对象(Vertex Buffer Objects, VBO)管理这个内存,它会在GPU内存中储存大量顶点。使用这些缓冲对象的好处是我们可以一次性的发送一大批数据到显卡上,而不是每个顶点发送一次。
顶点缓冲对象有一个独一无二的ID,所以我们可以使用glGenBuffers函数和一个缓冲ID生成一个VBO对象:

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

OpenGL允许我们同时绑定多个缓冲,只要它们是不同的缓冲类型。顶点缓冲对象的缓冲类型是GL_ARRAY_BUFFER,我们可以使用glBindBuffer函数把新创建的缓冲绑定到GL_ARRAY_BUFFER目标上:

glBindBuffer(GL_ARRAY_BUFFER, VBO);

我们可以调用glBufferData函数,它会把之前定义的顶点数据复制到缓冲的内存中:

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

顶点着色器

使用着色器语言GLSL(OpenGL Shading Language)编写顶点着色器,然后编译这个着色器,就可以在程序中使用它了。

编译着色器

为了能够让OpenGL使用顶点着色器的源代码,我们必须在运行时动态编译它。
先要做的是创建一个着色器对象,同样也还是用ID来引用的。用glCreateShader创建这个着色器:

unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);

然后我们把这个着色器源码附加到着色器对象上,编译它:

glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);

片段着色器

片段着色器只需要一个输出变量,这个变量是一个4分量向量,它表示的是最终的输出颜色。
编译片段着色器的过程与顶点着色器类似,只不过我们使用GL_FRAGMENT_SHADER常量作为着色器类型。

unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);

着色器程序

着色器程序对象(Shader Program Object)是多个着色器合并之后并最终链接完成的版本。如果要使用刚才编译的着色器我们必须把它们链接为一个着色器程序对象,然后在渲染对象的时候激活这个着色器程序。已激活着色器程序的着色器将在我们发送渲染调用的时候被使用。
当链接着色器至一个程序的时候,它会把每个着色器的输出链接到下个着色器的输入。当输出和输入不匹配的时候,你会得到一个连接错误。
创建一个程序对象:

unsigned int shaderProgram;
shaderProgram = glCreateProgram();

我们需要把之前编译的着色器附加到程序对象上,然后用glLinkProgram链接它们:

glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);

接着激活程序对象:

glUseProgram(shaderProgram);

在把着色器对象链接到程序对象以后,就可以删除着色器对象:

glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);

链接顶点属性

顶点缓冲数据会被解析为下面这样子:
入门OpenGL渲染_第2张图片

  • 位置数据被储存为32位(4字节)浮点值。
  • 每个位置包含3个这样的值。
  • 在这3个值之间没有空隙(或其他值)。这几个值在数组中紧密排列(Tightly Packed)。
  • 数据中第一个值在缓冲开始的位置。
    有了这些信息我们就可以使用glVertexAttribPointer函数告诉OpenGL该如何解析顶点数据(应用到逐个顶点属性上)了:

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

顶点数组对象

顶点数组对象(Vertex Array Object, VAO)可以像顶点缓冲对象那样被绑定,随后任何的顶点属性调用都会储存在这个VAO中。这样的好处就是,当配置顶点属性指针时,你只需要将那些调用执行一次,之后再绘制物体的时候只需要绑定相应的VAO就行了。这使在不同顶点数据和属性配置之间切换变得非常简单,只需要绑定不同的VAO就行了。刚刚设置的所有状态都将存储在VAO中。

一个顶点数组对象会储存以下这些内容:

  • glEnableVertexAttribArray和glDisableVertexAttribArray的调用。
  • 通过glVertexAttribPointer设置的顶点属性配置。
  • 通过glVertexAttribPointer调用与顶点属性关联的顶点缓冲对象。

入门OpenGL渲染_第3张图片
创建一个VAO和创建一个VBO很类似:

unsigned int VAO;
glGenVertexArrays(1, &VAO);

要想使用VAO,要做的只是使用glBindVertexArray绑定VAO。从绑定之后起,我们应该绑定和配置对应的VBO和属性指针,之后解绑VAO供之后使用。
一般当你打算绘制多个物体时,你首先要生成/配置所有的VAO(和必须的VBO及属性指针),然后储存它们供后面使用。当我们打算绘制物体的时候就拿出相应的VAO,绑定它,绘制完物体后,再解绑VAO。

绘制

使用glDrawArrays函数就可以绘制图形了,它使用当前激活的着色器,之前定义的顶点属性配置,和VBO的顶点数据(通过VAO间接绑定)来绘制图元。

你可能感兴趣的:(#,渲染,音视频,图形渲染)