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

xiaxveliang發表於2021-11-24

OpenGL ES 構建的三維空間,其中的三維實體由許多的三角形拼接構成。如下圖左側所示的三維實體圓錐,其由許多三角形按照一定規律拼接構成。而組成圓錐的每一個三角形,其任意一個頂點由三維空間中 x、y、z 三個座標分量來定義。
左側為三維空間中的圓錐體,右側為圓錐在三維空間中的構成方式
對於我們日常使用的移動手持裝置,手機螢幕視窗由不連續的有限的二維畫素小格子構成的,每一個畫素格子有x、y兩個分量來定義。

因此在OpenGL ES繪製流程中,其主要工作是將三維空間中的座標點(x,y,z)構成的三維圖形,轉化為手機螢幕上的二維畫素點
這個轉化過程主要分為兩個步驟:

  • 一個是頂點的變換;
    三維空間中的實體,經過各種變換(主要是通過矩陣相乘進行座標系的變換),變為可投影在二維平面上的有限的畫素點資料;
  • 二是渲染著色;
    根據傳入的頂點顏色資料或圖片紋理資料,將相應的顏色對應到響應的畫素點。

在OpenGL ES中,其大概的繪製流程如下圖所示:
OpenGL ES中 三角形的大概繪製流程

這裡將三維空間中構成三維實體的座標資訊,轉換為手機螢幕上有限的二維畫素的過程,便是由OpenGL ES渲染管線(Graphics Pipeline)來實現。

  • OpenGL ES Shading Language;
  • OpenGL ES 3.x 渲染管線;

一、著色語言版本

正文開始之前,需要先了解一下 OpenGL ES Shading Language 著色語言。
《一文了解 OpenGL ES》中,我們瞭解到 OpenGL ES 3.x採用的是可程式設計渲染管線
OpenGL ES 3.x將頂點著色器片元著色器的編碼許可權開放給開發者。開發者需要使用OpenGL ES Shading Language(著色語言) 編碼實現頂點著色器片元著色器處理邏輯,從而渲染出自己想的展示效果。

對於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開始才有可程式設計渲染管線,OpenGL ES 2.0對應的OpenGL ES Shading Language版本為1.00
  • OpenGL ES 3.x使用可程式設計渲染管線,OpenGL ES 3.0對應的OpenGL ES Shading Language版本為3.00

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中的渲染管線(OpenGL Rendering Pipeline)指的是一系列的變換與渲染過程輸入是需要渲染的3D物體的相關描述資訊資料(例:頂點座標、頂點顏色、頂點紋理等),經過渲染管線,輸出一幀想要的影像
渲染管線也稱之為渲染流水線,由顯示晶片(GPU)內部處理圖形訊號的並行處理單元組成,這些並行處理單元兩兩之間相互獨立,根據顯示晶片效能的不同處理單元的數量也存在很大的差異。將渲染工作通過顯示晶片中多個相互獨立的單元進行並行處理後,渲染的效率可以大大提高。

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

OpenGL ES3.x渲染管線

2.1 基本處理

該階段設定三維空間中物體的頂點座標頂點對應的顏色頂點的紋理座標等資料,並且指定三維物體的繪製方式,如:點繪製、線段繪製或者三角形繪製等。

2.2 頂點緩衝區

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

2.3 頂點著色器

頂點著色器是一個可程式設計的處理單元,功能為執行頂點的變換光照材質的應用與計算頂點的相關操作每個頂點執行一次
其工作過程為將原始的頂點幾何資訊(頂點座標、顏色、紋理)及其他屬性傳送到頂點著色器中,經過自定義的頂點著色程式處理產生變化後的頂點位置資訊,將變化後的頂點位置資訊傳遞給後續圖元裝配階段,對應的頂點紋理、顏色等資訊則經光柵化後傳遞到片元著色器。

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

不同於OpenGL ES 2.0中,頂點著色器輸入為attribute(屬性變數),輸出為varying(易變變數)
在OpenGL ES3.0中,頂點著色器的輸入主要為待處理頂點相應的in(輸入屬性)uniform(統一變數)取樣器以及臨時變數,輸出主要為經過頂點著色器後生成的out(輸出屬性)及一些內建輸出變數

OpenGL ES 3.x 頂點著色器

  • uniform(統一變數)
    指的是對於同一組頂點組成的三維物體其所有頂點屬性都相同的屬性量,一般為場景中當前的光源位置當前的攝像機位置投影系列矩陣等,,可以使用 uniform (統一變數) 傳入頂點著色器。
  • in(輸入屬性)
    指的是三維空間中物體每個頂點各自不同的資訊所屬的變數,一般為頂點的位置顏色法向量等,每個頂點各自不同的資訊都是以attribute變數的方式傳入頂點著色器的。
  • out(輸出屬性)
    對於從頂點著色器傳遞到片元著色器的量,如 用於傳遞到片元著色器中的頂點顏色,可以使用out (輸出屬性)。
  • gl_Position (內建變數):
    內建變數為輸出到OpenGL ES渲染管線的資料變數
    頂點著色器從應用程式中獲得原始的頂點位置資料,這些原始的頂點資料在頂點著色器中經過平移、旋轉、縮放等數學變換後,生成新的頂點位置。新的頂點位置通過在頂點著色器中寫入gl_Position傳遞到渲染管線的後繼階段繼續處理。
  • gl_PointSize (內建變數):
    內建變數為輸出到OpenGL ES渲染管線的資料變數
    gl_PointSize的值一般只有在採用了點繪製方式之後才有意義。頂點著色器中可以計算一個點的大小(單位為畫素),並將其賦值給gl_PointSize(float型別)以傳遞給渲染管線,如果沒有明確賦值的話,預設值為1。

頂點著色器程式碼舉例

著色器採用OpenGL ES Shading Language編寫,為一種類C的程式語言,頂點著色器程式碼實現舉例如下:

詳細瞭解著色語言語法,可檢視khronos官方
OpenGL ES Shading Language 3.00:
https://www.khronos.org/registry/OpenGL/specs/es/3.0/GLSL_ES_Specification_3.00.pdf

// 頂點著色器程式舉例如下:
#version 300 es // 為著色語言版本v3.00,必須出現在第一行
// 總變換矩陣
uniform mat4 uMVPMatrix; 
// 頂點位置
in vec3 aPosition; 
// 頂點顏色
in vec4 aColor; 
// 用於傳遞給片元著色器的變數
out  vec4 vColor; 

void main()     
{
   // 根據總變換矩陣計算此次繪製此頂點位置
   gl_Position = uMVPMatrix * vec4(aPosition,1); 
   // 將接收的顏色傳遞給片元著色器
   vColor = aColor; 
}        
  • uniform(統一變數)
    如:頂點著色器舉例程式中的uniform mat4 uMVPMatrix; 總變換矩陣
  • in(輸入屬性)
    如:頂點著色器舉例程式中的頂點位置aPosition、頂點顏色aColor。
  • out(輸出屬性)
    如:頂點著色器舉例程式中的out vec4 vColor; 頂點顏色
  • 內建輸出變數
    如:如輸出到渲染管線的 gl_Position(頂點的最終位置)。

注:
輸出屬性(頂點顏色、紋理)在頂點著色器賦值後並不直接賦值到片元著色器中,而是經由光柵化階段處理。將三維世界由頂點資料構成的物體離散為二維世界一個個畫素點,片元著色器處理的資料是已經離散到二維顯示平面上的一個個離散的畫素點(每個畫素點為一個片元)。而這個離散過程中位於兩個頂點之間的畫素頂資料(顏色值、紋理資料),大多在光柵化階段差值計算產生

光柵化階段的差值計算

上圖中,展示的是一個自上而下黑到白的一個漸變三角形
在OpenGL ES的輸入中,僅僅輸入了三個頂點顏色值,頂點1 (0,0,0) 黑色、,頂點2 (1,1,1) 白色、頂點3 (1,1,1) 白色。而在三角形在光柵化階段,投影到螢幕上之前,其他部分的顏色是由這三個輸入頂點顏色值差值計算產生的,例如:三角形高二分之一處的點顏色差值計算的結果為 (0.5,0.5,0.5) 灰色。

2.4 變換反饋

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

2.5 圖元裝配

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

  • 圖元組裝是頂點資料被結合成完整的圖元。
    例如:一個單獨的點就是一個圖元,它只需要一個點;一條直線也是一個圖元,為兩個點構成的圖元;三角形則需要三個頂點構成一個圖元。
    圖元組裝時會有效的收集足夠的頂點以組成一個單獨的圖元,方便進行後續處理。
  • 圖元處理最重要的是剪裁,其任務是消除位於顯示區域之外的部分幾何圖元。
    圖元處理階段:
    如果圖元完全位於視椎體內,則傳遞圖元進行後面的處理;
    如果有一部分位於視椎體內,則剪裁該圖元(剪裁圖元可能會額外增加頂點資料)。
    如果其完全位於視椎體外,則丟棄該圖元,以節省GPU效能。

圖元裝配中的剪裁操作

2.6 光柵化

光柵化就是將三維空間中連續的數學圖形,柵格化為二維顯示平面上一個個畫素點的過程。

將由三維頂點資訊組成的幾何圖元柵格化為幀緩衝區中由無數畫素點構成的二維影像。這裡,最終生成的幀緩衝區中每一個畫素點都被看做一個片元
例如,一條直線可能在螢幕上包含了5個畫素,而光柵化就是將這條直線轉化為5個片元,每個片元由頂點座標、頂點顏色、頂點紋理座標以及頂點的深度等資訊組成。

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

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

三維物體投影到二維平面

  • 將投影到二維平面上的幾何圖元,柵格化為幀緩衝區中一個個畫素:

幾何圖元柵格化為幀緩衝區的一個個畫素點

2.7 片元著色器

片元著色器是用於處理片元相關資料的可程式設計單元,可以執行紋理的取樣顏色的彙總計算霧顏色等操作,每片元執行一次(每個畫素執行一次)
片元著色器可程式設計單元替換了OpenGL ES 1.x固定渲染管線階段中紋理顏色求和霧以及Alpha測試等階段,被其替代的功能將需要由開發人員用著色器語言編碼完成。

OpenGL ES 3.x 頂點著色器

頂點著色器每頂點執行一次片元著色器每片元[畫素]執行一次
一般情況下片元的數量遠遠大於構成三維物體頂點的數量(例如:一個三角形由三個頂點資料構成,光柵化到螢幕上時,卻有成百上千個畫素點構成),因此片元著色器的執行次數明顯大於頂點著色器的執行次數,開發中為提高執行效率,應儘量減小片元著色器的運算量,將一些複雜運算放在頂點著色器中執行

  • out(輸出屬性)
    頂點著色器傳遞到片元著色器的資料變數,如頂點著色器所介紹,由系統在頂點著色器後的光柵化階段自動插值產生。out屬性變數個數隨需求而定,並沒有硬性的變數數量上限限制。
  • out vec4 fragColor
    片元著色器用 fragColor 向OpenGL ES渲染管線寫入計算完成的片元顏色值,此顏色值將進入渲染管線的後繼階段繼續處理。
    不同於OpenGL ES2.0的最終片元顏色輸出到內建變數gl_FragColor ,OpenGL ES 3.0最終片元的顏色輸出到輸出屬性fragColor

片元著色器程式碼舉例
著色器採用OpenGL ES Shading Language編寫,為一種類C的程式語言,片元著色器程式碼實現舉例如下:

詳細瞭解著色語言語法,可檢視khronos官方
OpenGL ES Shading Language 3.00:
https://www.khronos.org/registry/OpenGL/specs/es/3.0/GLSL_ES_Specification_3.00.pdf

// 片元著色器舉例(每個片元[畫素]執行一次)
#version 300 es // 為著色語言版本v3.00,必須出現在第一行
precision mediump float;
// 接收從頂點著色器過來的引數(具體數值由光柵化階段差值產生)
out vec4 vColor; 
// 輸出到的片元顏色
out vec4 fragColor 

void main()                         
{
  //片元的最終賦值
   fragColor = vColor;
}
  • varying(易變變數)
    例,接收從頂點著色器過來的顏色引數 varying vec4 vColor
  • out vec4 fragColor
    不同於OpenGL ES2.0的最終片元顏色輸出到內建變數gl_FragColor ,OpenGL ES 3.0最終片元的顏色輸出到輸出屬性fragColor
    例,片元著色器具體程式中的 fragColor = vColor

2.8 剪裁測試

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

2.9 深度測試和模板測試

  • 深度測試
  • 模板測試

a、深度測試

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

// 在Android中開啟深度測試
GLES30.glEnable(GLES20.GL_DEPTH_TEST);

b、模板測試

模板測試的主要功能為將繪製區域限定在一定的範圍內,一般用在湖面倒影、映象等場合。
模板測試涉及到一個詞模板緩衝區(Stencil Buffer)。在模板緩衝中,通常每個模板值(Stencil Value)是8位的,所以每個片元一共能有256種不同的模板值。
使用時,我們可以先將模板緩衝區刷成個某個固定的值,然後進行影像繪製,完成繪製後模板緩衝區中的值可能會被汙染[汙染],此時開發者就可以選擇丟棄或是保留這些被汙染的片段,從而構造湖面倒影或者映象。

2.10 顏色緩衝混合

經過前面的幾個階段,已經產生了候選片元,而候選片元最終要進入幀緩衝成為畫素。
在候選片元進入幀緩衝區成為畫素的過程中:

  • 若未開啟了Alpha混合,則當前片元成為螢幕的畫素,覆蓋掉螢幕上的原有畫素;
  • 若開啟了混合,則根據Alpha值將當前畫素與候選片元進行顏色混合得出最終的顏色。

2.11 抖動

抖動是一種簡單的操作,是一種在色彩空間較小的裝置上展示較大色彩空間的影像的一種方法。
例如:在一個RGB_565的裝置上展示RGB_888的影像,展示時如果簡單進行資料截斷位,會造成色彩的失真和生硬。抖動使用一個矩陣,來調整一個畫素周圍的畫素的值,來使人眼產生錯覺,而模擬出原來的色彩。
這種技巧,對於只支援8位或者16位顏色資訊的顯示系統中非常有用。

// 開啟抖動
GLES30.glEnable(GLES20.GL_DITHER);

2.12 幀緩衝

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

三、參考

OpenGL ES Shading Language 3.00:
https://www.khronos.org/registry/OpenGL/specs/es/3.0/GLSL_ES_Specification_3.00.pdf

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

= THE END =

歡迎關注我的公眾號

相關文章