OpenGL ES 2.0 總體概述
好記性不如爛筆頭啊,還是記錄一下!
1. OpenGL ES 的兩個小伙伴
雖然,我們教程的標(biāo)題是OpenGL ES
,但是我們的內(nèi)容將不僅限于OpenGL ES
碎节。OpenGL ES
是負(fù)責(zé)GPU
工作的,目的是通過GPU
計算,得到一張圖片,這張圖片在內(nèi)存中其實就是一塊 buffer
,存儲有每個點的顏色信息等。而這張圖片最終是要顯示到屏幕上,所以還需要具體的窗口系統(tǒng)來操作,OpenGL ES
并沒有相關(guān)的函數(shù)蓝撇。所以,OpenGL ES
有一個好搭檔EGL
崇决。
1.1 EGL
全稱embedded Graphic Interface
,是OpenGL ES
和底層Native
平臺視窗系統(tǒng)之間的接口。所以大概流程是這樣的:
- 首先,通過
EGL
獲取到手機(jī)屏幕 的handle
,獲取到手機(jī)支持的配置(RGBA8888/RGB565 之類,表示每個像素中包 含的顏色等信息的存儲空間是多少位), - 然后根據(jù)這個配置創(chuàng)建一塊包含默認(rèn)
buffer
的surface(buffer的大小是根據(jù)屏幕分辨率乘以每個像素信息所占大小計 算而得)
和用于存放OpenGL ES
狀態(tài)集的context
,并將它們enable
起來颤枪。 - 然后,通過
OpenGL ES
操作GPU
進(jìn)行計算,將計算的結(jié)果保存在surface
的buffer
中汗捡。 - 最后,使用 EGL,將繪制的圖片顯示到手機(jī)屏幕上。
而在 OpenGL ES 操作 GPU 計算的時候,還需要介紹 OpenGL ES 的另外一個好搭檔 GLSL畏纲。
1.2 GLSL
全稱:OpenGL Shading Language
,是OpenGL ES
中使用到的著色器的語言,用這個語言可以編寫小程序運(yùn)行在GPU
上扇住。
在這里需要先提到CPU
和GPU
的區(qū)別,它們的功能都是用于計算,也都是由很多核組成,區(qū)別在于CPU
的核比較少,但是單個核的計算能力比較強(qiáng),而GPU
的核很多,但是每個核的計算能力都不算特別強(qiáng)。目前GPU
的主要工作是用于生成圖片(現(xiàn)在也有通過GPU
進(jìn)行高性能運(yùn)算_并行運(yùn)算,但是在這里不屬于討論的范圍),原因就是圖片是由很多像素組成,每個像素都包含有顏色盗胀、深度等信息,而為了得到這些信息數(shù)據(jù),針對每個像素點的計算,是可以通過統(tǒng)一的算法來完成艘蹋。GPU
就擅長處理針對這種大規(guī)模數(shù)據(jù),使用同一個算法進(jìn)行計算。而這個算法,就是使用GLSL
寫成Shader
,供GPU
運(yùn)算使用票灰。
在圖形學(xué)的視角中,所有的圖片都是由三角形構(gòu)成的女阀。所以通過OpenGL ES
繪制圖片的時候,我們需要通過OpenGL ES API
創(chuàng)建用于在GPU
上運(yùn)行的shader
, 然后將通過CPU
獲取到的圖片頂點信息,傳入GPU
中的Shader
中宅荤。在Vertex Shader
中通過矩陣變換,將頂點坐標(biāo)從模型坐標(biāo)系轉(zhuǎn)換到世界坐標(biāo)系,再到觀察坐標(biāo)系,到裁剪坐標(biāo)系,最后投影到屏幕坐標(biāo)系中,計算出在屏幕上各個頂點的坐標(biāo)。然后,通過光柵化,以插值的方法得到所有像素點的信息,并在Fragment shader
中計算出所有像素點的顏色浸策。最后,通過OpenGL ES
的API
設(shè)定的狀態(tài),將得到的像素信息進(jìn)行depth/stencil test
冯键、blend
,得到最終的圖片。
2. 屏幕圖片的本質(zhì)和產(chǎn)生過程
當(dāng)我們買一個手機(jī)的時候,我們會非常關(guān)注這個手機(jī)的分辨率庸汗。分辨率代表著像素的多少,比如我們熟知的iphone6
的分辨率為1334×750
,而iphone6 plus
的分辨率是1920×1080惫确。
手機(jī)屏幕上的圖片,是由一個一個的像素組成,那么可以計算出來,一個屏幕上的圖片,是由上百萬個像素點組成。而每個像素點都有自己的顏色,每種顏色都是由 RGB 三原色組成蚯舱。三原色按照不同的比例混合,組成了手機(jī)所能顯示出來的顏色改化。
每個像素的顏色信息都保存在 buffer 中,這塊 buffer 可以分給 RGB 每個通 道各 8bit 進(jìn)行信息保存,也可以分給 RGB 每個通道不同的空間進(jìn)行信息保存, 比如由于人眼對綠色最敏感,那么可以分配給 G 通道 6 位,R 和 B 通道各 5 位。這些都是常見的手機(jī)配置晓淀。假如使用 RGB888 的手機(jī)配置,也就是每種顏色的取值從 0 到 255,0 最小,255 最大所袁。那么紅綠藍(lán)都為 0 的時候,這個像素點的顏色就是黑色,紅綠藍(lán)都為 255 的時候,這個像素點的顏色就是白色。當(dāng)紅為 255, 綠藍(lán)都為 0 的時候,這個像素點的顏色就是紅色凶掰。當(dāng)紅綠為 255,藍(lán)為 0 的時候, 這個像素點的顏色就是黃色燥爷。當(dāng)然不是只取 0 或者 255,可以取 0-255 中間的值, 100,200,任意在 0 和 255 中間的值都沒有問題。那么我們可以算一下,按照紅綠藍(lán)不同比例進(jìn)行搭配,每個像素點,可以顯示的顏色有 255255255=16581375 種,這個數(shù)字是非撑尘剑恐怖,所以我們的手機(jī)可以顯示出來各種各樣的顏色前翎。 這里在延伸的科普一下,我們看到手機(jī)可以顯示那么多種顏色了,但是是不是說我們的手機(jī)在顏色上就已經(jīng)發(fā)展到極致了呢?其實是遠(yuǎn)遠(yuǎn)沒有的,在這個手機(jī)配置下,三原色中每一種的取值可以從 0 到 255,而在現(xiàn)實生活中,它們的取 值可以從 0 到 1 億,而我們?nèi)祟惖难劬λ芸吹降姆秶?從 0 到 10 萬。所以手機(jī)硬件還存在很大的提升空間畅涂。而在手機(jī)硬件提升之前,我們也可以通過 HDR 等技術(shù)盡量的在手機(jī)中多顯示一些顏色港华。所以,講到這里,我們知道了,手機(jī)屏幕上顯示的圖片,是由這上百萬個像素點,以及這上百萬個像素點對應(yīng)的顏色組成的。
用程序員的角度來看,就是手機(jī)屏幕對應(yīng)著一塊 buffer,這塊 buffer 對應(yīng)上百萬個像素點,每個像素點需要一定的空間來存儲其顏色午衰。如果使用更加形象的例子來比喻,手機(jī)屏幕對應(yīng)的 buffer 就好像一塊巨大的棋盤,棋盤上有上百萬個格子,每個格子都有自己的顏色,那么從遠(yuǎn)處整體的看這個棋盤,就是我們看手機(jī)的時候顯示的樣子立宜。這就是手機(jī)屏幕上圖片的本質(zhì)。
通過我們對 EGL臊岸、GLSL橙数、OpenGL ES 的理解,借助一張圖片,從專業(yè)的角度來解釋一下手機(jī)屏幕上的圖片是如何生成的。
首先,通過 EGL 獲取手機(jī)屏幕,進(jìn)而獲取到手機(jī)屏幕對應(yīng)的這個棋盤,同時, 在手機(jī)的 GPU 中根據(jù)手機(jī)的配置信息,生成另外一個的棋盤和一個本子,本子是用于記錄這個棋盤初始顏色等信息帅戒。
然后,OpenGL ES 就好像程序員的畫筆,程序員需要知道自己想畫什么東西,比如想畫一個蘋果,那么就需要通過為數(shù)不多的基本幾何圖元(如點灯帮、直線、三 角形)來創(chuàng)建所需要的模型逻住。比如用幾個三角形和點和線來近似的組成這個蘋果 (圖形學(xué)的根本就是點钟哥、線和三角形,所有的圖形,都可以由這些基本圖形組成, 比如正方形或者長方形,就可以由兩個三角形組成,圓形可以由無數(shù)個三角形組成,只是三角形的數(shù)量越多,圓形看上去越圓潤)。
根據(jù)這些幾何圖元,建立數(shù)學(xué)描述,比如每個三角形或者線的頂點坐標(biāo)位置瞎访、每個頂點的顏色腻贰。得到這些信息之后,可以先通過 OpenGL ES 將 EGL 生成的棋盤 (buffer)進(jìn)行顏色初始化,一般會被初始化為黑色。然后將剛才我們獲取到的頂點坐標(biāo)位置,通過矩陣變化的方式,進(jìn)行模型變換装诡、觀察變換银受、投影變換,最后映射到屏幕上,得到屏幕上的坐標(biāo)践盼。這個步驟可以在 CPU 中完成,也就是在 OpenGL ES 把坐標(biāo)信息傳給 Shader 之前,在 CPU 中通過矩陣相乘等方式進(jìn)行更新,或者是直接把坐標(biāo)信息通過 OpenGL ES 傳給 Shader,同時也把矩陣信息傳給 Shader,通過 Shader 在 GPU 端進(jìn)行坐標(biāo)更新,更新的算法通過 GLSL 寫在 Shader 中。這個進(jìn)行坐標(biāo)更新的 Shader 被稱為 vertex shader,簡稱 VS,是 OpenGL ES2.0, 也是 GLSL130 版本對應(yīng)的最重要兩個 shader 之一,作用是完成頂點操作階段中的所有操作宾巍。經(jīng)過矩陣變換后的像素坐標(biāo)信息,為屏幕坐標(biāo)系中的坐標(biāo)信息咕幻。在 VS 中,最重要的輸入為頂點坐標(biāo)、矩陣(還可以傳入頂點的顏色顶霞、法線肄程、紋理 坐標(biāo)等信息),而最重要的運(yùn)算結(jié)果,就是這個將要顯示在屏幕上的坐標(biāo)信息。 VS 會針對傳入的所有頂點進(jìn)行運(yùn)算,比如在 OpenGL ES 中只想繪制一個三角形 和一條線,這兩個圖元不共享頂點,那么在 VS 中,也就傳入了 5 個頂點信息, 根據(jù)矩陣變換,這 5 個頂點的坐標(biāo)轉(zhuǎn)換成了屏幕上的頂點坐標(biāo)信息,從圖上顯示, 也就是從左上角的圖一,更新成了中上圖的圖二选浑。
再然后,當(dāng)圖二生成之后,我們知道了圖元在屏幕上的頂點位置,而頂點的顏色在 VS 中沒有發(fā)生變化,所以圖元的頂點顏色我們也是知道的蓝厌。下面就是根據(jù) OpenGL ES 中設(shè)置的狀態(tài),表明哪些點連成線,哪些點組成三角形,進(jìn)行圖元裝配,也就是我們在右上角的圖三中看到的樣子。這個樣子在 GPU 中不會顯示, 那幾條線也是虛擬的線,是不會顯示在棋盤 buffer 中的,而 GPU 做的是光珊化,這一步是發(fā)生在從 VS 出來,進(jìn)入另外一個Shader (Pixel shader,也稱 fragment shader)之前,在 GPU 中進(jìn)行的古徒。作用是把線上,或者三角形內(nèi)部所有的像素點找到,并根據(jù)插值或者其他方式計算出其顏色等信息(如果不通過插值,可以使用其他的方法,這些在 OpenGL ES 和 GLSL 中都可以進(jìn)行設(shè)置)拓提。也就生成了下面一行的圖四和圖五。
我們大概可以看到在圖 4 和圖 5 種出現(xiàn)了大量的頂點,大概數(shù)一下估計有 40 個點左右,這些點全部都會進(jìn)入 PS 進(jìn)行操作,在 PS 中可以對這些點的顏色進(jìn)行操作,比如可以只顯示這些點的紅色通道,其他的綠藍(lán)通道的值設(shè)置為 0, 比如之前某個點的 RGB 為 200,100,100隧膘。在 PS 中可以將其通過計算,更新為 200,0,0代态。這樣做的結(jié)果就是所顯示的圖片均為紅色,只是深淺不同。這也就好像戴上了一層紅色的濾鏡,其他顏色均為濾掉了疹吃。所以用 PS 來做濾鏡是非常方便的蹦疑。再比如,假如一盞紅色的燈照到了蘋果上,那么顯示出來的顏色就是在蘋果原本的顏色基礎(chǔ)上,紅色值進(jìn)行一定的增值。
所以,總結(jié)一下,經(jīng)過 VS 和 PS 之后,程序員想要畫的東西,就已經(jīng)被畫出來了萨驶。想要繪制的東西,也就是左下角圖五的樣子歉摧。然后再根據(jù) OpenGL ES 的設(shè)置,對新繪制出來的東西進(jìn)行 Depth/Stencil Test,剔除掉被遮擋的部分,將剩余部分與原圖片進(jìn)行 Blend,生成新的圖片。 最后,通過 EGL,把這個生成的棋盤 buffer 和手機(jī)屏幕上對應(yīng)的棋盤 buffer 進(jìn)行調(diào)換,讓手機(jī)屏幕顯示這個新生成的棋盤,舊的那個棋盤再去繪制新的圖片信息腔呜。周而復(fù)始,不停的把棋盤進(jìn)行切換,也就像過去看連環(huán)畫一樣,動畫就是由一幅幅的圖片組成,當(dāng)每秒切換的圖片數(shù)量超過 30 張的時候,我們的手機(jī)也就看到了動態(tài)的效果叁温。這就是屏幕上圖片的產(chǎn)生過程。
在這里再進(jìn)行一下延伸,這個例子中,VS 計算了 5 個頂點的數(shù)據(jù),PS 計算 了大概 40 個頂點的數(shù)據(jù),而我們剛才說過,手機(jī)中存在上百萬個像素點,這上百萬個像素點都可以是頂點,那么這個計算量是非常大的核畴。而這也是為什么要將 shader 運(yùn)算放在 GPU 中的原因,因為 GPU 擅長進(jìn)行這種運(yùn)算券盅。
我們知道 CPU 現(xiàn)在一般都是雙核或者 4 核,多的也就是 8 核或者 16 核,但是 GPU 動輒就是 72 核,多的還有上千核,這么多核的目的就是進(jìn)行并行運(yùn)算, 雖然單個的 GPU 核不如 CPU 核,但是單個的 GPU 核足夠進(jìn)行加減乘除運(yùn)算,所以大量的 GPU 核用在圖形學(xué)像素點運(yùn)算上,是非常有效的。而 CPU 雖然單個很強(qiáng)大,而且也可以通過多級流水來提高吞吐率,但是終究還是不如 GPU 的多核來得快膛檀。但是在通過 GPU 進(jìn)行多核運(yùn)算的時候,需要注意的是:如果 shader 中存放判斷語句,就會對 GPU 造成比較大的負(fù)荷,不同 GPU 的實現(xiàn)方式不同,多數(shù) GPU 會對判斷語句的兩種情況都進(jìn)行運(yùn)算,然后根據(jù)判斷結(jié)果取其中一個。
我們通過這個例子再次清楚了 OpenGL ES 繪制的整個流程,而這個例子也是最簡單的一個例子,其中有很多 OpenGL ES 的其他操作沒有被涉及到娘侍。比如,我們繪制物體的顏色大多是從紋理中采樣出來,那么設(shè)計到通過 OpenGL ES 對紋理 進(jìn)行操作咖刃。而 OpenGL ES 的這些功能,我們會在下面一點一點進(jìn)行學(xué)習(xí)。
3. OpenGL ES pipeline
EGL 是用于與手機(jī)設(shè)備打交道,比如獲取繪制 buffer,將繪制 buffer 展現(xiàn)到手機(jī)屏幕中憾筏。那么拋開 EGL 不說,OpenGL ES 與 GLSL 的主要功能,就是往這塊 buffer 上繪制圖片嚎杨。
所以,我們可以把OpenGL ES和GLSL的流程單獨(dú)拿出來進(jìn)行歸納總結(jié),而這幅流程圖就是著名的 OpenGL ES2.0 pipeline。
首先,最左邊的 API 指的就是 OpenGL ES 的 API,OpenGL ES 其實是一個圖形學(xué)庫,由 109 個 API 組成,只要明白了這 109 個 API 的意義和用途,就掌握了OpenGL ES 2.0氧腰。
然后,我們通過 API 先設(shè)定了頂點的信息,頂點的坐標(biāo)枫浙、索引刨肃、顏色等信息,將這些信息傳入 VS。
在 VS 中進(jìn)行運(yùn)算,得到最終的頂點坐標(biāo)箩帚。再把算出來的頂點坐標(biāo)進(jìn)行圖元裝配,構(gòu)建成虛擬的線和三角形真友。再進(jìn)行光珊化(在光珊化的時候,把頂點連接起來形成直線,或者填充多邊形的時候,需要考慮直線和多邊形的直線寬度、點的大小紧帕、漸變算法以及是否使用支持抗鋸齒處理的覆蓋算法盔然。最終的每個像素點,都具有各自的顏色和深度值)。
將光珊化的結(jié)果傳入 PS,進(jìn)行最終的顏色計算是嗜。
然后,這所謂最終的結(jié)果在被實際存儲到繪制 buffer 之前,還需要進(jìn)行一系列的操作愈案。這些操作可能會修改甚至丟棄這些像素點。
這些操作主要為 alpha test鹅搪、Depth/Stencil test站绪、Blend、Dither丽柿。
Alpha Test 采用一種很霸道極端的機(jī)制,只要一個像素的 alpha 不滿足條件, 那么它就會被 fragment shader 舍棄,被舍棄的 fragments 不會對后面的各種 Tests 產(chǎn)生影響;否則,就會按正常方式繼續(xù)下面的檢驗恢准。Alpha Test 產(chǎn)生的效果也很極端,要么完全透明,即看不到,要么完全不透明。
Depth/stencil test 比較容易理解航厚。由于我們繪制的是 3D 圖形,那么坐標(biāo)為 XYZ,而 Z 一般就是深度值,OpenGL ES 可以對深度測試進(jìn)行設(shè)定,比如設(shè)定深度值大的被拋棄,那么假如繪制 buffer 上某個像素點的深度值為 0,而 PS 輸出的 像素點的深度值為 1,那么 PS 輸出的像素點就被拋棄了顷歌。而 stencil 測試更加簡單,其又被稱為蒙版測試,比如可以通過 OpenGL ES 設(shè)定不同 stencil 值的配拋棄, 那么假如繪制 buffer 上某個像素點的 stencil 值為 0,而 PS 輸出的像素點的 stencil 值為 1,那么 PS 輸出的像素點就被拋棄了。
既然說到了 Depth/stencil,那么就在這里說一下繪制 buffer 到底有多大,存 儲了多少信息幔睬。按照我們剛才的說法,手機(jī)可以支持一百萬個像素,那么生成的 繪制 buffer 就需要存儲這一百萬個像素所包含的信息,而每個像素包含的信息, 與手機(jī)配置有關(guān),假如手機(jī)支持 Depth/stencil眯漩。那么通過 EGL 獲取的繪制 buffer 中,每個像素點就包含了 RGBA 的顏色值,depth 值和 stencil 值,其中 RGBA 每個分量一般占據(jù) 8 位,也就是 8bit,也就是 1byte,而 depth 大多數(shù)占 24 位,stencil 占 8 位。所以每個像素占 64bit,也就是 8byte麻顶。那么 iphone6 plus 的繪制 buffer 的尺寸為 1920×1080×8=16588800byte=16200KB=15.8MB
赦抖。
下面還有 blend,通過 OpenGL ES 可以設(shè)置 blend 混合模式。由于繪制 buffer 中原本每個像素點已經(jīng)有顏色了,那么 PS 輸出的顏色與繪制 buffer 中的顏色如何混合,生成新的顏色存儲在繪制 buffer 中,就是通過 blend 來進(jìn)行設(shè)定辅肾。
最后的 dither,dither 是一種圖像處理技術(shù),是故意造成的噪音,用以隨機(jī)化量化誤差,阻止大幅度拉升圖像時,導(dǎo)致的像 banding(色帶)這樣的問題队萤。也 是通過OpenGL ES 可以開啟或者關(guān)閉。(Patrick:Dither還需要再開一個課題好好說)
經(jīng)過了這一系列的運(yùn)算和測試,也就得到了最終的像素點信息,將其存儲到繪制 buffer 上之后,OpenGL ES 的 pipeline 也就結(jié)束了矫钓。
整個pipeline中要尔,縱向按照流水線作業(yè),橫線按照獨(dú)立作業(yè)新娜,多級并行赵辕、提高渲染性能。
3. OpenGL ES API 總覽
剛才我們已經(jīng)說了,OpenGL ES 就是一個圖形學(xué)庫,由 109 個 API 組成概龄。之后的教程我們會對這 109 個 API 進(jìn)行詳細(xì)解釋,在這里我們先介紹一下這 109 個 API 大概是做什么的还惠。
首先,有 9 個 API,用于從手機(jī)創(chuàng)建繪制 buffer。雖然 EGL 會提供給 OpenGL ES 一塊繪制 buffer,這塊繪制 buffer 之后還會顯示在屏幕上,但是 OpenGL ES 也可以根據(jù)自定義的配置生成一塊或者多塊 buffer,并且可以先把一些內(nèi)容繪制到這些備用 buffer 上,之后根據(jù)一定的處理,最后再將正式的內(nèi)容繪制到真正的繪制 buffer 上,然后顯示到屏幕上私杜〔霞或者也可以直接把備用 buffer 中的內(nèi)容顯示到屏幕上救欧。一般復(fù)雜一些的圖形程序,都會使用到這些 API。
然后,有 12 個 API,用于溝通 GPU 可編程模塊 Shader锣光。根據(jù)我們上面所說的內(nèi)容,講到了 shader 的概念笆怠。Shader 是由 OpenGL ES 創(chuàng)建的,在 OpenGL ES 看來 shader 就類似于一個 handle;然后通過 OpenGL ES 給 shader 中寫入內(nèi)容; 觸發(fā) GPU 對 Shader 進(jìn)行編譯;編譯成功之后,通過 OpenGL ES 將一個 VS 和一個 PS 連接在一起,綁定在一個叫做 program 的另外一個 handle 上面;通過 OpenGL ES 啟用這個 program,也就相當(dāng)于啟用 program 綁定的兩個編譯好的 shader,供 GPU 調(diào)用。
之后,有 27 個 API,用于傳入繪制信息,比如通過 API 從 CPU 把頂點信息嫉晶、 矩陣骑疆、紋理圖片等傳入 GPU 中。傳入的方式有很多種,我們之后再一一講解替废。
再之后,有 30 個 API,用于設(shè)置繪制狀態(tài),OpenGL ES 又被人稱為狀態(tài)集, 因為它就是不停的在設(shè)置各種狀態(tài)供 GPU 等調(diào)用,比如設(shè)置是否進(jìn)行顏色混合以及如何混合,比如設(shè)置是否需要 depth箍铭、stencil 等 test,以及如何 test 等。
其次,有 2 個 API,用于執(zhí)行繪制命令,也就是執(zhí)行這個 API 的時候,將之 前傳入 GPU 的信息關(guān)聯(lián)在一起椎镣。在這個 API 中,可以設(shè)置使用哪些頂點進(jìn)行繪制,以及如何將這些頂點進(jìn)行圖元裝配等诈火。
還有 25 個 API,用于查詢環(huán)境、清理狀態(tài),由于 OpenGL ES 是狀態(tài)集,所以 基本上所有的狀態(tài)信息都可以查詢出來,即使我們并沒有進(jìn)行設(shè)置,這些狀態(tài)也會有默認(rèn)值,獲取這些狀態(tài)信息,可以更有利的幫助我們做出判斷状答。
最后還有 4 個 API,用于其他用處,等我們說到的時候再進(jìn)行一一介紹冷守。
本節(jié)教程就到此結(jié)束,希望大家繼續(xù)閱讀我之后的教程。
謝謝大家,再見!
飲水思源
版權(quán)聲明:文章轉(zhuǎn)自OPENGL ES 2.0 知識串講(1)――OPENGL ES 2.0 概括