本文主要认识音视频开发的框架以及其作用,还有一些常见的专有名词和坐标系的认识,最后会用一个小小的代码案例作为入手来认识固定管线的渲染流程
具体内容
1、初步认识音视频开发
2、了解专有名词、坐标系
3、学会最简单的图片从文件渲染到屏幕上的渲染流程
4、学会最简单的固定管线渲染流程
1、音视频的认识
音视频开发包括OpenGL、OpenGL ES、Metal三种API,
OpenGL是一个跨平台、跨语言的编程图像程序接口,他将计算机资源抽象成一个个OpenGL对象,对这些资源的操作抽象成一个一个OpenGL指令。
OpenGL ES是OpenGL子集,是专门用在手机、iPad等嵌入式系统上的,去除了许多不必要和性能较低的API接口。
Metal是新开发的处理图像的API,在3D图形的渲染性能上比OpenGL更高,Metal是Apple为了解决3D渲染而推出的框架。
苹果底层渲染是用Metal实现的,苹果自己的内核已经从OpenGL直接迁移到Metal,但是我们的项目可以用OpenGL/OpenGL ES
OpenGL/OpenGL ES/Metal在任何项目中解决问题的本质就是利用GPU芯片来高效渲染图像。图像API是iOS开发者唯一接近GPU的方式。
2、专有名词
注:如果没有音视频基础,刚开始看比较难理解,不要求完全懂,先简单有个印象,后续如果碰到专业名词不知道是什么,可以来这里查看
OpenGL状态机
状态机描述了一个对象在其生命周期内所经历的各种状态,以及状态间的转变、转变的动因,转变的条件,还有转变中所执行的活动,可以根据输入修改条件改变当前状态。
特点:
- 具有记忆功能,能够记住当前状态
- 可以接收输入,根据输入和当前状态改变自己的状态,并且进行输出
- 当接收特殊输入的时候,可以进入到停止状态,此时不可以接收任何收入
OpenGL上下文(Context)
OpenGL上下文是一个巨大的状态机,保存了OpenGL中的各种状态。我们在调用任何OpenGL指令之前必须先创建上下文,这样指令就可以针对状态机的状态或对象进行操作。
注:由于OpenGL上下文是一个巨大的状态机,切换上下文往往会产生较大的开 销,但是不同的绘制模块,可能需要使用完全独立的状态管理。因此,可 以在应用程序中分别创建多个不同的上下文,在不同线程中使用不同的上 下文,上下文之间共享纹理、缓冲区等资源。这样的方案,会比反复切换 上下文,或者大量修改渲染状态,更加合理高效的.
渲染
将数学和图形的数据转换成3D空间图像的操作就是渲染。
渲染是通过GPU来实现的,将CPU得到的图像数据在GPU上渲染成图片显示到屏幕上。
顶点数组和顶点缓冲区
顶点数据就是一个图像的骨架,我们在绘制图像的时候,需要先获取到顶点数据。顶点数据在内存中存储在顶点数组,在显存中存储在顶点缓存区。
顶点数据一般是存储在内存中的,这样我们在进行绘制的时候,就可以直接从内存中拿出在顶点数据在CPU中绘制。性能更高的做法是在显存中开辟一个空间,保存顶点数据,这个就是顶点缓存区。
也就是说顶点数组和顶点缓存区的区别在于顶点数据存储方式不同。顶点数据存储在内存中,效率低;顶点数据存储在显存中,效率高。
顶点数组或顶点缓冲区都是需要开发者创建的。
管线
管线是一个抽象概念,指的是GPU在处理数据的时候需要按照一定的顺序来执行,是一个顺序执行流,就像水管中的水只能从一个水管流到下一个水管一样,所以就称为管线。
管线有两种,固定管线和可编程渲染管线
固定管线:也叫做存储着色器,对于一些简单的图像渲染,OpenGL提供了一套渲染流程,这个固定的渲染流程就可以叫做是固定管线。在使用时,只需要直接调用API,传入参数就可以完成渲染。
可编程渲染管线:固定管线无法完成所有的场景,其实大部分场景都满足不了,因此将一部分内容开放成可编程,这样就有更多的渲染流程,目前可编程的只有顶点着色器和片元着色器。
简单解释就是固定管线是OpenGL提供的固定渲染效果,可编程渲染管线可以通过对某些着色器进行编程而产生可自定义的渲染效果。
着色器程序(Shader)
着色器程序可以实现可编程渲染管线,可编程渲染管线额可编程部分就是通过着色器程序来实现的。(类似于一个函数,但是是给GPU使用的)。
着色器程序只能包含顶点着色器和片元着色器。
着色器类型:
顶点着色器(VertexShader):
顶点着色器是可编程处理单元的一种,顾名思义,顶点着色器是用来计算图像的顶点属性。它可以用来处理图像的每个顶点的变换,由自身坐标系转换到归一坐标系。
顶点着色器是逐顶点运算的程序,也就是说每个顶点都会进行计算,都会执行一次顶点着色器,采用并行运算方式,否则那么多的顶点串行运行太费时间。
一般需要运算的顶点属性:1、顶点坐标变换;2、逐顶点光照运算
顶点坐标的变换方式有:旋转、平移、缩放、投影
具体操作过程:
- 确定顶点位置
- 处理图像顶点的变换(旋转、平移、缩放)
- 3D图像数据投影换算为2D图形数据(投影)
片元着色器(Fragment)
片元着色器是用来处理片元值及相关数据的可编程单元,可以执行纹理的采样、颜色的汇总。又称为片段着色器、像素着色器。一般用来对每个像素点的颜色计算和填充。
片元着色器也是逐像素运算的,也就是说每个像素点都会运行一次,也是并行运算的
举例说明图片饱和度是如何完成的:通过片元着色器进行一个个像素点的修改来实现的
几何着色器
开发者无法操作,暂不考虑
曲面细分着色器
开发者无法操作,暂不考虑
GLSL(OpenGL Shading Language )
GLSL是OpenGL ES着色语言,运行与GPU中,操作的着色器包括顶点着色器和片元着色器,通过该着色语言就可以实现可编程渲染管线。
光栅化(Rasnterization)
把物体的数学描述以及与物体相关的颜色信息转换为屏幕上用于对应位置 的像素及用于填充像素的颜色,这个过程称为光栅化,这是一个将模拟信 号转化为离散信号的过程。
作用:其实是图元转换成片元的过程,将图元转换成一个个的栅格组成的图像。
理解:二维图像中的每个点都包含了颜色、深度和纹理数据,该点和相关信息叫做一个片元
片元与像素的区别:像素比片元多了其他的额外信息,比如颜色、深度等。
纹理
纹理其实就是图片,在OpenGL中我们专业角度上说是纹理,在渲染图像的时候,需要在图像上填充图片,这时的图片我们一般就称呼为纹理。
混合(Blending)
在测试阶段之后,如果像素依然没有被剔除,那么像素的颜色将会和帧缓 冲区中颜色附着上的颜色进行混合,
简单说就是将不同的颜色混合在一起显示,比如三原色的混合。
混合的算法可以通过OpenGL的函数进 行指定。但是OpenGL提供的混合算法是有限的,如果需要更加复杂的混合算法,一般可以通过像素着色器进行实现,当然性能会比原生的混合算法差一些。
变换矩阵(Transformation)
变换矩阵是顶点数据进行位置变换过程中所需要的矩阵,这个矩阵记录了变换过程,例如图形想发生平移,缩放,旋转变换.就需要使用变换矩阵.
最重要的是MVP(M:模型矩阵(Model)、V:视图矩阵(View)、P:投影矩阵(Projection))
投影矩阵(Projection)
用于将3D坐标转换为二维屏幕坐标,实际线条也将在二维坐标下进行绘制。
渲染上屏/交换缓冲区(SwapBuffer)
将缓冲区中的数据显示到屏幕上。
3、坐标系(简单了解)
笛卡尔坐标系
2D笛卡尔坐标系
也就是xy直角坐标系
图示:
3D笛卡尔坐标系
也就是xyz直角坐标系,增加了深度分量,从屏幕中心朝向屏幕外的直线
图示:
OpenGL坐标系
物体坐标系
物体坐标系与特定的物体关联,是物体自身的坐标系,每个物体都有自己特定的坐标系。也叫对象坐标系、模型坐标系。
物体坐标系就是当前绘图的坐标系,还没有任何变换,仅仅进行绘图操作时使用的坐标系。
物体坐标系与物体绑定,物体发生移动或者旋转,物体坐标系发生相同的平移或者旋转,物体坐标系和物体之间的运动同步,相互绑定。
举个例子说明:
张三和李四在向前走路,但是张三是向北走,李四向南走,在他们自己的物体坐标系中都是向前,但是在世界坐标系中是不一样的。
图示:
惯性坐标系
惯性坐标系是从物体坐标系到世界坐标系的一个过渡的坐标系
根据示意图可以看到,如果要从物体坐标系转换成世界坐标系,需要旋转成惯性坐标系,之后再将惯性坐标系平移成世界坐标系。
图示:
世界坐标系
世界坐标系是物体通过模型变换后物体放置在全局的世界坐标系,世界坐标系是所有物体交互的一个公共坐标系。可以看做是系统的绝对坐标系,建立了描述其他坐标系所需要的参考系。
图示:
摄像机坐标系
也叫做观察者坐标系,也就是某个观察视角的坐标系。
摄像机坐标系从我们眼睛出发朝向我们的手机设备看过去所能看到的还有一个z轴的最近距离和最远距离,也就是zNear和zFar。满足zNear和zFar的范围,并且也满足x轴和y轴坐标屏幕当中的坐标才会显示出来。并且远小近大效果,产生透视的效果。
图示:
裁剪坐标系
在裁剪坐标系中会将视野之外的内容裁剪掉,摄像机坐标系经过投影变换称为裁剪坐标系。
归一化设备坐标系
裁剪坐标通过透视除法得到规范化设备坐标系。将原来的观察体映射到规范化立方体的过程就是规范化,如果观察体是一个x、y、z坐标范围都是[-1,1]的立方体,则称之为规范化立方体。
图示:
屏幕坐标系
屏幕上的设备坐标系就是屏幕坐标系,又称为物理坐标系,规范化设备坐标系通过视口变换变成屏幕坐标系。
设备坐标的X轴向右为正,Y轴向下为正,Z轴指向视频外部为正,坐标原点位于窗口的左上角。
图示:
坐标变换
过程示意图:
开发者只可以操作模型坐标系、世界坐标系、观察者坐标系、裁剪坐标系,其他的无法操作,是OpenGL自动执行的
如图:
模型变换
物体坐标系通过模型变换到世界坐标系,可以将物体放置在一个全局的世界坐标系。
目的:通过模型变换使得使用顶点属性定义或3D建模软件构造的模型,能够按照需要,通过缩小、平移等操作防止到场景中合适的位置。
变换方式:旋转、平移、缩放。
多个模型变换时,不同的变换顺序影响变换的结果,一般按照缩放->旋转->平移的顺序执行。
视图变换
世界坐标系通过视图变换到摄像机坐标系。视变换是为了方便观察场景中物体而设立的坐标系。
摄像机坐标系的坐标就是从观察的角度来解释世界坐标系中的位置。
旋转方式有两种:第一种是摄像机旋转,一种是物体自身旋转。
视变换采用第二种方式变换,再举一个例子,比如,一个物体中心位于原点,照相机也位于初始位置原点,方向指向-Z轴。为了对物体的+Z面成像,那么必须将照相机从原点移走,如果照相机仍然指向-Z轴,需要将照相机沿着+Z轴方向后退。通过在世界坐标系中指定相机的位置,指向的目标位置,以及viewUp向量来构造一个相机坐标系,通过视变换矩阵将物体坐标由世界坐标系转换到相机坐标系。
投影变换
从摄像机坐标系通过投影变换到裁剪坐标系,投影方式决定以何种方式成像,投影方式有很多种,OpenGL主要使用透视投影和正交投影。
视口变换
从规范化设备坐标(NDC)转换为屏幕坐标。
左右手坐标系
1、OpenGL坐标系(物体、世界、摄像机)属于右手坐标系
2、规范化设备坐标系和屏幕坐标系使用的是左手坐标系
3、这里的左右手不是查看的角度不同,而是要注意轴的正负方向
图示:
坐标上顶点的旋转方式
通过右手法则决定进行旋转操作时需要指定的角度θ的方向。
大拇指的方向是坐标轴的正方向,其余四指的方向是转动的方向的正方向,比如转动30度,就是向其余四指方向转动30度,旋转-30度,就实现向其余四指反方向转动30度。
图示:
视口的认识
视口就是窗口内部用于绘制裁剪区域的客户区域,简单来说窗口上定义的一个绘制图形的区域就是视口。
视口区别于窗口,将坐标系的内容映射到窗口上,可以全部填充满窗口,也可以只显示窗口的一部分。OpenGL会自动建立世界窗口和视口的变换(缩放/平移),因此当世界窗口中所有对象都被绘制完成后,对象在世界窗口中的部分会被自动的映射到视口中。
当然一般情况下视口和窗口是等比的,可以看做视口就是窗口。
图示:
4、着色器渲染流程
[图片上传失败...(image-4c9194-1632992458628)]
具体流程:
1、顶点数据进行图元设置,成为图元(三种)
2、图元剪切并进行光栅化成为片元
3、片元通过片元着色器转换为帧缓存中的像素数据
4、通过渲染上屏,将帧缓存区的帧数据显示到屏幕上
图片从文件渲染到屏幕的过程
这个具体看视频卡顿的笔记,此处不再赘言。https://kdocs.cn/l/cacdlMNI7fBi
[金山文档] 卡顿优化.pof
5、三角形案例
通过简单的三角形案例来入手来认识固定管线的渲染流程
具体的案例可以查看:OpenGL案例
案例注释已足够详细,这里仅对GLBatch的具体使用进行简单说明
GLBatch可以作为图元的简单批次容器,也就是可以用来管理图元
具体使用过程:
- 定义一个批次类
//定义一个简单的批次容器,是GLTools的简单容器类 ,用来管理图元的,这里先只管理顶点
GLBatch triangleBatch;
- 创建三角形
- 初始化三角形数据
//设置三角形,其中数组vVert包含所有3个顶点的x,y,2D笛卡尔坐标系。 GLfloat vVerts[] = { -0.5f,0.0f,0.0f, 0.5f,0.0f,0.0f, 0.0f,0.5f,0.0f, };
- 建立三角形批次类
/* 建立一个三角形的批次 参数1:表示使用的图元类型 参数2:三角形数据 参数3:纹理坐标(可选) */ triangleBatch.Begin(GL_TRIANGLES,3);//GL_TRIANGLES表示是三角形,是图像的连接方式,3表示顶点是三个顶点**
- 复制三角形坐标
triangleBatch.CopyVertexData3f(vVerts);//copy顶点数据,也就是添加数据
- 复制结束
triangleBatch.End();//动作完成
- 渲染三角形
- 使用存储着色器
/* 2、使用存储着色器 */ //设置一组浮点数来表示红色 GLfloat vRed[] = {1.0f,0.0f,0.0f,1.0f}; //传递到存储着色器,即单元着色器(GLT_SHADER_IDENTITY着色器),这个着色器只是使用指定颜色以默认笛卡尔坐标第在屏幕上渲染几何图形 //UseStockShader使用固定管线 shaderManager.UseStockShader(GLT_SHADER_IDENTITY,vRed);
- 绘制
//提交着色器,表示可以绘制了,并不会直接绘制 triangleBatch.Draw();
- 渲染上屏
/* 3、将在后台缓冲区进行渲染,然后在结束时交换到前台, */ glutSwapBuffers();