相信作技術的同窗,特別是作客戶端開發的同窗,都據說過OpenGL。要想對客戶端的渲染機制有一個深刻的瞭解,不對OpenGL瞭解一番恐怕是作不到的。並且,近年來客戶端開發中對於圖像和視頻處理的需求,成上升趨勢,要想勝任這些稍具「專業性」的工做,對於OpenGL的學習也是必不可少的。然而,OpenGL的學習曲線相對來講比較陡峭,尤爲是涉及到一些計算機圖形學方面的專業知識,難免會讓不少人望而生畏。git
要想熟練地掌握OpenGL,有兩方面相關的知識是須要重點關注的。程序員
本文所要探討的主題,將主要圍繞上述第二個方面的知識,也就是座標變換。這部分涉及到一點數學知識,顯得更難理解一些,而且網上的資料也散落在各處,不多有系統而詳盡的描述。嚴格來講,這部分理論知識並不徹底屬於OpenGL規範所規定的範圍,但卻與之有着很是密切的關係。接下來,就座標變換這個主題,我會寫一個小系列,由多篇技術文章組成,將座標變換相關的資料整理在一塊兒,並盡力用通俗易懂的語言表達出來,但願能爲學習OpenGL和圖像處理的同窗掃清理論上的障礙。github
本着理論聯繫實際的原則,咱們將結合Android系統上的API介紹相關的理論。之因此選擇Android環境,是由於上手簡單,大部分程序員都能很快地跑起一個Android程序,而且OpenGL相關的編程環境在Android上是現成的,幾乎不用太多的配置。在Android上,實際普遍使用的是OpenGL ES 2.0,它能夠當作是OpenGL對應版本的一個子集。咱們在接下來的討論中,也以OpenGL ES 2.0爲準。編程
另外,不少實際中的開發任務只涉及到2D圖像的處理,而不會涉及3D的處理。使用OpenGL ES作2D的圖像處理,確實處理流程會簡化一些,然而,我的認爲,搞清3D的渲染機制,對於理解整件事有相當重要的做用。理解了3D,便能理解2D,反之則不成立。並且,只有在3D的語境下,座標變換的概念才能被完整地理解。所以,咱們一開始便從3D開始,等介紹完3D空間中的座標變換以後,咱們再回到2D的特殊狀況加以討論。c#
不少OpenGL的入門文章,都以畫一個三角形開始。可是,對於討論座標變換這件事來講,畫一個三角形的例子並不太合適,由於三角形是一個平面圖形,對它應用了完整的座標變換以後,會獲得看似很奇怪的結果,反而讓初學者比較迷惑。因此,本篇給出的例子程序畫的是立方體(cube)。程序下載地址:學習
下面是程序輸出截圖:測試
沒錯,程序畫了三個立方體的木箱子,它們的位置、大小、角度各不相同。但實際上,上面的大木箱子和下面的小木箱子都是由中間的那個木箱子通過必定的座標變換(縮放、旋轉、平移)以後獲得的。而中間的木箱子所在的位置是原始的位置,即世界座標的原點處(世界座標的概念咱們立刻就會介紹)。spa
在本篇中,咱們先不過早地深刻到代碼細節,而是留到後面的文章再討論。接下來,咱們先把座標變換的整個過程作一個概覽。3d
咱們前面提到過,座標變換的目標,簡單來講,就是把一個3D空間中的對象最終投射到2D的屏幕上去(嚴格來講,OpenGL ES支持離屏渲染,因此最終未必是繪製到一個「可見」的屏幕上,不過在本文中咱們忽略這一細節)。這也正是計算機圖形學(computer graphics)所要解決的其中一個基礎問題。當咱們觀察3D世界的時候,是經過一塊2D的屏幕,咱們真正看到的實際是3D世界在屏幕上的一個投影。座標變換就是要解決在給定的觀察視角下,3D世界的每一個點最終對應到屏幕上的哪一個像素上去。固然,對於一個3D對象的座標變換,實際中是經過對它的每個頂點(vertex)來執行相同的變換獲得的。最終每一個頂點變換到2D屏幕上,再通過後面的光柵化(rasterization)的過程,整個3D對象就對應到了屏幕的像素上,咱們看到的效果就至關於透過一個2D屏幕「看到了」3D空間的物體(3D對象)。orm
下面的圖展現了整個座標變換的過程:
咱們先來簡略地瞭解一下圖中各個過程:
爲了更好地理解以上各個步驟,下面咱們來看幾張圖。
上面這張圖展現的是本地座標。3D對象是一個立方體,本地座標的原點(0, 0, 0)位於立方體的中心。紅色、綠色、藍色的座標軸分別表示x軸、y軸、z軸。
上面這張圖展現的是世界座標。能夠這樣認爲,最初,世界座標系和立方體的本地座標系是重合的,但立方體通過了某些縮放、旋轉和平移以後,兩個座標系再也不重合。圖中虛線表示的座標軸,就是原來的本地座標系。
上面這張圖展現的是相機座標。左下實線表示的座標軸便是相機座標系,右邊虛線表示的座標軸是世界座標系。相機座標系能夠當作是相機(或眼睛)看向3D空間中的某一點造成的一個觀察視角,以上圖爲例,相機觀察的方向正對着世界座標系的(0,2,0)這一點。相機座標系的原點正是相機(或眼睛)所在的位置。這裏須要注意的一點細節是,按照OpenGL ES的定義習慣,相機座標系的z軸方向與觀察方向正好相反。也就是說,相機(或眼睛)看向z軸的負方向。
咱們前面提到的view變換,指的就是在世界座標系中的各個頂點(vertex),通過這樣一個變換,就到了相機座標系下,也就是各個頂點的座標變成了以相機座標的值來表示了。
仔細觀察的話,咱們會發現,相機座標系實際上能夠當作是由世界座標系通過旋轉和平移操做獲得的。這在後面咱們還會詳細討論。
至此,咱們已經轉換到了相機座標系下了。接下來是很是關鍵的一步變換,要將3D座標(以相機座標表示)投射到2D屏幕上。如前所述,這個變換是經過投影變換(projection)獲得的。爲了使得投射到2D屏幕上的圖像看起來像是3D的,咱們須要讓這個變換知足人眼的一些直覺。根據實際經驗,咱們眼中看到的東西,離咱們越遠,顯得越小;反之,離咱們越近,顯得越大。就像咱們正對着一列鐵軌或一個走廊看過去的那種效果同樣,以下圖:
因此,投影變換也要保持這種效果。通過投影變換後,咱們就獲得了裁剪座標,在此基礎上再附加一個perspective division的過程,就變換到了NDC座標。像前面所講的同樣,perspective division的細節咱們先不追究,咱們暫且認爲相機座標通過了投影變換就獲得了NDC座標。這個投影的過程,是經過從相機出發構建一個視錐體(frustum)獲得的,以下圖所示:
上圖中,從相機所在位置(也就是相機座標系原點)沿着相機座標系的z軸負方向望出去,同時指定一個近平面(N)和遠平面(F),在兩個平面之間就截出一個視錐體。它由6個面組成,近平面(N)和遠平面(F)分別是先後兩個面,另外它還有上下左右四個面。其中,近平面(N)對應着最終要投影的2D屏幕。落在視錐體內部的頂點座標,最終將投影到2D屏幕上;而落在視錐體外部的頂點座標,則被裁剪掉。並且,落在視錐體內部的3D對象,它的位置越是靠近近平面,這個3D對象在近平面上的投影越大;相反越是遠離近平面,則投影越小。
以視錐體中的某點爲原點,創建一個座標系,就獲得了NDC座標,也就是上圖中位於右上部的實線紅、綠、藍座標軸。視錐體的6個面正好對應着NDC座標每一個維度的最大取值(-1和1)。
有兩個細節須要注意一下:
上面左圖是左手座標系,右圖是右手座標系。到底應該用左手座標系仍是右手座標系,是一種約定俗成的習慣,不一樣的圖形系統和規範極可能選擇不同的座標系類型。但按照OpenGL的習慣,咱們應該使用如前面所講的座標系類型。
OpenGL ES涉及到的主要的座標變換過程,咱們把大概的概況已經討論清楚了。在這個系列後面的文章中,咱們將逐步討論各個變換過程的細節,包括理論推導,以及在Android上如何用代碼來實現。
(完)
最後,借這個地發則招聘小廣告,方向是計算機圖形學、計算機視覺和AR,座標北京。不佔用更多篇幅介紹了,以避免影響無關的讀者,任何感興趣的同窗歡迎到公衆號後臺勾搭我^-^
其它精選文章: