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閱讀 228,316評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,481評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,241評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,939評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,697評論 6 409
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,182評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,247評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,406評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,933評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,772評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,973評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,516評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,638評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,866評論 1 285
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,644評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,953評論 2 373

推薦閱讀更多精彩內容