OpenGL渲染管线

Graphics Pipeline 实际上指的是一堆原始图形数据途经一个输送管道,期间经过各种变化处理最终出现在屏幕的过程。图形渲染管线可以被划分为两个主要部分:第一部分把你的3D坐标转换为2D坐标,第二部分是把2D坐标转变为实际的有颜色的像素。下面来看看OpenGL渲染管线的主要流程吧!

OpenGL标准化设备坐标

Normalized Device Coordinates。OpenGL为了让坐标运算不受显示器分辨率的影响,将xyz坐标标准化到了[-1,1]之间。任何落在范围外的坐标都会被丢弃/裁剪,不会显示在你的屏幕上(流水线的第一步会处理顶点数据,你也可以在这一步再将顶点坐标进行标准化),标准化设备坐标看起来就像是下面这样:

所以OpenGL仅当3D坐标在3个轴(x、y和z)上都为-1.0到1.0的范围内时才处理它。所有在所谓的标准化设备坐标(Normalized Device Coordinates)范围内的坐标才会最终呈现在屏幕上,在这个范围以外的坐标都不会显示。

OpenGL 渲染管线

Graphics Pipeline 实际上指的是一堆原始图形数据途经一个输送管道,期间经过各种变化处理最终出现在屏幕的过程)管理的。图形渲染管线可以被划分为两个主要部分:第一部分把你的3D坐标转换为2D坐标,第二部分是把2D坐标转变为实际的有颜色的像素。

简单概括其实就是指三维渲染的过程中显卡执行的、从几何体到最终渲染图像的、数据传输处理计算的过程。

OpenGL采用cs模型:client是cpu,server是GPU,client给server的输入是Vertex(顶点数据)信息和Texture(纹理)信息,server的输出是显示器上显示的图像。 所以CPU作为客户端把数据传递给GPU,GPU作为服务端,只需要把把CPU传来的数据通过计算、处理并且最终展示在屏幕上的过程。

在OpenGL 渲染管线中需要清楚以下几个过程:

蓝色的部分表示此阶段可以进行编程。其中现代OpenGL渲染管线要求你必须提供一个顶点着色器和片元着色器。

1、顶点数据(Vertex Data)

这里的顶点数据并不要以为只是顶点坐标,它可以是顶点的颜色,法向量,纹理坐标等一系列用来绘制顶点的数据的统称,并且这里并不一定要求有顶点坐标。 顶点着色器与几何着色器都是在对数据进行处理,真正使用这些数据是从光栅化阶段开始,也就是说你可以在数据处理的过程中再生成顶点数据,比如你要画圆的话,可以只传入一个顶点坐标和一个半径长度,在之前的处理阶段生成圆形的顶点数据即可。

假设希望渲染出一个三角形,我们一共要指定三个顶点,每个顶点都有一个3D位置。以标准化设备坐标的形式(OpenGL的可见区域)定义为一个GLfloat数组如下:

1GLfloat vertices[] = {
2    -0.5f, -0.5f, 0.0f,
3     0.5f, -0.5f, 0.0f,
4     0.0f,  0.5f, 0.0f
5};

由于OpenGL是在3D空间中工作的,而我们渲染的是一个2D三角形,我们将它顶点的z坐标设置为0.0。这样子的话三角形每一点的深度都是一样的,从而使它看上去像是2D的。

2、顶点着色器 (Vertex Shader)

定义上面那样的顶点数据以后,我们会把它作为输入发送给图形渲染管线的第一个处理阶段:顶点着色器。它会在GPU上创建内存用于储存我们的顶点数据,还要配置OpenGL如何解释这些内存,并且指定其如何发送给显卡。顶点着色器接着会处理我们在内存中指定数量的顶点。

OpenGL着色器是用OpenGL着色器语言(OpenGL Shading Language, GLSL)写成的专门的处理程序。

CPU内存也就是显存,显存中存储了大量的顶点信息。每次CPU往显存里输入顶点信息的时候相对较慢,所以通常会把一大批顶点一起发送给显卡,而不是每个顶点都一个一个发。这些顶点信息被存储在 Vertex Buffer 中, Vertex Buffer 拥有独一无二的ID,通过这个ID即可访问顶点缓冲区。

简单来说顶点着色器(包括几何着色器)决定了一个图元应该位于屏幕的什么位置。

个人认为顶点着色器这个翻译很容易让人误以为只用来给顶点着色,其实翻译成定点处理器更为合理。

3、图元装配 (Primitive Assembly)

经过顶点着色器处理之后的顶点数据依旧是单独的顶点数据,如果没有东西来组织这堆数据,那么谁也不知道这组顶点是要构成怎样的形状。所以为了让OpenGL知道我们的坐标和颜色值构成的到底是什么,OpenGL需要你去指定这些数据所表示的渲染类型。我们是希望把这些数据渲染成一系列的点?一系列的三角形?还是仅仅是一个长长的线?这种信息就叫做图元。

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

比如在本例中,我们需要指定装配的图元是三角形 GL_TRIANGLES。

1glUseProgram(shaderProgram);
2glBindVertexArray(VAO);
3glDrawArrays(GL_TRIANGLES, 0, 3); // 指定装配的图元是三角形
4glBindVertexArray(0);  

下面是一些常见的图元:

4、光栅化(Rasterize)

光栅化就是矢量图形转化成像素点儿的过程。我们屏幕上显示的画面都是由像素组成,而三维物体都是点线面构成的。要让点线面,变成能在屏幕上显示的像素,就需要Rasterize这个过程。 就是从矢量的点线面的描述,变成像素的描述。

如下图,这是一个放大了1200%的屏幕,前面是告诉计算机我有一个圆形,后面就是计算机把圆形转换成可以显示的像素点。这个过程就是Rasterize:

5、片元着色器 (fragment shader)

片元着色器(有时也称作片段着色器)的主要目的是计算一个像素的最终颜色,这也是所有OpenGL高级效果产生的地方。通常,片段着色器包含3D场景的数据(比如光照、阴影、光的颜色等等),这些数据可以被用来计算最终像素的颜色。 比如如何处理光、阴影、遮挡、环境等等对物体表面的影响,最终生成一副处理后的图像。

总体上就是上面这些过程了,其中可编程的部分就是顶点着色器和片元着色器部分。