【WWDC 2017】浅谈 iOS 渲染与动画的艺术

发布于:2018-01-10 22:14,阅读数:426,点赞数:0


> 本文为小专栏[《Graphics Dance》](https://xiaozhuanlan.com/graphics_dance)特供文章,仅提供预览,欢迎订阅。

# 目录

- 引言
- iOS 中的渲染框架
- UIKit & AppKit
- Core Animation
- Core Graphics
- Core Image
- SceneKit & SpriteKit
- Metal
- 小结
- 一些例子
- UIKit Customization
- Core Image
- Core Graphics
- Core Animation
- SpriteKit
- SceneKit
- 小结
- Core Animation
- 图层原理
- Frame & Transform
- Mask & Shadow
- Tips & Tricks
- CAShapeLayer
- CAGradientLayer
- Layer Speed
- 小结


# 引言

本文的主题是渲染和动画。

本文将通过简单的例子来展现 iOS 系统中的各个渲染框架能做什么,适合做什么。之后会对 Core Animation 的图层原理以及一些日常开发中遇到的奇怪的现象背后的原因做一个简单的讲解。看完这一章节,相信读者会对 iOS 的渲染和动画会进一步的了解。

# iOS 中的渲染框架

iOS 包含了多套渲染框架。从开发者们接触得最多的`UIKit`、`Core Animation`,到今年 iOS 11 最新推出的`Metal 2`、`Metal for VR`。它们各自的职责大不相同。作为开发者,我们或许并不能各个框架都精通,至少应该知道它们大概是用来做什么的,遇到具体需求的时候,才会有一个大致的方向。

## UIKit & AppKit

作为开发者,对 UIKit 和 AppKit 应该是非常熟悉了,实际开发中大量应用到了它们。这不是本章节的重点,因此不再展开。

## Core Animation

Core Animation 是常用的框架之一。它比 UIKit 和 AppKit 更底层。正如我们所知,`UIView`底下封装了一层`CALayer`树,Core Animation 层是真正的渲染层,我们之所以能在屏幕上看到内容,真正的渲染工作是在 Core Animation 层的。关于`CALayer`的内容,会在下文中展开。

## Core Graphics

Core Graphics 也是常用的框架之一,相信开发者们都不陌生。它用于运行时绘制图像。开发者们可以通过 Core Graphics 绘制路径、颜色。当开发者需要在运行时创建图像时,可以使用 Core Graphics 去绘制。与之相对的是运行前创建图像,例如用 Photoshop 提前做好图片素材直接导入应用。相比之下,我们更需要 Core Graphics 去在运行时实时计算、绘制一系列图像帧来实现动画。

## Core Image

Core Image 与 Core Graphics 恰恰相反,Core Graphics 用于在运行时创建图像,而 Core Image 是用来处理已经创建的图像的。Core Image 框架拥有一系列现成的图像过滤器,能对已存在的图像进行高效的处理。

![](//cdn.yuusann.com/img/corpus/18003_1.png)

它的优点在于十分高效。大部分情况下,它会在 GPU 中完成工作,但如果 GPU 忙,会使用 CPU 进行处理。如果设备支持 Metal,那么会使用 Metal 处理。这些操作会在底层完成,Apple 的工程师们已经帮助开发者们完成这些操作了。

Core Image 的过滤器支持管道式串联,在下文中会有一个具体的使用 Core Image 框架的例子。

## SceneKit & SpriteKit

普通开发者可能会对 SceneKit 和 SpriteKit 感到陌生,SceneKit 主要用于某些 3D 场景需求,而 SpriteKit 更多的用于游戏开发。SceneKit 和 SpriteKit 都包含了粒子系统、物理引擎等,看上去似乎是专门为游戏准备的,但事实上在普通应用中开发者们一样也可以使用它们来完成一些比较炫酷的特效和物理模拟。

## Metal

Metal 存在于以上渲染框架的最底层。Metal 对于大多数开发者来说都比较神秘,大多数开发者都没有直接使用过 Metal,但其实所有开发者都在间接地使用 Metal。Core Animation、Core Image、SceneKit、SpriteKit等等渲染框架都是构建于 Metal 之上的。

细心的开发者会发现,当在真机上调试 OpenGL 程序时,控制台会打印出启用 Metal 的日志。根据这点,笔者猜测,Apple 已经实现了一套机制将 OpenGL 命令无缝桥接到 Metal 上,由 Metal 担任真正于硬件交互的工作。

## 小结

以上是本文中提到的渲染框架。正如上文所说,开发者们并不一定要对这些框架样样精通,真正的目的是让开发者在遇到具体某个需求时能想起来似乎有某个框架是专门做这个任务的,能有个大致的方向。

下面有一些具体的例子能更形象地展现这些框架的功能。

# 一些例子

这里将有一些简单的 Demo,代码可以从 [这里](https://developer.apple.com/sample-code/wwdc/2017/Advanced-Visual-Effects-Playground-Sample-Code.zip) 下载。例子使用 Swift 4 编写,因此需要最新的 Xcode 9 编译。

## UIKit Customization

此范例将`UISlider`的滑轨替换成了自定义的图片,效果如下图所示:

![](//cdn.yuusann.com/img/corpus/18003_2.png)

自定义图片经过`trackImage`方法,被重新渲染成了目标大小的图片:

```swift
func trackImage(_ image: UIImage, width: CGFloat, resizingMode: UIImageResizingMode) -> UIImage {
let capInsets = UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8)
let bounds = CGRect(x: 0, y: 0, width: width, height: image.size.height)
let renderer = UIGraphicsImageRenderer(bounds: bounds)
return renderer.image { _ in
image.resizableImage(withCapInsets: capInsets, resizingMode: .stretch).draw(in: bounds)
}.resizableImage(withCapInsets: capInsets, resizingMode: resizingMode)
}
```

之后通过设置`UISlider`的以下方法修改滑轨图片。

```swift
tintSlider.setMinimumTrackImage(tintMinTrackImage, for: .normal)
tintSlider.setMaximumTrackImage(tintMaxTrackImage, for: .normal)
temperatureSlider.setMinimumTrackImage(temperatureMinTrackImage, for: .normal)
temperatureSlider.setMaximumTrackImage(temperatureMaxTrackImage, for: .normal)
```

这是一个抛砖引玉的范例,我们接着看。

## Core Image

此范例将通过 Core Image,对图片进行实时滤镜处理。

![](//cdn.yuusann.com/img/corpus/18003_3.png)

上文中提到,Core Image 拥有大量现成的图像过滤器用于图像处理,过滤器称为`CIFilter`。但事实上 CIFilter 并不是真正处理任务的对象,而真正处理任务的对象为`CIContext`。CIContext 是底层硬件渲染的入口,因此在使用 Core Image 框架时,必须先要创建它。

```swift
let ciContext = CIContext()
```

Core Image 框架接收的图像为`CIImage`对象,它可以通过`UIImage`或`CGImage`去构建。

```swift
let originalCIImage = CIImage(cgImage: originalCGImage)
```

关于 CIFilter,构造方法一定是值得吐槽的一点。它通过接收一个字符串类型的 name 参数和一个参数字典来构造对象,使得它无法脱离文档使用,期待在将来, CIFilter 的构造上能有进一步的改进。

```swift
public /*not inherited*/ init?(name: String, withInputParameters params: [String : Any]?)
public /*not inherited*/ init?(name: String)
```

范例使用一个名为`CITemperatureAndTint`的过滤器,其参数接收一个`CIImage`原始图像和两个`CIVector`二维向量参数,它的两个维度是色温和色彩。通过界面上的滚动条,用户可以任意更改其中一个向量,以达到调整色彩的作用。

```swift
let neutralTemperature = CGFloat(temperatureSlider.value)
let neutralTint = CGFloat(tintSlider.value)
let neutral = CIVector(x: neutralTemperature, y: neutralTint)

let targetNeutralTemperature = CGFloat(6500)
let targetNeutralTint = CGFloat(0)
let targetNeutral = CIVector(x: targetNeutralTemperature, y: targetNeutralTint)

let parameters = [ "inputImage": originalCIImage,
"inputNeutral": neutral,
"inputTargetNeutral": targetNeutral ]

let filter = CIFilter(name: "CITemperatureAndTint", withInputParameters: parameters)
```

准备好了这一切以后,就可以进行真正的图像处理了:

```swift
let filteredImage = filter.outputImage
// CIContext 创建 CGImage 的过程会比较耗时,可以在放在子线程执行。
let filteredCGImage = self.ciContext.createCGImage(filteredImage, from: filteredImage.extent)
self.imageView.image = UIImage(cgImage: filteredCGImage)
```

以上就是 Core Image 的图像滤镜处理流程。

由于图像处理是一项耗时的操作,如果你想你的应用对图像操作响应如飞,请参考接下来的`Core Graphics`。

----

# 预览结束,查看全文请订阅小专栏[《Graphics Dance》](https://xiaozhuanlan.com/topic/3709854261)


评论:0条


返回列表

返回归档

返回主页