仿射变换
我们可以使用UIView的transform属性进行视图的旋转平移缩放,transform的属性是CGAffineTransform ,CGAffineTransform用于在二维空间做旋转,缩放和平
移。仿射变换主要是通过坐标乘以矩阵进行实现的
image.png
CGAffineTransform的基本用法
//旋转
CGAffineTransform CGAffineTransformMakeRotation(CGFloat angle)
//平移
CGAffineTransform CGAffineTransformMakeTranslation(CGFloat tx,
CGFloat ty)
//缩放
CGAffineTransform CGAffineTransformMakeScale(CGFloat sx, CGFloat sy)
CALayer也有一个transform属性但是该属性的类型却是 CATransform3D,
CALayer对应UIView的transform属性是affineTransform。
CALayer *layer = [CALayer layer];
layer.frame = CGRectMake(80, 50, 100, 100);
layer.contents = (__bridge id)[UIImage imageNamed:@"huhu"].CGImage;
[self.view.layer addSublayer:layer];
layer.affineTransform = CGAffineTransformMakeRotation(M_PI_2);
CALayer *layer2 = [CALayer layer];
layer2.frame = CGRectMake(80, 200, 100, 100);
layer2.contents = (__bridge id)[UIImage imageNamed:@"huhu"].CGImage;
[self.view.layer addSublayer:layer2];
image.png
混合变换
//在t的基础上旋转
CGAffineTransform CGAffineTransformRotate(CGAffineTransform t,
CGFloat angle)
//在t的基础上,缩放
CGAffineTransform CGAffineTransformScale(CGAffineTransform t,
CGFloat sx, CGFloat sy)
//在t的基础上,平移
CGAffineTransform CGAffineTransformTranslate(CGAffineTransform t,
CGFloat tx, CGFloat ty)
如果需要混合两个已经存在的变换矩阵,就可以使用如下方法,在两个变换 的基础上创建一个新的变换:
//t1和t2混合
CGAffineTransform CGAffineTransformConcat(CGAffineTransform t1,
CGAffineTransform t2)
当操纵一个变换的时候,初始生成一个什么都不做的变换很重要--也就是创建一 个 CGAffineTransform 类型的空值,矩阵论中称作单位矩阵,Core Graphics同 样也提供了一个方便的常量:
//单元矩阵
CGAffineTransform CGAffineTransformIdentity
3D变换
CGAffineTransform 类型属于Core Graphics框架,属于二维,所以CGAffineTransform仅支持2D变换,如果想实现3D,那就CATransform3D,CATransform3D比CGAffineTransform多一个维度,那就是深度zPosition
image.png
CATransform3D提供开发者的函数
//单元矩阵
CATransform3D CATransform3DIdentity
//生成平移矩阵
CATransform3D CATransform3DMakeTranslation (CGFloat tx,
CGFloat ty, CGFloat tz)
//生成缩放矩阵
CATransform3D CATransform3DMakeScale (CGFloat sx, CGFloat sy,
CGFloat sz)
//生成旋转矩阵
CATransform3D CATransform3DMakeRotation (CGFloat angle, CGFloat x,
CGFloat y, CGFloat z)
3D混合变换函数
CATransform3D CATransform3DTranslate (CATransform3D t, CGFloat tx,
CGFloat ty, CGFloat tz)
CATransform3D CATransform3DScale (CATransform3D t, CGFloat sx,
CGFloat sy, CGFloat sz)
CATransform3D CATransform3DRotate (CATransform3D t, CGFloat angle,
CGFloat x, CGFloat y, CGFloat z)
2D矩阵转换成3D矩阵
CATransform3D CATransform3DMakeAffineTransform (CGAffineTransform m)
事例
layer.transform = CATransform3DMakeAffineTransform(CGAffineTransformMakeRotation(M_PI_2));
image.png
透视投影
在真实世界中,当物体远离我们的时候,由于视角的原因看起来会变小,理论上说 远离我们的视图的边要比靠近视角的边更短,但实际上并没有发生,而我们当前的 视角是等距离的,也就是在3D变换中任然保持平行,和之前提到的仿射变换类似。
在等距投影中,远处的物体和近处的物体保持同样的缩放比例,这种投影也有它自 己的用处(例如建筑绘图,颠倒,和伪3D视频),但当前我们并不需要。
为了做一些修正,我们需要引入投影变换(又称作z变换)来对除了旋转之外的变换矩阵做一些修改,Core Animation并没有给我们提供设置透视变换的函数,因此 我们需要手动修改矩阵值,幸运的是,很简单:CATransform3D的透视效果通过一个矩阵中一个很简单的元素来控制:m34 ,m34用于按比例缩放X和Y的值来计算到底要离视角多远。
image.png
m34的默认值是0,我们可以通过设置m34为-1.0 / d 来应用透视效果,d代表了想象中视角相机和屏幕之间的距离,以像素为单位,那应该如何计算这个距离 呢?实际上并不需要,大概估算一个就好了。
因为视角相机实际上并不存在,所以可以根据屏幕上的显示效果自由决定它的防止 的位置。通常500-1000就已经很好了,但对于特定的图层有时候更小后者更大的值 会看起来更舒服,减少距离的值会增强透视效果,所以一个非常微小的值会让它看 起来更加失真,然而一个非常大的值会让它基本失去透视效果
CALayer *layer = [CALayer layer];
layer.frame = CGRectMake(80, 50, 100, 100);
layer.contents = (__bridge id)[UIImage imageNamed:@"huhu"].CGImage;
[self.view.layer addSublayer:layer];
CATransform3D transform3D = CATransform3DIdentity;
transform3D.m34 = -1.0/500;
layer.transform = CATransform3DRotate(transform3D, M_PI_4, 0, 1, 0);
CALayer *layer2 = [CALayer layer];
layer2.frame = CGRectMake(80, 200, 100, 100);
layer2.contents = (__bridge id)[UIImage imageNamed:@"huhu"].CGImage;
layer2.transform = CATransform3DMakeRotation(M_PI_4, 0, 1, 0);
[self.view.layer addSublayer:layer2];
image.png
从图片可以看出,上面的更真实,有层次感,下面的比较生硬。
灭点
当在透视角度绘图的时候,远离相机视角的物体将会变小变远,当远离到一个极限 距离,它们可能就缩成了一个点,于是所有的物体最后都汇聚消失在同一个点。
在现实中,这个点通常是视图的中心,于是为了在应用中创建拟真效果的透视,这个点应该聚在屏幕中点,或者至少是包含所有3D对象的视图中点。
image.png
Core Animation定义了这个点位于变换图层的anchorPoint,这就是说,当图层发生变换时,这个点永远位于图 层变换之前anchorPoint的位置
当改变一个图层的position,你也改变了它的灭点,做3D变换的时候要时刻记 住这一点,当你视图通过调整 来让它更加有3D效果,应该首先把它放置于屏 幕中央,然后通过平移来把它移动到指定位置(而不是直接改变它position),这样所有的3D图层都共享一个灭点
sublayerTransform
如果有多个视图或者图层,每个都做3D变换,那就需要分别设置相同的m34值,并 且确保在变换之前都在屏幕中央共享同一个 position ,如果用一个函数封装这些 操作的确会更加方便,但仍然有限制(例如,你不能在Interface Builder中摆放视 图),这里有一个更好的方法。
CALayer 有一个属性叫做sublayerTransform,它也是 CATransform3D 类型,但和对一个图层的变换不同,它影响到所有的子图层。这意味着你可以一次性 对包含这些图层的容器做变换,于是所有的子图层都自动继承了这个变换方法。
相较而言,通过在一个地方设置透视变换会很方便,同时它会带来另一个显著的优 势:灭点被设置在容器图层的中点,从而不需要再对子图层分别设置了。这意味着 你可以随意使用 position 和 frame 来放置子图层,而不需要把它们放置在屏幕中点,然后为了保证统一的灭点用变换来做平移。
CATransform3D perspective = CATransform3DIdentity;
perspective.m34 = -1.0/500;
self.view.layer.sublayerTransform = perspective;
CALayer *layer = [CALayer layer];
layer.frame = CGRectMake(80, 50, 100, 100);
layer.contents = (__bridge id)[UIImage imageNamed:@"huhu"].CGImage;
[self.view.layer addSublayer:layer];
CATransform3D transform3D = CATransform3DIdentity;
layer.transform = CATransform3DRotate(transform3D, -M_PI_4, 1, 0, 0);
CALayer *layer2 = [CALayer layer];
layer2.frame = CGRectMake(80, self.view.bounds.size.height-150, 100, 100);
layer2.contents = (__bridge id)[UIImage imageNamed:@"huhu"].CGImage;
layer2.transform = CATransform3DMakeRotation(M_PI_4, 1, 0, 0);
[self.view.layer addSublayer:layer2];
image.png
背面
我们既然可以在3D场景下旋转图层,那么也可以从背面去观察它。如果我们在清单 5.4中把角度修改为 M_PI (180度)而不是当前的 M_PI_4 (45度),那么将会 把图层完全旋转一个半圈,于是完全背对了相机视角。
CALayer *layer = [CALayer layer];
layer.frame = CGRectMake(80, 50, 100, 100);
layer.contents = (__bridge id)[UIImage imageNamed:@"huhu"].CGImage;
[self.view.layer addSublayer:layer];
CATransform3D transform3D = CATransform3DIdentity;
layer.transform = CATransform3DRotate(transform3D, M_PI, 0, 1, 0);
image.png
图层是双面绘制的,反面显示的是正面的一个镜像图片。
但这并不是一个很好的特性,因为如果图层包含文本或者其他控件,那用户看到这 些内容的镜像图片当然会感到困惑。另外也有可能造成资源的浪费:想象用这些图 层形成一个不透明的固态立方体,既然永远都看不见这些图层的背面,那为什么浪 费GPU来绘制它们呢?
CALayer有一个叫做doubleSided的属性来控制图层的背面是否要被绘制。这是一个BOOL类型,默认是YES,如果设置为NO,那么当图层正面从相机视角消失的时候,它将不会被绘制.
网友评论