概述:主要講述矩陣變換與圖形變化的聯(lián)系,逐步將矩陣應用到動畫編程,重點講述透視變換 M34;
參考文獻: (3D數(shù)學基礎(chǔ):圖形與游戲開發(fā) -- (美)Fletcher.Dunn)
原書購買地址:請尊重原創(chuàng)
一. 矩陣基礎(chǔ)
不做討論,請參閱[3D數(shù)學基礎(chǔ):圖形與游戲開發(fā) -- (美)Fletcher.Dunn),視跟人基礎(chǔ)選讀 Chapter7,Chapter8,Chapter9 即可,重點在矩陣乘法(X);
二.矩陣乘法的幾何意義:
矩陣乘法本質(zhì):矩陣乘法的本質(zhì)是線性空間運動的描述墓塌! 具體見下圖:
對于矩陣乘法豪直,主要是考察一個矩陣對另一個矩陣所起的變換作用木人。其作用的矩陣看作是動作矩陣,被作用的矩陣可以看作是由行或列向量構(gòu)成的幾何圖形涡贱。同樣,如果一連串的矩陣相乘威兜,就是多次變換的疊加么坑夯。而矩陣左乘無非是把一個向量或一組向量(即另一個矩陣)進行伸縮或旋轉(zhuǎn)。乘積的效果就是多個伸縮和旋轉(zhuǎn)的疊加纽帖!比如S=ABCDEF會把所有的矩陣線性變化的作用力傳遞并積累下去宠漩,最終得到一個和作用力S。工業(yè)上的例子就是機器人的手臂懊直,機械臂上的每個關(guān)節(jié)就是一個矩陣(比如可以是一個旋轉(zhuǎn)矩陣)扒吁,機械臂末端的位置或動作是所有關(guān)節(jié)運動的綜合效果。這個綜合效果可以用旋轉(zhuǎn)矩陣的乘法得到室囊。
在code 的世界里,可以簡單理解為一個視圖A(矩陣A) 到另一個視圖B(矩陣B)的映射;
三. CALayer的transform擴展解析
在iOS 開發(fā)中已經(jīng)給出了部分API,我們重點關(guān)注 M34 的透視效果;
CATransform3DMakeTranslation
CATransform3DMakeScale
CATransform3DMakeRotation
繞坐標軸的旋轉(zhuǎn)場景:
如圖:
使用
image.layer.transform = CATransform3DMakeRotation(M_PI/6, 0, 1, 0);
繞Y軸旋轉(zhuǎn)30度后的效果:可以發(fā)現(xiàn)雕崩,繞Y軸旋轉(zhuǎn)只是在X軸上進行了縮放,這是因為融撞,在CALayer的顯示系統(tǒng)中盼铁,默認的相機使用正交投影,正交投影沒有遠小近大效果尝偎,所以在本例中饶火,只能造成相應軸上的縮放。在這種情況下致扯,無論是繞Y軸旋轉(zhuǎn)30度還是-30度都是同樣的效果趁窃。
透視投影
CALayer默認使用正交投影,因此沒有遠小近大效果急前,而且沒有明確的API可以使用透視投影矩陣醒陆。所幸可以通過矩陣連乘自己構(gòu)造透視投影矩陣。構(gòu)造透視投影矩陣的代碼如下:
CATransform3D CATransform3DMakePerspective(CGPoint center, float disZ){
CATransform3D transToCenter = CATransform3DMakeTranslation(-center.x, -center.y, 0);
CATransform3D transBack = CATransform3DMakeTranslation(center.x, center.y, 0);
CATransform3D scale = CATransform3DIdentity; scale.m34 = -1.0f/disZ;
return CATransform3DConcat(CATransform3DConcat(transToCenter, scale), transBack);
}
CATransform3D CATransform3DPerspect(CATransform3D t, CGPoint center, float disZ){
return CATransform3DConcat(t, CATransform3DMakePerspective(center, disZ));
}
代碼中裆针,center指的是相機 的位置刨摩,相機的位置是相對于要進行變換的CALayer的來說的寺晌,原點是CALayer的anchorPoint在整個CALayer的位置,例如CALayer的大小是(320, 160), anchorPoint值為(0.5, 0.5)澡刹,此時anchorPoint在整個CALayer中的位置就是(160, 80)呻征,正中心的位置。傳入透視變換的相機位置為(0, 0)罢浇,那么相機所在的位置相對于CALayer就是(160, 80)陆赋。如果希望相機在左上角,則需要傳入(-160, -80)嚷闭。disZ表示的是相機離z=0平面(也可以理解為屏幕)的距離攒岛。
怎么解釋這段代碼呢?
- 第一步胞锰,layer在CATransform3DPerspect方法中首先進行了t變換灾锯,要注意的是這時候進行的t變換是以anchorPoint為中心點,默認情況下是在layer的中心位置嗅榕。
- 第二步顺饮,在CATransform3DMakePerspective方法先進行了一個(-center.x, -center.y, 0)的平移,然后進行了透視投影凌那,最后又做了一個(center.x, center.y, 0)平移兼雄。關(guān)鍵在于這兩個反向的平移。 當center為(0,0)也就是相機中心在CALayer的中心位置帽蝶,與anchorPoint(0.5, 0.5)重合時赦肋,平移的距離為0也就是沒有做平移,這時候是直接把已經(jīng)做過t變換的layer通過scale進行透視投影變換的嘲碱。
當center不在中心位置時,假設(shè)在CALayer的左上角局蚀,那么center為(-160, -80)麦锯。那center就平移到了(0,0)點,等于把相機點又平移回到了與anchorPoint重合的這一點琅绅,但由于平移此時這個重合點成矩形圖片的左上角了扶欣。那么為什么要平移至使相機點與anchorPoint點重合呢?
這里得明確一點千扶,相機點與layer同時在X-Y平面上做相同的偏移時料祠,因為沒有改變z值,在相機點看到的立體效果是相同的澎羞,只是相對原點的位置變動了而已髓绽。在相機點(-160, -80)看到的立體效果,就等效于在相機點(0,0)看到的把layer平移(160, 80)的立體效果.對一個layer來說妆绞,只要沒有修改anchorPoint顺呕,系統(tǒng)所認為的內(nèi)部相機點的投影是在anchorPoint這個位置枫攀,也就是相機點的(0,0)位置。因此要看到layer在相機點(-160, -80)透視投影的效果株茶,只能先作平移變換来涨,讓相機點與layer做相同的平移使相機點移到(0,0), 完成透視投影后再平移回去。
相應的代碼為:
CATransform3D rotate = CATransform3DMakeRotation(M_PI/6, 0, 1, 0);
image.layer.transform = CATransform3DPerspect(rotate, CGPointMake(0, 0), 200);
可以發(fā)現(xiàn)在進行逆時針旋轉(zhuǎn)30度時蹦掐,在中心點左側(cè)的圖離相機點比較近,呈現(xiàn)出了比原圖大的效果僵闯,右側(cè)的圖離相機點比較遠卧抗,呈現(xiàn)出了比原圖小的效果。對比原圖棍厂,圖的左邊界超出了屏幕颗味,而右邊界在屏幕之內(nèi),這可以通過下面的這個圖來解釋:圖中PQ為旋轉(zhuǎn)后的圖像在X-Z平面上的投影牺弹,相機點在O點浦马,從O點看過過,P张漂、Q兩點在X軸上的投影為C晶默、D點,C航攒、D兩點相對P磺陡、Q點在X軸上的原始位置都是靠左的,這也就解釋了旋轉(zhuǎn)后的透視投影效果左邊界超出了屏幕漠畜,右邊界在屏幕之內(nèi)币他。
上圖中A、B兩點是O點在無窮遠處所看到的P憔狞、Q兩點在X軸上的投影蝴悉,也就是我們在第二張圖片上看到的效果。這是因為在無窮遠處瘾敢,觀察P拍冠、Q時已經(jīng)失去了近大遠小的效果,所做的投影是正交投影簇抵。
立方體效果
CATransform3D可以使用CATransform3DConcat函數(shù)連接起來以構(gòu)造更復雜的變換, 通過這些方法,可以組合出更多的效果來碟摆。下面是個翻轉(zhuǎn)的動畫, 使用四張同樣大小的圖片圍成一個框晃财,讓這個框動畫旋轉(zhuǎn), 形成一個立方體旋轉(zhuǎn)的效果。
相關(guān)實現(xiàn)的代碼:
CATransform3D move = CATransform3DMakeTranslation(0, 0, 160);
CATransform3D back = CATransform3DMakeTranslation(0, 0, -160);
CATransform3D rotate0 = CATransform3DMakeRotation(-angle, 0, 1, 0);
CATransform3D rotate1 = CATransform3DMakeRotation(M_PI_2-angle, 0, 1, 0);
CATransform3D rotate2 = CATransform3DMakeRotation(M_PI_2*2-angle, 0, 1, 0);
CATransform3D rotate3 = CATransform3DMakeRotation(M_PI_2*3-angle, 0, 1, 0);
CATransform3D mat0 = CATransform3DConcat(CATransform3DConcat(move, rotate0), back);
CATransform3D mat1 = CATransform3DConcat(CATransform3DConcat(move, rotate1), back);
CATransform3D mat2 = CATransform3DConcat(CATransform3DConcat(move, rotate2), back);
CATransform3D mat3 = CATransform3DConcat(CATransform3DConcat(move, rotate3), back);
image0.layer.transform = CATransform3DPerspect(mat0, CGPointZero, 500);
image1.layer.transform = CATransform3DPerspect(mat1, CGPointZero, 500);
image2.layer.transform = CATransform3DPerspect(mat2, CGPointZero, 500);
image3.layer.transform = CATransform3DPerspect(mat3, CGPointZero, 500);
解析:要形成一個立方體旋轉(zhuǎn)的效果典蜕,首先需要構(gòu)造出一個立方體出來拓劝,怎么構(gòu)造呢雏逾?在這個例子里構(gòu)造的立方體是前后左右四個面的,如果把屏幕當做立方體的“前”面郑临,它的“左”栖博、“后”、“右”面我們是看不見厢洞,但是這三個面可以通過“前”面旋轉(zhuǎn)一個角度得到的:以立方體的中心點為支點仇让,將“前”面分別順時針旋轉(zhuǎn)90度、180度躺翻、270丧叽。因為屏幕寬度為320,這個立方體的中心點應在屏幕中心點后方160px的地方公你。
現(xiàn)在需要解決的一個問題就是:怎么實現(xiàn)以立方體的中心點為支點的旋轉(zhuǎn)踊淳。我們知道,在CALayer中l(wèi)ayer的旋轉(zhuǎn)是以anchorPoint為支點的陕靠,而這個anchorPoint并沒有在z軸上的維度迂尝,所以修改anchorPoint是不可能的,怎么辦呢剪芥?答案還是通過平移實現(xiàn)垄开,雖然不能修改anchorPoint,但我們可以改變圖片的位置税肪,將圖片往z軸正方向(靠近用戶的方向)平移160px的距離溉躲,這時候圖片與anchorPoint的相對位置,就等同于圖片在原始位置與立方體中心的相對位置益兄,它們所進行的旋轉(zhuǎn)效果是相同的锻梳,只是在z軸上的絕對距離不同。旋轉(zhuǎn)完成后净捅,再平移回去疑枯,即可達到繞立方體的中心點旋轉(zhuǎn)的效果。這也是變換矩陣mat0為什么要先進行z軸正方向160px平移灸叼,執(zhí)行rotate0旋轉(zhuǎn)之后又進行z軸負方向160px平移的緣故神汹。
要實現(xiàn)旋轉(zhuǎn)動畫庆捺,就需要動態(tài)改變這個立方體的繞y軸的角度古今,在這個例子里就是添加了一個動態(tài)變化的angle達到這個目的。另外注意此例的旋轉(zhuǎn)是繞y軸旋轉(zhuǎn)的滔以,根據(jù)此前一篇文章的判斷方法捉腥,此時旋轉(zhuǎn)的正方向應該是z軸正方向頂點指向x軸正方向頂點,從用戶眼睛看來就是逆時針你画。如果angle是遞增的抵碟,那么-angle就是遞減的桃漾,因此實際看到的旋轉(zhuǎn)動畫會是順時針的
也許分析完又有了疑問,對一個layer做平移,會修改它的anchorPoint和position嗎拟逮?很顯然撬统,對旋轉(zhuǎn)和綻放必須要有一個固定的支點,感覺上平移不需要支點也行敦迄,是不是就會修改anchorPoint呢恋追?答案是否定時,簡單做一下測試罚屋,就知道layer在做平移時苦囱,anchorPoint和position都不會改變,frame會變化脾猛,說明frame不僅受anchorPoint和position影響撕彤,還受translation影響.