一個座標轉換的心路歷程。git
在前面繪製基本圖形中,遇到了很明顯的問題,圓形不像圓形,正多邊形不像正多邊形?就像下面圖形同樣:github
好好的正五邊形卻東倒西歪的,這就是由於咱們前面的繪製都是把它當成 二維 的繪製,而在 OpenGL 中倒是繪製 三維的。在二維和三維之間還有個轉換,而以前爲了方便學習則忽略了這個轉換,如今就要開始理解它了 —— 座標系統
!!spring
在立體幾何的座標系裏面定義一個點的位置,須要 x、y、z 三個座標軸的值,而在 OpenGL 中繪製 3D 物體也是須要的。bash
在繪製基本形狀時,只是定義了 x、y 軸的座標,這樣 z 軸的座標就默認爲 0 了。微信
OpenGL 將定義好的座標軸的值轉換爲實際繪製的座標,須要通過五個座標系統的轉換。函數
以下圖所示:post
這裏面涉及到了五個座標空間和三個轉換矩陣:學習
空間:ui
矩陣:spa
根據流程圖,每一個座標空間的轉換都須要一個轉換矩陣來完成。
最後裁剪空間到屏幕空間的轉換,就是將通過這一系列轉換後的座標映射到屏幕的座標上,這一過程就不須要轉換矩陣了。
在進入不一樣的座標空間以前,須要先了解 OpenGL 的座標系:
OpenGL 是一個右手座標系,正 X 軸在右手邊,正 Y 軸朝上,正 Z 軸穿過屏幕朝向你。
與之相對的就是左手座標系,其正 Z 軸穿過屏幕朝向裏面了。
局部空間座標是 OpenGL 繪製座標的起點,接下來全部的轉換操做都是在局部空間座標基礎上進行的。
局部空間座標就是咱們本身定義的起始座標點,是相對於原點 的。
此時所在的空間就是局部空間,也就是說咱們在局部空間裏面定義物體的起始座標。
咱們定義每個座標點都是在局部空間,相對於 的。這樣一來,當多個物體同時繪製時,就會扎堆了。
而世界空間就是當全部物體一塊兒繪製、仍然相對於原點的、更大的一個座標系。
局部空間和世界空間有點相像,能夠在局部空間定義座標系時就考慮到世界座標系,避免多個物體繪製時出現扎堆現象。
固然還有更好的方法,就是使用模型矩陣(Model Matrix)。
使用模型矩陣,能夠對物體進行位移、縮放、旋轉。
這樣的話就能夠將物體從座標原點移開,而且還可以進行一些相關操做,不用去考慮在局部空間來定義世界空間的座標了。
橫當作嶺側成峯 遠近高低各不一樣
當物體在世界空間中就位了,接下來就是要考慮從哪一個方向和角度來觀察物體了。
觀察空間,又是 OpenGL 的攝像機,是將世界空間的座標轉化爲攝像機的視角所觀察到的空間座標。
也就是說,在觀察空間裏,座標原點再也不是世界空間的座標原點了,而是以攝像機的視角做爲場景原點,這就再也不是簡單地進行平移、旋轉了,而是切換到另外一種座標系裏。
OpenGL 自己是沒有攝像機的概念的,不過能夠經過把場景中的全部物體往相反的方向移動來模擬出攝像機。這樣就場景沒動,而攝像機在移動。
要定義一個攝像機,或者說要定義一個攝像機視角爲座標原點的座標系,須要:
如圖,最終創建了一個以攝像機位置爲原點的座標系。
其中,藍色箭頭爲攝像機座標系中的 Z 軸,綠色箭頭爲攝像機座標系中的 Y 軸,紅色箭頭爲攝像機座標系中的 X 軸。
而接下來要作的就是將物體在世界空間中的座標轉換到以攝像機視角爲原點的觀察空間座標中。
這其中也須要用到一個轉換矩陣:視圖矩陣(View Matrix)。經過視圖矩陣來切換座標系。
當物體座標都位於觀察空間後,接下來要作的就是裁剪。根據咱們的須要來裁剪必定範圍內的物體,而在這個範圍以外的座標就會被忽略掉。
裁剪空間實質上仍是進行座標的操做。
從觀察空間到裁剪空間,須要用到:投影矩陣(Projection Matrix)。
投影矩陣會指定一個座標範圍,這個範圍內的座標將變換爲歸一化設備座標
,不在這個範圍內的座標就會被裁剪掉。
觀察空間中的座標通過投影矩陣的變換以後稱爲投影座標,又叫作裁剪座標
。
說是裁剪座標,實際上是待裁剪,接下來的裁剪過程將由 OpenGL 來完成的。投影矩陣的變換,只是篩選出那些不須要被裁剪的座標。
由投影矩陣建立的範圍,是一個封閉的空間幾何體,被稱爲視景體
。
投影矩陣有兩種不一樣的形式,建立的視景體也有兩種樣式。
正交投影會建立一個相似立方體的視景體。它由左、上、右、下 四個方向距離和近平面距離、遠平面距離組成。四個方向距離定義了近平面和遠平面的大小。而在近平面和遠平面以外的座標點就會被裁剪掉了。
在場景中處於視景體內的物體會被投影到近平面上,而後再將近平面上投影出的內容映射到屏幕上。
它所用到的矩陣是正交投影矩陣。
因爲正交投影是平行投影的一種,其投影線是平行的,因此投影到近平面上的圖形不會產生真實世界中的近大遠小
的效果。由於正交投影沒有把透視考慮進去,因此,遠處的物體不會變小,這適用於一些特定的場合。
透視投影是可以產生近大遠小
效果的,就像咱們人眼同樣,看遠處的物體就變得很小了。
它所用到的矩陣就是透視投影矩陣。
透視投影也會建立一個視景體,相似於錐形。它一樣也有着近平面距離和遠平面距離,並且也是將近平面的內容映射到屏幕視口中,但不一樣與正交投影近平面和遠平面大小相同,因此它的左、上、右、下距離都是相對於近平面的。
能夠看到,透視投影的投影線互不平行,都相交於視點。所以,一樣尺寸的物體,纔會近處的投影出來大,遠處的投影出來小。
當座標通過投影矩陣的變換到裁剪空間以後,緊接着就會進行透視除法
的操做。
透視除法是在三維繪製中產生近大遠小
效果很是關鍵重要的一步。
在此以前要先來了解一下 OpenGL 中的 w 份量
。
OpenGL 座標系中除了 x、y、z 座標外,還有 w 份量,默認狀況下都是 1 。而通過透視投影變換以後,w 份量再也不是 1 了,正交投影不改變 w 份量。
而 OpenGL 進行裁剪,實質上是 GPU 進行裁剪的過程,就是將 x、y、z 座標的絕對值與 w 份量絕對值進行比較,只要有一個份量的絕對值大於 w 的絕對值,就認爲不在視景體內,會被裁剪掉。
通過裁剪以後,再進行透視除法。就是將 x、y、z 座標分別除以 w 份量,獲得新的 x、y、z 座標。因爲 x、y、z 座標的絕對值都小於 w 的絕對值,因此獲得新的座標值都是位於 的區間內的。此時獲得的座標,也就是
歸一化設備座標
。
歸一化設備座標是獨立於屏幕的,並且它的座標系用的是左手座標系。
通過透視投影矩陣變換以後,每一個座標的 w 份量都不相同了,這樣再通過透視除法操做,就會使得遠處的物體看起來變小了。
有了歸一化設備座標,最後一步就是將座標投射到屏幕上,這一步是由 OpenGL 來完成的。
OpenGL 會使用 glViewPort
函數來將歸一化設備座標映射到屏幕座標,每一個座標都關聯了屏幕上的一個點,這個過程稱爲視口變換
。這一步操做再也不須要變換矩陣了。
就這樣,一個點的座標就完成了從局部空間座標 到屏幕座標
的轉變。
點的座標能夠看做是一個向量,用 表示,而矩陣用
表示。
那麼,從 局部空間 -> 世界空間 -> 觀察空間 -> 裁剪空間 ,四個空間的轉換,須要用到三個轉換矩陣,點從某個座標系變換到另外一個座標系的時候都要左乘某個變換矩陣,最後裁剪空間的座標能夠表示以下:
而在着色器腳本中,gl_Position
對應的也是 裁剪座標。
有了裁剪空間座標後,接下來的事情就交個 OpenGL 去完成裁剪和透視除法就行了。
在文章一開始提到的,繪製的圓形變成了橢圓,繪製的正多邊形卻東倒西歪的,如今也能給出緣由了。
默認狀況下,局部空間、世界空間、觀察空間、裁剪空間的座標系都是重合的,都是以爲座標原點。一開始只是給出了理想狀態下的平面座標點,而且定義着色器腳本以下:
attribute vec4 a_Position;
void main(){
gl_Position = a_Position;
}
複製代碼
那麼它通過一系列轉換後,最後 OpenGL 用來裁剪的座標仍是咱們定義的基於平面的座標,只有值,而
座標默認爲 0,
座標默認爲 1 。通過透視除法後的歸一化設備座標依舊是
。
而歸一化設備座標假定的座標空間是一個正方形,但手機屏幕的視口倒是一個長方形,這樣的話,就會有一個方向被拉伸。一樣的份數,但長度越長,致使每一份的長度也增長了,因此也就被拉伸了。
要解決這種問題,能夠在歸一化設備座標上進行操做,將較長的一邊乘以相應的比例係數,轉化到一樣的長度比上。
// 1280 * 720 的寬高比
aspect = width / height ;
x = x * aspect
y = y
複製代碼
這樣一來,將較長的一邊的比例放大了,取較短的那一邊做爲 1 的標準。
固然也能夠在座標轉換成歸一化設備座標以前,也就是在投影時就把拉伸的狀況考慮進去。
使用正交投影,再將物體的寬高投影到近平面上時,就把屏幕的寬高比例係數考慮進去,這樣在轉換成歸一化設備座標以前就已經完成了圖形的寬高比適應。
這樣的話,就須要修改着色器腳本語言,把投影矩陣考慮在內。
attribute vec4 a_Position;
uniform mat4 u_Matrix;
void main(){
gl_Position = u_Matrix * a_Position;
}
複製代碼
具體代碼詳情,能夠參考個人 Github 項目: https://github.com/glumes/AndroidOpenGLTutorial
最後,若是以爲文章不錯,歡迎關注微信公衆號:【紙上淺談】