iOS-从三维立方体到理解CATransform3D/CGAffineTransform/m34

前言

在写Custom Layout的demo时,用到了CATransform3D的m34参数,不务正业的想探究下这个矩阵到底为什么能影响到图形的透视旋转等等变换,所以通过本篇文章总结一下收获,供以后参考

目录

  • 简单实现三维立方体
  • CATransform3D&CGAffineTransform使用介绍
  • 原理探究及理解

简单实现三维立方体

Cube.gif

实现这个蛮简单的,只需要合理的调整旋转角度和平移,再加上一个定时器,完美(显然这个效果没什么卵用,但是笔者这只鶸来说,刚蹦出来的时候还是蛮开心的)

实现步骤 : 1.定义一个Basic View -> 2.调整并添加立方体的六个面 3.定时器调整Basic View的layer旋转
特别注意:旋转时,我们需要同时作用于6个子layer,所以请留意self.animateCube.layer.sublayerTransform = transform中使用的是sublayerTransform而非transform


    CGRect targetBounds = (CGRect){CGPointZero,CGSizeMake(200, 200)};
    self.animateCube = [[UIView alloc] initWithFrame:targetBounds];
    _animateCube.center = self.view.center;
    [self.view addSubview:self.animateCube];
    
    
    UIView *test = [[UIView alloc] initWithFrame:targetBounds];// front
    test.backgroundColor = [[UIColor blueColor] colorWithAlphaComponent:0.25];
    test.layer.transform = CATransform3DTranslate(test.layer.transform, 0, 0, 100);
    
    UIView *test1 = [[UIView alloc] initWithFrame:targetBounds];// back
    test1.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.5];
    test1.layer.transform = CATransform3DTranslate(test1.layer.transform, 0, 0, -100);
    
    UIView *test2 = [[UIView alloc] initWithFrame:targetBounds];// left
    test2.backgroundColor = [[UIColor yellowColor] colorWithAlphaComponent:0.5];
    test2.layer.transform = CATransform3DTranslate(test2.layer.transform, -100, 0, 0);
    test2.layer.transform = CATransform3DRotate(test2.layer.transform, M_PI_2, 0, 1, 0);
    
    UIView *test3 = [[UIView alloc] initWithFrame:targetBounds];// right
    test3.backgroundColor = [[UIColor purpleColor] colorWithAlphaComponent:0.5];
    test3.layer.transform = CATransform3DTranslate(test3.layer.transform, 100, 0, 0);
    test3.layer.transform = CATransform3DRotate(test3.layer.transform, M_PI_2, 0, 1, 0);
    
    UIView *test4 = [[UIView alloc] initWithFrame:targetBounds];// head
    test4.backgroundColor = [[UIColor orangeColor] colorWithAlphaComponent:0.5];
    test4.layer.transform = CATransform3DTranslate(test4.layer.transform, 0, 100, 0);
    test4.layer.transform = CATransform3DRotate(test4.layer.transform, M_PI_2, 1, 0, 0);
    
    UIView *test5 = [[UIView alloc] initWithFrame:targetBounds];// foot
    test5.backgroundColor = [[UIColor greenColor] colorWithAlphaComponent:0.5];
    test5.layer.transform = CATransform3DTranslate(test5.layer.transform, 0, -100, 0);
    test5.layer.transform = CATransform3DRotate(test5.layer.transform, M_PI_2, -1, 0, 0);

    [self.animateCube addSubview:test];
    [self.animateCube addSubview:test1];
    [self.animateCube addSubview:test2];
    [self.animateCube addSubview:test3];
    [self.animateCube addSubview:test4];
    [self.animateCube addSubview:test5];
    
    self.animateCube.transform = CGAffineTransformMakeScale(0.5, 0.5);//CGAffineTransform
    
    __block CATransform3D transform = CATransform3DIdentity;

    NSLog(@"%@",[NSString logForCATransform3D:transform]);

    // Label
    UILabel *label = [[UILabel alloc] init];
    label.frame = CGRectOffset(self.animateCube.frame, 0, - 100);
    label.text = @"AnimatedCube";
    [label sizeToFit];
    [self.view addSubview:label];

    transform.m34 = 1.0/-500;
    
    float angle = M_PI / 360;
    self.animateCube.layer.sublayerTransform = transform;
    NSTimer *timer = [NSTimer timerWithTimeInterval:1.0/60 repeats:YES block:^(NSTimer * _Nonnull timer) {
        transform = CATransform3DRotate(transform, angle, 1, 1, 0.5);
        self.animateCube.layer.sublayerTransform = transform;//
    }];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

CATransform3D&CGAffineTransform使用介绍

CGAffineTransform

CGAffineTransform.png

CGAffineTransform
An affine transformation matrix for use in drawing 2D graphics
用于绘制2D图形的仿射变换矩阵

以上为官方文档定义,那么什么是仿射变换,看看这里数学咖们的解释就能大概理解了→如何通俗地讲解「仿射变换」这个概念?

  1. CGAffineTransformMake返回直接控制每一个参数的仿射变换矩阵
CGAffineTransform CGAffineTransformMake(CGFloat a, CGFloat b, CGFloat c, CGFloat d, CGFloat tx, CGFloat ty);
  1. CGAffineTransformScale返回通过缩放现有仿射变换构造的仿射变换矩阵,sx/sy即为x方向y方向的缩放比例
CGAffineTransform CGAffineTransformScale(CGAffineTransform t, CGFloat sx, CGFloat sy);
  1. CGAffineTransformRotate返回通过旋转现有仿射变换构造的仿射变换矩阵,angle为旋转弧度
CGAffineTransform CGAffineTransformRotate(CGAffineTransform t, CGFloat angle);
  1. CGAffineTransformInvert返回通过反转现有仿射变换构造的仿射变换矩阵。
CGAffineTransform CGAffineTransformInvert(CGAffineTransform t);
  1. CGAffineTransformTranslate返回实现平移的仿射变换矩阵。tx/ty为偏移量
CGAffineTransform CGAffineTransformTranslate(CGAffineTransform t, CGFloat tx, CGFloat ty);
  1. CGAffineTransformConcat返回通过组合两个现有仿射变换构造的仿射变换矩阵。本质就是两个矩阵的乘法,上述平移、旋转、缩放的操作,如配图所示,都是可以通过点的齐次坐标与仿射变换矩阵的乘积获得,原理笔者会在第三部分解释
CGAffineTransform CGAffineTransformConcat(CGAffineTransform t1, CGAffineTransform t2);
  1. 几个特殊的仿射变换矩阵
    CGAffineTransformMakeScale/CGAffineTransformMakeRotation/CGAffineTransformMakeTranslation 都是在原视图初始坐标的基础上变化,所以并不会累加效果,相比上述Api它少了基础仿射矩阵参数CGAffineTransform t
// scale
CGAffineTransform CGAffineTransformMakeScale(CGFloat sx, CGFloat sy);
// rotaion
CGAffineTransform CGAffineTransformMakeRotation(CGFloat angle);
// translation
CGAffineTransform CGAffineTransformMakeTranslation(CGFloat tx, CGFloat ty);
  1. CGAffineTransformIdentity单位矩阵如下图所示,他并不会对图形造成任何影响,通常用来恢复初始状态
CGAffineTransformIdentity.png

我们要想将文字视图翻转该怎么做呢?

2D翻转

很简单,只需要将CGAffineTransformMakeScale中的参数设置为-1即可。是不是很有趣

label.transform = CGAffineTransformMakeScale(-1, -1);

CATransform3D

CATransform3D.png

CATransform3D
Defines the standard transform matrix used throughout Core Animation.
定义核心动画中使用的标准变换矩阵

CATransform同样定义为结构体,代表了三维图形4x4的变换矩阵。网上有几篇文章,对每个参数功能都进行了标注,但是这样并不十分严谨。因为左上3x3的矩阵区域,各个参数需要相互作用才能达到理想的准确状态,并不单纯的是某个值负责某种准确的三维图形变换。笔者将在本文的第三部分原理探究及理解中具体解释,感(hen)兴(wu)趣(liao)的同学可以往下看看。

因为只是从2D变换变成3D变换,而且4x4矩阵本身就是3x3矩阵的拓展,原理是一样的,所以有很多相似相通的地方。

  1. CATransform3DScale返回通过缩放现有变换构造的变换矩阵,sx/sy/sz即为x方向y方向z方向的缩放比例
CATransform3D CATransform3DScale (CATransform3D t, CGFloat sx,
    CGFloat sy, CGFloat sz)
  1. CATransform3DRotate返回通过旋转现有变换构造的变换矩阵,angle代表弧度,x,y,z代表各个轴上旋转的弧度倍数
CATransform3D CATransform3DRotate (CATransform3D t, CGFloat angle,
    CGFloat x, CGFloat y, CGFloat z)
  1. CATransform3DInvert返回反转后的变换矩阵
CATransform3D CATransform3DInvert (CATransform3D t)
  1. CATransform3DTranslate返回实现x/y/z轴上平移相应距离的变换矩阵
CATransform3D CATransform3DTranslate (CATransform3D t, CGFloat tx,
    CGFloat ty, CGFloat tz)
  1. CATransform3DConcat返回同时作用两种变换矩阵的矩阵
CATransform3D CATransform3DConcat (CATransform3D a, CATransform3D b)
  1. 几个特殊的变换矩阵
    CATransform3DMakeScale/CATransform3DMakeRotation/CATransform3DMakeTranslation同样是作用于原始视图的变换矩阵
/* Returns a transform that translates by '(tx, ty, tz)':
 * t' =  [1 0 0 0; 0 1 0 0; 0 0 1 0; tx ty tz 1]. */

CATransform3D CATransform3DMakeTranslation (CGFloat tx,
    CGFloat ty, CGFloat tz)

/* Returns a transform that scales by `(sx, sy, sz)':
 * t' = [sx 0 0 0; 0 sy 0 0; 0 0 sz 0; 0 0 0 1]. */

CATransform3D CATransform3DMakeScale (CGFloat sx, CGFloat sy,
    CGFloat sz)

/* Returns a transform that rotates by 'angle' radians about the vector
 * '(x, y, z)'. If the vector has length zero the identity transform is
 * returned. */

CATransform3D CATransform3DMakeRotation (CGFloat angle, CGFloat x,
    CGFloat y, CGFloat z)
  1. CATransform3DIdentity[1 0 0 0; 0 1 0 0; 0 0 1 0; 0 0 0 1],人畜无害矩阵,通常用于恢复初始状态

原理解释

CGAffineTransform

以下是苹果官方文档对于CGAffineTransform二维变换矩阵对图形影响的注解

CGAffineTransform官方文档注解
An affine transformation matrix is used to rotate, scale, translate, or skew the objects you draw in a graphics context. The CGAffineTransform
type provides functions for creating, concatenating, and applying affine transformations.
Affine transforms are represented by a 3 by 3 matrix:

A 3 by 3 matrix.

Because the third column is always (0,0,1), the CGAffineTransform
data structure contains values for only the first two columns.
Conceptually, an affine transform multiplies a row vector representing each point (x,y) in your drawing by this matrix, producing a vector that represents the corresponding point (x’,y’):
A row vector multiplying a 3 by 3 matrix.

Given the 3 by 3 matrix, the following equations are used to transform a point (x, y) in one coordinate system into a resultant point (x’,y’) in another coordinate system.
Transformation equations.

看到这里,线代大神一定会嘴角上扬了,所以如果以下内容有任何理解或书写错误,请您务必留言给笔者鶸渣评论勘误打脸,千万不要高抬贵手。

二维的变换都可以看作是坐标的齐次坐标同[a b 0; c d 0; tx ty 1]的乘法运算

矩阵乘法图示-阮一峰博客

矩阵的乘法规则:第m行与第n行交叉位置的值,等于第一个矩阵第m行与第二个矩阵第n列对应位置的乘积之和。依据这个规则,再看下图,是不是感觉豁然开朗

image.png

知道了原理后,我们继续探究translate/scale/rotate到底“背地里”干了些什么

上文中我们提到了,单独对结构体中某个的参数(Translate平移的参数当然不会出错,问题集中在对于线性变化区域的标注,即CGAffineTransform左上2x2区域CATransform3D左上3x3区域)进行功能注释是不够严谨的。

为了证明这一观点,笔者绘制了CGAffineTransformRotate仿射矩阵的推导过程图(这一结论在CATransform3DRotate矩阵中,同样适用,证明具体参数标注的不严谨

CGAffineTransformRotate的仿射矩阵推导

  • 相关推导用到的转换公式
sin(A+B) = sinAcosB + cosAsinB 
sin(A-B) = sinAcosB - cosAsinB
cos(A+B) = cosAcosB - sinAsinB 
cos(A-B) = cosAcosB + sinAsinB 

下面给出相对简单的CGAffineTransformTranslateCGAffineTransformScale的矩阵的简单推导

CGAffineTransformTranslate
CGAffineTransformScale

细心的同学可能会发现,系统Api修改的都是矩阵的前两列参数。那么调整第三列,会有什么样的效果?

第三列参数,直接作用到齐次坐标的n+1维的参数上,它同普通坐标的转换关系以我们一直讨论的二维为例,如下图。为保证x = x',y = y',所以默认转换后,齐次坐标的d = 1,如果第三列前两个参数均为0,那么修改右下位置的参数,可以直接控制图形的缩放。前两个参数则是随着图形中点的x、y值的变化改变对于齐次坐标中d值得影响。


不过苹果并没有给我们提供这三个参数,而是选择在二维变换时把这一列去掉了-。- 可能觉得意义不大吧。但是,在CATransform3D中,这一列得到了保留,我们熟悉的m34,正是受z轴影响的透视关键参数,相信看到这里的你,应该已经能够理解为什么改变m34能够影响屏幕坐标中的透视关系了。

CATransform3D

3D矩阵变换,其实就是2D矩阵变换的拓展,相应的3x3矩阵变成CATransform3D代表的4x4矩阵

经过对于CGAffineTransform的学习,这里我们就不再推导常用的3D Api对应的变换了,直接贴上结果图

相关矩阵

变换矩阵根据功能划分为四个区域,这里不做T1、T3区域的单独标注,观点在上文中已经提出,不再赘述

变换矩阵功能区分

  • 投影变换

目前大部分电子设备的图形显示,都是只能用二维图形表示三维物体,因此三维物体就要靠投影来降低位数得到二维平面图形,3D到2D转换的过程,称为投影变换

  • m34为什么能改变透视关系
    m34影响到了T3区域的r值r值会对投影的图形在z轴方向产生线性影响,为什么通常会用1/d负值,因为用户特殊的观察视角,决定了感官上近大远小的特性,同时iOS中的坐标系,是左手坐标系,远离我们的方向,是z轴的负方向,所以越深入屏幕,图像中点的齐次坐标中的D就越大,屏幕上的投影就越小
  • 旋转正方向的确定:左手坐标系适用于左手定律(反之亦然),握住指定轴,拇指指向该轴正方向,四指指向方向即为正方向

笔者博客地址:Tr2e

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,873评论 4 370
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,483评论 1 306
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,525评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,595评论 0 218
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,018评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,958评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,118评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,873评论 0 208
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,643评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,813评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,293评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,615评论 3 262
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,306评论 3 242
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,170评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,968评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,107评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,894评论 2 278

推荐阅读更多精彩内容