作者|尚怀君电脑或手机的渲染是一个非常复杂的过程。本文介绍了渲染相关的一些基础知识,并结合iOS和Android的技术框架介绍了移动端渲染的原理,最后详细分析了iOS中的离屏渲染和一些圆角优化的方法。渲染基础屏幕绘制位图的原始数据源我们需要在屏幕上绘制图像的原始数据称为位图。位图(Bitmap)是一种数据结构。一张位图由n*m个像素组成,每个像素的颜色信息用RGB组合或灰度值表示。根据位深度的不同,位图可以分为1位、4位、8位、16位、24位和32位图像等。每个像素使用的信息位越多,可用的颜色越多,图像越逼真和丰富。色彩表现,相应的数据量越大。物理像素和逻辑像素位图一般存储物理像素,而应用层一般使用逻辑像素,物理像素和逻辑像素之间存在一定的对应关系。例如iOS中物理像素和逻辑像素的对应关系如下:iOS1双屏1pt对应1个物理像素iOS2双屏1pt对应2个物理像素iOS3双屏1pt对应3个物理像素绘制位图到display在屏幕上绘制图像所需的原始数据称为位图。那么问题来了,有了位图数据后,如何将图像绘制到屏幕上呢?如下图所示:电子枪从上到下逐行扫描,扫描完成后显示屏会呈现一帧画面。然后电子枪回到屏幕的初始位置进行下一次扫描。为了使监视器的显示过程与视频控制器的扫描过程同步,监视器产生一系列带有硬件时钟的定时信号。当电子枪换行扫描时,显示器会发出水平同步信号;绘制完一帧后,电子枪回到原位,在准备绘制下一帧之前,显示器会发出一个垂直同步信号。显示通常以固定频率刷新,这是生成垂直同步信号的频率。CPU、GPU、显示器协同工作流程上一节介绍了视频控制器在物理屏幕上显示位图数据的过程,那么如何获取位图数据呢?实际上,位图数据是通过CPU和GPU的协同工作得到的。下图是一个常见的CPU、GPU、显示器协同工作的过程。CPU计算显示内容,提交给GPU。GPU渲染完成后,渲染结果存储在帧缓冲区中。接下来,需要将获取到的像素信息显示在物理屏幕上。这时,视频控制器(VideoController)会读取帧缓冲区中的信息,传递给监视器(Monitor)进行显示。完整的流程如下图所示:CPU和GPU的区别说到CPU、GPU、显示器的协同工作流程,就不得不提到CPU和GPU的区别。CPU是中央处理器,适合单一的复杂逻辑,而GPU是图形处理单元,适合高并发的简单逻辑。GPU有特别多的计算单元和超长的流水线,但是控制逻辑很简单,而且还省了缓存,适合对延迟要求不高的运算。CPU不仅在Cache中占用了大量空间,而且控制逻辑也特别复杂。相比之下,计算能力只是CPU的一小部分。图形渲染涉及到很多矩阵运算,而矩阵相关的运算可以拆分成并行的简单运算,所以渲染处理特别适合GPU。总结一下:GPU的工作计算量大,但技术含量不高,需要简单重复多次。就好像有一份工作,需要一百以内的加减乘除数百次。CPU就像一个老教授,会计算积分和微分,适合处理单一的复杂逻辑运算。GeneralRenderingPipeline我们通常把图像绘制的完整过程称为渲染管线,它是由CPU和GPU共同完成的。一般来说,一个渲染过程可以分为四个概念阶段,即:应用阶段、几何阶段、光栅化阶段和像素处理阶段。在《Real–Time Rendering 4th》中,实时渲染的各种知识点讲解的非常透彻。如果你对渲染原理感兴趣,可以看看这本书。本书堪称“实时渲染的圣经”。下面将简要介绍这些过程。应用阶段(ApplicationStage)简称,就是应用程序中的图像处理阶段。说白了就是跑在CPU上的程序,此时没有GPU。这一阶段CPU主要负责处理用户交互和操作,然后做一些与应用层布局相关的处理,最后输出图元(点、线、三角形)信息到下一阶段。你可能会想,难道图元只有简单的点、线、三角形,能代表丰富的三维图形吗?下方立体感很强的海豚可以给出肯定的答案。简单的三角形加上不同的Coloring就可以呈现出立体的图形。几何阶段(GeometryStage)1.顶点着色器(VertexShader)顶点着色器可以对顶点的属性进行一些基本的处理。进行视角转换、添加光照信息、为顶点信息添加纹理等操作。CPU抛给GPU的信息,就好像是把这个角度看到的所有信息,都从上帝的角度给了GPU。GPU是从人的角度出发,在显示器上输出人可以观察到的图像。所以这里就是以人的视角为中心的坐标变换。2.ShapeAssembly(形状组装)该阶段将顶点着色器输出的所有顶点作为输入,将所有的点组装成指定图元的形状。基元,例如点、线和三角形。这个阶段也称为原始组装。3.GeometryShader在图元之外添加额外的顶点,并将原始图元转换为新的图元,以构建更复杂的模型。光栅化阶段(RasterizerStage)光栅化阶段将经过前三个几何阶段处理后得到的图元(primitives)转化为一系列的像素。如上图所示,我们可以看到每个像素点的中心都有一个点,光栅化就是以这个中心点来划分的。如果中心点在图元内部,则中心点对应的像素属于图元。简而言之,这个阶段就是将连续的几何图形转化为离散化的像素。像素处理阶段(PixelProcessing)1.片段着色器(FragmentShader)经过上面的光栅化阶段,我们得到了每个图元对应的像素,而这个阶段最后要做的就是正确填充每个像素的颜色,然后通过一系列的处理计算,得到相应的图像信息,最后输出到显示器上。插值在这里完成,就像补间动画一样。例如,如果要将一系列散乱的点连接成一条平滑的曲线,相邻的已知点之间可能会存在大量的缺失点。这时候就需要通过插值来填补缺失的数据,最后平滑曲线除了已知点之外的所有点都通过插值得到。同理,给出三角形的三个角色值后,其他片段根据插值计算,也呈现出渐变效果。2.TestsandBlending(测试和混合)这个阶段会检测对应的深度值(z坐标)来判断这个像素是在其他图层像素的前面还是后面,并决定是否应该丢弃它。此外,此阶段检查alpha值(alpha值定义像素的透明度)以混合图层。(简单来说就是检查图层深度和透明度,进行图层混合。)R=S+D*(1-Sa)含义:R:Result,最终的像素颜色。S:Source,源像素(上面的图层像素)。D:Destination,目标像素(下层像素)。a:alpha,透明度。Result=S(top)color+D(bottom)color*(1-S(top)transparency)经过上面那条长长的管道,我们就可以得到屏幕绘制需要的原始数据源——位图数据,然后显示视频控制器在物理屏幕上的位图数据。iOS渲染原理在渲染技术栈上铺垫了渲染的一些基础知识之后,下面主要介绍iOS渲染相关的一些原理和知识。下图展示了iOS的图形渲染技术栈。相关的核心系统框架有3个:CoreGraphics、CoreAnimation、CoreImage。这三个框架主要用于绘制可视化内容。它们都是使用OpenGL调用GPU进行实际渲染,然后生成最终的位图数据并存储在帧缓冲区中,视频控制器将帧缓冲区数据显示在物理屏幕上。UIKitUIKit是iOS开发者最常用的框架。您可以通过设置UIKit组件的布局和相关属性来绘制界面。然而,UIKit不具备在屏幕上显示图像的能力。该框架主要负责响应用户操作事件(UIView继承自UIResponder),事件通过响应链传递。CoreAnimationCoreAnimation主要负责将屏幕上不同的视觉内容进行组合。这些可视化内容可以分解成独立的层,也就是我们在日常开发过程中经常接触到的CALayer。这些图层存储在图层树中。CALayer主要负责页面渲染,它是用户在屏幕上看到的一切的基础。CoreGraphicsCoreGraphics主要用于运行时绘制图像。开发人员可以使用这个框架来处理基于路径的绘图、变换、颜色管理、离屏渲染、图案、渐变和阴影等。CoreImageCoreImage与CoreGraphics刚好相反,CoreGraphics在运行时创建图像,而CoreImage在运行前创建图像。OpenGLES和MetalOpenGLES和Metal都是第三方标准,基于这些标准的具体内部实现由相应的GPU厂商开发。Metal是一套来自Apple的第三方标准,由Apple实施。很多开发者从未直接使用过Metal,而是通过CoreAnimation、CoreImage等核心系统框架间接使用Metal。CoreAnimation与UIKit框架的关系上面渲染框架中提到的CoreAnimation是iOS和OSX上图形渲染和动画的基础框架,主要用于对应用程序的视图和其他视觉元素进行动画处理。CoreAnimation的实现逻辑是将大部分实际绘制工作交给GPU加速渲染,不会对CPU造成负担,实现流畅的动画效果。CoreAnimation的核心类是CALayer,UIKit框架的核心类是UIView。下面详细介绍这两个类的关系。UIView和CALayer的关系如上图所示。UIView和CALayer是一对一的关系。每个UIView都有一个CALayer与之对应。一个负责布局和交互响应,另一个负责页面渲染。它们的两个核心关系如下:CALayer是UIView的属性之一,负责渲染和动画,提供视觉内容的呈现。UIView提供了对CALayer功能的封装,负责处理交互事件。举个更形象的例子,UIView是画板,CALayer是画布。当你创建画板时,它会自动绑定一个画布,画板会响应你的操作。比如你可以移动画板,画布负责呈现具体的图形,两者职责明确。一个负责交互,一个负责渲染绘制。为什么要把CALayer和UIView分开?iOS平台和MacOS平台的用户交互方式根本不同,但渲染逻辑是通用的。在iOS系统中,我们使用的是UIKit和UIView,而在MacOS系统中,我们使用的是AppKit和NSView,所以这里在这种情况下,将显示部分的逻辑分离出来,跨平台复用。CALayer中的contents属性保存了设备渲染管线(通常称为backingstore)渲染出来的bitmap位图,也就是我们一开始提到的屏幕绘制最原始的数据源。当设备屏幕刷新时,生成的位图将从CALayer中读取并呈现在屏幕上。@interfaceCALayer:NSObject
