OpenGL ES 構建的三維空間,其中的三維
實體由許多的三角形拼接構成
。如下圖左側所示的三維實體圓錐,其由許多三角形按照一定規律拼接構成。而組成圓錐的每一個三角形,其任意一個頂點由三維空間中 x、y、z 三個坐標分量來定義。
對於我們日常使用的移動手持設備,手機屏幕窗口由不連續的有限的二維像素小格子
構成的,每一個像素格子有x、y兩個分量來定義。
因此在OpenGL ES繪制流程中,其主要工作是將三維空間中的坐標點(x,y,z)構成的三維圖形,轉化為手機屏幕上的二維像素點
。
這個轉化過程主要分為兩個步驟:
- 一個是頂點的變換;
三維空間中的實體,經過各種變換(主要是通過矩陣相乘進行坐標系的變換),變為可投影在二維平面上的有限的像素點數據; - 二是渲染着色;
根據傳入的頂點顏色數據或圖片紋理數據,將相應的顏色對應到響應的像素點。
在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詳細的渲染管線如下圖所示:
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(輸出屬性)
及一些內建輸出變量
。
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測試
等階段,被其替代的功能將需要由開發人員用着色器語言編碼完成。
頂點着色器每頂點執行一次
,片元着色器每片元[像素]執行一次
。
一般情況下片元的數量遠遠大於構成三維物體頂點的數量(例如:一個三角形由三個頂點數據構成,光柵化到屏幕上時,卻有成百上千個像素點構成),因此片元着色器的執行次數明顯大於頂點着色器的執行次數,開發中為提高運行效率,應盡量減小片元着色器的運算量
,將一些復雜運算放在頂點着色器中執行
。
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