引子
PD:我們需要的界面大概是這樣子的,可以實現(xiàn)嗎?

技術(shù):ok,這個界面很簡單,我們用基本的view就可以實現(xiàn)。
數(shù)日后
UED:我們的設(shè)計是這樣子的

技術(shù):呃,晴天霹靂啊,為什么搞的這么復雜,又要弧度,又要曲線?為什么不能用標準的框架來
UED:這就是藝術(shù),激烈而又平滑的過渡才能彰顯她旺盛的生命力,而明暗錯落的顏色賦予了她燃燒不盡的激情,仿佛一個少女,身材高挑、烈焰紅唇......
技術(shù):就不能少搞些奇技淫巧嗎,我已經(jīng)連吐槽的力氣都沒有了...
相信每個前端開發(fā)都有過這樣的經(jīng)歷,我們認為可以用通用視圖解決的需求,視覺非要加上點特殊的元素,不規(guī)則圖形、不均勻顏色。做為一個有追求的Coder雖然嘴上說著不要,但身體卻很誠實的去查資料。google一下iOS 繪圖,各種繪圖的技術(shù)簡介和demo就搜出來了,隨之而來的是各種意思好像都差不多,好像不是一回事的名詞:UIBezierPath 、Quartz、Quartz 2D 、QuartzCore、Core Graphic、OpenGL 、OpenGL ES......
此時的我是這樣子的

完全搞不懂到底在哪種場景下應(yīng)該使用哪種技術(shù),有木有。本文的目的就是梳理在iOS平臺中供開發(fā)人員使用的繪圖框架以及他們之間的恩怨糾葛,以便選擇最合適的技術(shù)框架完成需求。
iOS、MacOS系統(tǒng)圖形架構(gòu)

由此圖可見:
- iOS提供了兩套繪圖框架,分別是UIBezierPath和Core Graphics。UIBezierPath屬于UIKit。UIBezierPath是對Core Graphics框架的進一步封裝。
- OpenGL和Core Graphics都是繪圖專用的API類族,調(diào)用圖形處理器(GPU)進行圖形的繪制和渲染。在架構(gòu)上是平級的,相比UIkit更接近底層。
UIBezierPath
用于創(chuàng)建基于矢量的路徑,如圓形、橢圓形和矩形,或者由多個直線和曲線組成的形狀。繪圖步驟也非常的簡單:
- 1、重寫drawRect方法
- 2、創(chuàng)建UIBezierPath對象
- 3、設(shè)置繪圖屬性,lineWidth
- 4、渲染
UIBezierPath繪制圖形代碼
//繪制圓形
- (void)drawRect:(CGRect)rect{
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:rect];
//設(shè)置繪圖屬性
path.lineWidth = 1;
UIColor *color = [UIColor readColor];
[color set];
//按照路徑繪制圖形
[path stroke];
}
Quartz
在介紹 Core Graphics 之前,我們先把 Quartz 的概念理順清楚。嫌啰嗦可以直接拉到下面看結(jié)論
Quartz 2D is similar to NeXT's Display PostScript in its use of contexts. It first appeared as the 2D graphics rendering library called Core Graphics Rendering; along with Core Graphics Services (Compositing), it was wrapped into the initial incarnation of Quartz. Quartz (and its renderer) were first demonstrated at WWDC in May 1999.
Presently, the name Quartz 2D more precisely defines the 2D rendering capabilities of Core Graphics (Quartz). With the release of Mac OS X 10.2, marketing attention focused on Quartz Extreme, the composition layer, leaving the term "Quartz" to refer to the Core Graphics framework or just its 2D renderer. Presently, Quartz technologies can describe all of the rendering and compositing technologies introduced by macOS (including Core Image for example).
Prior to Mac OS X Tiger, QuickDraw rendering outperformed that of Quartz 2D. Mac OS X 10.4 rectified this, substantially increasing the standard rendering performance of Quartz 2D.Tiger also introduced Quartz 2D Extreme: optional graphics processor (GPU) acceleration for Quartz 2D, although it is not an officially supported feature. Quartz 2D Extreme is disabled by default in Mac OS X 10.4 because it may lead to video redraw issues or kernel panics. In Mac OS X Leopard, Quartz 2D Extreme was renamed
簡單來說:
- 1、Quartz由Quartz Compositor和Quartz 2D兩部分組成
- 2、Core Graphics是基于Quartz框架的2D繪圖引擎。所以很多資料將Core Graphics稱為Quartz是不準確的。
- 3、Core Graphics同Quartz 2D是等價的。
那么Quartz同OpenGL是什么關(guān)系呢?他的底層是否通過OpenGL調(diào)用GPU?
Mac OS X v10.2 introduced Quartz Extreme: graphics processor (GPU) acceleration for the Quartz Compositor. With Quartz Extreme, far fewer central processor (CPU) cycles are needed for scene composition. Instead, the Quartz Compositor encapsulates each rendered backing store in an OpenGL texture map or surface. It then directs the GPU to compose the surfaces and maps to provide the final image, which is delivered to the frame buffer.
Quartz Extreme only uses OpenGL commands, and requires a graphics card connected to an AGP 2X or faster bus (including AGP 4X, 8X, and PCI Express), supporting textures and maps of arbitrary size, since many of the renderers have no size limitation (Quartz 2D for example).
QuartzGL (called Quartz 2D Extreme when it was introduced in Mac OS X Tiger) is GPU acceleration for the Quartz 2D API. With QuartzGL enabled, all Quartz drawing commands are translated to OpenGL commands and executed on the GPU. This differs from Quartz Extreme, which still executes Quartz drawing commands on the CPU but performs final composition using the GPU.
結(jié)論
- 1、CoreGraphics是基于Quartz框架的繪圖引擎,同Quartz 2D是等價的。
- 2、Quartz Extreme是針對Quartz底層的GPU加速。
- 3、Quartz僅使用OpenGL的命令集,直接連接AGP(圖形加速接口)
- 4、Quartz在CPU上執(zhí)行繪圖命令,在GPU上最終合成成圖形
- 5、QuartzGL是Quartz 2D API的GPU加速。啟用QuartzGL后,所有Quartz繪圖命令都將轉(zhuǎn)換為OpenGL命令并在GPU上執(zhí)行。這個模式默認是關(guān)閉的
- 6、Quartz在進行3D圖形渲染時是基于OpenGL的,通過OpenGL連接AGP
Core Graphics
Core Graphics是基于Quartz框架的高保真輸出2D圖形的渲染引擎??商幚砘诼窂降睦L圖、抗鋸齒渲染、漸變、圖像、顏色管理、PDF文檔等。 Core Graphics提供了一套2D繪圖功能的C語言API,使用C結(jié)構(gòu)體和C的函數(shù)模擬了一套面向?qū)ο蟮木幊虣C制。Core Graphics中沒有OC的對象和方法。
無論圖片、PDF還是視圖的圖層,都是由CoreGraphics框架完成繪制的。UIImage、UIBezierPath和NSString都提供了至少一種用于在drawRect:中繪圖的方法,實現(xiàn)原理是將Core Graphics代碼封裝在其中,降低繪圖難度。
Context
CoreGraphics中最重要的對象是graphics context,既圖形上下文。context是CGContextRef的對象,負責存儲繪畫狀態(tài)和繪制內(nèi)存所處的內(nèi)存空間。
我以前一直無法很好的理解Context,后來接觸Android繪圖的時候發(fā)現(xiàn)Android沒有這個東西。Android的2D圖形繪制通過Canvas和Paint實現(xiàn)的。Android的繪圖框架理解起來就非常的容易,你想畫圖首先你要有畫的地方Canvas,其次你要有筆Paint。而在iOS中Context就是要畫圖的畫板和畫筆。
Core Graphics繪制圖形代碼
//繪制圓形
- (void)drawInContext:(CGContextRef)ctx
{
//保存當前的繪圖Context
CGContextSaveGState(ctx);
CGRect rectAngle = CGRectMake(0, 0, 100, 100);
//添加一個橢圓路徑
CGContextAddEllipseInRect(ctx, rectAngle);
//設(shè)置邊框?qū)挾? CGContextSetLineWidth(ctx, self.ellipseBorderWidth);
//設(shè)置邊框顏色
CGContextSetStrokeColorWithColor(ctx, self.ellipseBorderColor.CGColor);
CGContextStrokePath(ctx);
//設(shè)置填充色
CGContextSetFillColorWithColor(ctx, self.ellipseFillColor.CGColor);
CGContextFillEllipseInRect(ctx, rectAngle);
CGContextRestoreGState(ctx);
}
Core Graphics的Ref后綴類型
帶有Ref后綴的類型是CoreGraphics中用來模擬面向?qū)ο髾C制的C結(jié)構(gòu)。CoreGraphics對象是在堆上分配內(nèi)存,因此創(chuàng)建CoreGraphics對象時,會返回一個指向?qū)ο髢?nèi)存地址的指針。
使用這種分配方式的C結(jié)構(gòu)都有一個用來表示結(jié)構(gòu)指針的類型定義(type definition)。例如,CGColor結(jié)構(gòu),不會被直接使用的類型,有一個表示Color * 的類型定義—CGColorRef,用來被使用的類型。使用這種類型定義是為了區(qū)分指針變量,方便開發(fā)者判斷指針變量是指向C結(jié)構(gòu)還是可以接收消息的Objective-C對象。
CGRect和CGPoint這種比較簡單直接在棧上分配的結(jié)構(gòu)體,不需要使用結(jié)構(gòu)指針,因此類型名稱后不帶Ref后綴。
帶有Ref后綴的類型的對象可能是強引用指針,成為指向?qū)ο蟮膿碛姓?。ARC是無法識別Core對象的所有權(quán),必須在使用后手動釋放。規(guī)則是,如果使用名稱中帶有create或者copy的函數(shù)創(chuàng)建了一個CoreGraphics對象,就必須調(diào)用Release函數(shù)并傳入該對象的指針。
Core Graphics 能完成的工作
- 繪制圖形 : 線條、三角形、矩形、圓、圓弧弧等
- 繪制文字
- 繪制生成圖片(圖像)
- 讀取生成PDF
- 繪制漸變
Core Graphics相比UIBezierPath在使用上更復雜一些,但是支持的效果也更多,程序運行效率更高。所以現(xiàn)在iOS系統(tǒng)上繪圖需求基本上都使用Core Graphis來完成。
OpenGL && OpenGL ES
OpenGL ES 是OpenGL 三維圖形API的子集,針對嵌入式操作系統(tǒng)設(shè)計。iOS和Android都在系統(tǒng)內(nèi)集成了OpenGL ES。所以針對OpenGL ES 的代碼可以實現(xiàn)跨平臺,大多數(shù)游戲框架都基于OpenGL。
OpenGL編程流程
- 1、編寫shader腳本
- 編寫vertex shader
- 編寫fragment shader
- 2、創(chuàng)建shader實例
- 創(chuàng)建 shader
- 裝載 shader
- 編譯 shader
- 3、創(chuàng)建program實例
- 創(chuàng)建program
- 裝配shader
- 鏈接program
- 使用program
裝載OpenGL shader 、創(chuàng)建OpenGL program示例代碼
- (void)setupProgram
{
NSString *vertexShaderPath = [[NSBundle mainBundle] pathForResource:@"VertexShader" ofType:@"glsl"];
NSString *fragmentShaderPath = [[NSBundle mainBundle] pathForResource:@"FragmentShader" ofType:@"glsl"];
GLuint vertexShader = [GLESUtils loadShader:GL_VERTEX_SHADER withFilepath:vertexShaderPath];
GLuint fragmentShader = [GLESUtils loadShader:GL_FRAGMENT_SHADER withFilepath:fragmentShaderPath];
//Create program,attach shaders
_programHandle = glCreateProgram();
if (!_programHandle)
{
NSLog(@"Failed to create program");
return;
}
glAttachShader(_programHandle, vertexShader);
glAttachShader(_programHandle, fragmentShader);
glLinkProgram(_programHandle);
GLint linked;
glGetProgramiv(_programHandle, GL_LINK_STATUS, &linked);
if (!linked)
{
GLint infoLen = 0;
glGetProgramiv(_programHandle, GL_INFO_LOG_LENGTH, &infoLen);
if (infoLen > 1)
{
char *infoLog = malloc(sizeof(char) *infoLen);
glGetProgramInfoLog(_programHandle, infoLen, NULL, infoLog);
free(infoLog);
}
glDeleteProgram(_programHandle);
_programHandle = 0;
return;
}
glUseProgram(_programHandle);
_positionSlot = glGetAttribLocation(_programHandle, "vPosition");
}
iOS系統(tǒng)使用OpenGL編程流程
- 1、設(shè)置CAEAGLLayer
CAEAGLLayer是iOS中用于呈現(xiàn)OpenGL ES的渲染內(nèi)容的 - 2、設(shè)置EAGLContext
OpenGL ES 渲染上下文。這個context管理所有使用OpenGL ES 進行描繪的狀態(tài),命令以及資源信息。 - 3、創(chuàng)建Renderbuffer
緩沖區(qū)用于存儲繪圖數(shù)據(jù),Render Buffer有三種類型,分別是color、depth、stencil buffer - 4、創(chuàng)建Framebuffer object
Framebuffer object是buffer的管理者,color、depth、stencil可以添加到一個Framebuffer object上 - 5、銷毀Renderbuffer和Framebuffer
當UIView變化后,layer的寬高也隨之變化,導致原來的renderbuffer 不再相符,需要銷毀既有 renderbuffer 和 framebuffer
iOS系統(tǒng)調(diào)用OpenGL示例代碼
//在iOS設(shè)備中通過CAEAGLLayer使用OpenGL接口渲染內(nèi)容
+ (Class)layerClass
{
return [CAEAGLLayer class];
}
//設(shè)置呈現(xiàn)圖層
- (void)setupLayer
{
_eaglLayer = (CAEAGLLayer *)self.layer;
_eaglLayer.opaque = YES;
_eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking,
kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat ,nil];
}
//設(shè)置圖形上下文
- (void)setupContext
{
EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2;
_context = [[EAGLContext alloc] initWithAPI:api];
if (!_context)
{
NSLog(@"Failed to initialize");
exit(1);
}
if (![EAGLContext setCurrentContext:_context])
{
exit(1);
}
}
//設(shè)置渲染緩沖區(qū)
- (void)setupRenderBuffer
{
glGenRenderbuffers(1, &_colorRenderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderBuffer);
[_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:_eaglLayer];
}
//設(shè)置FrameBuffer
- (void)setupFrameBuffer
{
glGenFramebuffers(1, &_frameBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _colorRenderBuffer);
}
- (void)destoryRenderAndFrameBuffer
{
glDeleteFramebuffers(1 , &_frameBuffer);
_frameBuffer = 0;
glDeleteRenderbuffers(1, &_colorRenderBuffer);
_colorRenderBuffer = 0;
}
示例代碼地址:
http://gitlab.alibaba-inc.com/xunfeng.zy/opengl/blob/master/OpenGLDemo/
OpenGL ES的詳細介紹:OpenGL ES渲染管線與著色器
結(jié)論
UIBezierPath的優(yōu)勢是方便,能用最少的代碼得要想要的圖形,并且不需要管理圖形上下文、緩沖區(qū)等容易出問題的地方,只需要關(guān)注圖形本身就行了。最主要的缺點是支持的效果有限,當需要實現(xiàn)一些復雜圖形、復雜漸變效果的時候就無能為力了。所以如果只是一個簡單的圖形沒有特別的要求,可以用UIBezierPath實現(xiàn)。
Core Graphics的功能就比UIBezierPath強大很多,使用起來也更復雜,而且需要自己管理圖形上下文,需要投入更多的開發(fā)工作量。在效率和可做更多工作這兩個方面上Core Graphics全面壓制UIBezierPath,所以如果是復雜的圖形、多個圖形疊加、多種顏色漸變等需求可以使用Core Graphics實現(xiàn)。
OpenGL ES是直接操作GPU繪圖,而Core Graphics框架是先把命令輸入到CPU再調(diào)用GPU繪圖。很明顯OpenGL ES繪圖的效率更高,使用OpenGL ES繪圖也可以實現(xiàn)跨平臺,而Core Graphics只能在iOS系統(tǒng)中使用。相比Core Graphics只支持2D繪圖,OpenGL ES對2D\3D都有很好的支持。
可見OpenGL ES是全方面的碾壓Core Graphics,但是除了特別大的圖片運算需求外,我們很少使用OpenGL ES。其實上面的示例代碼已經(jīng)很好的說明了,繪制同一個圖形UIBezierPath需要5行代碼,Core Graphics需要10行代碼,雖然Core Graphics明顯比UIBezierPath工作量大,但還是同一個數(shù)量級的。而使用OpenGL ES繪制,算上Vertex Shader和Fragment Shader繪制一個圖形要300~400行代碼。整整增加了兩個數(shù)量級。再加上調(diào)試、debug的工作量,就算有跨平臺的加成也無法抵消增加的時間成本。想使用OpenGL進行繪圖,基本上都需要二次封裝,這也是為什么我們在繪圖的時候一般首選Core Graphics。
參考資料:
https://en.wikipedia.org/wiki/Quartz_2D
https://developer.apple.com/library/content/documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_overview/dq_overview.html
https://en.wikipedia.org/wiki/Quartz
https://en.wikipedia.org/wiki/Quartz_Compositor
https://en.wikipedia.org/wiki/OpenGL_ES