3D变换动画精髓

来源:互联网 发布:mac下载的安装包在哪 编辑:程序博客网 时间:2024/05/16 01:34

郁闷的要死,写了N长的博客,结果按到command+Q了,对于学习动画的点点滴滴记载就销毁一旦了,哭!

如何开发一个可以平滑控制并且很有趣的交互动画,学习下边只是一个基础。


现在只记录干货:

图层知识看图一图说尽:



2D平面动画    一图举例说尽,这里的坐标系用的是mac的坐标系 和IOS的坐标系不同 可以举一反三地。



图层几何变换:

可以通过矩阵变换来改变一个图层的几何形状,CATransform3D 的数据结构定义一个同质的三维变换(4x4 CGFloat 值的矩阵),用于 图层的旋转,缩放,偏移,歪斜和应用的透视。图层的两个属性指定了变换矩阵:transform 和 sublayerTransform 属性。图层的 transform 属性指定的矩阵结合图层的 anchorPoint 属性作用于图层和图层的子图层上 面,图层的 sublayerTransform 属性指定的矩阵只会影响图层的子图层,而不会对 图层本身产生影响。

你可以通过以下的任何一个方法改变 CATransform3D 的数据结构:(1) 使用CATransform3D函数(2) 直接修改数据结构的成员(3) 使用键-值编码改变键路径

CATransform3DIdentity 是单位矩阵,该矩阵没有缩放、旋转、歪斜、透视。把该 矩阵应用到图层上面,会把图层几何属性修改为默认值。

对我门来说学习3D动画的困惑点在于,如何灵活自如掌握变换函数,我门先初步了解下3D动画的变换函数  也是一图说尽,先了解下 然后干货在下边:


矩阵的结构如下:

ATransform3D 数据结构的定义,结构的成员都在其相应的矩阵位置。

struct CATransform3D               

{                 

CGFloat m11, m12, m13, m14;                 

CGFloat m21, m22, m23, m24;                

CGFloat m31, m32, m33, m34;                 

CGFloat m41, m42, m43, m44;                 

};  typedef struct CATransform3D CATransform3D;   


关于m34,我门看到很多立体动画例子都用到这个值 ,那么这个值到底是干什么的呢? 我门用数学的知识告诉你。

原网址:

在iOS中使用CATransform3D这个结构体来表示三维的齐次坐标变换矩阵. 齐次坐标是一种坐标的表示方法,n维空间的坐标需要用n+1个元素的坐标元组来表示,在Quartz 2D Transform中就有关于齐次坐标的应用,那边是关于二维空间的变换,其某点的齐次坐标的最后一个元素始终设置为1。使用齐次坐标而不是简单的数学坐标是为了方便图形进行仿射变换,仿射变换可以通过仿射变换矩阵来实现,3D的仿射变换可以实现诸如 平移(translation),旋转(rotation),缩放(scaling),切变(shear)等变换。如果不用齐次坐标那么进行坐标变换可能就涉及到两种运算了,加法(平移)和乘法(旋转,缩放),而使用齐次坐标以及齐次坐标变换矩阵后只需要矩阵乘法就可以完成一切了。上面的这些如果需要深入了解就需要去学习一下图形变换的相关知识,自己对矩阵的乘法进行演算。

iOS中的CALayer的3D本质上并不能算真正的3D(其视点即观察点或者所谓的照相机的位置是无法变换的),而只是3D在二维平面上的投影,投影平面就是手机屏幕也就是xy轴组成的平面(注意iOS中为左手坐标系),那么视点的位置是如何确定的呢?可以通过CATransform3D中的m34来间接指定, m34 = -1/z,其中z为观察点在z轴上的值,而Layer的z轴的位置则是通过anchorPoint来指定的,所谓的anchorPoint(锚点)就是在变换中保持不变的点,也就是某个Layer在变换中的原点,xyz三轴相交于此点。在iOS中,Layer的anchorPoint使用unit coordinate space来描述,unit coordinate space无需指定具体真实的坐标点而是使用layer bounds中的相对位置,下图展示了一个Layer中的几个特殊的锚点, 


m34 = -1/z中,当z为正的时候,是我们人眼观察现实世界的效果,即在投影平面上表现出近大远小的效果,z越靠近原点则这种效果越明显,越远离原点则越来越不明显,当z为正无穷大的时候,则失去了近大远小的效果,此时投影线垂直于投影平面,也就是视点在无穷远处,CATransform3D中m34的默认值为0,即视点在无穷远处.

还有一个需要说明一下的就是齐次坐标到数学坐标的转换 通用的齐次坐标为 (a, b, c, h),其转换成数学坐标则为 (a/h, b/h, c/h).

二.代数解释

假设一个Layer anchorPoint为默认的 (0.5, 0.5 ), 其三维空间中一个A点 (6, 0, 0),m34 = -1/1000.0, 则此点往z轴负方向移动10个单位之后,则在投影平面上看到的点的坐标是多少呢?

A点使用齐次坐标表示为 (6, 0, 0, 1)

QuartzCore框架为我们提供了函数来算出所需要的矩阵,

    CATransform3D transform = CATransform3DIdentity;    transform.m34 = -1/1000.0;    transform = CATransform3DTranslate(transform, 0, 0, -10);

计算出来的矩阵为

 { 1,    0,    0,     0;     0,    1,    0,     0;     0,    0,    1,     -0.001;     0,    0,  -10,    1.01;      }   

其实上面的变换矩阵本质上是两个矩阵相乘得到的 变换矩阵 * 投影矩阵 变换矩阵为

{1,    0,    0,    0;    0,    1,    0,    0;    0,    0,    1,    0;    0,    0,   -10,  1;      }     

投影矩阵为

 {1,    0,    0,    0;    0,    1,    0,    0;    0,    0,    1, -0.001;    0,    0,    0,    1;   }     

上面的两个矩阵相乘则会得到最终的变换矩阵(如果忘记矩阵乘法的可以去看下线性代数复习下),所以一个矩阵就可以完成变换和投影。

将A点坐标乘上最终的变换矩阵,则得到 {6, 0 , -10, 1.01}, 转换成数学坐标点为 {6/1.01, 0, 10/1.01},则可以知道其在投影平面上的投影点为 {6/1.01, 0, 0} 也就是我们看到的变换后的点。其比之前较靠近原点。越往z轴负方向移动,则在投影平面上越靠近原点。

三.几何解释

将上面的例子使用几何的方式来进行解释分析,当我们沿着y轴的正方向向下看时候,可以得到如下的景象

虚线为投影线,其和x轴的交点即为A点的投影点。 由相似三角形的定理我们很容易算出投影的点,

1000/(1000 + 10) = x/6,则x = 6*1000/1010 = 6/1.01


现实例子:

完成使用CALayer创建的一个旋转的3D立方体。


 


很简单,注意保持一个清晰的空间想象力,然后把每一个CALayer执行相应的3D变换,最后使用Core Animation是主Layer动起来,OK。


让我们开始动手!首先,在ViewController中定义主Layer,这个CALayer用来存放其他子Layer,我们一共需要6个子Layer,每一个子Layer代表正方体的一个面。


//主Layer
CALayer *_rootLayer;
 


接着,也是最重要的,定义一个创建3D变换后的CALayer辅助函数。


这里为了使Layer有渐变色,所以使用CAGradientLayer类型,因此第一步就是设置好CAGradientLayer的那些杂七杂八的属性(颜色,位置等),第二步,从参数中获取偏移和旋转3D变换的值,然后执行相应的变换。具体参数我们会在之后调用这个方法时传入,这里总共需要用来偏移的X,Y,Z参数和用来旋转的角度,X,Y,Z参数,一共7个参数。设置好3D Transform后,这个方法的第三步就是把这个新的Layer加入到主Layer中。


整个方法代码如下:


//用来添加经过3D变换的CALayer
- (void)addLayer:(NSArray*)params
{
    //创建支持渐变背景的CAGradientLayer
    CAGradientLayer *gradient = [CAGradientLayer layer];
    //设置位置,和颜色等参数
    gradient.contentsScale = [UIScreen mainScreen].scale;
    gradient.bounds = CGRectMake(0, 0, 100, 100);
    gradient.position = CGPointMake(CGRectGetMidX(self.view.bounds), CGRectGetMidY(self.view.bounds));
    gradient.colors = @[(id)[UIColor grayColor].CGColor, (id)[UIColor blackColor].CGColor];
    gradient.locations = @[@0, @1];
    gradient.startPoint = CGPointMake(0, 0);
    gradient.endPoint = CGPointMake(0, 1);
    
    //根据参数对CALayer进行偏移和旋转Transform
    CATransform3D transform = CATransform3DMakeTranslation([[params objectAtIndex:0] floatValue], [[params objectAtIndex:1] floatValue], [[params objectAtIndex:2] floatValue]);
    transform = CATransform3DRotate(transform, [[params objectAtIndex:3] floatValue], [[params objectAtIndex:4] floatValue], [[params objectAtIndex:5] floatValue], [[params objectAtIndex:6] floatValue]);
    //设置transform属性并把Layer加入到主Layer中
    gradient.transform = transform;
    [_rootLayer addSublayer:gradient];
}
 


接着,在ViewController的viewDidLoad方法内,开始利用这个辅助函数来创建每一个面,注意最后要将主Layer进行一次3D变换,这样才能看出3D效果。


如下代码:


//创建主Layer
_rootLayer = [CALayer layer];
_rootLayer.contentsScale = [UIScreen mainScreen].scale;
_rootLayer.frame = self.view.bounds;


//前
[self addLayer:@[@0, @0, @50, @0, @0, @0, @0]];
//后
[self addLayer:@[@0, @0, @(-50), @(M_PI), @0, @0, @0]];
//左
[self addLayer:@[@(-50), @0, @0, @(-M_PI_2), @0, @1, @0]];
//右
[self addLayer:@[@50, @0, @0, @(M_PI_2), @0, @1, @0]];
//上
[self addLayer:@[@0, @(-50), @0, @(-M_PI_2), @1, @0, @0]];
//下
[self addLayer:@[@0, @50, @0, @(M_PI_2), @1, @0, @0]];


//主Layer的3D变换
CATransform3D transform = CATransform3DIdentity;
transform.m34 = -1.0 / 700;
//在X轴上做一个20度的小旋转
transform = CATransform3DRotate(transform, M_PI / 9, 1, 0, 0);
//设置CALayer的sublayerTransform
_rootLayer.sublayerTransform = transform;
//添加Layer
[self.view.layer addSublayer:_rootLayer];
 


OK,这个时候,静态的模型已经定义好了,最后我们要把整个正方体动起来,为了达到更好的视觉效果,之前我们已经对主Layer在X轴上做了20度角的小旋转,而对于动画,我们则需要对主Layer在Z轴上做一个360度的旋转,然后设置动画的重复次数为无限次,这样的话,方块就会无限次得转起来,代码:


//动画
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"sublayerTransform.rotation.y"];
//从0到360度
animation.toValue = [NSNumber numberWithFloat:2 * M_PI];
//间隔3秒
animation.duration = 3.0;
//无限循环
animation.repeatCount = HUGE_VALF;
//开始动画
[_rootLayer addAnimation:animation forKey:@"rotation"];




   


0 0