接觸3D程式設計,難免要認識各種座標系統,並在各個系統之間進行轉換,直接使用程式碼實現公式是個方法,然而3D的世界中經常運用矩陣運算,因此,認識齊次座標也是必備的,因為,這不單只是數學,與程式碼本身的效率也有很大的關係!

這是左手還是右手?

在3D世界中,最常運用的是直角座標,然而卻有多種不同的表示方式,也就是X、Y、Z軸正方向會有各自不同的表示,軟體或程式庫可能採用不同系統,這是種困擾,然而木已成舟,開發者只能在運用程式庫之前,先分辨它們到底採用哪個系統,粗略來說,直角座標系統可以分為兩種:左手座標(Left-hand Coordinate)、右手座標(Right-hand Coordinate)。

對於前者,把左手拿出來,姆指指向若符合X軸正方向,食指符合Y軸正方向,而掌心往上符合Z正方向的話,那麼該系統是採用左手座標,例如,WebGL裁剪空間(Clip Space)就是左手座標系統;對於後者,就是把右手拿出來,姆指與食指對應X、Y軸後,看看掌心往上是不是符合Z軸正方向,如果是的話,就是採用右手座標,例如glMatrix、Three.js就屬於這類右手座標系統。

如果你一開始設計3D物件時,是採用左手座標,然而接下來想要套用glMatrix呢?表面上看來,右手座標似乎只是將Z軸正方向反過來,對glMatrix是如此,然而對其他程式庫或軟體就不一定了。

例如,OpenSCAD、Blender是右手座標,然而,它的X、Y是代表2D平面,Z代表高度,也就是說,同為右手座標系統,還是要注意一下三個軸各自的意義為何;至於3D雕塑軟體的部份,似乎偏好左手座標,例如:Sculptris、SculptGL,不過,它們的z軸正方向是朝上,而不是像WebGL裁剪空間的z軸代表深度。

左手座標或右手座標的差別,會影響到座標轉換時導證出來的公式結果,如果試圖結合兩種不同系統的程式庫,就必須自行實作轉換,不然繪製出來就會是錯誤的畫面。

例如,一個右手系統的(x,y,z)要換成左手系統的話,並不是只有(x,y,-z)一種轉換方式,(−x,y,z)、(x,−y,z)、(−x,−y,z)、(−x,y,−z)、(x,−y,−z),也都是轉換為左手座標的可能方式。

這是哪個空間?

剛開始接觸WebGL時,至少會接觸兩個空間:裁剪空間與螢幕空間(Screen Space)。對WebGL來說,螢幕指的就是Canvas,基本上,若頂點正確出現在裁剪空間中,WebGL會自動處理螢幕空間的繪製,不過,如果要處理像滑鼠點選的問題時,就必須處理繪圖座標至裁剪空間的轉換問題。

要繪製的幾何物件是由一組頂點定義,雖然裁剪空間是左手座標,然而這組頂點可以基於左手或右手座標定義,也就是說頂點定義在一個獨立的物件空間(Object space),或說是局部空間(Local Space),這個空間有個獨立原點,頂點都是相對於該原點而定義。

接著,我們會縮放、轉動、移動這些幾何物件,實際上這些轉換動作,是將這些物件中的頂點,轉換至世界空間(World Space)。也就是開發者建構的3D世界,在世界空間中可能有許多物件,然而可能只想繪製出某範圍內的物件,這就像是有個相機,世界很大,然而,只有相機內看到的範圍才會被繪製出來,這個範圍稱為觀察空間(View Space)。

開發者可能會用左、右、上、下,以及近面、遠面邊界,來定義觀察空間,而觀察空間會透過一些計算,對應至裁剪空間,這個過程稱為投影,常見的作法有正交投影或透視透影──正交投影不會創造出遠近感,然而,透視透影會因為視體(Viewing frustum)的視場角度(fov)等參數不同,呈現出不同的遠近感。

簡單來說,從物件空間、世界空間、觀察空間一直到裁剪空間等,必須經過多個空間轉換,才能順利地繪製出想要的3D物件,這些轉換,說穿了,就是一連串的線性轉換數學公式,然而,透過矩陣運算的話會更好,因為這會牽涉到程式運算時的效率問題。

這是點還是向量?

若要透過矩陣運算來執行空間轉換,此時,就不得不接觸齊次座標(Homogeneous coordinate)。多半的說詞是,齊次座標在進行仿射轉換(Affine Transformation)時很方便,像是混合縮放、轉動、移動等操作的轉換時計算方便,不過,實際上,齊次座標還可以用來區別點與向量的差別。

若單純只使用直角座標,(a, b, c)會是代表什麼呢?一個點?一個向量?誰知道!

使用齊次座標的話,(a, b, c, 1)表示這是個點,而(a, b, c, 0)表示是個向量。至於為什麼會這麼定義,事實上,這會牽涉到一些線性代數的基礎,有興趣的話,我們可以看看〈Understanding of homogeneous coordinates〉(https://bit.ly/2KECitX)。

齊次座標用第四個分量來代表點,這表示直角座標中的同一個點(Px, Py, Pz),與之對應的齊次座標可以有無數個,也就是說(1, 2, 3, 1)、(2, 4, 6, 2)、(3, 6, 9, 3)等都代表著同一點(1, 2, 3),也就是說,對於齊次座標中的點(Px, Py, Pz, w),對應的直角座標點是(Px/w, Py/w, Pz/w)。

這就表示,當轉換公式涉及某分量的除法時,透過齊次座標可以化為矩陣運算。例如,公式轉換中,若x'=near*x/z,y'=near*y/z,z'=z,就可以用齊次座標[near * x, near * y, z, z]來表示;也就是說,計算用的矩陣若以列為主(row-major)撰寫的話,會是[near, 0, 0, 0, 0, near, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0]。

一旦線性轉換公式可以化為矩陣運算,那麼,就能將各種轉換予以組合,最後得到一個矩陣,也就是說,若打算進行縮放、旋轉、平移等n個轉換操作,對象是1,000個頂點,直接用函式實作個別轉換公式的話,必須進行n*1000的呼叫;相對地,如果使用一個結合了n個轉換的矩陣,就只需要1,000次計算,若轉換對象是圖像的像素的話,這樣的差異會更為驚人。

不是都有程式庫/框架嗎?

是的,3D程式設計也會有現成的程式庫或框架,來做這類的轉換操作,然而,別以為這樣,就可以輕鬆了事。

最起碼,還是要先搞清楚它採用的座標系統,以及相關的參數意義,最好的方式,是親自導證一次縮放、旋轉、平移、投影等矩陣等是怎麼來的,這會讓開發者在轉換座標系統時,更為得心應手。

對齊次座標與矩陣轉換有興趣的話,不妨可以看看〈Interactive guide to homogeneous coordinates〉(https://bit.ly/2ZgpNHU),該文從程式人的角度,提供互動的方式,來協助你認識齊次座標與矩陣轉換。別以為這只是數學,正如該文中所談到的:「認識選擇的框架背後的數學,寫出來的程式碼才會有效率」。

專欄作者

熱門新聞

Advertisement