- 对于计算机软件的从业人员来说我们知道计算机有两大核心处理器:
- CPU(Central Processing Unit)中央处理器:现代计算机的三大核心部分之一,作为整个系统的运算和控制单元。CPU 内部的流水线结构使其拥有一定程度的并行计算能力。其功能主要是解释计算机指令以及处理计算机软件中的数据。CPU是计算机中负责读取指令,对指令译码并执行指令的核心部件。
- GPU(Graphics Processing Unit)图形处理器:顾名思义GPU是一种可进行绘图运算工作的专用微处理器。GPU 能够生成 2D/3D 的图形图像和视频,从而能够支持基于窗口的操作系统、图形用户界面、视频游戏、可视化图像应用和视频播放。GPU 具有非常强的并行计算能力。
- 对于视图来说最终是呈现给用户使用户肉眼能够看到,这里优惠涉及到计算机的输出设备
- 显示器:以最经典的CRT显示器来说,显示原理是通过CRT 的电子枪从上到下逐行扫描,每一次扫描发射电子束激发屏幕表面荧光粉点亮,来呈现一帧画面。引入显示器的概念主要是想引出像素点。
- 像素(Pixel):是显示器显示图像的最小单位。以iPhone XS 为例它的像素是2436x1125,也就是有2436x1125 个小正方形像素点。通过像素点的着色绘制,手机屏幕上才能够显示不同的画面。
- 位图(bitmap): 亦称为点阵图像或栅格图像,是由像素点组成的。.png、.jpg图片都是位图的格式,而且是经过压缩的位图
- CPU得到需要显示的图片后需要告知GPU,GPU处理完图片后需要通知CPU
-
CPU和GPU的两种架构系统
左边是分离性结构,CPU、GPU拥有各自的存储系统,两者通过PCI-e总线进行连接。这种结构的缺点在于PCI-e相对于两者具有低带宽和高延迟,数据的传输成了其中的性情瓶颈。目前使用非常广泛,如PC、智能手机等
右边是耦合式结构,CPU、GPU共享内存和缓存。AMD的APU采用的就是此结构,目前主要使用在游戏主机中,如PS4。
在存储管理方面,分离式结构中 CPU 和 GPU 各自拥有独立的内存,两者共享一套虚拟地址空间,必要时会进行内存拷贝。对于耦合式结构,GPU 没有独立的内存,与 GPU 共享系统内存,由 MMU 进行存储管理。
图形应用程序调用 OpenGL 或 Direct3D API 功能,将 GPU 作为协处理器使用。API 通过面向特殊 GPU 优化的图形设备驱动向 GPU 发送命令、程序、数据。 - CPU-GPU工作流
当CPU遇到图像处理需求时,会调用GPU
该过程主要分为4步:
1. 将主存的处理数据复制到显存中
2. CPU指令驱动GPU
3. GPU处理
4. GPU将显存结果传回主存
-
屏幕显示图像的原理
上边已经介绍过显示器和像素的概念,还是以CTR显示器为例,CTR电子枪从上到下逐行扫描,完成一帧画面的展示。当电子枪换行扫描时显示器会发出一个水平同步信号(horizonal synchronization),简称HSync;而当一帧画面绘制完成后,电子枪复位,准备下一帧前,显示器会发出一个垂直同步信号(vertical synchronization),简称VSync。显示器通常以固定的频率进行刷新,这个刷新频率是Vsync信号产生的频率。(在iOS上我们知道这个频率是60fps,即1秒60次)。
CPU计算好显示内容的frame、顶点等信息提交给GPU,GPU渲染完成后将结果存入帧缓冲区,视频控制器会按照 VSync 信号逐帧读取帧缓冲区的数据,经过数据转换后最终由显示器进行显示。
最简单的情况下,帧缓冲区只有一个。此时,帧缓冲区的读取和刷新都会有比较大的效率问题。为了解决效率问题,GPU 通常会引入两个缓冲区,即 双缓冲机制(iOS中也采用的是双缓存机制)。在这种情况下,GPU 会预先渲染一帧放入一个缓冲区中,用于视频控制器的读取。当下一帧渲染完毕后,GPU 会直接把视频控制器的指针指向第二个缓冲器。
双缓冲虽然能解决效率问题,但会引入一个新的问题。当视频控制器还未读取完成时,即屏幕内容刚显示一半时,GPU 将新的一帧内容提交到帧缓冲区并把两个缓冲区进行交换后,视频控制器就会把新的一帧数据的下半段显示到屏幕上,造成画面撕裂现象。为了解决这个问题,GPU 通常有一个机制叫做垂直同步(简写也是 V-Sync),当开启垂直同步后,GPU 会等待显示器的 VSync 信号发出后,才进行新的一帧渲染和缓冲区更新。这样能解决画面撕裂现象,也增加了画面流畅度,但需要消费更多的计算资源,也会带来部分延迟。
GPU对图形数据的处理
- 通过OpenGL ES / Metal 的API CPU提交数据给GPU,GPU内部像流水线一样处理所接收到的数据,处理之后生成图形渲染所需要的数据并存储到帧缓存区。GPU的这个处理过程我们叫做GPU的图形渲染管线
- 图形渲染管线具体的可以分为六个阶段
顶点着色器(Vertex Shader)
形状装备(Shape Assembly),又称图元装配
几何着色器(Geometry Shader)
光栅化(Rasterization)
片段着色器(Fragment Shader),又称片元着色器
测试与混合(Tests and Blending)
在计算机中图形都由图元组成,而在OpenGL和大多GPU处理工具中,图元的类型只有三种:点、线、三角形,也就是说所有显示到屏幕上的图像都是由点、线或者三角形构成的
顶点着色器:顶点着色器对每个顶点执行一次运算,它可以使用顶点数据来计算该顶点的坐标,颜色,光照,纹理坐标等,在渲染管线中每个顶点都是独立地被执行。
形状(图元)装配:该阶段将顶点着色器输出的所有顶点作为输入,并将所有的点装配成指定图元的形状。图中则是一个三角形。图元(Primitive) 用于表示如何渲染顶点数据,如:点、线、三角形。
以Metal为例,图元装配的类型也是点、线、三角形组成的:
typedef NS_ENUM(NSUInteger, MTLPrimitiveType) {
MTLPrimitiveTypePoint = 0,
MTLPrimitiveTypeLine = 1,
MTLPrimitiveTypeLineStrip = 2,
MTLPrimitiveTypeTriangle = 3,
MTLPrimitiveTypeTriangleStrip = 4,
} NS_ENUM_AVAILABLE(10_11, 8_0);
每个图元由一个或者多个顶点组成,每个顶点定义一个点,一条边的一端或者三角形的一个角。每个顶点关联一些数据,这些数据包括顶点坐标,颜色,法向量以及纹理坐标等。所有这些顶点相关的信息就构成顶点数据。
这里由于顶点数据较多,因此性能更高的做法是,提前分配一块显存,将顶点数据预先传入到显存当中。这部分的显存,就被称为顶点缓冲区。
另外,在绘制图像时,总是会有一些顶点被多个图元共享,而反复对这个顶点进行运算常常是没有必要的(也有某些特殊场景需要)。因此对通过索引数据,指示OpenGL绘制顶点的顺序,不但能防止顶点的重复运算,也能在不修改顶点数据的情况下,一定程度的重新组合图像。
和顶点数据一样,索引数据也可以以索引数组的形式存储在内存当中,调用绘制函数时传入;或者提前分配一块显存,将索引数据存储在这块显存当中,这块显存就被称为索引缓冲区。同样的,使用缓冲区的方式,性能一般会比直接使用索引数组的方式更加高效。
显存,也被叫做帧缓存,它的作用是用来存储显卡芯片处理过或者即将提取的渲染数据。如同计算机的内存一样,显存是用来存储要处理的图形信息的部件。
几何着色器:该阶段把图元形式的一系列顶点的集合作为输入,它可以通过产生新顶点构造出新的(或是其它的)图元来生成其他形状。通俗来讲:几何着色器就是提供图元相互之间的连接信息,将原本独立的图元连接起来。
注意:几何着色器是一个可选的阶段,比如在图元装配阶段,已经可以根据顶点坐标、索引值、图元类型(GL_QUADS)就可以确定图形,就无需再经过几何着色器这个阶段。
光栅化:在光栅化阶段,基本图元被转换为供片段着色器使用的片段(Fragment),Fragment 表示可以被渲染到屏幕上的像素,它包含位置,颜色,纹理坐标等信息,这些值是由图元的顶点信息进行插值计算得到的。这些片元接着被送到片元着色器中处理。这是从顶点数据到可渲染在显示设备上的像素的质变过程。
在片段着色器运行之前会执行裁切(Clipping)。裁切会丢弃超出你的视图以外的所有像素,用来提升执行效率。
片段着色器:片段着色器的主要作用是计算每一个片段最终的颜色值。
可编程的片段着色器是实现一些高级特效如纹理贴图,光照,环境光,阴影等功能的基础,这就是最精彩的部分。
在片段着色器之前的阶段,渲染管线都只是在和顶点,图元打交道。而在 3D 图形程序开发中,贴图是最重要的部分,我们的 Resources,可以包含纹理等数据,这些纹理可以被片段着色器使用。片段着色器可以根据顶点着色器输出的顶点纹理坐标对纹理进行采样,以计算该片段的颜色值。从而调整成各种各样不同的效果图。
测试与混合:
测试:在着色器程序完成之后,我们得到了像素数据。这些数据必须要通过测试才能最终绘制到画布,也就是帧缓冲上的颜色附着上。
测试主要可以分为像素所有者测试(PixelOwnershipTest)、裁剪测试(ScissorTest)、模板测试(StencilTest)和深度测试(DepthTest),执行的顺序也是按照这个顺序进行执行。
最开始进行的测试是像素所有者测试,主要是剔除不属于当前程序的像素运算。
之后裁剪测试,主要是剔除窗口区域之外的像素。这两个测试都是由OpenGL/Metal内部实现的,无需开发者干预。
模板测试是通过模板测试程序去决定最终的像素是否丢弃,同样也是根据OpenGL / Metal的模板覆写状态决定是否更新像素的模板值。模板测试给开发者提供了高性能的裁剪方案, 三维物体 的描边技术,就是 模板测试 典型的用处之一。
深度测试,主要是通过对像素的运算出来的深度,也就是像素离屏幕的距离进行对比,根据OpenGL / Metal设定好的深度测试程序,决定是否最终渲染到画布上。一般默认的程序是将离屏幕较近的像素保留,而将离屏幕较远的像素丢弃。如果像素最终被渲染到画布上,根据设定好的OpenGL / Metal深度覆写状态,可能会更新帧缓冲区上深度附着的值,方便进行下一次的比较。
混合:在 测试阶段 之后,如果像素依然没有被剔除,那么 像素的颜色 将会和 帧缓冲区 中颜色附着上的颜色进行混合, 混合的算法可以通过 OpenGL/ Metal的函数进行指定。但是OpenGL/ Metal提供的混合算法是有限的,如果需要更加复杂的混合算法,一般可以通过像素着色器进行实现,当然性能会比原生的混合算法差一些。
抖动:在混合阶段过后,根据OpenGL/Metal的状态设置,会决定是否有抖动这个阶段。
抖动是一种针对对于可用颜色较少的系统,可以以牺牲分辨率为代价,通过颜色值的抖动来增加可用颜色数量的技术。抖动操作是和硬件相关的,允许程序员所做的操作就只有打开或关闭抖动操作。实际上,若机器的分辨率已经相当高,激活抖动操作根本就没有任何意义。默认情况下,抖动是激活的。
经过GPU图形渲染管线处理后的数据会存放在帧缓冲区。然后视频控制器会按照 VSync 信号逐帧读取帧缓冲区的数据,经过数据转换后最终由显示器进行显示。至此,一个图形的渲染过程完成。