[iOS Animation]-CALayer 变换-灭点

灭点

当在透视角度绘图的时候,远离相机视角的物体将会变小变远,当远离到一个极限距离,它们可能就缩成了一个点,因而全部的物体最后都汇聚消失在同一个点。 git

在现实中,这个点一般是视图的中心(图5.11),因而为了在应用中建立拟真效果的透视,这个点应该聚在屏幕中点,或者至少是包含全部3D对象的视图中点。 github

图5.11

图5.11 灭点 app

Core Animation定义了这个点位于变换图层的anchorPoint(一般位于图层中心,但也有例外,见第三章)。这就是说,当图层发生变换时,这个点永远位于图层变换以前anchorPoint的位置。 ide

当改变一个图层的position,你也改变了它的灭点,作3D变换的时候要时刻记住这一点,当你视图经过调整m34来让它更加有3D效果,应该首先把它放置于屏幕中央,而后经过平移来把它移动到指定位置(而不是直接改变它的position),这样全部的3D图层都共享一个灭点。 函数

sublayerTransform属性

若是有多个视图或者图层,每一个都作3D变换,那就须要分别设置相同的m34值,而且确保在变换以前都在屏幕中央共享同一个position,若是用一个函数封装这些操做的确会更加方便,但仍然有限制(例如,你不能在Interface Builder中摆放视图),这里有一个更好的方法。 ui

CALayer有一个属性叫作 sublayerTransform 。它也是CATransform3D类型,但和对一个图层的变换不一样,它影响到全部的子图层。这意味着你能够一次性对包含这些图层的容器作变换,因而全部的子图层都自动继承了这个变换方法。 atom

相较而言,经过在一个地方设置透视变换会很方便,同时它会带来另外一个显著的优点:灭点被设置在容器图层的中点,从而不须要再对子图层分别设置了。这意味着你能够随意使用position和frame来放置子图层,而不须要把它们放置在屏幕中点,而后为了保证统一的灭点用变换来作平移。 spa

咱们来用一个demo举例说明。这里用Interface Builder并排放置两个视图(图5.12),而后经过设置它们容器视图的透视变换,咱们能够保证它们有相同的透视和灭点,代码见清单5.6,结果见图5.13。 code

图5.12

图5.12 在一个视图容器内并排放置两个视图 orm

清单5.6 应用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; }
复制代码

 

图5.13

图5.13 经过相同的透视效果分别对视图作变换

背面

咱们既然能够在3D场景下旋转图层,那么也能够从背面去观察它。若是咱们在清单5.4中把角度修改成M_PI(180度)而不是当前的M_PI_4(45度),那么将会把图层彻底旋转一个半圈,因而彻底背对了相机视角。

那么从背部看图层是什么样的呢,见图5.14

图5.14

图5.14 视图的背面,一个镜像对称的图片

如你所见,图层是双面绘制的,反面显示的是正面的一个镜像图片。

但这并非一个很好的特性,由于若是图层包含文本或者其余控件,那用户看到这些内容的镜像图片固然会感到困惑。另外也有可能形成资源的浪费:想象用这些图层造成一个不透明的固态立方体,既然永远都看不见这些图层的背面,那为何浪费GPU来绘制它们呢?

CALayer有一个叫作 doubleSided 的属性来控制图层的背面是否要被绘制。这是一个BOOL类型,默认为YES,若是设置为NO,那么当图层正面从相机视角消失的时候,它将不会被绘制。

扁平化图层

若是对包含已经作过变换的图层的图层作反方向的变换将会发什么什么呢?是否是有点困惑?见图5.15

图5.15

图5.15 反方向变换的嵌套图层

注意作了-45度旋转的内部图层是怎样抵消旋转45度的图层,从而恢复正常状态的。

若是内部图层相对外部图层作了相反的变换(这里是绕Z轴的旋转),那么按照逻辑这两个变换将被相互抵消。

验证一下,相应代码见清单5.7,结果见5.16

清单5.7 绕Z轴作相反的旋转变换

复制代码
@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
复制代码

 

图5.16

图5.16 旋转后的视图

运行结果和咱们预期的一致。如今在3D状况下再试一次。修改代码,让内外两个视图绕Y轴旋转而不是Z轴,再加上透视效果,以便咱们观察。注意不能用 sublayerTransform 属性,由于内部的图层并不直接是容器图层的子图层,因此这里分别对图层设置透视变换(清单5.8)。

清单5.8 绕Y轴相反的旋转变换

复制代码
- (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; }
复制代码

 

预期的效果应该如图5.17所示。

图5.17

图5.17 绕Y轴作相反旋转的预期结果。

但其实这并非咱们所看到的,相反,咱们看到的结果如图5.18所示。发什么了什么呢?内部的图层仍然向左侧旋转,而且发生了扭曲,但按道理说它应该保持正面朝上,而且显示正常的方块。

这是因为尽管Core Animation图层存在于3D空间以内,但它们并不都存在同一个3D空间。每一个图层的3D场景实际上是扁平化的,当你从正面观察一个图层,看到的实际上由子图层建立的想象出来的3D场景,但当你倾斜这个图层,你会发现实际上这个3D场景仅仅是被绘制在图层的表面。

图5.18

图5.18 绕Y轴作相反旋转的真实结果

相似的,当你在玩一个3D游戏,实际上仅仅是把屏幕作了一次倾斜,或许在游戏中能够看见有一面墙在你面前,可是倾斜屏幕并不可以看见墙里面的东西。全部场景里面绘制的东西并不会随着你观察它的角度改变而发生变化;图层也是一样的道理。

这使得用Core Animation建立很是复杂的3D场景变得十分困难。你不可以使用图层树去建立一个3D结构的层级关系--在相同场景下的任何3D表面必须和一样的图层保持一致,这是由于每一个的父视图都把它的子视图扁平化了。

至少当你用正常的CALayer的时候是这样,CALayer有一个叫作CATransformLayer的子类来解决这个问题。具体在第六章“特殊的图层”中将会具体讨论。

相关文章
相关标签/搜索