說起OpenGL的矩陣變換,我是之前在我們的項目天天P圖、布丁相機中開發3D效果時才比較深入地研究了其中的原理,一直想寫這篇文章,由於很忙(lǎn),拖了很久,再不寫我自己也要忘了。
一開始時,也只是知道怎麼去用這些矩陣,卻不知道這些矩陣是怎麼得來的,當出現一些莫名其妙的問題時,如果不了解其中的原理,就不知道如何解決,於是想徹底搞懂其中的原理,還好自己對數學挺有興趣,於是從頭到尾把推導過程研究了一遍,總算掌握了其中的奧秘,不得不佩服OpengGL的設計者,其中的數學變換過程令人陶醉,下面我們一起來看看。
這些矩陣當中最重要的就是模型矩陣(Model Matrix)、視圖矩陣(View Matrix)、投影矩陣(Projection Matrix),本文也只分析這3個矩陣的數學推導過程。這三個矩陣的計算OpenGL的API都為我們封裝好了,我們在實際開發時,只需要給API傳對應的參數就能得到這些矩陣,下面帶大家來看看究竟是怎樣計算得到的。
什麼是OpenGL的矩陣變換
我們先來看一張經典圖:
這張圖相信很多同學在學習OpenGL的過程中都看到過,它比較直觀地展示了OpenGL矩陣變換的過程,下面我詳解一下其中的含義:
首先OpenGL有個世界坐標系,我們渲染的物體就是在世界坐標系中,我們的模型需要放到世界坐標系中,那麼當我們還沒放的時候,模型就和世界坐標系沒有聯繫,它就還處於自己的坐標系中,我們叫做模型坐標系、局部空間、局部坐標系,也就是圖中的LOCAL SPACE。
當我們把模型放到世界坐標系中,模型就在世界坐標系裡有了坐標,也就是原來在LOCAL SPACE中的那些坐標值,變成了世界坐標系中的坐標值,幫助我們完成這個變換的就是模型矩陣,對應圖中的MODEL MATRIX,於是這樣我們就把模型放到了圖中的世界坐標系WORLD SPACE中
放到世界坐標系後,是不是就確定了我們渲染出來看到的樣子?還沒有,大家可以想像一下,我把一個東西放在世界坐標系的某個地方,我可以從近處看觀察它,也可以從遠處觀察它,還可以從上下左右觀察它,甚至還可以倒著觀察它,因些還需要確定我們觀察它的狀態。OpenGL裡幫我們虛擬出了一個Camera(特別注意,這裡的Camera不是指我們硬體的Camera),從API的層面上看,我們只需要設置Camera的位置、朝向的點坐標、以及Camera的上方向向量就能將觀察狀態定下來,而這些設置最終會轉換成OpenGL中的視圖矩陣,對應圖中的VIEW MATRIX
經過View Matrix的變換後,我們觀察它的結果就確定了,圖中是從距離它一定的距離、上往下觀察它,這時候的點坐標就來到了視圖坐標系下,對應圖中的VIEW SPACE
這時候,我們能看到什麼東西,基本已經確定了,不過還有一步投影變換,這是什麼東西?大家想像一下,我們看到同一個東西,是不是通常都是近大遠小?那麼如何實現近大遠小?就要靠投影變換,OpenGL提供正交投影和透視投影,正交投影沒有近大遠小的效果,不管在什麼距離上看,都一樣大,透視投影則有近大遠小的效果,也是符合我們實際生活的一種效果,透視投影應用得比較多,可看下面這張經典圖:
完成投影變換就需要靠投影矩陣,即圖中的PROJECTION MATRIX
我們看可以從圖中看到經過投影變換後就到了裁剪坐標系CLIP SPACE,什麼?裁剪坐標系?我們不是投影嗎?裁剪了什麼東西?實際上,我們的投影操作也順帶做了裁剪,所謂裁剪就是說把那些我們視野內看不到的東西去掉,什麼是視野?就是我們在生成投影矩陣時會設置近平面、遠平面、視角,這些東西會構成一個可見的空間,對應上圖中的虛線和近平面、遠平面包圍起來的空間
下一步就是上屏(如果是離屏渲染就是到一個frame buffer上),這些坐標畢竟只是OpenGL坐標系下的坐標,那麼最終以什麼樣的大小呈現在屏幕上呢?就要通過視口變換映射到屏幕上
以上就是一個完整的矩陣變換過程,裡面最重要的就是MVP三個矩陣,M即模型矩陣(Model Matrix),V即視圖矩陣(View Matrix),P即投影矩陣(Projection Matrix),本文將針對這三個矩陣的來由詳解其中的數學推導,其中投影矩陣只講解透視投影矩陣,因此它比較常用且其推導過程比正交投影矩陣複雜得多。
模型矩陣(Model Matrix)推導
相信大家在數學中都學過平移、縮放、旋轉三種基本變換,將模型放到世界坐標系中就是利用這三種變換的組合來實現的,我們來看一下平移、縮放、旋轉三種變換對應的矩陣:
縮放變換
旋轉變換
1)繞x軸旋轉
2)繞y軸旋轉
3)繞z軸旋轉
大家可以看到旋轉變換有三個矩陣?為什麼不寫成一個,注意繞軸旋轉的先後順序不同,最終的結果可能是不一樣的,因此有三個獨立的矩陣,根據實際情況組合。
模型矩陣相對來說簡單一些,相信大家還能回憶起來之前學數學時的知識,就是通過將平移、縮放、旋轉三種矩陣的組合實現將模型以某種姿態、某種大小放到世界坐標系的某個地方。
視圖矩陣(View Matrix)推導
前面提到過,視圖矩陣對應Camera的位置、朝向的點坐標、以及Camera的上方向向量,我們先來看一張圖:
下面我們來看看怎樣通過Camera的位置、朝向的點坐標、以及Camera的上方向向量得到對應的View Matrix,首先給Camera定一個坐標系:
NUV這三個向量是怎麼來的呢?我們將Camera的坐標記為eye,朝向的點坐標記為lookat,上方向向量記為up,那麼:
N向量: eye - lookat
U向量:up X N並歸一化
V向量:N X U並歸一化
我們要把Camera以某種姿態放在世界坐標系中的某個地方,這個放的過程就是對應Camera的旋轉和平移,這裡表示為TR,其中T表示平穩變換矩陣,R表示旋轉變換矩陣。
我們雖然設置的是Camera,但最終動的是點坐標,因為Camera壓根就不存在,是一個假想的東西。假設我們不動攝像機,動坐標點,那麼對坐標點的變換就應該是對相機變換的逆變換T^-1R^-1(就是對TR整體求逆矩陣),注意,這裡的T^-1R^-1看起來貌不驚人,實際上就是我們要求的View Matrix。
根據前面的知識,我們能很容易得到T^-1:
這個直觀上也好理解,比如本來是平移Tx,逆過來就是平移-Tx,依此類推。
再回顧一下我們的目標T^-1R^-1,現在還差R^-1,現在再次回到我們假想的Camera,前面說要對它做TR,當做完R後,Camera會旋轉至某個姿態:
XYZ和UVN都可以看成是一組基,根據線性代數公式可將一個點在XYZ基下的坐標轉成在UVN基下的坐標,R就相當於是把基XYZ變換成UVN的變換矩陣,其中:
假設:
則有:
於是:
由於R是正交矩陣,有性質:R^-1=R^T(R^T代表R的轉置),為什麼R是正交矩陣?Tips:方陣A正交的充要條件是A的行(列) 向量組是單位正交向量組。
於是:
現在我們T^-1和R^-1都有了,T^-1R^-1也就是最終的View Matrix可以很容易地計算出來了,因為OpenGL中坐標是4維的,所以這裡將矩陣寫成4*4的:
下面是投影矩陣的推導,是最為複雜的一個矩陣,前面提到,投影矩陣是由視野決定的,而視野又是由近平面、遠平面和視角決定的,我們把視野在坐標系中畫出來,請看下圖:
簡單起見,我們不妨把Camera擺在原點,讓它朝z軸負方向來討論問題。
h表示近平面高度
w表示近平面寬度
n表示Camera到近平面的距離
f表示Camera到遠平面的距離
P代表視野中的一個點
那麼接下來要求的投影矩陣,就是能將P點正確地投影到近平面上,設P(x0, y0, z0),我們從y軸正嚮往負向看,即看xoz平面,看到的畫面是這樣的:
假設投影后的x坐標為x1 ,由三角形相似原理則易得:
同理有:
設l和r分別為近平面左、右邊框的x坐標,則有l=-w/2,r=w/2,投影歸一化後坐標範圍為-1~1,最左邊是-1,最右邊是1,l和r歸一化至-1~1是線性變換,於是列一個kx+b類型的方程組並解得k和b:
令xn表示點P的x坐標投影歸一化後的值,代入kx+b得:
同理可得點P的y坐標投影歸一化後的值yn:
下面我們來構造帶有未知數的投影矩陣然後求解它們,設待投影點為(x0,y0,z0,1),我們先來構造投影矩陣的第一第二行:
這裡強調一個細節,投影矩陣僅幫我們完成投影變換,不會歸一化,上面的x2、y2、z2指的是投影后歸一化前的值,還記得前面計算的xn和yn嗎?我們用一個括號把其中一個部分括了起來,外面乘了一個因子(-1/z0),後面會說這個因子是什麼東西,現在只需要知道,x2、y2實際上就是前面括號裡那堆東西,所以上面投影矩陣的第一行和第二行就自然能輕鬆地構造出來。
接下來就構造第三第四行,我們先看第四行,第四行計算的結果是投影后的第四維坐標,也就是w,前面提到了歸一化,而OpenGL的歸一化操作就是通過將坐標除以其對應的w值來完成的,再回頭看我們前面計算的xn和yn,它們是歸一化後的值。
還記得括號外面乘了一個因子(-1/z0)嗎?乘(-1/z0)可以看成是除以-z0,因此希望w就是-z0,於是構造第四行讓w的計算結果為-z0:
接下來就是最複雜的第三行,如何去構造第三行?第三行有4個值,現在都不知道是什麼,我們需要構造4個未知數嗎?對於解方程來說,在能解決問題的情況下,未知數能少就儘量少,不然只會徒增煩惱。
這裡其實不需要4個未知數,為什麼呢?那就要理解z2這個值是什麼東西,它就是投影之後未歸一化的深度值,而深度和x0、y0沒有關係,這個如何理解?就是說我把一個東西放在左,上邊,還是右邊,不影響它的深度,要改變深度需要前後移動。
既然z2和x0、y0沒有關係,那麼x0、y0不管是什麼值,都不會影響z2的值,因此用0去乘x0、y0,即第三行的第一第二個元素是0。
再看第三行的第三第四個元素,我們假設第三個元素是0,會發生是什麼?那麼z2就等於B,而B最後求出來放到矩陣中肯定是一個定值,這就意味著z2也是定值,於是z2就無法表示不同的點的不同深度,這不是我們想要的結果,因此第三個元素不能是0,是一個待求的未知數。同理,我們假設第四個元素是0會發生什麼?這樣投影矩陣第四列全是0,根據線性代數的知識,這個矩陣行列式等0,它必定不可逆,而我們希望投影矩陣是可逆的,這樣我們可以對坐標做一些逆變換來實現一些特殊的功能,因此第四個元素也不能是0,於是設它為一個未知數。
這樣,我們就構造出了一個包含未知數A和B的投影矩陣:
下面就是求解A和B:
我們將z0為-f和-n代進去,-f就是遠平面,-n就是近平面,求歸一化後的坐標,-f最遠,深度最深,歸一化後是1,反之,-n代進去後是-1,注意,深度是值越大越深,於是有:
可解得:
於是投影矩陣為:
至此,我們就完成了模型矩陣(Model Matrix)、視圖矩陣(View Matrix)和投影矩陣(Projection Matrix)的數學推導,可以看到裡面的變換還是很精彩的,原來神秘的矩陣變換過程已經清晰可見,希望能對大家有幫助!謝謝!
文章後記
天天P圖是由騰訊公司開發的業內領先的圖像處理,相機美拍的APP。歡迎掃碼或搜索關注我們的微信公眾號:「天天P圖攻城獅」,那上面將陸續公開分享我們的技術實踐,期待一起交流學習!
加入我們
天天P圖技術團隊長期招聘:
(1) 深度學習(圖像處理)研發工程師(上海)
工作職責
開展圖像/視頻的深度學習相關領域研究和開發工作;
負責圖像/視頻深度學習算法方案的設計與實現;
支持社交平臺部產品前沿深度學習相關研究。
工作要求
計算機等相關專業碩士及以上學歷,計算機視覺等方向優先;
掌握主流計算機視覺和機器學習/深度學習等相關知識,有相關的研究經歷或開發經驗;
具有較強的編程能力,熟悉C/C++、python;
在人臉識別,背景分割,體態跟蹤等技術方向上有研究經歷者優先,熟悉主流和前沿的技術方案優先;
寬泛的技術視野,創造性思維,富有想像力;
思維活躍,能快速學習新知識,對技術研發富有激情。
(2) AND / iOS 開發工程師
(3) 圖像處理算法工程師
期待對我們感興趣或者有推薦的技術牛人加入我們(base 上海)!聯繫方式:ttpic_dev@qq.com