一、仿射变换
1. 几何学
的英文是什么?几何学主要研究哪方面的知识?
-
Geometry
简称几何,是数学的一部分。
- 主要研究:形状、大小、图形的相对位置等空间区域关系以及空间形式的度量。
2. 向量
的英文名字是什么?向量的定义是什么?
-
向量(Vector,矢量,欧几里得向量)
是数学、物理学和工程科学等多个自然科学中的基本概念。
-
向量的定义:
指一个同时具有大小
和方向
,且满足平行四边形法则
的几何对象。
3. 什么是线性变换
(线性变换从几何直观有三个要点)?
- 变换前是直线的,变换后依然是直线。
- 直线比例保持不变。
- 变换前是原点的,变换后依然是原点。
4. AffineTransform 译为 仿射变换
,请说一下什么意思?
- 简单来说,
仿射变换
= 线性变换
+ 平移
- 对比
线性变换
,从几何直观角度,少了 原点保持不变
这一条

5. 矩阵、矩阵乘法
最初的目的是为了什么?
二、CGAffineTransform
1. 理解:CGAffineTransform中的“仿射”?下图哪些符合放射变换
?
- CGAffineTransform中的“仿射”的意思是无论变换矩阵用什么值,图层中平行的两条线在变换之后任然保持平行


2. CGAffineTransform 是做什么的?
-
UIView
的 transform
属性是一个 CGAffineTransform
类型。
-
CALayer
的 affineTransform
属性是一个 CGAffineTransform
类型。
-
CGAffineTransform
:用于在二维空间做旋转、缩放和平移。
3. CGAffineTransform 效果可以叠加吗?
//rotate the layer 45 degrees
CGAffineTransform transform = CGAffineTransformMakeRotation(M_PI_4);
self.layerView.layer.affineTransform = transform;
//create a new transform
CGAffineTransform transform = CGAffineTransformIdentity; //scale by 50%
transform = CGAffineTransformScale(transform, 0.5, 0.5); //rotate by 30 degrees
transform = CGAffineTransformRotate(transform, M_PI / 180.0 * 30.0); //translate by 200 points
transform = CGAffineTransformTranslate(transform, 200, 0);
//apply transform to layer
self.layerView.layer.affineTransform = transform;
三、3D 变换(CATransform3D)
1. 理解 iOS 中的三维坐标体系

- 理解:由上图所见,绕Z轴的旋转等同于之前二维空间的仿射旋转,但是绕X轴和Y轴的旋转就突破了屏幕的二维空间,并且在用户视角看来发生了倾斜。
2. CATransform3D 和 CGAffineTransform 的区别?
-
CGAffineTransform
:属于 Core Graphics
框架,Core Graphics
是一个严格意义上的 2D 绘图 API,并且 CGAffineTransform 仅仅对 2D 变换有效。
-
CATransform3D
:属于 Core Animation
框架,可以让图层在 3D 空间内移动或旋转。
3. 借助 CATransform3D 实现视图的图层绕 Y 轴做一个 45 度角的旋转?
//rotate the layer 45 degrees along the Y axis
CATransform3D transform = CATransform3DMakeRotation(M_PI_4, 0, 1, 0);
self.imageView.layer.transform = transform;

- 看起来图层并没有旋转,而是仅仅在水平方向上的一个压缩,是哪里出了问题呢?
- 其实完全没错,视图看起来更窄实际上是因为我们在用一个斜向的视角看它,而不是透视。
4. 为了解决上述问题,做到近大远小的视角效果,要怎么办?

- 使用
CATransform3D
的 m34
元素,来做透视效果
- m34 的默认值是 0,我们可以通过设置 m34 为
-1.0 / d
来应用透视效果,d
代表了想象中相机和屏幕之间的距离,以像素为单位,通常 500~1000
即可。
//create a new transform
CATransform3D transform = CATransform3DIdentity;
//apply perspective
transform.m34 = - 1.0 / 500.0;
//rotate by 45 degrees along the Y axis
transform = CATransform3DRotate(transform, M_PI_4, 0, 1, 0);
//apply to layer
self.layerView.layer.transform = transform;

5. 什么是灭点?如果有多个视图要做透视变化(近大远小)
,如果保证灭点一致?

- Core Animation定义了这个灭点位于变换图层的 anchorPoint。
- 使用一个容器包裹所有需要透视的视图,然后用 sublayerTransform 在父视图上做透视变换,就能保证所有子视图的灭点一致。
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIView *containerView;
@property (nonatomic, weak) IBOutlet UIView *layerView1;
@property (nonatomic, weak) IBOutlet UIView *layerView2;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//apply perspective transform to container
CATransform3D perspective = CATransform3DIdentity;
perspective.m34 = - 1.0 / 500.0;
self.containerView.layer.sublayerTransform = perspective;
//rotate layerView1 by 45 degrees along the Y axis
CATransform3D transform1 = CATransform3DMakeRotation(M_PI_4, 0, 1, 0);
self.layerView1.layer.transform = transform1;
//rotate layerView2 by 45 degrees along the Y axis
CATransform3D transform2 = CATransform3DMakeRotation(-M_PI_4, 0, 1, 0);
self.layerView2.layer.transform = transform2;
}

6. 我们既然可以在 3D 场景下旋转图层,那么也可以从背面
去观察它,请问默认情况下视图的背面是什么?
- 所有图层都是双面绘制的,反面显示的是正面的一个镜像图片。
- 这并不是一个很好的特性,因为我们基本上永远都看不见这些图层的背面,那为什么要浪费 GPU 来绘制它们呢?
-
CALayer
有一个叫做 doubleSided
的属性来控制图层的背面是否需要绘制。当设置为 NO
,那么当图层正面从相机视角消失的时候,它将不会被绘制。
7. 请问如下代码,能将 innerView 恢复正常状态吗?
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIView *outerView;
@property (nonatomic, weak) IBOutlet UIView *innerView;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//rotate the outer layer 45 degrees
CATransform3D outer = CATransform3DMakeRotation(M_PI_4, 0, 0, 1);
self.outerView.layer.transform = outer;
//rotate the inner layer -45 degrees
CATransform3D inner = CATransform3DMakeRotation(-M_PI_4, 0, 0, 1);
self.innerView.layer.transform = inner;
}
@end

8. 如果将上题的绕 Z 轴旋转,换成绕 Y 轴旋转,innerView
能恢复正常状态吗?
- (void)viewDidLoad
{
[super viewDidLoad];
//rotate the outer layer 45 degrees
CATransform3D outer = CATransform3DIdentity;
outer.m34 = -1.0 / 500.0;
outer = CATransform3DRotate(outer, M_PI_4, 0, 1, 0);
self.outerView.layer.transform = outer;
//rotate the inner layer -45 degrees
CATransform3D inner = CATransform3DIdentity;
inner.m34 = -1.0 / 500.0;
inner = CATransform3DRotate(inner, -M_PI_4, 0, 1, 0);
self.innerView.layer.transform = inner;
}

四、固体对象
- 像如下创建出筛子 6 个面,然后再创建出一个容器 view

#import "SevenViewController.h"
#import <QuartzCore/QuartzCore.h>
#import <GLKit/GLKit.h>
#define LIGHT_DIRECTION 0, 1, -0.5
#define AMBIENT_LIGHT 0.5
@interface SevenViewController ()
@property (weak, nonatomic) IBOutlet UIView *containerView;
@property (strong, nonatomic) IBOutletCollection(UIView) NSArray *faces;
@end
@implementation SevenViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
self.containerView.backgroundColor = [UIColor clearColor];
for (UIView *view in self.faces) {
view.backgroundColor = [UIColor whiteColor];
}
[self createDice];
}
- (void)applyLightingToFace:(CALayer *)face
{
//add lighting layer
CALayer *layer = [CALayer layer];
layer.frame = face.bounds;
[face addSublayer:layer];
//convert the face transform to matrix
//(GLKMatrix4 has the same structure as CATransform3D)
//译者注:GLKMatrix4和CATransform3D内存结构一致,但坐标类型有长度区别,所以理论上应该做一次float到CGFloat的转换,感谢[@zihuyishi](https://github.com/zihuyishi)同学~
CATransform3D transform = face.transform;
GLKMatrix4 matrix4 = [self matrixFrom3DTransformation:transform];
GLKMatrix3 matrix3 = GLKMatrix4GetMatrix3(matrix4);
//get face normal
GLKVector3 normal = GLKVector3Make(0, 0, 1);
normal = GLKMatrix3MultiplyVector3(matrix3, normal);
normal = GLKVector3Normalize(normal);
//get dot product with light direction
GLKVector3 light = GLKVector3Normalize(GLKVector3Make(LIGHT_DIRECTION));
float dotProduct = GLKVector3DotProduct(light, normal);
//set lighting layer opacity
CGFloat shadow = 1 + dotProduct - AMBIENT_LIGHT;
UIColor *color = [UIColor colorWithWhite:0 alpha:shadow];
layer.backgroundColor = color.CGColor;
}
- (GLKMatrix4)matrixFrom3DTransformation:(CATransform3D)transform {
GLKMatrix4 matrix = GLKMatrix4Make(transform.m11, transform.m12, transform.m13, transform.m14,
transform.m21, transform.m22, transform.m23, transform.m24,
transform.m31, transform.m32, transform.m33, transform.m34,
transform.m41, transform.m42, transform.m43, transform.m44);
return matrix;
}
- (void)addFace:(NSInteger)index withTransform:(CATransform3D)transform {
// get the face view and add it to the container
UIView *face = self.faces[index];
[self.containerView addSubview:face];
//center the face view within the container
CGSize containerSize = self.containerView.bounds.size;
face.center = CGPointMake(containerSize.width / 2.0, containerSize.height / 2.0);
//apply the transform
face.layer.transform = transform;
//apply lighting
[self applyLightingToFace:face.layer];
}
- (void)createDice {
CGFloat containerHalfHeight = self.containerView.bounds.size.height / 2.0;
//add cube face 1
CATransform3D transform = CATransform3DMakeTranslation(0, 0, containerHalfHeight);
[self addFace:0 withTransform:transform];
//add cube face 2
transform = CATransform3DMakeTranslation(containerHalfHeight, 0, 0);
transform = CATransform3DRotate(transform, M_PI_2, 0, 1, 0);
[self addFace:1 withTransform:transform];
//add cube face 3
transform = CATransform3DMakeTranslation(0, -containerHalfHeight, 0);
transform = CATransform3DRotate(transform, M_PI_2, 1, 0, 0);
[self addFace:2 withTransform:transform];
//add cube face 4
transform = CATransform3DMakeTranslation(0, containerHalfHeight, 0);
transform = CATransform3DRotate(transform, -M_PI_2, 1, 0, 0);
[self addFace:3 withTransform:transform];
//add cube face 5
transform = CATransform3DMakeTranslation(-containerHalfHeight, 0, 0);
transform = CATransform3DRotate(transform, -M_PI_2, 0, 1, 0);
[self addFace:4 withTransform:transform];
//add cube face 6
transform = CATransform3DMakeTranslation(0, 0, -containerHalfHeight);
transform = CATransform3DRotate(transform, M_PI, 0, 1, 0);
[self addFace:5 withTransform:transform];
CATransform3D perspective = CATransform3DIdentity;
perspective.m34 = -1.0 / 500.0;
perspective = CATransform3DRotate(perspective, -M_PI_4, 1, 0, 0);
perspective = CATransform3DRotate(perspective, -M_PI_4, 0, 1, 0);
self.containerView.layer.sublayerTransform = perspective;
// 把视图1、2、3提到最前面,响应点击事件
[self.containerView bringSubviewToFront:self.faces[0]];
[self.containerView bringSubviewToFront:self.faces[1]];
[self.containerView bringSubviewToFront:self.faces[2]];
}
@end
