CoreAnimation初探(一) —— 圖形學基礎

關于CoreAnimation

CoreAnimation是蘋果提供的一套基于繪圖的動畫框架,下圖是官方文檔中給出的體系結構。


CoreAnimation所在位置

從圖中可以看出,最底層是圖形硬件(GPU);上層是OpenGL和CoreGraphics,提供一些接口來訪問GPU;再上層的CoreAnimation在此基礎上封裝了一套動畫的API。最上面的UIKit屬于應用層,處理與用戶的交互。所以,學習CoreAnimation也會涉及一些圖形學的知識,了解這些有助于我們更順手的使用以及更高效的解決問題。本篇從以下幾點簡單整理了一些圖形學的知識,以對其中原理有個概念上的認識。

什么是圖形學

圖形變換

曲線和曲面


1.什么是圖形學

簡單的說,計算機圖形學是指用計算機產生對象圖形輸出的技術。更確切的說,是研究通過計算機將數據轉換為圖形,并在專門顯示設備上顯示的原理、方法和技術的學科。

舉一個簡單的例子,如果只提供給我們一個方法setPixel(x,y)用來點亮屏幕上坐標為(x,y)的像素點,如何在兩點(x1,y1)、(x2,y2)之間畫一條直線呢?(這是圖形學研究的一個最基本問題:圖形基元的顯示。圖形基元指一些基本的集合圖形,如線段、圓、多邊形等。)

我們最容易想到、也是最直接的方法便是計算出該直線在每個整數點對應的坐標值,四舍五入點亮與其最近的像素點(稱為數值微分分析,DDA),如下圖所示。


直線掃描轉換(斜率<1時)

但這種方法有很大的缺點:對于直線y=mx+b,我們在計算每個整數點坐標時,需要一次乘法和一次加法運算。而m、b都不一定是整數,所以更準確的說是浮點數乘法,這對于底層硬件實現是很傷的。

基于這種消除乘法和浮點數運算的思想便有了中心點畫線法
首先對于消除乘法運算,不必每次算坐標時都乘以斜率m,而是采用增量計算的方法;
而對于消除浮點數,則可以使用只含整數參數的直線方程計算:ax+by+c=0(這里a,b,c可以都為整數,因為我們的起點(x1,y1)和終點(x2,y2)是屏幕上的像素點一定都為整數,可以計算得出一個滿足條件的整數方程:a=y1-y2, b=x2-x1, c=x1y2-x2y1)。

為方便討論我們仍假設直線的斜率<1,在循環點亮路徑上的像素點時,x坐標每次加1,y坐標每次要么加1要么不變取決于交點的位置。通過判斷中心點位于直線的上方還是下方即可確定y坐標是否需要加1,如下。


中心點畫線(斜率<1時)

我們可以構造判別式d=F(x+1,y+0.5)=a(x+1)+b(y+0.5)+c:d>0則中點在直線上方,反之在下方。用增量方式計算下一個中點的判別式:
若d>0,則y不變,d1=F(x+2,y+0.5)=a(x+2)+b(y+0.5)+c=d+a;
若d<0,則y+1,d2=F(x+2,y+1.5)=a(x+2)+b(y+1.5)+c=d+(a+b);
初值d0=F(x0+1,y0+0.5)=a(x0+1)+b(y0+0.5)+c=a+0.5b,由于只對d判別正負,可以用2d消除浮點數運算:

x=x0, y=y0, d=2*a+b;
d1=2*a, d2=2*(a+b);

setPixel(x,y);
while(x < x1)
{ 
    if(d < 0)
    {
        x++;
        y++;
        d+=d2;
    }
    else
    {
        x++;
        d+=d1;
    }
    setPixel(x,y);
}

中點畫線法只包含整數變量且沒有乘法運算,適合硬件實現。

還有一種更好的方法Bresenham畫線算法,原理和中點畫線法一樣,不同的是使用交點到上下兩個像素點的距離差作為判別式,同樣采用增量計算,只含整數變量和加法、乘2(位移)運算,不再贅述。

以上便是圖形學里最簡單的圖形基元——線段的掃描轉換。為了方便底層硬件實現,這些算法都會盡可能的使用整數變量,使用增量計算減少乘除法(或轉化為2的冪次通過位移實現)。按照這種套路不難理解圓、橢圓、多邊形的掃描轉換算法,這里就不一一探討了。

有了這些圖形基元后,通過變換、投影、裁剪、組合等方式便可以得到更加復雜的圖形。

2.圖形變換

計算機本身只能處理數字信息,各種圖形在計算機系統內也是以數字的形式存在的。為了使被顯示的對象數字化,就需要在被顯示對象所在的空間中定義一個坐標系。

這里稍微區分一下幾種不同坐標系的概念:

  • 本體坐標系 也稱模型坐標系,為將對象數字化而建立的長度單位和坐標軸方向適合被現實對象描述的坐標系。一個復雜模型可能包含很多簡單物體,可以分別對給他們建立一個方便建模的本體坐標系。
  • 用戶坐標系 也稱世界坐標系,我們對一個復雜模型建立了很多個本體坐標系,需要在一個大坐標系中將它們組合成一個整體,即世界坐標系。
  • 觀察坐標系,以觀察姿態引入的坐標系。通常約定眼睛的位置為坐標原點,x軸水平向右,y軸豎直向上,z軸離開眼睛射向前方稱為右手系,反之為左手系。
  • 設備坐標系,為了最終將被描述的物體在顯示器上顯示或繪制出來,需要在顯示器屏幕上定義一個二維直角坐標系。而為了達到與具體設備無關而引入的規范化設備坐標系規定坐標范圍為均為0到1。

搞這么多坐標系,目的其實都是為了方便建模和處理,可見物體從建模到被顯示出來的過程離不開坐標的變換。而為了統一地處理各種坐標變換,需要引入齊次坐標的概念。

齊次坐標表示法就是用n+1維向量表示一個n維向量。n維空間中的一個點(P1,P2,...,Pn)在n+1維空間中的齊次坐標表示為(hP1,hP2,...,hPn,h)。齊次坐標是不唯一的,當h=1時前n個坐標即為原n維空間中的點。

齊次坐標可以表示無窮遠點,例如:(a,b,0)表示二維空間中直線bx-ay=0上的無窮遠點。

應用齊次坐標可以有效地用矩陣運算將點從一個坐標系轉換到另一個坐標系中。

可以理解為低維空間中的某個點p朝著某方向無限延伸到高一維空間中,延伸路徑上的點p1,p2,......都映射到低維空間的p點上,p1,p2......在低維空間中都相當于原始點p。(個人理解。。)

為什么用齊次坐標就能統一處理變換,從表面上看增加一個維度可以將不同的變換放在同一個矩陣中表示,利用矩陣乘法的結合律也能輕松處理各種變換的組合,至于其中包含的理論原理就不做深入了,可以從變換矩陣的定義中簡單體會一下:


二維齊次坐標變換矩陣形式

將每一行看做齊次坐標,如果是一個單位矩陣,則第一行(1,0,0)表示x軸上的無窮遠點,第二行(0,1,0)表示y軸上的無窮遠點,第三行(0,0,1)則表示坐標原點。所以當二維變換矩陣是單位矩陣時,相當于定義了二維空間中的直角坐標系。

將點p=(x,y,1)變換后的坐標記為p'=(x',y',1),可得到下面的幾何變換矩陣:

  • 平移變換:x方向移動Tx,y方向移動Ty,則p'=(x+Tx, y+Ty, 1)


    二維平移變換矩陣
  • 比例變換:x方向縮放Sx,y方向縮放Sy,則p'=(x·Sx, y·Sy, 1)


    二維比例變換矩陣
  • 旋轉變換:繞坐標原點逆時針旋轉θ,則p'=(x·cosθ-y·sinθ, x·sinθ+y·cosθ, 1)


    二維旋轉變換矩陣

這些變換都對應3×3的變換矩陣,根據矩陣乘法結合律可以組合出一些復雜的變換,例如繞任意點(x0,y0)旋轉θ:可以先平移讓其位于坐標原點,相對于原點作旋轉變換后在平移回去:


繞點(x0,y0)旋轉θ的變換矩陣

這便是用齊次坐標變換矩陣的方便之處。

類似的,可以推導出三維圖形變換的齊次坐標變換矩陣,這里不再羅列公式了,我們從蘋果提供的API中簡單了解一下:

/* Homogeneous three-dimensional transforms. */
/* 齊次三維變換 */

/* 齊次三維變換的數據結構,為一個4×4矩陣 */
struct CATransform3D
{
  CGFloat m11, m12, m13, m14;
  CGFloat m21, m22, m23, m24;
  CGFloat m31, m32, m33, m34;
  CGFloat m41, m42, m43, m44;
};

typedef struct CATransform3D CATransform3D;

CATransform3D.h中提供的一些函數:

/* The identity transform: [1 0 0 0; 0 1 0 0; 0 0 1 0; 0 0 0 1]. */
/* 返回一個4×4單位矩陣 */
CA_EXTERN const CATransform3D CATransform3DIdentity
    CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);

/* Returns true if 't' is the identity transform. */
/* 判斷變換矩陣t是否為單位矩陣 */
CA_EXTERN bool CATransform3DIsIdentity (CATransform3D t)
    CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);

/* Returns true if 'a' is exactly equal to 'b'. */
/* 判斷變換矩陣a和b是否相等 */
CA_EXTERN bool CATransform3DEqualToTransform (CATransform3D a,
    CATransform3D b)
    CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);

/* 下面三個函數用來生成三維平移、比例、旋轉變換矩陣 */

/* 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]. 
 * 返回一個三維平移變換矩陣 */
CA_EXTERN CATransform3D CATransform3DMakeTranslation (CGFloat tx,
    CGFloat ty, CGFloat tz)
    CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);

/* 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]. 
 * 返回一個三維比例變換矩陣 */
CA_EXTERN CATransform3D CATransform3DMakeScale (CGFloat sx, CGFloat sy,
    CGFloat sz)
    CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);

/* 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. 
 * 返回一個三維旋轉變換矩陣,旋轉軸為(x,y,z) */
CA_EXTERN CATransform3D CATransform3DMakeRotation (CGFloat angle, CGFloat x,
    CGFloat y, CGFloat z)
    CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);

/* 下面三個函數用來“對變換做變換”,??注意都是左乘 
 * 這里有點繞,比如對一個平移變換T做縮放變換S,得到的變換
 * 效果是先縮放在平移。我的理解是好比我們看3D電影時,一般
 * 都將3D鏡片放在我們的近視鏡前面,其實是對3D影像先進行處
 * 理,然后通過近視鏡呈現給我們的眼睛,對于影片來說是先進
 * 行3D處理再做近視的變換;而對于眼鏡來說,是它的近視變換
 * 之前被加上了一層3D處理。放在前面的變換才會影響到后面的
 * 變換,即對后面的變換“做了變換”。這里也是按著蘋果注釋中
 * 的定義強行理解了一波,不知道是不是這樣,還請指教。。 */

/* Translate 't' by '(tx, ty, tz)' and return the result:
 * t' = translate(tx, ty, tz) * t. */
 * 對變換t進行(tx, ty, tz)的平移 */
CA_EXTERN CATransform3D CATransform3DTranslate (CATransform3D t, CGFloat tx,
    CGFloat ty, CGFloat tz)
    CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);

/* Scale 't' by '(sx, sy, sz)' and return the result:
 * t' = scale(sx, sy, sz) * t. 
 * 對變換t做(sx, sy, sz)的縮放 */
CA_EXTERN CATransform3D CATransform3DScale (CATransform3D t, CGFloat sx,
    CGFloat sy, CGFloat sz)
    CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);

/* Rotate 't' by 'angle' radians about the vector '(x, y, z)' and return
 * the result. If the vector has zero length the behavior is undefined:
 * t' = rotation(angle, x, y, z) * t. 
 * 對變換t做旋轉angle,旋轉軸為(x,y,z) */
CA_EXTERN CATransform3D CATransform3DRotate (CATransform3D t, CGFloat angle,
    CGFloat x, CGFloat y, CGFloat z)
    CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);

/* Concatenate 'b' to 'a' and return the result: t' = a * b. 
 * 組合a,b兩個變換,返回矩陣a*b,相當于先做a變換再做b變換 */
CA_EXTERN CATransform3D CATransform3DConcat (CATransform3D a, CATransform3D b)
    CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);

/* Invert 't' and return the result. Returns the original matrix if 't'
 * has no inverse. 
 * 反向變換,相當于對矩陣求逆 */
CA_EXTERN CATransform3D CATransform3DInvert (CATransform3D t)
    CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);

3.曲線和曲面

我們開發時經常會用到或見到一個叫貝塞爾曲線的東西,它到底是什么,能做什么用,這里同樣僅從概念上總結下計算機處理曲線和曲面的一些基礎知識以及貝塞爾曲線的原理。

  • 曲線和曲面的表示形式
    我們都知道x2+y2=r^2 表示一個半徑為r的圓,x2+y2=r^2叫做這個圓的方程;我們還可以用x=r·cosθ,y=r·sinθ表示這個圓,稱為它的參數方程。
  • 在空間曲線的參數表示中,曲線上每一個點的坐標均要表示成某個參數t的函數,即:x=x(t), y=y(t), z=z(t)
  • 類似可以得出曲面的參數方程形式:x=x(u,v), y=y(u,v), z=z(u,v)
  • 曲面的一般形式f(x,y,z)=0,而非參數形式的曲線可以定義為兩個柱面的交線
  • 1963年美國波音飛機公司的Ferguson首先提出將曲線曲面表示為參數的向量方程的方法。在此之前的畫法幾何和機械制圖中,很難對自由型曲線進行清晰的表示。
  • 插值和逼近
    計算機中通常事先給定一些離散點(稱為型值點),由這些點得出曲線的方法大體分為兩類:一類要求曲線通過這些離散點,稱為插值;另一類用這些點形成控制多邊形來控制形狀,稱為逼近
    當型值點太多時,構造插值函數通過所有型值點是很困難的;或者當型值點本身帶有誤差時也沒有必要尋找一個插值函數通過所有型值點,此時我們往往希望構造一條曲線在某種意義上逼近這些型值點。由型值點求插值或逼近曲線曲面的問題稱為曲線或曲面的擬合。

  • 貝塞爾曲線(Bézier curve)

法國雷諾(Renault)汽車公司的工程師Bézier于1971年發表了一種有控制多邊形定義曲線的方法。設計員只要移動控制點就可以方便的修改曲線的形狀,而且形狀變化完全在意料之中,漂亮的解決了整體形狀控制的問題。

貝塞爾曲線的定義其實并不復雜,我們用P0,P1,...,Pn表示給定的n+1個型值點(為什么是n+1,因為至少要有兩個點才能構造線。。),而貝塞爾曲線的實質就是用n次多項式函數對這n+1個點進行混合:


貝塞爾曲線的參數定義

所以,n次貝塞爾曲線其實就是一條n次參數多項式曲線。由于組合多項式的特殊性,貝塞爾曲線也有很多有意思的性質,比如:

  • 起點和終點處的切線與控制多邊形的第一和最后一邊重合;
  • 某一起點或終點的r階導數由起點或終點以及它們的r個鄰近的控制點決定,事實上正是由該性質推導出的貝塞爾曲線;
  • 對稱性:從起點出發和從終點出發得到同一條曲線。
  • 分割遞推性:由P0,P1,...,Pn所確定的n次貝塞爾曲線在點t的值可以由點P0,P1,...,Pn-1所確定的n-1次貝塞爾曲線在點t的值,與由點P1,P2,...,Pn所確定的n-1次貝塞爾曲線在點t的值通過線性組合求得:
分割遞推性

貝塞爾曲線的幾何作圖法即是基于這一性質。


以上這些是圖形學中最常見的問題,總結一下主要有:

  • 圖形基元的顯示
  • 為方便建模引入的各種坐標系的區別
  • 齊次坐標與幾何變換
  • 曲線的表示以及插值、逼近的概念
  • 貝塞爾曲線

參考資料

計算機圖形學基本圖形生成算法
幾何變換詳解
OpenGL 投影矩陣的推導
CATransform3D vs. CGAffineTransform
貝塞爾曲線初探
貝塞爾曲線掃盲
談談貝塞爾曲線

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,321評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,559評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,442評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,835評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,581評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,922評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,931評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,096評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,639評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,374評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,591評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,104評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,789評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,196評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,524評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,322評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,554評論 2 379

推薦閱讀更多精彩內容

  • 1 前言 OpenGL渲染3D模型離不開空間幾何的數學理論知識,而本篇文章的目的就是對空間幾何進行簡單的介紹,并對...
    RichardJieChen閱讀 7,086評論 1 11
  • 引言 請不要質疑你的眼睛,文章的題目就是“3D圖形學基礎理論”。可能有人要疑惑了,作為一個 iOS 開發者為什么要...
    ZhengYaWei閱讀 8,641評論 5 36
  • 一:canvas簡介 1.1什么是canvas? ①:canvas是HTML5提供的一種新標簽 ②:HTML5 ...
    GreenHand1閱讀 4,707評論 2 32
  • 倏忽三十載,一朝歸故鄉。 老宅如老翁,頹頹亦蒼蒼。 庭前存犬舍,不見我阿黃。 屋后生雜草,秋日舞凄傷。 物非昨日物...
    云飄逸閱讀 212評論 0 1
  • 隨著一陣上課鈴聲的響起,一節語文課開始了,這節課我們準備學習《山市》這篇文言文。 當我走進教室的時候,...
    肖慶娜閱讀 322評論 0 0