一看就懂的OpenGL ES教程——图形渲染管线的那些事

·  阅读 2581
一看就懂的OpenGL ES教程——图形渲染管线的那些事

更多博文,请看音视频系统学习的浪漫马车之总目录

实践项目: 介绍一个自己刚出炉的安卓音视频播放录制开源项目

视频理论基础:
视频基础知识扫盲
音视频开发基础知识之YUV颜色编码
解析H264视频编码原理——从孙艺珍的电影说起(一)
解析H264视频编码原理——从孙艺珍的电影说起(二)
H264码流结构一探究竟

Android平台MediaCodec系列:
Android硬编解码利器MediaCodec解析——从猪肉餐馆的故事讲起(一)
Android硬编解码工具MediaCodec解析——从猪肉餐馆的故事讲起(二)
Android硬编解码工具MediaCodec解析——从猪肉餐馆的故事讲起(三)

轻松入门OpenGL系列
一看就懂的OpenGL ES教程——图形渲染管线的那些事
一看就懂的OpenGL ES教程——再谈OpenGL工作机制
一看就懂的OpenGL ES教程——这或许是你遇过最难画的三角形(一)
一看就懂的OpenGL ES教程——这或许是你遇过最难画的三角形(二)
一看就懂的OpenGL ES教程——这或许是你遇过最难画的三角形(三)
一看就懂的OpenGL ES教程——这或许是你遇过最难画的三角形(四)

回顾上个系列

上个系列《Android平台MediaCodec系列》中,已经带大家好好参观了MediaCodec这一座“猪肉餐馆”,摸清了“老板”“厨师”们的工作时间流程,然而视频数据光解码出来,那也只不过是一堆冰冷的二进制数据罢了,只有将数据渲染到屏幕上,才能真正让人感受诗与远方,体验看片的快乐~

Android平台最常用的渲染工具就是大名鼎鼎的OpenGL,行内人士基本多少有听闻过它的大名,众多有名的游戏引擎就是由OpenGL编写的,所以接下来的渲染系列博文,重点就是讲解OpenGL,从0开始讲解,目标依然是尽量讲清楚讲生动,讲不容易理解的知识点讲透彻。

学习目标

本系列的学习目标就是掌握OpenGL的工作机制,可以通过OpenGL绘制基本图形以及渲染图片,最终能够做出像我的项目UnitedPlayer一样的视频动态滤镜效果:

  1. 反色: 在这里插入图片描述

  2. 灰度图: 在这里插入图片描述

  3. 闪白: 在这里插入图片描述

  4. 缩放: 在这里插入图片描述

  5. 灵魂出窍: 在这里插入图片描述

  6. 颜色偏移抖动: 在这里插入图片描述

看起来还有点炫把哈哈,其实只要掌握了OpenGL基础,就会发现也不是难事~

1658027123297.png

当然OpenGL也不是学习的终极目标,毕竟它只是一个工具,以后还是会被其他更优秀的工具取代,我们需要学习的是图形学知识,这才是OpenGL的内核,才是真正贯穿整个图形开发始终的秘密武器,所以在本系列中,也会插入一些图形学基础知识在其中。

OpenGL

OpenGL是什么

上面说了那么多,那么OpenGL到底是什么呢? 很多人误以为OpenGL是一个图形渲染库,然而,OpenGL本身并不是一个API库,它仅仅是一个由Khronos组织制定并维护的规范(Specification)。

以下为著名OpenGL学习网站learnopengl 对OpenGL的介绍:

OpenGL规范严格规定了每个函数该如何执行,以及它们的输出值。至于内部具体每个函数是如何实现(Implement)的,将由OpenGL库的开发者自行决定(译注:这里开发者是指编写OpenGL库的人)。因为OpenGL规范并没有规定实现的细节,具体的OpenGL库允许使用不同的实现,只要其功能和结果与规范相匹配(亦即,作为用户不会感受到功能上的差异)。

既然是规范,那就一定有官方的规范文档,这是官方规范文档,是权威的文档,是对OpenGL的各种概念和整个工作机制的详细论述,也可以当做API文档使用。

规范文档中对OpenGL的定义是:

OpenGL (for “Open Graphics Library”) is a software interface to graphics hard- ware. The interface consists of a set of several hundred procedures and functions that allow a programmer to specify the objects and operations involved in produc- ing high-quality graphical images, specifically color images of three-dimensional objects.

To the programmer, OpenGL is a set of commands that allow the specification of geometric objects in two or three dimensions, together with commands that control how these objects are rendered into the framebuffer.

To the implementor, OpenGL is a set of commands that affect the operation of graphics hardware.

以上属于“官话”,用通俗的话来讲就是:OpenGL就是一个建立在图形硬件(一般就是gpu)之上的软件编程接口,这些接口有一套官方制定的规范,具体实现由制造商(一般是显卡制造商)去实现,而编程者通过这些软件编程接口,就可以在计算机中绘制出2D以及3D的图形。

不同制造商的实现有所差异,比如同一个api:glDrawArrays,规范制定这是一个绘制图形的api硬件制造商就必须实现其为绘制图形api,不允许将其实现为清屏api,不然那就是违反规范,不讲武德。

f6922ba48d0fe7bc6863b708bad5fa92.jpeg

其实这样的规范类似我们以前接触过得Java虚拟机规范,通过规范额约束,在各种硬件制造商中统一了标准,也对编程者隔离了具体硬件实现,使得编程者只需要学习一套开发的api。

常见的学习困难

说起学习OpenGL,可能是很多初学者的梦魇,入门各种困难层出不穷,画一个最基本的三角形就能把人折腾得够呛,这里我也将曾经学习碰到的坎梳理一下,相信很多人学习过程中也曾碰过这些坎:

  • OpenGL学习资料太少,网上的资料零碎不成系统,让人很难理解OpenGL的工作机制。(learnopengl 可以说是网上最好的学习网站了,讲得非常详细和系统,不过我个人觉得它其实对于初学者来说还是不够友好,讲得比较抽象不够通俗易懂。)

  • OpenGL的api设计比较反人类,特别是对于像写惯了Java的开发者来说,OpenGL的api真的是太难用了,很啰嗦,各种绑定和解绑能把人搞晕。

  • OpenGL的api经常不理解是什么意思,在网上也查不到api的解释。

  • OpenGL的着色器怎么工作的不好理解,而且不能调试,光是解决编译问题就够呛。

在这个系列我也努力解决这些学习痛点,让初学者可以比较“柔顺丝滑”地入门,而即使是老司机也能在这里有所收获,所有解惑。

ff178b6459473384828db1717ac8bfe2.jpeg

OpenGL工作机制

学习OpenGL首当其冲的就是要清楚它的工作机制,它不像平时做Android应用开发那样,只要几个api理解就差不多可以做一个功能开发了,OpenGL需要的是大局观,我们要像一位将军一样,在战役中统揽大局,把整个处理流程搞清楚,才能指定好战略,才能“拿下”OpenGL,而这里的“大局”,指的就是OpenGL的图形渲染管线。上一章说的学习困难中提到的很难理解OpenGL的Api和着色器,很大程度就是对图形渲染管线缺乏清晰的理解。即使有些人可以通过模仿套用模板代码可以实现一个功能,但是对工作机制理解的不足依旧无法让他真正学会OpenGL,更不要说游刃有余地掌握它。只有掌握了工作机制,才能理解它Api,才能真正掌握OpenGl。

图形渲染管线

想象以下,现在如果需要你在屏幕上绘制一个三角形,你会怎么做呢?

第一步需要什么呢?当然是确定三角形三个顶点的位置,于是你操作者鼠标在三个地方先画了三个点,然后自己量了一下,位置角度完美,自己不由自主点了点头,三个顶点如下图所示:

1657427782386.png

第二步,那当然是将三个点用线段连起来了,于是你拿起鼠标,很快将三个点刷刷连起来:

1657427966396.png

第三步,你觉得这样的三角形太过于单调,于是准备给三角形上色,因为是在屏幕上的,而屏幕本质是一个个像素显示颜色的,所以上色之前要先确定好哪些像素是属于三角形的,于是你叫计算机把属于三角形内部的像素一个个圈出来

1657428300765.png

第四步,你想画一个比较炫的三角形,所以想给每个像素都上不同的颜色,于是你给一个个像素精心上色

1657428450120.png

于是一个很漂亮的三角形就呈现在眼前了!

1658029783128.png

你回想画三角形的几个步骤,发现整个过程和车间流水线有一点相似之处。

和车间的生产流水线类似,OpenGL中也存在这样一条流水线在默默地工作,每一个岗位都在默默勤劳地工作,当然,OpenGL要画一个图形,需要的步骤肯定是要比上面你画三角形复杂很多,毕竟人家可是要画各种非常复杂的3D场景的

以下为OpenGL的图形渲染管线,图来自learn OpenGL

image.png

一言以蔽之,OpenGL图形渲染管线,就是将输入的3D的坐标,转化为显示在屏幕上的2D的像素的一个处理流程。

整个渲染管线的每个“工序”依次是:

顶点着色器——图元装配——几何着色器——光栅化——片段着色器——测试与混合

每个“工序”的输出都可以作为向下一个“工序”的输入,那么接下来,就来详细说明这几个步骤:

首先不得不提到的一个很重要的东西叫做着色器(shader)

早期的OpenGL使用立即渲染模式(Immediate mode,也就是固定渲染管线),固定渲染管线就是只可配置(configurable)的管线。这个模式下绘制图形很方便。OpenGL的大多数功能都被库隐藏起来,开发者很少有控制OpenGL如何进行计算的自由。而开发者迫切希望能有更多的灵活性。随着时间推移,规范越来越灵活,开发者对绘图细节有了更多的掌控,现代OpenGL转变为可编程渲染管线,把很多部分从可配置改为可编程(programmable)。

着色器

首先不得不提的就是着色器,着色器就是一段运行在GPU中的程序,这段程序由开发者编写,所以说为开发者提供了很大的灵活度和可掌控度。现在OpenGL主要有三种着色器,分别为顶点着色器、几何着色器、片段着色器,其中顶点着色器和片段着色器为开发者必须提供,几何着色器为可选提供。

1.顶点着色器(Vertex Shader)

第一个阶段是顶点着色器,主要用于确定绘制图形的形状,以及接收开发者传入的数据并传给后面阶段。接收外部传入的顶点数据,根据需要对顶点数据进行变换处理之后,再将顶点数据传入下一个阶段图元装配。另外也接收外部传进来的颜色值以及纹理采样器,然后再传递给下一个阶段——图元装配处理。

1657427782386.png

每个顶点着色器只接收处理一个顶点坐标有多少个顶点就会执行多少次。关于顶点着色器,后面博文还会重点细讲

2.图元装配

接下来的阶段是图元装配,接收顶点着色器的输出数据,将顶点着色器传来的顶点数据组装为图元,就如上面画三角形中所说的将三角形三个顶点连接起来,具体连接方式需要开发者指定。所谓图元,指的就是点、线、三角形等最基本的几何图形,再复杂的图形也离不开这些基本图形的组成。另外,图元装配阶段还会将超出屏幕的顶点坐标进行裁剪,裁剪之后,顶点坐标被转化为屏幕坐标,之后将图元数据传递给管线的下一个阶段——光栅化(几何着色器为非必须阶段,这里入门就暂时不讲了)

1657427966396.png

3.光栅化

接下来的阶段是光栅化,拿到图元装配传递过来的图元数据,光栅化要做的就是将一个图元转化为一张二维的图片,而这张图片由若干个片段(fragment)组成(可以当做将这张图拆解为一个个类似屏幕上像素的小片段),片段可以近似看成像素,但是又略有不同,一个片段包含渲染该片段所需要的位置、颜色和深度的全部信息。

1657428300765.png

光栅化完成之后,就把每个片段传给片段着色器1657452209669.png

4.片段着色器

接下来的阶段是片段着色器, 这是另外一个必须有的重要着色器,也是最后一个可以通过编程来控制屏幕是上显示颜色的阶段(后面的混合测试阶段还可以改变片段的颜色),在这个阶段主要是计算是片段的颜色。这里每个片段着色器接收一个片段数据的输入,所以有几个片段就会执行所少次,根据具体需要灵活设置该片段的颜色。然后片段数据就被传递到下一个阶段测试与混合

1657428450120.png

5.测试和混合

接下来的阶段是测试和混合,这个阶段的测试是专门用来丢弃一些不需要显示的片段的,其中测试主要包含深度测试模板测试

1.深度测试是在显示3D图形的时候,根据片段的深度来防止被阻挡的面渲染到其它面的前面。这里是OpenGL内部维护一个深度缓冲,保存这一帧中深度最小的片段的深度,然后对屏幕同一个位置的其他片段的深度再进行比较,深度比缓冲中大的片段则丢弃,直到找到深度最小的片段,就将其显示出来。

如图所示(图来自:GAMES101-现代计算机图形学入门-闫令琪): image.png

图中每个方格表示一个片段,片段上的数值表示当前片段的深度,R则表示深度无限,加号表示2个图形叠加一起,则由下面部分的图可知,当2个图形叠加在一起的时候,同一个位置的片段总是显示深度较小的那一个

2.模板缓冲区是用于控制屏幕需要显示的内容,屏幕大小决定了模板缓冲区大小;模板测试基于模板缓冲区,从而让我们完成想要的效果。模板测试类似于“与”运算:

图来自 模板测试 image.png

上图可以看出,模板就是每个片段位置有0也有1,然后和缓冲中的图像数据对应片段进行类似与运算,也类似与拿一个遮罩罩住,只留下1的对应片段显示出来。

3.混合则是带有透明度的片段,在这个阶段会与显示在它背后的片段的颜色按照透明度进行叠加行成新的颜色,通俗讲就是形成透明物体的效果。

图来自混合 image.png

由图可以看出,通过混合,右边的窗户既有部分自己的颜色,又有窗户里面物体的部分颜色,就是两者透明度按照比例叠加的结果。

于是走完整个渲染管线流程,就会看到在屏幕中渲染出3D世界的图像了。

让我们再回顾下这条“流水线”做了什么:

首先我们传入了图形的顶点数据,然后OpenGL内部会经过变换投影将3D顶点坐标映射为2D的屏幕坐标,然后自动将顶点连成图形,然后再将图形内的区域切成一个个小片段,然后给每个小片段自由上色,最后把被挡住的或者我们不想显示的区域的下片段丢弃,并且对有透明度的片段进行前后片段颜色的混合

80e76cb51c3916962580e99acf84a9a2.gif

图形渲染管线学习的意义

为什么理解这条渲染管线很重要呢?因为如果没有理解好这条渲染管线,后面学习着色器的时候,会遇到很多不理解的东西,比如数据是怎么从顶点着色器传递到片段着色器的,或者可能着色器代码都能看懂,就是完全不知道这样写能做什么。所以着色器的程序不能单独来看,一定要放在整个渲染管线里面来看,就会有拨云见日的感觉。

总结

本文主要介绍了OpenGL是什么的概念,并主要针对图形渲染管线的每个步骤进行比较详细的阐述。 介于篇幅关系,太长的篇幅容易导致读者不易消化理解,也容易看困,本篇博文就先写到这,工作机制流程还有下半场,下一篇文章:一看就懂的OpenGL ES教程——再谈OpenGL工作机制敬请关注~

代码地址

(项目代码将不断更新)
github.com/yishuinanfe…

参考

《OpenGL规范文档》
learn opengl
《OpenGL超级宝典第五版》
《OpenGL编程指南第8八版》

原创不易,如果觉得本文对自己有帮助,别忘了随手点赞和关注,这也是我创作的最大动力~

我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿

收藏成功!
已添加到「」, 点击更改