入門AR,VR,OpenGL必備知識--3D圖形學(xué)基礎(chǔ)理論

引言

請不要質(zhì)疑你的眼睛,文章的題目就是“3D圖形學(xué)基礎(chǔ)理論”??赡苡腥艘苫罅?,作為一個 iOS 開發(fā)者為什么要來學(xué)習(xí)什么 3D 圖像學(xué)相關(guān)的東西,莫非這是要轉(zhuǎn)行的節(jié)奏。開玩笑,怎么可能呢? 作為一個對技術(shù)有追求的人,怎么可能轉(zhuǎn)行。接下來讓我告訴你,為什么要學(xué)習(xí)3D圖形學(xué)的一些基礎(chǔ)理論。

  • 1、遠的先不說,就拿 iOS來說。iOS 中在做一些基本動畫開發(fā)的過程中,相信絕大多數(shù)開發(fā)者都是用過 Core Graphics 框架中的 CGAffineTransform 這個類,雖然只是普通的平面動畫,但其實現(xiàn)原理和 3D 圖形學(xué)中的矩陣很類似。有多少開發(fā)者想過其中的各種動畫效果諸如縮放、旋轉(zhuǎn)、平移等是如何做到的?
  • 2、當然看到CGAffineTransform這個類,自然很容易想到CATransform3D這個類,這里既然是3D動畫,肯定和3D圖形學(xué)有關(guān)了。
  • 3、再說一個很火的名詞 AR,Apple 已經(jīng)讓一部分開發(fā)者早先接觸了 ARKit 。ARKit 在接下來的一段時間中必然會很火。其實簡單用 ARKit 實現(xiàn)一個 AR 的 Demo 很簡單,因為 蘋果封裝的 ARKit 十分簡單易用。學(xué)習(xí)ARKit重點在于理解 ARKit 中各個類的關(guān)系、運行原理。其中運行原理就涉及到 3D 圖像學(xué)相關(guān)的知識。
  • 4、說到AR,自然會想到VR,VR全景視頻、全景圖片自然也會涉及到3D場景。
  • 5、除此之外,還有 OpenGL ,當然這個屬于相對比較片底層的技術(shù)了。iOS 中的各種控件的底層再底層都是基于其實現(xiàn)的。AR、VR技術(shù)都和其脫不了干系。筆者目前所在的公司,就有一套公司自己的OpenGL繪圖引擎,基于高德地圖在地圖上繪制各種各樣的炫酷模型。
  • 6、還有蘋果的3D游戲開發(fā)框架ScenceKit,毫無疑問會涉及。
    除了上述所講到的這些,3D 圖像學(xué)在實際開發(fā)中涉及了還有很多。所以還是有必要稍稍了解下。學(xué)一些大學(xué)的高數(shù)知識。

一、3D成像原理

三維(3D)這個術(shù)語表示顯示的物體具有三個維度:寬度、高度和深度。如放在書桌上的紙張是一個二維物體,因為它沒有讓人感覺到所謂的深度。而一罐可口可樂放在桌上卻是有一定的深度。

幾個世紀以來,藝術(shù)家已經(jīng)知道如何讓一幅畫看上去有深度。本質(zhì)上來說,畫其實是一個二維物體,只是顯示在計算機屏幕上的二維圖像或畫板上的水墨,但是卻可以提供深度的錯覺。

有這樣一個公式:2D + 透視 = 3D 通過下面這幅圖,就可以看懂這個公式。我們可以看到 12 條線段組成了一個三維立方體的圖像。

實際上為了真正的看到 3D 圖像需要用兩個眼睛觀察一個物體或者為每只眼睛分別提供某個特定物體的一副獨立而又唯一角度的圖像。實際上人的每一只眼睛看到的是一副獨立的二維圖像,非常類似于每個視網(wǎng)膜(位于眼睛的后半部分)上顯示了一副臨時照片。隨后大腦對這兩幅圖像進行圖像組合,在腦海中形成一副合成的 3D 圖片。中所周知現(xiàn)在的iPhone基本都是雙攝像頭,蘋果之所以早在 iPhone7 Plus 就增加了雙攝,其實那時已經(jīng)是在為AR埋下伏筆了。因為同人眼一樣,為了識別 3D 場景,AR 需要雙攝像頭的硬件設(shè)備。那些不在雙攝像頭的硬件上運行AR場景的在筆者認為是偽 AR ,因為筆者認為AR的核心是在于場景的識別和分析,而這一點就要依賴于雙攝;再說一個典型的例子,VR 應(yīng)用的頭盔,一般都會分為左右屏,左右屏的實時圖片或視頻場景是有一定視角差的,正式基于此,佩戴頭盔的玩家才能體驗到 3D 場景。

單憑透視本身就足以創(chuàng)建三維的外觀,但是如果長時間盯著上面的立方體看的話,會產(chǎn)生線條錯亂的幻覺。我們?nèi)绻檬终谧∫恢谎劬θタ船F(xiàn)實世界,仍然是三維的,也不會產(chǎn)生錯亂的幻覺,這是因為現(xiàn)實世界中有關(guān)。如果上面的立方體通過紋理貼圖、光照效果處理等就不會再產(chǎn)生錯亂的感覺。

二、3D編程基本原則

2.1 坐標系統(tǒng)

笛卡爾坐標系統(tǒng)相信很多人都了解,被熟悉的主要有 2D 和 3D 笛卡爾坐標系。關(guān)于這兩個坐標系統(tǒng)不用做過多解釋,很容易理解。這里我主要想說兩個概念坐標裁剪和視口。

  • 坐標裁剪
    窗口(電腦屏幕)是以像素為單位度量的。開始在窗口繪制點、線或形狀之前,必須告訴編程框架如何把制定的坐標翻譯為屏幕坐標。我們可以通過指定占據(jù)窗口的笛卡爾控件區(qū)域完成這個任務(wù),這個區(qū)域就稱為裁剪區(qū)域。如下圖顯示了兩種常見的裁剪區(qū)域。
  • 視口: 把繪圖左邊映射到窗口坐標
    裁剪區(qū)域的寬度和高度很少和窗口的寬度或高度相匹配(以像素為單位)。因此,坐標系統(tǒng)必須從笛卡爾坐標系映射到物理屏幕像素坐標。這個映射就是通過視口的設(shè)置來指定的。視口就是窗口內(nèi)部用于繪制裁剪區(qū)域的客戶端區(qū)域。視口簡單的把裁剪區(qū)域映射到窗口的一個區(qū)域。通常視口被定義為整個窗口,但這并非是嚴格的。如下面兩幅圖所示,視口分別被定義為裁剪區(qū)域的兩倍、與裁剪區(qū)域相同的大小。

2.2 從3D到2D(投影)

不管我們覺得自己的眼睛看到的三維圖像有多么真實,但是屏幕上的像素實際上是二維的。所以我們要理解的第一個概念是投影。通過指定投影,可以指定在窗口的視景體,并制定如何對它進行變換。投影主要分為兩種:正投影和透視投影。

如下圖所示,兩種投影方式,一種是平行投影也叫作正投影,正投影的特點是所有的投影線都是平行的,另外一種則是透視投影,透視投影的特點是投影線是相交于一點的,相交于這個點叫做投影中心。這一點在現(xiàn)實生活中可以得到很好的驗證,想想此刻你順著平行的火車軌道望去,會發(fā)現(xiàn)隨著距離的增加,火車的軌道原來越靠近。在后面我會專門抽出一個模塊來說一下透視投影的幾何原理,這里暫時知道即可。

正投影和透視投影

三、向量

3.1 向量基本介紹

說道這里就涉及到一些數(shù)學(xué)知識了,不要害怕,我會結(jié)合實際的空間來說,高等數(shù)學(xué)中關(guān)于向量和矩陣的知識并不是那么的難。實際上作為開發(fā)者的我們也只要掌握一些基礎(chǔ)的便足夠。

3D 笛卡爾坐標系中的一個點,是通過三個值如(x,y,z)來表示的。這三個值組合起來實際上表示兩個重要的值:方向數(shù)量。如下圖所示的(X,Y,Z)實際上表示了一個方向,方向是朝箭頭方向。一個向量的數(shù)量就是這個向量的長度。對于 x 軸向量(1,0,0)來說,向量長度是 1。我們把長度為 1 的向量稱為單位向量。

OpenGL 中的 math3d 庫有這樣的兩個數(shù)據(jù)類型M3DVector3f 和 M3DVector4f 。前者是一個三維向量(x,y,z);后者是一個四維向量(x,y,z,w),典型情況下 w 值為 1.0 ,x、y、z 值通過除以 w 進行縮放。明明就是 3D 圖形學(xué),又不是 4D 圖像學(xué),為何要使用 4 個分量來表示一個向量 ?之所以這樣做是因為 3D 頂點變化是要乘以一個 4 * 4 的變換矩陣。有個規(guī)則是必須用一個四分量向量乘以一個 4 * 4 的矩陣,后面講到矩陣時會詳細說明。

3.2 向量的運算

向量可以進行加法、減法運算,也可以簡單的通過減法、減法進行縮放。然而這里主要說關(guān)于向量有趣的兩個運算: 點乘和叉乘。

  • 點乘
    兩個三分量之間的點乘運算將得到一個標量(只有一個值),它表示兩個向量之間夾角對應(yīng)的三角函數(shù) Cos 值。
  • 叉乘
    兩個向量之間叉乘所得的結(jié)果是另外一個向量,這個新向量與原來兩個向量定義的平面垂直。和點乘不同,在進行叉乘運算的時候向量的順序非常重要,不同的叉乘順序會得到兩個方向相反的向量。

四、矩陣及其空間變化

4.1 理解場景變化

4.1.1 視覺坐標

視覺坐標是相對于觀察則而言的,無論進行何種變換,都可以將它視為絕對的屏幕坐標。這樣,視覺坐標就表示一個虛擬固定的坐標系,通常作為參考坐標系使用。

4.1.2 兩種變換

在說矩陣之前,先來說一下3D場景中的一些變化。關(guān)于變化,主要有兩種變換視圖變換和模型變換。

  • 視圖變換
    所謂的視圖就是觀察者或相機的位置。視圖變換允許我們把觀察點放置在任何希望的位置,允許在任何方向上觀察。確定視圖變換就像在場景中放置照相機并讓它指定某個方向。
    總的來說在任何其他模型變換之前,必須先進行視圖變換。這是因為對視覺坐標系而言,視圖變化就移動了當前的工作坐標系,后續(xù)的一切變換隨后都是基于新調(diào)整的坐標系進行的。
  • 模型變換
    模型變換用于操作模型和其中的特定的對象。模型變換通常是現(xiàn)將模型對象移動到需要的位置上,然后再對他們進行旋轉(zhuǎn)和移動。就拿先旋轉(zhuǎn)后平移和先平移后旋轉(zhuǎn)來說得到的是兩種不同情況。如下圖所示,無論是先執(zhí)行旋轉(zhuǎn)還是平移,后一個操作(旋轉(zhuǎn)或平移)都是基于第一個操作結(jié)果產(chǎn)生的坐標系進行下一步操作。

4.2模型視圖矩陣

與其說矩陣的幾何意義這么生澀難懂,不如說的是矩陣在幾何中到底是有什么作用呢?一般來說,方陣可以描述任意的線性變換。也就說,在幾何當中,我們用矩陣表示幾何體的空間變換。比如我們在程序中常用的平移、旋轉(zhuǎn)、縮放等等

模型視圖矩陣是一個 4 * 4 矩陣,它表示一個變化后的坐標系,可以用來放置對象和確定對象的位置。頂點作為一個單列矩陣(也就是一個響亮)的形式來表示,并乘以一個模型視圖矩陣來獲得一個相對視覺坐標系的經(jīng)過變換的新坐標。

如下代碼所形成的矩陣計算所示,一個包含單個頂點數(shù)據(jù)的矩陣乘以模型視圖矩陣后得到新的視覺坐標。頂點數(shù)據(jù)實際是4個數(shù)據(jù),其中包含一個附加值 w ,它表示一個縮放因子,默認情況下是 1.0 ,一般情況下很少去改動。

[x]        [a  b  c  d]          [x0]
[y]        [a  b  c  d]          [y0]
[z]  *    [a  b  c  d]   =     [z0]
[w]       [a  b  c  d]          [w0]

接下來我們就來詳細說明,上面是矩陣計算是怎么做到:將一個頂點乘以一個矩陣來對它進行變換。

4.2.1 矩陣構(gòu)造

矩陣的前三列的前三個元素只是方向向量,表示空間上 x y z 軸上的方向(在這里用向量來表示一個方向)。一般情況下這三個向量之前總是成 90 度夾角,并且通常為單位長度。數(shù)學(xué)書中中稱之為標準正交(向量為單位長度)和正交(向量不是單位長度)。如下圖對矩陣進行了標注。另外要注意下圖的矩陣最后一行都為 0 ,只有一個元素為 1。



如果有一個包含不同坐標系的位置和方向的 4 * 4 矩陣,然后用一個表示原來坐標系的向量(表示為一個矩陣或向量)乘以這個矩陣,得到的結(jié)果是一個轉(zhuǎn)換到新坐標系下的新向量。這就意味著,空間中任何位置和方向都可以由一個 4 * 4矩陣唯一確定。如果一個對象的所有向量乘以這個矩陣,那么我們將得到整個對象變換到的空間中的位置和方向。

4.2.2 單位矩陣

在講矩陣的變化之前,先簡單說下單位矩陣的概念。將一個向量乘以一個單位矩陣,就相當于用這個向量乘以 1 ,不會發(fā)生任何變化。單位矩陣中除了一條對角線上的元素為 1,其他元素全為 0 。

4.3 矩陣變化操作

矩陣的變化操作主要分為縮放、平移、旋轉(zhuǎn)。為了更好的理解矩陣的變化操作,以及矩陣是怎么形成的,筆者就直接從縮放講起。把縮放講清除,之后的平移以及旋轉(zhuǎn)都是按照同樣的套路進行推導(dǎo)。

4.3.1 縮放

為了更好的理解縮放,我先從 2D 環(huán)境說起,之后在延伸到3D環(huán)境。



如果沿著坐標軸進行縮放,那么每一個坐標軸都有縮放因子,所以2D環(huán)境下有兩個縮放因子Kx和Ky,那么基向量p和q根據(jù)縮放因子的影響,我們可以得到下面公式。


根據(jù)變化,可以得到在2D環(huán)境下的縮放矩陣。如下所示:


同理,通過2D環(huán)境下的縮放矩陣,我們可以得到3D環(huán)境下的縮放矩陣。

4.3.2 平移

一個平移矩陣僅僅是將頂點沿著 3 個左邊軸中的一個或者多個進行移動。這個應(yīng)該是很好理解的,這里就不做過多解釋。列如在 OpenGL的 math3d 庫中,我們就可以通過m3dTranslationMatrix44 函數(shù)使用矩陣直接做平移操作。

4.3.3 旋轉(zhuǎn)

關(guān)于旋轉(zhuǎn)操作的矩陣實際上推導(dǎo)起來稍稍有些麻煩,需要通過大量的圖解和注釋來說明。筆者感覺沒必要進行詳細的推導(dǎo),畢竟我們不是專業(yè)研究數(shù)學(xué)的,只要知旋轉(zhuǎn)矩陣形成過程和縮放以及平移的原理是一樣,只是推到起來會更麻煩些。這里我就簡單的列出一些推到結(jié)果。當然,有興趣的同學(xué)可以自行研究下。

五、關(guān)于CGAffineTransform的實現(xiàn)

在Core Graphics框架圖形繪制的時候,經(jīng)常會有對圖形進行平移、縮放、旋轉(zhuǎn)這樣的要求。那么我們該如何實現(xiàn)呢?這就需要Core Graphics框架中的CGAffineTransform(矩陣)這個結(jié)構(gòu)體來進行實現(xiàn)了。下面我們就對CGAffineTransform這個矩陣結(jié)構(gòu)體,進行簡單的說明。

// CGAffineTransform結(jié)構(gòu)體樣式
struct CGAffineTransform {
  CGFloat a, b, c, d;
  CGFloat tx, ty;
};

齊次坐標概念

所謂的其次坐標就是把一個圖形用一個三維矩陣表示,其中第三列總是(0,0,1),用來作為坐標系的標準。也就是z軸,是不發(fā)生改變的。

|a  b  0|
|c  d  0|
|tx ty 1|

接下來就來看看二維空間中的點坐標是如和借助齊次坐標進行仿射變化。具體運算原理如下:

               |a  b  0|

  [X,Y,  1]    |c  d  0|   =  [aX + cY + tx   bX + dY + ty  1] ;

                |tx ty 1|

運算原理就是如此簡單。CGAffineTransform的平移、旋轉(zhuǎn)、縮放操作,都是借助這個公式進行計算的,只不過平移、旋轉(zhuǎn)、縮放只不過是其中的一些特殊情況而已。

CGAffineTransform的平移、旋轉(zhuǎn)、縮放變換。

  • 平移變換
    條件: a = d = 1 ,b = c = 0
              |1  0  0|

  [X,Y,  1]    |0  1  0|   =  [X + tx   ,  Y + ty  , 1] ;

                |tx ty 1|

坐標由 [X,Y, 1] 變成了 [X + tx , Y + ty , 1],與原坐標相比,z軸沒發(fā)生任何的改變,x 軸方向平移了 tx 個單位, y 軸方向平移了 ty 個單位。平移對應(yīng)Core Graphics框架中的CGAffineTransformMakeTranslation(CGFloat tx,CGFloat ty)API。

  • 縮放變換
    條件: b = c = 0 ,tx = ty = 0。
               |a  0  0|

  [X,Y,  1]    |0  d  0|   =  [aX  ,  dY  ,1] ;

                |0 0 1|

坐標由[X,Y, 1]變?yōu)?[aX , dY ,1] ,X軸擴大了 a 倍,Y 軸擴大了 d 倍。縮放對應(yīng)Core Graphics框架中的 GAffineTransformMakeScale(CGFloat sx, CGFloat sy) API。

  • 旋轉(zhuǎn)變換
    條件 : tx=ty=0,a=cos?,b=sin?,c=-sin?,d=cos?
           |cos?   sin?  0|

  [X,Y,  1]    |-sin?  cos?  0|   = [Xcos? - Ysin? ,   Xsin? + Ycos? , 1] ;

                |tx     ty    1|

其中 ? 是旋轉(zhuǎn)的角度,逆時針為正,順時針為負。旋轉(zhuǎn)對應(yīng)Core Graphics框架中的 CGAffineTransformMakeRotation(CGFloat angle) API。

六、關(guān)于CATransform3D實現(xiàn)

回想一下前面所說的矩陣的相關(guān)知識,如下圖是 3D 仿射變化矩陣,和我們前面在矩陣那一塊講的結(jié)果一致。


3D仿射變化矩陣

先來看一下這個結(jié)構(gòu)體長什么樣。

//CATransform3D基本結(jié)構(gòu)體
struct CATransform3D{
   CGFloat m11, m12, m13, m14;
   CGFloat m21, m22, m23, m24;
   CGFloat m31, m32, m33, m34;
   CGFloat m41, m42, m43, m44;
};

下面的代碼是 CATransform3D 實現(xiàn) 3D 效果的簡單使用。

-(CATransform3D)getTransForm3DWithAngle:(CGFloat)angle{
    CATransform3D transform =CATransform3DIdentity;//獲取一個標準默認的CATransform3D仿射變換矩陣
    transform.m34=1.0/-2000;//透視效果
    transform=CATransform3DRotate(transform,angle,0,1,0);//獲取旋轉(zhuǎn)angle角度后的rotation矩陣。
    return transform;
}

上面最重要的是m34這個屬性,CATransform3DRotate獲取的旋轉(zhuǎn)如果之前聯(lián)合的transform不支持透視,那在x、y軸上做旋轉(zhuǎn)是只有frame放大縮小的變化,我們需要的是在旋轉(zhuǎn)的時候要使得離視角近的地方放大,離視角遠的地方縮小,就是所謂的視差來形成3D的效果。


根據(jù)前面的講解,通過上圖不難看到 m34 實際上影響了 z 軸方向的translation(移動) ,所以實際開發(fā)中為了實現(xiàn) 3D 效果,我們只需要簡單控制 m34 這個參數(shù)即可。除了 m34 這個參數(shù)之外,另外幾個參數(shù)各有其用,有興趣的可以看下這篇文章。另外, 要知道,m34= -1/D, 默認值是0,也就是說D無窮大,這意味layer in projection plane(投射面)和 layer in world coordinate 重合了。D越小透視效果越明顯。所謂的D,是eye(觀察者)到投射面的距離。

結(jié)語

本文顯示介紹了 3D 成像原理、3D編程基本原則,之后介紹了向量和矩陣,矩陣這一塊對平移、旋轉(zhuǎn)、縮放進行了簡單的推到。最后基于前面所講的這些基礎(chǔ)知識和 iOS 中的CGAffineTransform、CATransform3D做了簡單的關(guān)聯(lián)和解釋。
該篇文章僅僅只能作為 3D 圖像學(xué)的簡單入門,對于向接觸 AR、VR等相關(guān) 3D 方向的開發(fā)者來說,這些都是工作必備的知識。歡迎指正?。。。?!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1 前言 OpenGL渲染3D模型離不開空間幾何的數(shù)學(xué)理論知識,而本篇文章的目的就是對空間幾何進行簡單的介紹,并對...
    RichardJieChen閱讀 7,526評論 1 11
  • 1 前言 一直想沿著圖像處理這條線建立一套完整的理論知識體系,同時積累實際應(yīng)用經(jīng)驗。因此有了從使用AVFounda...
    RichardJieChen閱讀 5,932評論 5 12
  • >*很不幸,沒人能告訴你母體是什么,你只能自己體會* --駭客帝國 在第四章“可視效果”中,我們研究了一些增強圖層...
    夜空下最亮的亮點閱讀 1,726評論 0 2
  • 變換(Transformations) 我們可以嘗試著在每一幀改變物體的頂點并且重設(shè)緩沖區(qū)從而使他們移動,但這太繁...
    IceMJ閱讀 4,486評論 0 1
  • 謝康樂出身名門,富有才學(xué),但仕途坎坷,因而他的情感寄托多在乎于山水,山水之樂,雖仍受到東晉玄言詩的影響,間或有《周...
    王家人寧閱讀 479評論 0 0

友情鏈接更多精彩內(nèi)容