天天看點

一文詳解 OpenGL ES 3.x 渲染管線

在OpenGL ES繪制流程中,其主要工作是将三維空間中的坐标點(x,y,z)構成的三維圖形,轉化為手機螢幕上的二維像素點。這個轉化過程主要分為兩個步驟:一個是頂點的變換;二是渲染着色;

OpenGL ES 建構的三維空間,其中的<code>三維</code>實體<code>由許多的三角形拼接構成</code>。如下圖左側所示的三維實體圓錐,其由許多三角形按照一定規律拼接構成。而組成圓錐的每一個三角形,其任意一個頂點由三維空間中 x、y、z 三個坐标分量來定義。

一文詳解 OpenGL ES 3.x 渲染管線

對于我們日常使用的移動手持裝置,手機螢幕視窗由<code>不連續的有限的二維像素小格子</code>構成的,每一個像素格子有x、y兩個分量來定義。

是以在OpenGL ES繪制流程中,其主要工作是<code>将三維空間中的坐标點(x,y,z)構成的三維圖形,轉化為手機螢幕上的二維像素點</code>。

這個轉化過程主要分為兩個步驟:

一個是頂點的變換;

三維空間中的實體,經過各種變換(主要是通過矩陣相乘進行坐标系的變換),變為可投影在二維平面上的有限的像素點資料;

二是渲染着色;

根據傳入的頂點顔色資料或圖檔紋理資料,将相應的顔色對應到響應的像素點。

在OpenGL ES中,其大概的繪制流程如下圖所示:

一文詳解 OpenGL ES 3.x 渲染管線

這裡<code>将三維空間中構成三維實體的坐标資訊,轉換為手機螢幕上有限的二維像素的過程</code>,便是由OpenGL ES渲染管線(Graphics Pipeline)來實作。

OpenGL ES Shading Language;

OpenGL ES 3.x 渲染管線;

正文開始之前,需要先了解一下 OpenGL ES Shading Language 着色語言。

在《一文了解 OpenGL ES》中,我們了解到 OpenGL ES 3.x采用的是<code>可程式設計渲染管線</code>。

OpenGL ES 3.x将<code>頂點着色器</code>、<code>片元着色器</code>的編碼權限開放給開發者。開發者需要使用<code>OpenGL ES Shading Language(着色語言)</code> 編碼實作<code>頂點着色器</code>、<code>片元着色器</code>處理邏輯,進而渲染出自己想的展示效果。

對于OpenGL ES Shading Language,我們有必要先了解一下OpenGL ES 與 OpenGL ES Shading Language 的對應關系(了解對應的API版本,才能編寫對應版本要求的Shading Language腳本)。

OpenGL ES Version

GLSL Version

1.0

--

1.1

2.0

100

3.0

300

3.1

310

3.2

320

OpenGL ES 1.x為固定渲染管線,無Shading Language對應版本;

OpenGL ES 2.x開始才有可程式設計渲染管線,<code>OpenGL ES 2.0</code>對應的<code>OpenGL ES Shading Language</code>版本為<code>1.00</code>

OpenGL ES 3.x使用可程式設計渲染管線,<code>OpenGL ES 3.0</code>對應的<code>OpenGL ES Shading Language</code>版本為<code>3.00</code>

Shading Language 3.00 文檔:

https://www.khronos.org/registry/OpenGL/specs/es/3.0/GLSL_ES_Specification_3.00.pdf

各OpenGL ES版本API文檔:

https://www.khronos.org/registry/OpenGL/specs/es/

OpenGL ES中的<code>渲染管線(OpenGL Rendering Pipeline)</code>指的是<code>一系列的變換與渲染過程</code>,<code>輸入</code>是需要渲染的<code>3D物體的相關描述資訊資料</code>(例:頂點坐标、頂點顔色、頂點紋理等),<code>經過渲染管線,輸出一幀想要的圖像</code>。

<code>渲染管線</code>也稱之為<code>渲染流水線</code>,由顯示晶片(GPU)内部處理圖形信号的<code>并行處理單元</code>組成,這些并行處理單元兩兩之間互相獨立,根據顯示晶片性能的不同處理單元的數量也存在很大的差異。将渲染工作通過顯示晶片中多個互相獨立的單元進行并行處理後,渲染的效率可以大大提高。

OpenGL ES 3.x詳細的渲染管線如下圖所示:

一文詳解 OpenGL ES 3.x 渲染管線

該階段設定三維空間中物體的<code>頂點坐标</code>、<code>頂點對應的顔色</code>、<code>頂點的紋理坐标</code>等資料,并且指定三維物體的<code>繪制方式</code>,如:點繪制、線段繪制或者三角形繪制等。

頂點緩沖區在應用程式中是可選的,對于某些在整個場景中頂點資料基本不變的情況,可以在初始化階段将頂點資料經基本處理後送入頂點緩沖區,在繪制每一幀想要的圖像時就省去了頂點資料IO的步驟,直接從頂點緩沖區中擷取頂點資料即可。相比于每次繪制時單獨将頂點資料送入GPU的方式,可以在一定程度上節省GPU的 IO 帶寬,提高渲染效率。

頂點着色器是一個<code>可程式設計的處理單元</code>,功能為執行<code>頂點的變換</code>、<code>光照</code>、<code>材質的應用與計算</code>等<code>頂點的相關操作</code>,<code>每個頂點執行一次</code>。

其工作過程為将原始的<code>頂點幾何資訊(頂點坐标、顔色、紋理)</code>及其他屬性傳送到頂點着色器中,經過自定義的頂點着色程式處理産生變化後的頂點位置資訊,将變化後的頂點位置資訊傳遞給後續圖元裝配階段,對應的頂點紋理、顔色等資訊則經光栅化後傳遞到片元着色器。

<code>頂點着色器</code>替代了原有<code>固定管線</code>(OpenGL 1.x采用固定渲染管線)的<code>頂點變換、光照計算</code>,采用 着色語言進行開發(OpenGL ES Shading Language) ,開發人員可以根據自己的需求采用着色語言自行開發頂點變換、光照等功能,大大增加了程式的靈活性。

不同于OpenGL ES 2.0中,頂點着色器輸入為<code>attribute(屬性變量)</code>,輸出為<code>varying(易變變量)</code>。

在OpenGL ES3.0中,頂點着色器的輸入主要為待處理頂點相應的<code>in(輸入屬性)</code>、<code>uniform(統一變量)</code>、<code>采樣器以及臨時變量</code>,輸出主要為經過頂點着色器後生成的<code>out(輸出屬性)</code>及一些<code>内建輸出變量</code>。

一文詳解 OpenGL ES 3.x 渲染管線

<code>uniform(統一變量)</code>:

指的是對于<code>同一組頂點組成的三維物體其所有頂點屬性都相同的屬性量</code>,一般為場景中目前的<code>光源位置</code>、<code>目前的錄影機位置</code>、<code>投影系列矩陣</code>等,,可以使用 uniform (統一變量) 傳入頂點着色器。

<code>in(輸入屬性)</code>

指的是三維空間中物體每個頂點各自不同的資訊所屬的變量,一般為<code>頂點的位置</code>、<code>顔色</code>、<code>法向量</code>等,每個頂點各自不同的資訊都是以attribute變量的方式傳入頂點着色器的。

<code>out(輸出屬性)</code>

對于<code>從頂點着色器傳遞到片元着色器的量</code>,如 用于傳遞到片元着色器中的頂點顔色,可以使用out (輸出屬性)。

gl_Position (内建變量):

内建變量為<code>輸出到OpenGL ES渲染管線的資料變量</code>。

頂點着色器從應用程式中獲得原始的頂點位置資料,這些原始的頂點資料在頂點着色器中經過平移、旋轉、縮放等數學變換後,生成新的頂點位置。新的頂點位置通過在頂點着色器中寫入gl_Position傳遞到渲染管線的後繼階段繼續處理。

gl_PointSize (内建變量):

gl_PointSize的值一般隻有在采用了點繪制方式之後才有意義。頂點着色器中可以計算一個點的大小(機關為像素),并将其指派給gl_PointSize(float類型)以傳遞給渲染管線,如果沒有明确指派的話,預設值為1。

頂點着色器代碼舉例

着色器采用OpenGL ES Shading Language編寫,為一種類C的程式設計語言,頂點着色器代碼實作舉例如下:

詳細了解着色語言文法,可檢視<code>khronos</code>官方:

OpenGL ES Shading Language 3.00:

<code>uniform(統一變量)</code>

如:頂點着色器舉例程式中的<code>uniform mat4 uMVPMatrix; 總變換矩陣</code>。

如:頂點着色器舉例程式中的頂點位置aPosition、頂點顔色aColor。

如:頂點着色器舉例程式中的<code>out vec4 vColor; 頂點顔色</code>。

<code>内建輸出變量</code>

如:如輸出到渲染管線的 gl_Position(頂點的最終位置)。

注:

輸出屬性(頂點顔色、紋理)在頂點着色器指派後并不直接指派到片元着色器中,而是經由光栅化階段處理。将三維世界由頂點資料構成的物體離散為二維世界一個個像素點,片元着色器處理的資料是<code>已經離散到二維顯示平面上的一個個離散的像素點(每個像素點為一個片元)</code>。而這個離散過程中位于兩個頂點之間的像素頂資料(顔色值、紋理資料),大多在<code>光栅化階段內插補點計算産生</code>。

一文詳解 OpenGL ES 3.x 渲染管線

上圖中,展示的是一個<code>自上而下</code>從<code>黑到白的一個漸變三角形</code>。

在OpenGL ES的輸入中,僅僅輸入了三個頂點顔色值,頂點1 (0,0,0) 黑色、,頂點2 (1,1,1) 白色、頂點3 (1,1,1) 白色。而在三角形在光栅化階段,投影到螢幕上之前,<code>其他部分的顔色是由這三個輸入頂點顔色值內插補點計算産生的</code>,例如:三角形高二分之一處的點顔色內插補點計算的結果為 (0.5,0.5,0.5) 灰色。

變化回報能夠将 “頂點着色器” 的計算結果輸出,也就是可以将大量的向量、矩陣等運算交給GPU來運作,在一定程度上節省CPU的運算量。變化回報的計算結果,可以輸出到一個緩沖區中,該緩沖區中的内容還可作為另一個 “頂點着色器” 程式的輸入資料。

在這個階段,主要有兩個任務,一個是<code>圖元組裝</code>,另一個是<code>圖元處理</code>。

<code>圖元組裝</code>是頂點資料被結合成完整的圖元。

例如:一個單獨的點就是一個圖元,它隻需要一個點;一條直線也是一個圖元,為兩個點構成的圖元;三角形則需要三個頂點構成一個圖元。

圖元組裝時會有效的收集足夠的頂點以組成一個單獨的圖元,友善進行後續處理。

<code>圖元處理</code>最重要的是<code>剪裁</code>,其任務是<code>消除位于顯示區域之外的部分</code>幾何圖元。

圖元處理階段:

如果圖元完全位于<code>視椎體</code>内,則傳遞圖元進行後面的處理;

如果有一部分位于<code>視椎體</code>内,則<code>剪裁</code>該圖元(<code>剪裁圖元可能會額外增加頂點</code>資料)。

如果其完全位于<code>視椎體</code>外,則丢棄該圖元,以節省GPU性能。

一文詳解 OpenGL ES 3.x 渲染管線

光栅化就是将<code>三維空間中連續的數學圖形</code>,栅格化為<code>二維顯示平面上一個個像素點</code>的過程。

将由<code>三維</code>頂點資訊組成的<code>幾何圖元</code>,<code>栅格化</code>為幀緩沖區中由<code>無數像素點</code>構成的<code>二維圖像</code>。這裡,最終生成的幀緩沖區中<code>每一個像素點</code>都被看做一個<code>片元</code>。

例如,一條直線可能在螢幕上包含了5個像素,而光栅化就是将這條直線轉化為5個片元,每個片元由頂點坐标、頂點顔色、頂點紋理坐标以及頂點的深度等資訊組成。

光栅化實際是由以下兩個過程組成:

将三維空間中的幾何圖元,投影到二維平面上:

一文詳解 OpenGL ES 3.x 渲染管線

将投影到二維平面上的幾何圖元,栅格化為幀緩沖區中一個個像素:

一文詳解 OpenGL ES 3.x 渲染管線

<code>片元着色器</code>是用于處理<code>片元相關資料的可程式設計單元</code>,可以執行<code>紋理的采樣</code>、<code>顔色的彙總</code>、<code>計算霧顔色</code>等操作,<code>每片元執行一次(每個像素執行一次)</code>。

<code>片元着色器</code>可程式設計單元替換了OpenGL ES 1.x固定渲染管線階段中<code>紋理顔色求和</code>、<code>霧以及Alpha測試</code>等階段,被其替代的功能将需要由開發人員用着色器語言編碼完成。

一文詳解 OpenGL ES 3.x 渲染管線

<code>頂點着色器每頂點執行一次</code>,<code>片元着色器每片元[像素]執行一次</code>。

一般情況下片元的數量遠遠大于構成三維物體頂點的數量(例如:一個三角形由三個頂點資料構成,光栅化到螢幕上時,卻有成百上千個像素點構成),是以片元着色器的執行次數明顯大于頂點着色器的執行次數,開發中為提高運作效率,應<code>盡量減小片元着色器的運算量</code>,将一些<code>複雜運算放在頂點着色器中執行</code>。

從<code>頂點着色器傳遞到片元着色器的資料變量</code>,如頂點着色器所介紹,由系統在頂點着色器後的光栅化階段自動插值産生。out屬性變量個數随需求而定,并沒有硬性的變量數量上限限制。

<code>out vec4 fragColor</code>

片元着色器用 fragColor 向OpenGL ES渲染管線寫入計算完成的片元顔色值,此顔色值将進入渲染管線的後繼階段繼續處理。

不同于OpenGL ES2.0的最終片元顔色輸出到<code>内建變量gl_FragColor</code> ,OpenGL ES 3.0最終片元的顔色輸出到<code>輸出屬性fragColor</code>。

片元着色器代碼舉例

着色器采用OpenGL ES Shading Language編寫,為一種類C的程式設計語言,片元着色器代碼實作舉例如下:

<code>varying(易變變量)</code>

例,接收從頂點着色器過來的顔色參數 <code>varying vec4 vColor</code>

例,片元着色器具體程式中的 <code>fragColor = vColor</code>。

如果程式中啟用了剪裁測試,OpenGL ES 會檢查每個片元在幀緩沖中對應的位置,若對應位置在剪裁視窗中則将此片元送入下一階段,否則丢棄此片元。。

深度測試

模闆測試

深度測試是在片元處理過程中通過測試一個片元在<code>視椎體</code>的深度坐标,來判斷它是否被更小深度坐标的片斷遮擋而決定是否真去繪制它(開啟深度測試有助于節省GPU性能);沒有深度測試,物體展現與否就會取決于繪制順序而不是擺放的坐标。

模闆測試的主要功能為将繪制區域限定在一定的範圍内,一般用在湖面倒影、鏡像等場合。

模闆測試涉及到一個詞<code>模闆緩沖區</code>(Stencil Buffer)。在模闆緩沖中,通常每個模闆值(Stencil Value)是8位的,是以每個片元一共能有256種不同的模闆值。

使用時,我們可以先将模闆緩沖區刷成個某個固定的值,然後進行圖像繪制,完成繪制後模闆緩沖區中的值可能會被污染[污染],此時開發者就可以選擇丢棄或是保留這些被污染的片段,進而構造湖面倒影或者鏡像。

經過前面的幾個階段,已經産生了候選片元,而候選片元最終要進入幀緩沖成為像素。

在候選片元進入幀緩沖區成為像素的過程中:

若未開啟了Alpha混合,則目前片元成為螢幕的像素,覆寫掉螢幕上的原有像素;

若開啟了混合,則根據Alpha值将目前像素與候選片元進行顔色混合得出最終的顔色。

抖動是一種簡單的操作,是一種在色彩空間較小的裝置上展示較大色彩空間的圖像的一種方法。

例如:在一個<code>RGB_565</code>的裝置上展示<code>RGB_888</code>的圖像,展示時如果簡單進行資料截斷位,會造成色彩的失真和生硬。抖動使用一個矩陣,來調整一個像素周圍的像素的值,來使人眼産生錯覺,而<code>模拟</code>出原來的色彩。

這種技巧,對于隻支援8位或者16位顔色資訊的顯示系統中非常有用。

OpenGL ES的物體繪制并不是直接回執在螢幕上的,而是預先在幀緩沖區中進行繪制,每一繪制完成一幀再将繪制的結果交換到螢幕上。是以,每次繪制新的一幀資料時都需要清理緩沖區中的相關資料,否則可能産生錯誤的顯示效果。

OpenGL ES 3.0 API:

https://www.khronos.org/registry/OpenGL-Refpages/es3.0/

gl_pipeline:

http://www.songho.ca/opengl/gl_pipeline.html

learnopengl:

https://learnopengl-cn.github.io/01%20Getting%20started/01%20OpenGL/

GLSL詳解:

https://zhuanlan.zhihu.com/p/349296191

OpenGL ES 變換回報:

https://www.shangmayuan.com/a/4a48e882e765411b8597e12c.html

一文詳解 OpenGL ES 3.x 渲染管線

繼續閱讀