OpenGL基础知识
OpenGL发展至今,已经有20余年,作为一个成熟并久负盛名的跨平台计算机图形应用程序接口规范,它被广泛使用在游戏、影视、军事、航空航天、地理、医学、机械设计,以及各类科学数据可视化等领域.
OpenGL是一种图形应用程序编程接口(Application Programming Interface,API)。它是一种可以对图形硬件设备特性进行访问的软件库,OpenGL被设计为一个现代化的、硬件无关的接口,因此我们可以在不考虑计算机操作系统或窗口系统的前提下,在多种不同的图形硬件系统上,完全通过软件的方式实现OpenGL的接口。OpenGL自身并不包含任何执行窗口任务或者处理用户输入的函数,也没有提供任何用于表达三维物理模型,或者读取图像文件(例如PNG、JPEG文件)的操作,一个用来渲染图像的OpenGL程序需要执行的主要操作如下:
- 从OpenGL的几何图元中设置数据,用于构建形状。
- 使用不同的着色器(shader)对输入的图元数据执行计算操作,判断它们的位置,颜色,以及其他渲染属性。
- 将输入图元的数学描述转换为与屏幕位置对应的像素片元(fragment)。这一步也成为光栅化(rasterization)。(OpenGL的片元若最终渲染为图像,那它就是像素。)
- 最后,针对光栅化过程产生的片元,执行片元着色器(fragment shader),从而决定这个片元的最终颜色和位置。
- 如果有必要,还需要对每个片元执行一些额外的操作,例如判断片元对应的对象是否可见,或者将片元的颜色与当前屏幕位置的颜色进行融合。
1. 图形API简介
OpenGL(Open Graphics Library)
是一种图形应用程序接口规范, 所以它才具有跨编程语⾔、跨平台的特点. 我们可以将设备的资源抽象称为⼀个个OpenGL的对象,对这些资源的操作抽象为⼀个个的OpenGL指令。
OpenGL ES(OpenGL for Embedded Systems)
是OpenGL三维图形API的⼦集,针对手机、PDA和游戏主机等嵌⼊式设备而设计,去除了了许多不必要和性能较低的API接口。
DirectX
是由很多API组成的,DirectX并不是⼀个单纯的图形API. 最重要的是DirectX是属于Windows上⼀个多媒体处理框架.并不支持Windows以外的平台,所以不是跨平台框架.按照性质分类,可以分为四大部分,显示部分、声音部分、输⼊入部分和⽹络部分。
Metal
为发展自己的渲染技术,提升画面的渲染性能,在2014年苹果推出自己的渲染技术平台Metal,该技术能够为3D图像提高10倍的渲染性能。
(Metal 是一种低层次的渲染应用程序编程接口,提供了软件所需的最低层, 保证软件可以运行在不同的图形芯片上。Metal 提升了 A7 与 A8 处理器效能, 让其性能完全发挥。)
2. OpenGL相关
OpenGL/OpenGLES/Metal在任何项⽬中解决问题的本质就是利⽤GPU芯⽚来⾼效渲染图形图像. 图形API是iOS开发者唯⼀接近GPU的⽅式
CoreGraphics/CoreAnimation/CoreImage都是基于OpenGLES、Metal的上层封装, 通过OpenGLES、Metal驱动GPU
OpenGL解决哪些问题:
- 系统针对按钮、图⽚、视图、图层进行渲染.
- 2D/3D游戏引擎开中对游戏人物、游戏场景进行渲染.
- 科学/医学可视化软件开发.
- 虚拟实境(AR,VR)/人工智能(AI)
- 音视频开发中,对解码后的视频数据进行渲染.
- 在动画中,实现动画渲染 (旋转,缩放,移动, 图层特效)
2018苹果弃用了OpenGL ES,底层渲染迁移至metal. 弃用不等于不能使用, 替代原因:
- 不可能⼀直把⾮常重要的渲染核⼼框架使⽤第三⽅
- 目前OpenGL ES只能用于调⽤GPU来进⾏图像处理和GPU并发运算, Metal可以调⽤GPU来⾃定义处理事件, 借助Metal调度GPU来给你做自定义运算.
OpenGL与OpenCV
- OpenGL: 做图形渲染,显示位图
- OpenCV:识别(人脸、物体、身份识别)
- Open CV face++ 与人工智能结合使用
3. OpenGL专业名词
OpenGL上下⽂(context)
在应⽤程序调⽤任何OpenGL的指令之前,需要安排⾸先创建⼀个OpenGL的上下⽂。这个上下⽂是⼀个⾮常庞⼤的状态机,保存了OpenGL中的各种状态,这也是OpenGL指令执⾏的基础.
OpenGL的函数不管在哪个语⾔中,都是类似C语⾔⼀样的⾯向过程的函
数,本质上都是对OpenGL上下⽂这个庞⼤的状态机中的某个状态或者对象进⾏操作,当然你得⾸先把这个对象设置为当前对象。因此,通过对
OpenGL指令的封装,是可以将OpenGL的相关调⽤封装成为⼀个⾯向对象的图形API的
由于OpenGL上下⽂是⼀个巨⼤的状态机,切换上下⽂往往会产⽣较⼤的开销,但是不同的绘制模块,可能需要使⽤完全独⽴的状态管理。因此,可以在应⽤程序中分别创建多个不同的上下⽂,在不同线程中使⽤不同的上下⽂,上下⽂之间共享纹理、缓冲区等资源。这样的⽅案,会⽐反复切换上下⽂,或者⼤量修改渲染状态,更加合理⾼效的.
- OpenGL中记录生命周期和各种状态的状态机。
OpenGL状态机
状态机是理论上的⼀种机器.这个⾮常难以理解.所以我们把这个状态机这么理解.状态机描述了⼀个对象在其⽣命周期内所经历的各种状态,状态间的转变,发⽣转变的动因,条件及转变中所执⾏的活动。或者说,状态机是⼀种⾏为,说明对象在其⽣命周期中响应事件所经历的状态序列以及对那些状态事件的响应。因此具有以下特点:
- 有记忆功能,能记住其当前的状态。
- 可以接收输⼊,根据输⼊的内容和⾃⼰的原先状态,修改⾃⼰当前状态,并且可以有对应输出。
- 当进⼊特殊状态(停机状态)的时候,变不再接收输⼊,停⽌⼯作。
渲染
将数学和图形数据转换成 3D 空间图像的操作叫做渲染(Rendering)。当这个术语作为动词使用时,指的是计算机创建三维图像时所经历的过程。它也作为名词使用,指的仅仅是最终的图像作品。
- 将图形/图像数据转换成2D空间图像操作叫做渲染(Rendering)。
顶点数组(VertexArray)和顶点缓冲区(VertexBuffer)
画图⼀般是先画好图像的⻣架,然后再往⻣架⾥⾯填充颜⾊,这对于OpenGL也是⼀样的。顶点数据就是要画的图像的⻣架,和现实中不同的是,OpenGL中的图像都是由图元组成。在OpenGLES中,有3种类型的图元:点、线、三⻆形。那这些顶点数据最终是存储在哪⾥的呢?开发者可以选择设定函数指针,在调⽤绘制⽅法的时候,直接由内存传⼊顶点数据,也就是说这部分数据之前是存储在内存当中的,被称为顶点数组。⽽性能更⾼的做法是,提前分配⼀块显存,将顶点数据预先传⼊到显存当中。这部分的显存,就被称为顶点缓冲区
顶点指的是我们在绘制⼀个图形时,它的顶点位置数据.⽽这个数据可以直接存储在数组中或者将其缓存到GPU内存中
- 顶点数组: 顶点数据放在内存中。
- 顶点缓冲区: 顶点数据放在到GPU显存当中。
位图
位图图像(bitmap),亦称为点阵图像或栅格图像,是由称作像素(图片元素)的单个点组成的。这些点可以进行不同的排列和染色以构成图样。当放大位图时,可以看见赖以构成整个图像的无数单个方块。扩大位图尺寸的效果是增大单个像素,从而使线条和形状显得参差不齐。然而,如果从稍远的位置观看它,位图图像的颜色和形状又显得是连续的。用数码相机拍摄的照片、扫描仪扫描的图片以及计算机截屏图等都属于位图。位图的特点是可以表现色彩的变化和颜色的细微过渡,产生逼真的效果,缺点是在保存时需要记录每一个像素的位置和颜色值,占用较大的存储空间。常用的位图处理软件有Photoshop(同时也包含矢量功能)、Painter和Windows系统自带的画图工具等,Adobe Illustrator则是矢量图软件。
- 位图是像素点通过不同排列和染色的组成的图样。
映射
在数学里,映射是个术语,指两个元素的集之间元素相互“对应”的关系,为名词。映射,或者射影,在数学及相关的领域经常等同于函数。 基于此,部分映射就相当于部分函数,而完全映射相当于完全函数。
- 在图像渲染中主要指, 点与点, 点与颜色值之间的相互对应关系。
渲染管线
渲染管线也称为渲染流水线/像素流水线/像素管线,它是将一系列图形数据经过转换、处理渲染成图像的过程。
它是显示芯片内部处理图形信号相互独立的并行处理单元,而显卡在处理数据的时候是按照一个固定的顺序进行。
- 渲染管线也称渲染流水线,它是将一系列图形数据经过转换、处理渲染成图像的过程。
固定管线/可编程管线
早期,OpenGL封装了很多种着⾊器程序块内置的⼀段包含了光照、坐标变换、裁剪等等诸多功能的固定shader程序,来帮助开发者来完成图形的渲染。
固定渲染管线的OpenGLES不允许开发者自定义顶点渲染和像素渲染的具体逻辑,它内部已经固化了一套完整的渲染流程,开发者只需要在CPU代码端输入渲染所需要的参数并指定特定的开关,就能完成不同的渲染。
但是由于OpenGL的使⽤场景⾮常丰富,固定管线或存储着⾊器⽆法完成每⼀个业务. 比如就光照计算而言,固定管线可能只能某几种固定的光照模型(例如Phong模型),你能做的只是改下光源颜色、方向等参数。但是如果要实现环境光遮蔽(Ambient Occlusion)等,固定管线就无能为力了。
而在可编程管线里,顶点的位置、颜色、贴图座标、贴图传进来之后,如何对数据进行改动,产生的片元如何生成结果,可以很自由地控制,适用于各种固定管线无法实现的场景。
- 固定管线: OpenGL已经封装好的一些包含了光照、坐标变换、裁剪等等诸多功能的固定shader程序。
- 可编程管线: 由开发者自行实现渲染逻辑和功能自定义shader程序。
着⾊器
OpenGL在实际调⽤绘制函数之前,还需要指定⼀个由shader编译成的着⾊器程序。常⻅的着⾊器主要有顶点着⾊器(VertexShader),⽚段着⾊器(FragmentShader)/ 像素着⾊器(PixelShader),⼏何着⾊器(GeometryShader),曲⾯细分着⾊器(TessellationShader)。⽚段着⾊器和像素着⾊器只是在OpenGL和DX中的不同叫法⽽已。可惜的是,直到OpenGLES3.0,依然只⽀持了顶点着⾊器和⽚段着⾊器这两个最基础的着⾊器。其中Vertex Shader主要负责顶点的几何关系等的运算,Pixel Shader主要负责片源颜色等的计算
OpenGL在处理shader时,和其他编译器⼀样。通过编译、链接等步骤,⽣成了着⾊器程序(glProgram),着⾊器程序同时包含了顶点着⾊器和⽚段着⾊器的运算逻辑。在OpenGL进⾏绘制的时候,⾸先由顶点着⾊器对传⼊的顶点数据进⾏运算。再通过图元装配,将顶点转换为图元。然后进⾏光栅化,将图元这种⽮量图形,转换为栅格化数据。最后,将栅格化数据传⼊⽚段着⾊器中进⾏运算。⽚段着⾊器会对栅格化数据中的每⼀个像素进⾏运算,并决定像素的颜⾊。
- 着⾊器:一些具体实现图像渲染效果、逻辑的代码段。
固定着色器
OpenGL固定管线下的着色器包括:
单元着色器(GLT_SHADER_IDENTITY)
平面着色器(GLT_SHADER_FLAT)
上色着色器(GLT_SHADER_SHADED)
默认光源着色器(GLT_SHADER_DEFAULT_LIGHT)
点光源着色器(GLT_SHADER_POINT_LIGHT_DIEF)
纹理替换矩阵着色器(GLT_SHADER_TEXTURE_REPLACE)
纹理调整着色器(GLT_SHADER_TEXTURE_MODULATE)
纹理光源着色器(GLT_SHADER_TEXTURE_POINT_LIGHT_DIEF)
- OpenGL系统已封装好的具有特定功能的一些渲染代码段,
自定义着色器
可编程的着色器叫自定义着色器。目前只⽀持了顶点着⾊器和⽚段着⾊器这两个最基础的着⾊器。
- 可编程的着色器叫自定义着色器。
顶点着色器(Vertex Shader)
⼀般⽤来处理图形每个顶点变换(旋转/平移/投影等)。
顶点着⾊器是OpenGL中⽤于计算顶点属性的程序。顶点着⾊器是逐顶点运算的程序,也就是说每个顶点数据都会执⾏⼀次顶点着⾊器,当然这是并⾏的,并且顶点着⾊器运算过程中⽆法访问其他顶点的数据
⼀般来说典型的需要计算的顶点属性主要包括顶点坐标变换、逐顶点光照运算等等。顶点坐标由⾃身坐标系转换到归⼀化坐标系的运算,就是在这⾥发⽣的。
- ⼀般⽤来处理图形每个顶点变换(旋转/平移/投影等效果)的代码段。
如图,顶点着色器分为输入和输出两部分,负责的功能是把输入的数据进行矩阵变换位置,计算光照公式生成逐顶点颜⾊,⽣成/变换纹理坐标.并且把位置和纹理坐标这样的参数发送到片段着色器.
输入参数介绍:
1.着色器程序(Shader Program,图中没有画出):由 main 申明的一段程序源码或可执行文件,描述在顶点上执行的操作:如坐标变换、计算光照公式产生每个顶点颜色、计算纹理坐标。
2.属性(Attribute):由 vertext array 提供的顶点数据,如空间位置,法向量,纹理坐标以及顶点颜色,属性可以理解为针对每一个顶点的输入数据。属性只在顶点着色器中才有,片元着色器中没有属性。
3.统一值(Uniforms): Uniforms保存由应用程序传递给着色器的只读常量数据。在顶点着色器中,这些数据通常是变换矩阵,光照参数,颜色等。由 uniform 修饰符修饰的变量属于全局变量,该全局性对顶点着色器与片元着色器均可见,也就是说,这两个着色器如果被连接到同一个应用程序中,它们共享同一份 uniform 全局变量集。因此如果在这两个着色器中都声明了同名的 uniform 变量,要保证这对同名变量完全相同:同名+同类型,因为它们实际是同一个变量。
4.采样器(Samplers): 一种特殊的 uniform,用于呈现纹理。sampler 可用于顶点着色器和片元着色器。
输出参数介绍:
1.可变变量(Varying):varying 变量用于存储顶点着色器的输出数据,也存储片元着色器的输入数据。varying 变量会在光栅化处理阶段被线性插值。顶点着色器如果声明了 varying 变量,它必须被传递到片元着色器中才能进一步传递到下一阶段,因此顶点着色器中声明的 varying 变量都应在片元着色器中重新声明为同名同类型的 varying 变量。
2.gl_Position: 在顶点着色器阶段至少应输出位置信息-即内建变量
3.gl_FrontFacing: 为back-face culling stage阶段生成的变量,无论精选是否被禁用,该变量都会生成。
gl_PointSize: 点大小。
⽚元着⾊器程序(Fragment Shader)
⼀般⽤来处理图形中每个像素点颜⾊计算和填充。
⽚段着⾊器是OpenGL中⽤于计算⽚段(像素)颜⾊的程序。⽚段着⾊器是逐像素运算的程序,也就是说每个像素都会执⾏⼀次⽚段着⾊器,当然也是并⾏的。
- ⼀般⽤来处理图形中每个像素点颜⾊计算和填充代码段。
片元着色器的作用是处理由光栅化阶段生成的每个片元,最终计算出每个像素的最终颜色。归根结底,实际上就是数据的集合。这个数据集合包含每一个像素的各个颜色分量和像素透明度的值。
输入参数介绍:
1.着色器程序(Shader program): 由 main 申明的一段程序源码,描述在片元上执行的操作。
2.可变变量(Varyings): 顶点着色器阶段输出的 varying 变量在光栅化阶段被线性插值计算之后输出到片元着色器中作为它的输入,即上图中的 gl_FragCoord,gl_FrontFacing 和 gl_PointCoord。
3.统一值(Uniforms): 用于片元着色器的常量,如雾化参数,纹理参数等。
4.采样器(Samples): 一种特殊的 uniform,用于呈现纹理。
输出参数介绍:
1.gl_FragColor: 在顶点着色器阶段只有唯一的 varying 输出变量-即内建变量gl_FragColor。
GLSL(OpenGL Shading Language)
OpenGL着⾊语⾔(OpenGL Shading Language)是⽤来在OpenGL中着⾊编程的语⾔,也即开发⼈员写的短⼩的⾃定义程序,他们是在图形卡的GPU
(GraphicProcessorUnit图形处理单元)上执⾏的,代替了固定的渲染管线的⼀部分,使渲染管线中不同层次具有可编程性。
⽐如:视图转换、投影转换等。GLSL(GLShadingLanguage)的着⾊器代码分成2个部分:VertexShader(顶点着⾊器)和Fragment(⽚元着⾊器)
- GLSL是OpenGL⽤来在着⾊器编程的语⾔
光栅化(Rasterization)
是把顶点数据转换为⽚元的过程,具有将图转化为⼀个个栅格组成的图象的作⽤,特点是每个元素对应帧缓冲区中的⼀像素。
光栅化就是把顶点数据转换为⽚元的过程。⽚元中的每⼀个元素对应于帧缓冲区中的⼀个像素。
光栅化其实是⼀种将⼏何图元变为⼆维图像的过程。该过程包含了两部分的⼯作。第⼀部分⼯作:决定窗⼝坐标中的哪些整型栅格区域被基本图元占⽤;第⼆部分⼯作:分配⼀个颜⾊值和⼀个深度值到各个区域。
光栅化过程产⽣的是⽚元,把物体的数学描述以及与物体相关的颜⾊信息转换为屏幕上⽤于对应位置的像素及⽤于填充像素的颜⾊,这个过程称为光栅化,这是⼀个将模拟信号转化为离散信号的过程。
- 就是把顶点数据转换为⽚元的过程。
纹理
纹理可以理解为图⽚.⼤家在渲染图形时需要在其编码填充图⽚,为了使得场景更加逼真.⽽这⾥使⽤的图⽚,就是常说的纹理.但是在OpenGL,我们更加习惯叫纹理,⽽不是图⽚.
- 可以理解为用于编码填充的图⽚。
混合(Blending)
将帧缓存中已有的颜色与输入的片元颜色进行混合计算得出新的颜色的过程。从绘制图片的角度说,其实就是上层图片的颜色和下层图片的颜色的合成方式。
原因:
颜色缓冲区存储着像素点的颜色信息,当深度缓冲区开启时,当该像素点深度小于原来的深度时的颜色信息就会被替换为新的颜色信息。当我们的颜色携带透明度信息时,就需要用到混合。
混合颜色:
目标颜色:已经存储在颜色缓存区的颜色值
源颜色:作为当前渲染命令结果进入颜色缓存区的颜色值
当混合功能被启动时,源颜色和⽬标颜色的组合方式是混合方程式控制的。在默认情况下,混合⽅方程式如下所示:
Cf = (Cs * S) + (Cd * D)
Cf :最终计算参数的颜色
Cs : 源颜色
Cd :目标颜色
S:源混合因子
D:⽬标混合因子
在测试阶段之后,如果像素依然没有被剔除,那么像素的颜⾊将会和帧缓冲区中颜⾊附着上的颜⾊进⾏混合,混合的算法可以通过OpenGL的函数进⾏指定。但是OpenGL提供的混合算法是有限的,如果需要更加复杂的混合算法,⼀般可以通过像素着⾊器进⾏实现,当然性能会⽐原⽣的混合算法差⼀些。
- 将帧缓存中已有的颜色与输入的片元颜色进行混合计算得出新的颜色的过程。
变换矩阵 (Transformation)
创建一个物体、着色以及加入纹理,他们都是静态的物体,如果想要物体发生运动,需要使用多个矩阵Matrix对象可以更好的变换Transform一个物体。
- 例如图形想发⽣平移,缩放,旋转变换.就需要使⽤变换矩阵.
投影矩阵 Projection
绝大部分计算机的显示器是二维的。在OpenGL中一个3D场景需要被投影到屏幕上成为一个2D图像(image)。这称为投影变换,需要用到投影矩阵(projection matrix)。
- 用于将3D坐标转换为二维屏幕坐标,实际线条也将在二维坐标下进行绘制。
4. 框架理解
OpenGL渲染简化流程
渲染流畅大致分为两部分:客户端和服务端
- 客户端:主要是应用程序的相关代码以及OpenGL API的调用,工作硬件是CPU。将渲染的命令和数据组合起来发送给服务端。
- 服务端:拿到客户端发来的数据,进行服务端的渲染流程,工作硬件是GPU。
- 服务端的流程: 顶点着色器->图元组合->片元着色器->渲染
从客户端传递过来的数据有三种类型:
- Texture:纹理数据,此数据不仅可以传递给顶点着色器,也可以直接传递给片元着色器。通常有顶点数据(x,y,z,w)、矩阵数据或者纹理坐标等等。
- Uniform:不可变的数据,可以传递给顶点着色器和片元着色器。
- Attribute:属性,只能传递给顶点着色器。
管线与着色器的关系