QOpenGL入门教程(一)——OpenGL渲染管线

前言

这是本教程的第一个篇章,你是否已经迫不及待的想动手写代码了?很可惜的是,这一篇章我们并不会涉及到Qt和代码,而是说一些有用,并且有意思的事情。

如果没有进行思考就直接上手写代码的话,会让你产生一些“先入为主”的错误观念,这些观念将会成为你之后学习过程中极大的阻碍,因此,沉住气,认真看完这一章,到之后你真正学习的代码时候,你就会发现自己tm跟开了挂一样:“卧槽,原来OpenGL这么简单!!!?”

能够看到这篇文章,说明你大概率也是一名程序开发者,你为什么来学OpenGL呢?是因为兴趣使然,还是工作需要呢?如果不介意的话,可以在评论区分享一下你的原因

OpenGL与普通绘图API的差异

作为一名程序开发者,你可能已经使用过一些GUI框架,这些框架大多会提供一套使用CPU进行绘图的API来绘制2D图形,这类API(下方统称为普通API】)一般都会提供类似下面这样的绘图函数:

画线函数:提供两个端点的坐标来进行绘制
画圆函数:提供圆形坐标和半径来进行绘制
画矩形函数:提供长宽以及其中一个规定点的坐标(一般是左上角,或矩形中心)来进行绘制
画多边形函数:通过一系列的顶点来绘制多边形
画图函数:提供一张外部图像和绘制的矩形区域
....

通过这些函数,已经可以绘制出任何的2D图形了,那我们还学OpenGL干嘛?

:“普通API只能画2D图形,不能画3D图形啊,所以要学OpenGL。”

大错特错!不是因为普通API不能绘制“3D图形”,而是因为它们的绘制效率远远无法满足“3D图形”的绘制需求。之所以给 3D图形 打上双引号是因为它们并不是真正的3D图形,试想二维的电脑屏幕怎么可能显示出三维的图像呢?

:“那你的意思是普通API也能绘制3D图形?”

那是当然,绘制3D图形的过程,只是在假想的三维空间中计算3D图形的顶点数据,然后再将这些顶点数据从三维空间映射到二维屏幕,最后再利用这些二维的顶点数据绘制图形,因次,3D图形的绘制本质上绘制的其实是一个2D图形,既然是2D图形,为什么不能用普通API来进行绘制呢?你只需要了解“三维空间空间到二维空间转换计算”,就能很轻松地完成这件事,如果感兴趣的话,可以试试。

:“那OpenGL与普通API的差异在哪呢?”

我就跟你讲一个我瞎编的故事吧。

原来的时候,开发者用的就是普通API来进行绘图,你可以把自己想象成当时的开发者,仔细查看一下上方列举的那些基本绘图函数,你会发现其中有一个比较特殊的函数——画多边形函数,它特殊在哪?

通过这个函数,我们可以绘制出任何的图形,因为任何图形都是由顶点构成的。

所以这是否意味着:只要我们将这个函数的底层绘制算法优化到极致,可以大幅度绘图的效率?

那怎么优化呢?对了,任意多边形都可以分割成若干个三角形,如果我们能找到一个超高性能的三角形绘制算法,不就行了?

但当我们将理论变成现实的时候,发现效率确实是提高了,幅度却并不是很大,你不断地不断地优化代码,却仍然无法满足实时绘制3D图形的要求。

你甚至开始怀疑自己是不是想错了?不可能!如果这个方法都不行,别的方法就更不行了!

难道说实时3D图形绘制根本就是个泡沫?可是如果能做到的话,....

你仍不死心,一遍又一遍的检阅着写好的代码,终于,发现了突破口——代码中有很多相似的运算操作,并且有很多操作明明是可以一起运行的,而电脑的处理器却不支持这么做,我是否能通过硬件手段来优化这部分代码?

于是,一种专门用于图形绘制的处理器——GPU(Graphics Processing Unit,图形处理单元)诞生了。它的出现,让实时3D绘制最终变成了现实。

:“说这么多废话,差异究竟在哪呢?”

我还以为你看懂了呢,那我再总结一下:

图形特征处理不同 :

  • OpenGL(包括其他GPU绘图API)采用的是一种大道至简的处理方式:将图形数据进行拆解——通过顶点数据+图元类型(点、线、三角面)来描述任意的图形
  • 普通API则是划分一些基本图形(点,线,圆,矩形...),用最少的特征来进行描述这些图形(比如描述一个圆只需要两个参数:圆的中心坐标和半径)

硬件不同:

  • OpenGL使用GPU进行图形绘制
  • 普通API使用CPU绘图(一些普通API也会将代码转换成GPU API以通过GPU进行绘图)

本质上的不同也就造成了:

  • 性能差异:GPU绘图明显高于CPU绘图
  • 用法差异:普通绘图API可以使用高级函数提供几个简单的参数就能绘制图形,GPU绘图API则要求你必须完成图形渲染中的核心阶段(以圆为例,普通API可以直接通过圆的中心坐标和半径进行绘制,而GPU绘图API关注的是圆上所有点的坐标)

好了,现在你对OpenGL可能有了一个初步的了解了,但是要你说一下OpenGL究竟是做什么的?你可能也说不出来。为什么呢?

:“因为没图啊!全是文字谁看得懂!?”

明白,下面我给你整几张尺度大一点的图。

OpenGL图形绘制流程

(很多人喜欢称之为图形渲染管线,老实说,我不太喜欢这个名字,初次看到这个名称的时候不解其意)

在现实生活中,如果你想以极高的效率来生产某种产品,那么搭建一个自动化工厂无疑是最好的选择,搭建完毕之后,你只需要提供原材料,调整好各个零部件,按下电源开关,原材料便开始通过传送带输送,途中被各个子工厂加工,最终生产出产品。

下面有个可爱的小短片演示流水线的工作流程,强烈推荐看一下哦。

【宝贝老板】流水线生产的宝宝

图形的绘制其实就是在生产产品,因此,为了能够高效的绘制图形,OpenGL也采用的是工厂流水线的方式,这也是为什么很多人喜欢把这个绘制过程叫做图形渲染管线

在这以前,你还需要了解OpenGL标准化设备坐标(Normalized Device Coordinates, NDC)

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

好了,现在我们可以开始学习图形绘制流程(图形渲染管线)了。

我们提供给这座工厂的原材料是顶点数据,这些顶点数据经过六个处理阶段之后将完成图形的绘制,这六个处理阶段是:

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

看不懂这图没关系,你只需要了解有这六个阶段就行,本身这张图片并没有很好的解释图形渲染管线,待会我会非常详细的解释这六个阶段

 顶点数据(Vertex Data)

 这里的顶点数据并不要以为只是顶点坐标,它可以是顶点的颜色,法向量,纹理坐标等一系列用来绘制顶点的数据的统称,并且这里并不一定要求有顶点坐标。

:“不用顶点坐标,还怎么绘图?画空气?”

顶点着色器,几何着色器都是在对数据进行处理,真正使用这些数据是从光栅化阶段开始,也就是说你可以在数据处理的过程中再生成顶点数据,(比如你要画圆的话,可以只传入一个顶点坐标和一个半径长度,在之前的处理阶段生成圆形的顶点数据),或许这么说你可能还是不太懂,等之后我们讲解了其他阶段之后你可能就明白了 。

这些顶点数据是从CPU传入到GPU中,传输是一个开销非常大的过程,因此传输的数据越少越好

由于OpenGL一般是通过三角形绘制绘制多边形,比如矩形,需要传递两个三角形的顶点数据,也就是6个顶点数据,而其中有2个顶点是重复的,本身绘制一个矩形只需要4个顶点,重复数据导致额外开销50%,这显然是得不偿失的。那么OpenGL是怎么解决这个问题的呢?之后的教程中你就知道了。

顶点着色器(Vertex Shader)

  • 加工顶点数据,一般是指顶点数据的变换

shader翻译成着色器很容易让人产生误解:着色器是不是就是用来填充颜色的?并不是!(或许称之为顶点处理器会更好一些)

这一阶段是用来处理一个单独的顶点数据,因此这阶段其实是并行操作(可能有很多顶点数据都同时在使用这个顶点着色器程序进行处理)

:“究竟是怎么处理呢?”

这取决你想怎样处理这些顶点数据,因为这部分需要自己来实现代码,你可以把顶点坐标进行平移、旋转、缩放等各种变换操作,修改各种顶点数据,比如纹理坐标,法向量等,原则只有一个:需要保证最终只有一个顶点坐标,并且将一些会在其他阶段使用的数据传递输出。

图元装配(Shape Assembly)

OpenGL支持四种基本图元类型:点,线,三角形,四边形。并提供了一些扩展图元供使用,如下图:

几何着色器(Geometry Shader)

  • 生成新的顶点数据(例如,输入一个顶点,能够生成0个以上新的顶点数据)
  • 修改输出的图元类型(例如,输入图元是点,能够输出线条图元或者三角形图元)

它能起到一定的裁剪作用、同时也能产生比顶点着色器输入更多的基础图元。

几个用例:

粒子系统:一个粒子系统往往是通过GL_POINTS图元来进行绘制,在几何着色器中再生成各种形状的粒子(它可以是2D的,也可以是3D)

图形扩展:在learn OpenGL教程——加载3D模型的章节中,可以使用几何着色器生成模型各个顶点的法向量(由点→线扩展)

光栅化(rasterization)

  • 将前两阶段处理之后的几何数据经过一系列变换转换为像素

打个比方,如果我们要绘制的是一个三角形,那么经过前两阶段,现在拥有的是三角形三个顶点的几何数据,光栅化的目的就是将几何数据经过一系列变换后最终转换为像素,从而呈现在显示设备上的过程,如下图:

片段着色器(Fragment Shader)

  • 计算光栅化中产生片段(“像素”)的颜色
  • 带有一定的裁剪功能(discard)

这就跟我们传统意义上的“着色器”是一样的意思,没错,片段着色器就是用来填充颜色的。

测试(Tests)

  • 筛选并处理从片段处理器中传递过来的像素

测试的目的就是为了通过设置一些测试的方法,按照某种机制来筛选出我们需要的方法

OpenGL提供了四种测试方法:

  • 深度测试:当绘制立体物体的时候,使用深度测试可以丢弃物体背面的像素
  • Alpha测试:可以通过alpha值来丢弃像素。
  • 剪裁测试:通过设置裁剪区域来丢弃像素。
  • 模板测试:模板测试是根据一个模板来进行测试,其目的为了让开发者能够定义自己的测试方法。

混合(Blending)

  • 混合技术常用于表现物体的透明度

透明的物体的颜色常与位于其之后的物体的颜色进行混合,来达到透明的效果。物体的透明度由颜色的alpha通道的值定义,例如下面的图片:

总结

OpenGL的整个渲染流程大致如此,看完你可能会一脸懵逼,没关系,这是正常的,在接下来的教程我们会通过代码的形式,来一步一步的深入了解Qt平台下的OpenGL。

猜你喜欢

转载自blog.csdn.net/qq_40946921/article/details/111473660