前言
在寫Custom Layout的demo時,用到了CATransform3D的m34參數,不務正業的想探究下這個矩陣到底為什么能影響到圖形的透視旋轉等等變換,所以通過本篇文章總結一下收獲,供以后參考
目錄
- 簡單實現三維立方體
- CATransform3D&CGAffineTransform使用介紹
- 原理探究及理解
簡單實現三維立方體
實現這個蠻簡單的,只需要合理的調整旋轉角度和平移,再加上一個定時器,完美(顯然這個效果沒什么卵用,但是筆者這只鶸來說,剛蹦出來的時候還是蠻開心的)
實現步驟 : 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
An affine transformation matrix for use in drawing 2D graphics
用于繪制2D圖形的仿射變換矩陣
以上為官方文檔定義,那么什么是仿射變換,看看這里數學咖們的解釋就能大概理解了→如何通俗地講解「仿射變換」這個概念?
-
CGAffineTransformMake
返回直接控制每一個參數的仿射變換矩陣
CGAffineTransform CGAffineTransformMake(CGFloat a, CGFloat b, CGFloat c, CGFloat d, CGFloat tx, CGFloat ty);
-
CGAffineTransformScale
返回通過縮放現有仿射變換構造的仿射變換矩陣,sx
/sy
即為x方向
和y方向
的縮放比例
CGAffineTransform CGAffineTransformScale(CGAffineTransform t, CGFloat sx, CGFloat sy);
-
CGAffineTransformRotate
返回通過旋轉現有仿射變換構造的仿射變換矩陣,angle
為旋轉弧度
CGAffineTransform CGAffineTransformRotate(CGAffineTransform t, CGFloat angle);
-
CGAffineTransformInvert
返回通過反轉現有仿射變換構造的仿射變換矩陣。
CGAffineTransform CGAffineTransformInvert(CGAffineTransform t);
-
CGAffineTransformTranslate
返回實現平移的仿射變換矩陣。tx
/ty
為偏移量
CGAffineTransform CGAffineTransformTranslate(CGAffineTransform t, CGFloat tx, CGFloat ty);
-
CGAffineTransformConcat
返回通過組合兩個現有仿射變換構造的仿射變換矩陣。本質就是兩個矩陣的乘法,上述平移、旋轉、縮放的操作,如配圖所示,都是可以通過點的齊次坐標與仿射變換矩陣的乘積獲得,原理筆者會在第三部分解釋
CGAffineTransform CGAffineTransformConcat(CGAffineTransform t1, CGAffineTransform t2);
- 幾個特殊的仿射變換矩陣
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);
-
CGAffineTransformIdentity
即單位矩陣如下圖所示,他并不會對圖形造成任何影響,通常用來恢復初始狀態
我們要想將文字視圖翻轉該怎么做呢?
很簡單,只需要將
CGAffineTransformMakeScale
中的參數設置為-1即可。是不是很有趣
label.transform = CGAffineTransformMakeScale(-1, -1);
CATransform3D
CATransform3D
Defines the standard transform matrix used throughout Core Animation.
定義核心動畫中使用的標準變換矩陣
CATransform
同樣定義為結構體,代表了三維圖形4x4的變換矩陣。網上有幾篇文章,對每個參數功能都進行了標注,但是這樣并不十分嚴謹。因為左上3x3的矩陣區域,各個參數需要相互作用才能達到理想的準確狀態,并不單純的是某個值負責某種準確的三維圖形變換。筆者將在本文的第三部分原理探究及理解中具體解釋,感(hen)興(wu)趣(liao)的同學可以往下看看。
因為只是從2D變換變成3D變換,而且4x4矩陣本身就是3x3矩陣的拓展,原理是一樣的,所以有很多相似相通的地方。
-
CATransform3DScale
返回通過縮放現有變換構造的變換矩陣,sx
/sy
/sz
即為x方向
、y方向
和z方向
的縮放比例
CATransform3D CATransform3DScale (CATransform3D t, CGFloat sx,
CGFloat sy, CGFloat sz)
-
CATransform3DRotate
返回通過旋轉現有變換構造的變換矩陣,angle
代表弧度,x
,y
,z
代表各個軸上旋轉的弧度倍數
CATransform3D CATransform3DRotate (CATransform3D t, CGFloat angle,
CGFloat x, CGFloat y, CGFloat z)
-
CATransform3DInvert
返回反轉后的變換矩陣
CATransform3D CATransform3DInvert (CATransform3D t)
-
CATransform3DTranslate
返回實現x
/y
/z
軸上平移相應距離的變換矩陣
CATransform3D CATransform3DTranslate (CATransform3D t, CGFloat tx,
CGFloat ty, CGFloat tz)
-
CATransform3DConcat
返回同時作用兩種變換矩陣的矩陣
CATransform3D CATransform3DConcat (CATransform3D a, CATransform3D b)
- 幾個特殊的變換矩陣
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)
-
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列對應位置的乘積之和。依據這個規則,再看下圖,是不是感覺豁然開朗
知道了原理后,我們繼續探究
translate
/scale
/rotate
到底“背地里”干了些什么
上文中我們提到了,單獨對結構體中某個的參數(Translate平移的參數當然不會出錯,問題集中在對于線性變化區域的標注,即CGAffineTransform左上2x2區域、CATransform3D左上3x3區域)進行功能注釋是不夠嚴謹的。
為了證明這一觀點,筆者繪制了CGAffineTransformRotate
仿射矩陣的推導過程圖(這一結論在CATransform3DRotate矩陣中,同樣適用,證明具體參數標注的不嚴謹)
- 相關推導用到的轉換公式
sin(A+B) = sinAcosB + cosAsinB
sin(A-B) = sinAcosB - cosAsinB
cos(A+B) = cosAcosB - sinAsinB
cos(A-B) = cosAcosB + sinAsinB
下面給出相對簡單的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