5.2 3D變換

2018-02-24 14:50 更新

3D變換

CG的前綴告訴我們,CGAffineTransform類型屬于Core Graphics框架,Core Graphics實際上是一個嚴格意義上的2D繪圖API,并且CGAffineTransform僅僅對2D變換有效。

在第三章中,我們提到了zPosition屬性,可以用來讓圖層靠近或者遠離相機(用戶視角),transform屬性(CATransform3D類型)可以真正做到這點,即讓圖層在3D空間內(nèi)移動或者旋轉(zhuǎn)。

CGAffineTransform類似,CATransform3D也是一個矩陣,但是和2x3的矩陣不同,CATransform3D是一個可以在3維空間內(nèi)做變換的4x4的矩陣(圖5.6)。

圖5.7 X,Y,Z軸,以及圍繞它們旋轉(zhuǎn)的方向

由圖所見,繞Z軸的旋轉(zhuǎn)等同于之前二維空間的仿射旋轉(zhuǎn),但是繞X軸和Y軸的旋轉(zhuǎn)就突破了屏幕的二維空間,并且在用戶視角看來發(fā)生了傾斜。

舉個例子:清單5.4的代碼使用了CATransform3DMakeRotation對視圖內(nèi)的圖層繞Y軸做了45度角的旋轉(zhuǎn),我們可以把視圖向右傾斜,這樣會看得更清晰。

結(jié)果見圖5.8,但并不像我們期待的那樣。

清單5.4 繞Y軸旋轉(zhuǎn)圖層

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    //rotate the layer 45 degrees along the Y axis
    CATransform3D transform = CATransform3DMakeRotation(M_PI_4, 0, 1, 0);
    self.layerView.layer.transform = transform;
}

@end

圖5.9?CATransform3Dm34元素,用來做透視

m34的默認值是0,我們可以通過設(shè)置m34為-1.0 /?d來應(yīng)用透視效果,d代表了想象中視角相機和屏幕之間的距離,以像素為單位,那應(yīng)該如何計算這個距離呢?實際上并不需要,大概估算一個就好了。

因為視角相機實際上并不存在,所以可以根據(jù)屏幕上的顯示效果自由決定它的防止的位置。通常500-1000就已經(jīng)很好了,但對于特定的圖層有時候更小后者更大的值會看起來更舒服,減少距離的值會增強透視效果,所以一個非常微小的值會讓它看起來更加失真,然而一個非常大的值會讓它基本失去透視效果,對視圖應(yīng)用透視的代碼見清單5.5,結(jié)果見圖5.10。

清單5.5 對變換應(yīng)用透視效果

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    //create a new transform
    CATransform3D transform = CATransform3DIdentity;
    //apply perspective
    transform.m34 = - 1.0 / 500.0;
    //rotate by 45 degrees along the Y axis
    transform = CATransform3DRotate(transform, M_PI_4, 0, 1, 0);
    //apply to layer
    self.layerView.layer.transform = transform;
}

@end

圖5.11 滅點

Core Animation定義了這個點位于變換圖層的anchorPoint(通常位于圖層中心,但也有例外,見第三章)。這就是說,當(dāng)圖層發(fā)生變換時,這個點永遠位于圖層變換之前anchorPoint的位置。

當(dāng)改變一個圖層的position,你也改變了它的滅點,做3D變換的時候要時刻記住這一點,當(dāng)你視圖通過調(diào)整m34來讓它更加有3D效果,應(yīng)該首先把它放置于屏幕中央,然后通過平移來把它移動到指定位置(而不是直接改變它的position),這樣所有的3D圖層都共享一個滅點。

sublayerTransform屬性

如果有多個視圖或者圖層,每個都做3D變換,那就需要分別設(shè)置相同的m34值,并且確保在變換之前都在屏幕中央共享同一個position,如果用一個函數(shù)封裝這些操作的確會更加方便,但仍然有限制(例如,你不能在Interface Builder中擺放視圖),這里有一個更好的方法。

CALayer有一個屬性叫做sublayerTransform。它也是CATransform3D類型,但和對一個圖層的變換不同,它影響到所有的子圖層。這意味著你可以一次性對包含這些圖層的容器做變換,于是所有的子圖層都自動繼承了這個變換方法。

相較而言,通過在一個地方設(shè)置透視變換會很方便,同時它會帶來另一個顯著的優(yōu)勢:滅點被設(shè)置在容器圖層的中點,從而不需要再對子圖層分別設(shè)置了。這意味著你可以隨意使用positionframe來放置子圖層,而不需要把它們放置在屏幕中點,然后為了保證統(tǒng)一的滅點用變換來做平移。

我們來用一個demo舉例說明。這里用Interface Builder并排放置兩個視圖(圖5.12),然后通過設(shè)置它們?nèi)萜饕晥D的透視變換,我們可以保證它們有相同的透視和滅點,代碼見清單5.6,結(jié)果見圖5.13。

圖5.13 通過相同的透視效果分別對視圖做變換

背面

我們既然可以在3D場景下旋轉(zhuǎn)圖層,那么也可以從背面去觀察它。如果我們在清單5.4中把角度修改為M_PI(180度)而不是當(dāng)前的M_PI_4(45度),那么將會把圖層完全旋轉(zhuǎn)一個半圈,于是完全背對了相機視角。

那么從背部看圖層是什么樣的呢,見圖5.14

圖5.15 反方向變換的嵌套圖層

注意做了-45度旋轉(zhuǎn)的內(nèi)部圖層是怎樣抵消旋轉(zhuǎn)45度的圖層,從而恢復(fù)正常狀態(tài)的。

如果內(nèi)部圖層相對外部圖層做了相反的變換(這里是繞Z軸的旋轉(zhuǎn)),那么按照邏輯這兩個變換將被相互抵消。

驗證一下,相應(yīng)代碼見清單5.7,結(jié)果見5.16

清單5.7 繞Z軸做相反的旋轉(zhuǎn)變換

@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIView *outerView;
@property (nonatomic, weak) IBOutlet UIView *innerView;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    //rotate the outer layer 45 degrees
    CATransform3D outer = CATransform3DMakeRotation(M_PI_4, 0, 0, 1);
    self.outerView.layer.transform = outer;
    //rotate the inner layer -45 degrees
    CATransform3D inner = CATransform3DMakeRotation(-M_PI_4, 0, 0, 1);
    self.innerView.layer.transform = inner;
}

@end

圖5.17 繞Y軸做相反旋轉(zhuǎn)的預(yù)期結(jié)果。

但其實這并不是我們所看到的,相反,我們看到的結(jié)果如圖5.18所示。發(fā)什么了什么呢?內(nèi)部的圖層仍然向左側(cè)旋轉(zhuǎn),并且發(fā)生了扭曲,但按道理說它應(yīng)該保持正面朝上,并且顯示正常的方塊。

這是由于盡管Core Animation圖層存在于3D空間之內(nèi),但它們并不都存在同一個3D空間。每個圖層的3D場景其實是扁平化的,當(dāng)你從正面觀察一個圖層,看到的實際上由子圖層創(chuàng)建的想象出來的3D場景,但當(dāng)你傾斜這個圖層,你會發(fā)現(xiàn)實際上這個3D場景僅僅是被繪制在圖層的表面。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號