為什麼directX里表示三維坐標要建一個4*4的矩陣?

如題,就是向量的平移或者縮放操作也要用矩陣乘法操作,乘法難道不是應該很費時間嗎?就拿平移操作,為什麼我覺得加法就夠了呢?就是用到乘法的時候三維矩陣也夠了啊?


的確,如果要表示仿射變換(affine transformation),是不需要用 4×4 矩陣的,只需要 3×3 矩陣再加上平移矢量。

egin{bmatrix}
x

一些遊戲引擎會使用 4×3 或 3×4 矩陣去表示仿射變換。

但是,在使用到透示投影時,一般會使用到齊次坐標(homogeneous coordinates),這時候就需要 4×4 矩陣。而在渲染過程,通常是把同一組變換施於多個點或矢量,例如:

mathbf{x}_i

使用矩陣表示變換的好處之一,是矩陣乘法具結合性(associative):

egin{align*}
mathbf{x}_i

這個代數性質令到我們可以先計算 mathbf{M} = mathbf{M}_3mathbf{M}_2mathbf{M}_1,然後施於大量點或矢量

mathbf{x}_i

由於透示投影需要以 4×4 矩陣表示,所以一般把各種變換串接(複合)後,會形成一個 4×4 矩陣。這樣做,無論需要做多少個變換,最後對每個點或矢量而言也只需要做一個矩陣對矢量的乘法(就是4個點積)。在底層而言,這個運算也很適合使用 4-way SIMD 實現(4個乘法和3個加法運算)。

還有一點是,現在的主流硬體都提供可編程渲染管道,變換已經不是通過 API 處理,而是自行在著色器里處理。一部分的變換可能會採用矩陣以外的形式,例如使用 dual quaternion 計算蒙皮。

因此,題目中「為什麼DirectX里表示三維坐標要建一個4*4的矩陣」這個說法有兩個問題。第一,矩陣是用於表示變換而不是坐標,第二,在 DirectX 9 及以後基本上都是用著色器做變換,所以和 DirectX 的關係不是很大。然而,DirectX 渲染管道的 raster stage 里還是保留了固定的 perspective divide 單元,也就是說,著色器輸出的坐標是以齊次坐標表示。

《遊戲引擎架構》第4.3節矩陣、第10.1.4節虛擬攝像頭有相關的介紹。

----

更新:補充一下,仿射變換也是可以如矩陣變換般串接(複合)的。因為矩陣也具分配律,把兩個仿射變換施於一個矢量:

mathbf{M}_2(mathbf{M}_1mathbf{x} + mathbf{t}_1) + mathbf{t}_2 = 
(mathbf{M}_2mathbf{M}_1)mathbf{x}+(mathbf{M}_2mathbf{t}_1 + mathbf{t}_2)

可見,串接後仍然是仿射變換的形式,換句話說這串接(複合)運算是閉合的。


0x00 前言

正如Milo Yip大神所說的這個標題事實上是存在問題的:矩陣是用於表示變換而不是坐標的。再了解了矩陣的作用之後,我們就要繼續思考為什麼變換要使用一個4×4的矩陣而不是3×3的矩陣呢?是不是多此一舉呢?下面我們就來聊聊這個話題。

0x01 怎麼平移一個三維空間中的點?

我們應該怎麼平移一個三維空間中的點呢?答案很簡單,我們只需要對這個點的坐標中的每個分量(x,y,z)和對應軸上的平移距離相加即可。

例如,點p1(x1,y1,z1)在X軸Y軸以及Z軸上分別平移Δx,Δy,Δz到新的點p2(x2,y2,z2),那麼我們只需在坐標對應的分量上加上這些增量就可以確定點p2的坐標了。

x2 = x1 + Δx

y2 = y1 + Δy

z2 = z1 + Δz

很簡單是嗎?那麼接下來再讓我們來看一看另一種變換:旋轉。

0x02 再來旋轉一個點

旋轉相比較平移來說,會略顯複雜一些。因為我們需要指明以下幾個方面來描述一個旋轉:

  1. 旋轉軸

  2. 旋轉方向

  3. 旋轉角度

在這裡,我們假設點p需要繞Z軸順時針旋轉β度。

如這個很難看的圖所示,我們的點P1(x1,y1,z1)以Z軸位軸順時針旋轉β度之後來到了點P2(x2,y2,z2)。接下來,讓我們假設原點到P1的距離位L,且P1和Y軸之間的夾角位α,那麼根據三角函數公式我們就可以計算出P1點的具體坐標了:

x1 = L·sinα

y1 = L·cosα

由於是繞Z軸旋轉,因此z坐標不變,因此此處不考慮。

同樣根據三角函數公式,我們可以繼續計算出P2的具體坐標:

x2 = L·sin(α + β)

y2 = L·cos(α + β)

再讓我們回憶一下中學時代的幾何數學的內容,對青春的回憶又把我們帶回了課堂上老師聲嘶力竭向我們傳授的內容——兩角和與差:

cos(α+β)=cosα·cosβ-sinα·sinβ

cos(α-β)=cosα·cosβ+sinα·sinβ

sin(α+β)=sinα·cosβ+cosα·sinβ

sin(α-β)=sinα·cosβ-cosα·sinβ

回憶起老師傳授給我們的知識之後,接下來結合P1的坐標表示形式我們就可以把P2的坐標換一個表示形式了。

x2 = L·(sinα·cosβ+cosα·sinβ) = cosβ·x1 + sinβ·y1

y2 = L·(cosα·cosβ-sinα·sinβ) = cosβ·y1 - sinβ·x1

z2 = z1

因此,在已知P1點坐標以及旋轉角度β的情況下,我們就可以根據以上公式分別求出P2點坐標的各個分量。如果再結合上一小節中平移一個點的公式來看,我們可以發現似乎並不需要矩陣,而僅僅通過兩組表達式就能實現坐標的變換。但是......

0x03 帶來便捷的矩陣

當然,從理論上講我們的確可以只通過數學公式就能實現變換,但實際的情況卻是在變換十分複雜時,直接使用數學表達式來進行運算也是相當繁複的。因此,在現實中常常使用矩陣(由m × n個標量組成的長方形數組)來表示諸如平移、旋轉以及縮放等線性變換。而兩個變換矩陣A和B的積P=AB,則變換矩陣P相當於A和B所代表的變換。舉一個例子,若A為旋轉矩陣,B為平移矩陣,則矩陣P就能夠實現旋轉和平移變換。不過需要注意的是,矩陣乘法不符合交換律,因此AB和BA並不相等。

說了這麼多,我們似乎還是沒有回答為什麼三維空間需要一個4×4矩陣來實現變換呢?下面我們就帶著這個問題,分別看看3×3矩陣和4×4矩陣的使用吧。

3×3矩陣和旋轉

首先,我們先來看一個矩陣乘以一個三維矢量的算式:

可以看到該矩陣為一個3×3的矩陣,矩陣的右側是點P1的坐標,而矩陣的左側則是點P2的坐標。根據這個表達式,我們可以求出x2、y2、z2的值:

x2 = a·x1 + b·y1 + c·z1

y2 = d·x1 + e·y1 + f·z1

z2 = g·x1 + h·y1 + i·z1

為了將矩陣等式和之前小節的數學表達式聯繫起來,下面我們就將旋轉表達式和該矩陣等式做一個對比。

x2 = a·x1 + b·y1 + c·z1

x2 = cosβ·x1 + sinβ·y1

y2 = d·x1 + e·y1 + f·z1

y2 = cosβ·y1 - sinβ·x1

z2 = g·x1 + h·y1 + i·z1

z2 = z1

通過對比x2,我們可以發現a=cosβ,b=sinβ,c=0;

對比y2,也可以發現d=-sinβ,e=cosβ,f=0;

最後對比z2,可以確定g=0,h=0,i=1;

將這個結果帶入到之前的矩陣中,我們的等式就可以變成下面這個樣子:

也就是說,通過這個3×3的變換矩陣,我們就已經實現了三維空間的旋轉變換。那麼為什麼還需要使用4×4矩陣呢?

搞不定的平移,4×4矩陣來救場

我們已經通過一個3×3矩陣搞定了旋轉變換,顯然如果這個3×3矩陣真的是完美的解決變換的方案的話,那麼它顯然也必須要適合於其他的變換,例如平移。但是它到底能否滿足平移的需求呢?下面我們還是通過對比矩陣等式和數學表達式的方式,來尋找答案。

x2 = a·x1 + b·y1 + c·z1

x2 = x1 + Δx

y2 = d·x1 + e·y1 + f·z1

y2 = y1 + Δy

z2 = g·x1 + h·y1 + i·z1

z2 = z1 + Δz

通過對比,我們發現平移和旋轉之間很有趣的一個區別,那就是平移的表達式中帶有常量Δx,而無論是旋轉的表達式還是矩陣等式中都不存在這樣一個常量能夠與之對應。那麼問題就來,我們沒有辦法使用3×3的矩陣來表示平移。答案其實很簡單,那就是使用4×4矩陣來實現。但是隨之而來的一個問題就是如何一個三維坐標如何能和一個4×4的矩陣相乘呢?

齊次坐標

為了解決三維矢量和4×4矩陣相乘的問題,我們機智的為三維矢量添加了第四個分量,這樣之前的三維矢量(x,y,z)就變成了四維的(x,y,z,w),這樣由4個分量組成的矢量便被稱為齊次坐標。需要說明的是,齊次坐標(x,y,z,w)等價於三維坐標(x/w,y/w,z/w),因此只要w分量的值是1,那麼這個齊次坐標就可以被當作三維坐標來使用,而且所表示的坐標就是以x,y,z這3個值為坐標值的點。

因此,為了和4×4矩陣相乘,我們的P1點坐標就變成了(x1,y1,z1,1)。而矩陣等式也變成了下面這個樣子:

我們再將這個新的矩陣等式和平移的數學表達式做一番對比:

x2 = a·x1 + b·y1 + c·z1 + d

x2 = x1 + Δx

y2 = e·x1 + f·y1 + g·z1 + h

y2 = y1 + Δy

z2 = i·x1 + j·y1 + k·z1 + l

z2 = z1 + Δz

1 = m·x1 + n·y1 + o·z1 + p

通過對比x2,我們可以發現a=1,b=0,c=0,d=Δx;

對比y2,也可以發現e=0,f=1,g=0,h=Δy;

再對比z2,可以確定i=0,j=0,k=1,l=Δz;

最後還可以根據表達式求出m=0,n=0,o=0,p=1;

這樣,我們就求出了我們的4×4的平移矩陣:

0x04 總結

寫到這裡,不知各位是否還記得之前在介紹矩陣乘法的時候我有提到過兩個變換矩陣A和B的積P=AB,相當於A和B所代表的變換。事實上在遊戲編程中,常常需要把一連串的變換預先通過計算成為單一矩陣,所以就不能即存在3×3的矩陣又存在4×4的矩陣。而將3×3矩陣拓展成4×4矩陣還是相對更加容易的。這樣,就通過一個4×4矩陣整合了平移矩陣、旋轉矩陣。


可以用一個4X4 的矩陣整合平移矩陣和旋轉矩陣等,3X3 實現不了。

矩陣乘法在 GPU 很快。

可以用一個矩陣代替 N 個矩陣,比如

WVP = World * View * Projection

然後 World = Scale * Rotation * Translation

所以 WVP = Scale * Rotation * Translation * View * Projection,其中每一個都是 4X4


需要4x4的不是表示「三維坐標」,而是表示「三維坐標的變換」。


它們都沒說到點子上。首先api並不要求你所有運算都使用矩陣,只要求傳入時使用(其實非固定管線允許傳多個Vector4來靈活選擇4*3,4*4等)。程序設計在於性能,通用與兼容性。

用你舉的例子來說,單一的平移運算加法確實是最快的方式,但是矩陣也提供了這樣的高性能可能性,即m41,m42,m43。

同時三維運算經常涉及交叉變換,平移與旋轉,先旋轉後平移還是先平移後旋轉的問題就有不同演算法,但用矩陣很容易解決,且通用。

最後一點,位移旋轉縮放側翻需要4*3矩陣,考慮到投影,api使用4*4矩陣保證了最大可能性。

因此4*4矩陣是最佳選擇。


這樣的變換叫仿射變換 Affine transformation

這類變換的好處就是可以統一平移和旋轉操作, 簡化計算流程. 計算機最擅長做大量重複的事情, 不喜歡你打一槍換一個靶子.

直觀上說, 就是把N維空間里的物體拿到N+1維空間里去, 並向著多出來的維度方向(隨便起個名字就叫W方向)做一個1單位長度的"拖拽", 生成一個N+1維的物體, 其W方向上的每一個N維"截面"都是原N維物體的一個複製. 所有的變換操作都是對著這個高一維的物體. 當我們做完變換要取得變換後的N維物體, 只要取W=1所代表的那個N維截面就行了. 這樣一來, N維的平移操作, 在N+1維空間就是對整個N+1維物體做垂直於W方向的切向拉伸. wiki鏈接里的那個動畫很形象的說明了2維圖形平移的操作在3維空間是如何對應起來的.


首先每個基本變換都可以表示為 P" = M1 * P + M2 的形式。

列向量 P P" 表示坐標位置, M1是一個包含乘法係數的 3 x 3 矩陣, M2是包含平移項的三元素列矩陣。對於平移,M1是單位矩陣,對於旋轉或縮放,M2包含與基準點或縮放固定點相關的平移項。

以上就是題主所說的平移操作加法就夠了。

當利用這個公式要進行單一的變化操作,似乎問題不大,但是如果要進行連續的變換就會很麻煩,比如先縮放再旋轉再平移。

更方便的方法是把M2消除掉,直接一個矩陣就可表示任意變換操作。

需要把3 x 3的矩陣擴展為 4 x 4矩陣,就可以把任意的三維幾何變換的乘法和加法組合為單一矩陣表示。

三維的點怎麼表示為四元列向量呢,一般使用齊次坐標(xh,yh,zh,h),最方便是設h=1,有(x,y,z,1)。同時(x,y,z,0)表示三維中的向量,被任意位移變換矩陣乘之後依然是(x,y,z,0)滿足向量特性。

所以還是4x4的矩陣好用。

見 《計算機圖形學》第三版第五章 Donald Hearn M.Pauline Baker


加一維,統一了平移、縮放等運算,將它們變成乘法運算。

這個好像叫齊次坐標?


第四維是為了做除法。

比如你用三維矩陣的話, (x", y", z") = M * (x, y, z) 只能做乘法和加法

四維矩陣在齊次坐標上可以做到:

(x", y", z", 1) = (x"h, y"h, z"h, h) = M * (x, y, z, 1)

這樣 x"h 是 (x, y, z, 1) 的線性組合,h 是 (x, y, z, 1) 的另一個線性組合,

那麼 x" = f(x, y, z, 1) / g(x, y, z, 1) 比乘法和加法可以多做很多事情。


補充一點,也是我認為之前的回答都沒有提到的最重要的一點:

之所以要把本來可以計算量更小的坐標變化演算法擴展到齊次坐標變換而使用4x4矩陣乘法,

目的是為了使用一個簡潔的,統一的運算操作來處理所有的變換。

目的是為了使用一個簡潔的,統一的運算操作來處理所有的變換。

目的是為了使用一個簡潔的,統一的運算操作來處理所有的變換。

這樣做有幾個好處:

1. 邏輯上所有的變換不必逐步處理了,可以合併為最終變換,於是一個無論多歷程崎嶇的終變換,可以被很容易地拷貝到另一個物體上(拷貝所有的變換過程vs拷貝一個矩陣)。

2. 這個運算操作(矩陣乘法),可以被高度優化,甚至實現到電路板上,比軟體的實現快到那裡去。


其實最後一句才是重點 homogeneous coordinates可以表示某個方向無限遠的點。

上圖是2d 情況。

如果在3d情況下 (1,1,1,0)表示 在(1 1 1)^T 方向的無限遠的點。

所以在 homogeneous coordinates下,我們可以使無限遠的點參加運算

比如說 求水平線在視野里的位置。

竟然沒人說到重點。。。。。


第4維是為了平移。3維空間的變換實質都是在做形如ax + by + cz + d的點積,3維但有4個參數,缺了d的話所有的變換都在繞著原點轉。


你用 Homogeneous Coordinate 需要區分點和向量,三維矩陣你表示不了。

然後 3D Rotation 繞 xyz 三個軸轉,需要分開表示。

你要是把 Tralstation、Scale 和三個維度的 Rotation 擠到一個三維矩陣裏……你就表示不了了。


類比下平面中,點的運動表述。


用四維的計算來解決三維計算解決不了的三維問題。


最近正在看《Real-Time Renderging》這本書,先回答其中一個原因,其他留著看到在回答吧。

在三維坐標軸中,在表示向量和點時,如果只是用三個元素表示,那麼表示形式都是一個樣,這是十分含糊不清的。但是使用了4個元素,就可以避免這種情況了,例如第四個元素0代表當前是向量,1代表當前是點。同事,使用4*4矩陣形式,對於矩陣操作有極大的優勢(書上說在第4章和A.4提到,目前還沒看到,就不講來)。


分頁阅读: 1 2