本文目錄
一. 前言
二. OpenGL ES中的形狀
三. 渲染管線的流程
四. 讀取頂點(diǎn)數(shù)據(jù) Vertex Data
五. 頂點(diǎn)著色器 Vertex Shader
五. 組裝圖元 Shape Assembly
五. 光柵化 Rasterization
六. 片段著色器 Fragment Shader
七. 測(cè)試與混合 Tests and Blending
八. 幀緩存 / 渲染緩存
前言
在上一課的學(xué)習(xí)里面锉罐,我們簡(jiǎn)單的認(rèn)識(shí)了OpenGL ES2.0程序的基本寫法,必須要有GLSurfaceView
渲染表面和GLSurfaceView.Renderer
渲染器才可以實(shí)現(xiàn)寸士。同時(shí)我們也清楚知道OpenGLES繪畫的內(nèi)容主要在Rederer里面去編寫权悟,那么我們這次就學(xué)習(xí)實(shí)現(xiàn)二維的形狀——三角形锌订。在學(xué)習(xí)之前也要學(xué)習(xí)一些知識(shí)點(diǎn):管線渲染的流程、著色器編程GLSL急膀,它們的作用非常大沮协,所以一定要好好學(xué)。
OpenGL ES中的形狀
在OpenGL ES中只有點(diǎn)
卓嫂、直線
和三角形
慷暂,點(diǎn)和直線可以用于某些效果,但是晨雳,只有三角形才能用來構(gòu)建復(fù)雜的對(duì)象和紋理的場(chǎng)景行瑞。具體使用是將點(diǎn)放到一個(gè)組里構(gòu)建出三角形,再告訴OpenGL ES 如何連接這些點(diǎn)餐禁。如果想要構(gòu)建出更復(fù)雜的圖形血久,例如拱形,圓球等等帮非,那么我們就需要足夠的點(diǎn)擬合這樣的曲線氧吐。
渲染管線的流程
在 OpenGL ES 的 3D 空間中讹蘑,屏幕和窗口卻是 2D 像素?cái)?shù)組,這就導(dǎo)致 OpenGL ES 的大部分工作都是關(guān)于把 3D 坐標(biāo)轉(zhuǎn)變?yōu)檫m應(yīng)屏幕的 2D 像素筑舅。3D 坐標(biāo)轉(zhuǎn)為 2D 坐標(biāo)的處理過程是由 OpenGL ES 的圖形渲染管線
(Graphics Pipeline座慰,實(shí)際上指的是一堆原始圖形數(shù)據(jù)途經(jīng)一個(gè)輸送管道,期間經(jīng)過各種變化處理最終出現(xiàn)在屏幕的過程)管理的豁翎。圖形渲染管線可以被劃分為兩個(gè)主要部分:
- 第一部分是把
3D 坐標(biāo)轉(zhuǎn)換成 2D 坐標(biāo)
角骤; - 第二部分是將
2D 坐標(biāo)轉(zhuǎn)換成有顏色的像素
。
2D 坐標(biāo)和像素的區(qū)別:2D 坐標(biāo)精確表示一個(gè)點(diǎn)在 2D 空間中的位置心剥,而 2D 像素是這個(gè)點(diǎn)的近似值,2D 像素受到屏幕/窗口分辨率的限制背桐。
OpenGL ES 采用C/S編程模型优烧,客戶端運(yùn)行在 CPU 上,服務(wù)端運(yùn)行在 GPU 上链峭,調(diào)用 OpenGL ES 函數(shù)的時(shí)畦娄,由客戶端發(fā)送至服務(wù)器端,并被服務(wù)端轉(zhuǎn)換成底層圖形硬件支持的繪制命令弊仪。
讀取頂點(diǎn)數(shù)據(jù) Vertex Data
為了渲染一個(gè)三角形熙卡,我們以數(shù)組的形式傳遞3個(gè) 3D 坐標(biāo)作為圖形渲染管線的輸入,用來表示一個(gè)三角形励饵,這個(gè)數(shù)組叫做頂點(diǎn)數(shù)據(jù)
(Vertex Data)驳癌;
//設(shè)置三角形顏色和透明度(r,g,b,a),綠色不透明
float color[] = {0.0f, 1.0f, 0.5f, 1.0f};
//設(shè)置三角形頂點(diǎn)數(shù)組,默認(rèn)按逆時(shí)針方向繪制
static float triangleCoords[] = {
0.0f, 0.5f, 0.0f, // 頂點(diǎn)
-0.5f, -0.5f, 0.0f, // 左下角
0.5f, -0.5f, 0.0f // 右下角
};
頂點(diǎn)數(shù)據(jù)是一系列頂點(diǎn)的集合。一個(gè)頂點(diǎn)(Vertex)是一個(gè) 3D 坐標(biāo)的數(shù)據(jù)的集合役听。而頂點(diǎn)數(shù)據(jù)是用頂點(diǎn)屬性(Vertex Attribute)表示颓鲜,它可以包含任何我們想用的數(shù)據(jù),但是簡(jiǎn)單起見典予,我們假定每個(gè)頂點(diǎn)只由一個(gè) 3D 位置和一些顏色值組成甜滨。
public static FloatBuffer floatBuffer;
public static FloatBuffer fBuffer(float[] a) {
//先初始化buffer,數(shù)組的長(zhǎng)度*4,因?yàn)橐粋€(gè)float占4個(gè)字節(jié)
ByteBuffer mbb = ByteBuffer.allocateDirect(a.length * 4);
//數(shù)組排列用nativeOrder,設(shè)置字節(jié)順序?yàn)楸镜夭僮飨到y(tǒng)順序
mbb.order(ByteOrder.nativeOrder());
//轉(zhuǎn)換為浮點(diǎn)型緩沖
floatBuffer = mbb.asFloatBuffer();
//在緩沖區(qū)內(nèi)寫入數(shù)據(jù)
floatBuffer.put(a);
//設(shè)置緩沖區(qū)起始位置
floatBuffer.position(0);
return floatBuffer;
}
以上就是讀取頂點(diǎn)數(shù)據(jù)的代碼瘤袖,在 allocateDirect
方法分配了內(nèi)存并指定了大小之后衣摩,下一步就是 order
告訴 ByteBuffer 按照本地字節(jié)序組織它的內(nèi)容。本地字節(jié)序是指捂敌,當(dāng)一個(gè)值占用多個(gè)字節(jié)時(shí)艾扮,比如 32 位整型數(shù),字節(jié)按照從最重要位到最不重要位或者相反順序排列黍匾。接下來 asFloatBuffer
方法可以得到一個(gè)反映底層字節(jié)的 FloatBuffer 類實(shí)例栏渺,避免直接操作單獨(dú)的字節(jié),而是使用浮點(diǎn)數(shù)锐涯。最后磕诊,通過 put
方法就可以把數(shù)據(jù)從 Java 層內(nèi)存復(fù)制到緩沖區(qū),當(dāng)進(jìn)程結(jié)束時(shí),這塊內(nèi)存就會(huì)被釋放掉霎终。
頂點(diǎn)著色器Vertex Shader
著色器(Shader):是在GPU上運(yùn)行的小程序滞磺,此程序使用OpenGL ES SL語言來編寫,它是一個(gè)描述頂點(diǎn)或像素特性的簡(jiǎn)單程序莱褒。
在渲染管線中傳輸?shù)拿總€(gè)頂點(diǎn)坐標(biāo)位置击困,OpenGL ES都會(huì)調(diào)用頂點(diǎn)著色器
對(duì)每個(gè)頂點(diǎn)執(zhí)行一次運(yùn)算,它還可以使用頂點(diǎn)數(shù)據(jù)來計(jì)算該頂點(diǎn)的坐標(biāo)广凸、顏色阅茶、光照和紋理坐標(biāo)等。
在頂點(diǎn)著色器中最主要的任務(wù)是執(zhí)行頂點(diǎn)坐標(biāo)變換
谅海,我們?cè)O(shè)定的圖元坐標(biāo)是一種本地坐標(biāo)脸哀,但GL是無法識(shí)別出的,所以可以在頂點(diǎn)著色器中對(duì)本地坐標(biāo)執(zhí)行模型視圖變換扭吁,將本地坐標(biāo)轉(zhuǎn)化為裁剪坐標(biāo)系的坐標(biāo)值撞蜂。
頂點(diǎn)著色器的另一個(gè)功能是向后面的片段著色器提供一組易變變量
(varying)。易變變量會(huì)在圖元裝配階段(簡(jiǎn)單說侥袜,圖元裝配之后蝌诡,所有 3D 的圖元將被轉(zhuǎn)化為屏幕上 2D 的圖元。)之后被執(zhí)行插值計(jì)算枫吧,如果是單重采樣
浦旱,其插值點(diǎn)為片段的中心,如果多重采樣
由蘑,其插值點(diǎn)可能為多個(gè)采樣片段中的任意一個(gè)位置闽寡。易變變量可以用來保存插值計(jì)算片段的顏色,紋理坐標(biāo)等信息
尼酿。
想要定義一個(gè)著色器程序爷狈,還要通過一種特殊的語言去編寫:OpenGL Shading Language,簡(jiǎn)稱GLSL裳擎。GLSL語言類似于 C 語言或者 Java 語言涎永,它的程序入口也是一個(gè)名為 main 的函數(shù)。關(guān)于 GLSL 的部分鹿响,完全可以單獨(dú)寫一篇博客了羡微,暫時(shí)先不詳細(xì)闡述。
"attribute vec4 vPosition;" +
"void main() {" +
" gl_Position = vPosition;" +
"}";
首先我們定義了 a_Position
變量惶我,它是 vec4
類型的妈倔,而 attribute
只能存在于頂點(diǎn)著色器中,一般用于保存頂點(diǎn)數(shù)據(jù)绸贡,它可以在數(shù)據(jù)緩沖區(qū)中讀取數(shù)據(jù)盯蝴。然后毅哗,數(shù)據(jù)緩存區(qū)中的頂點(diǎn)坐標(biāo)會(huì)賦值給 a_Position ,a_Position 會(huì)傳遞給最終坐標(biāo) gl_Position
捧挺。
組裝圖元Shape Assembly
在頂點(diǎn)著色器程序輸出頂點(diǎn)坐標(biāo)之后虑绵,各個(gè)頂點(diǎn)被按照繪制命令中的圖元類型參數(shù),以及頂點(diǎn)索引數(shù)組被組裝成一個(gè)個(gè)圖元闽烙。通過組裝圖元翅睛,所有 3D 的圖元已經(jīng)被轉(zhuǎn)化為屏幕上 2D 的圖元。
光柵化 Rasterization
在光柵化階段黑竞,基本圖元被轉(zhuǎn)換為供片段著色器使用的片段捕发,片段是由很多小的像素組成,它們包含位置摊溶、顏色和紋理坐標(biāo)等信息爬骤,這些值是由圖元的頂點(diǎn)信息進(jìn)行插值計(jì)算得到的。隨后這些片段會(huì)送到片段著色器中處理莫换,也就是這個(gè)階段:從頂點(diǎn)數(shù)據(jù)到可渲染在顯示設(shè)備上的像素的質(zhì)變過程。
另外骤铃,在片段著色器運(yùn)行之前會(huì)執(zhí)行裁切
(Clipping)拉岁。裁切會(huì)丟棄超出你的視圖以外的所有像素,用來提升執(zhí)行效率惰爬。
片元在成為像素之前喊暖,還會(huì)做多種測(cè)試(比如深度測(cè)試、透明度測(cè)試撕瞧、模板測(cè)試等陵叽,這些測(cè)試目前接觸到的一般在3D圖像中更常使用,比如深度測(cè)試進(jìn)行物體的遮擋效果的渲染丛版,模板測(cè)試可以用于描邊等巩掺,2D中應(yīng)用較少)以決定其最終是否會(huì)被顯示為像素。所以页畦,嚴(yán)格來說胖替,“片元”和“像素”并不是一一對(duì)應(yīng)的。
片段著色器 Fragment Shader
片段著色器的主要作用是計(jì)算每一個(gè)片段最終的顏色值
(或者丟棄該片段)豫缨。
在片段著色器之前的階段独令,渲染管線都只是在和頂點(diǎn)、圖元打交道好芭。在 3D 圖形程序開發(fā)中燃箭,貼圖是最重要的部分,程序可以通過 GL 命令上傳紋理數(shù)據(jù)至 GL 內(nèi)存中舍败,這些紋理可以被片段著色器使用招狸。片段著色器可以根據(jù)頂點(diǎn)著色器輸出的頂點(diǎn)紋理坐標(biāo)對(duì)紋理進(jìn)行采樣敬拓,以計(jì)算該片段的顏色值。
另外瓢颅,片段著色器也是執(zhí)行光照等高級(jí)特效的地方恩尾,比如可以傳給片段著色器一個(gè)光源位置和光源顏色,可以根據(jù)一定的公式計(jì)算出一個(gè)新的顏色值挽懦,這樣就可以實(shí)現(xiàn)光照特效翰意。
"precision mediump float;" +
"uniform vec4 vColor;" +
"void main() {" +
" gl_FragColor = vColor;" +
"}";
第一行的 mediump
指的就是片段著色器的精度了,有三種可選信柿,這里用中等精度就行了冀偶。uniform
則表示該變量是不可變的了,也就是固定顏色了渔嚷,目前顯示固定顏色就好了进鸠。
gl_FragColor
變量就是 OpenGL 最終渲染出來的顏色的全局變量,而u_Color
就是我們定義的變量形病,通過在 Java 層綁定到 u_Color
變量并給它賦值客年,就會(huì)傳遞到 Native 層的 gl_FragColor
中。
測(cè)試與混合 Tests and Blending
像素所有權(quán)測(cè)試用來判斷幀緩沖區(qū)中該位置的像素是否屬于當(dāng)前 OpenGL ES漠吻,例如在窗口系統(tǒng)中該位置可能會(huì)被其他應(yīng)用程序窗口遮擋量瓜,此時(shí)該像素則不會(huì)被顯示。
在片段測(cè)試之后途乃,片段要么被丟棄绍傲,要么每個(gè)片段對(duì)應(yīng)的顏色,深度耍共,模板值會(huì)被寫入幀緩沖區(qū)烫饼,最終呈現(xiàn)在設(shè)備屏幕上。幀緩沖區(qū)中的顏色值也可以被讀回到客戶端應(yīng)用程序中试读,這樣可以實(shí)現(xiàn)繪制到紋理的效果杠纵。
至此,OpenGL ES 渲染管道最終將每個(gè)像素點(diǎn)的顏色鹏往,深度淡诗,模板等數(shù)據(jù)輸送到幀緩存中(Framebuffer)。
幀緩存 / 渲染緩存
總的來說伊履,幀緩存是接收渲染結(jié)果的緩沖區(qū)韩容,為GPU指定存儲(chǔ)渲染結(jié)果的區(qū)域。它存儲(chǔ)著 OpenGL ES 繪制每個(gè)像素點(diǎn)最終的所有信息:顏色唐瀑,深度和模板值群凶。更通俗點(diǎn),可以理解成存儲(chǔ)屏幕上最終顯示的一幀畫面的區(qū)域哄辣。
而渲染緩存則存儲(chǔ)呈現(xiàn)在屏幕上的渲染圖像请梢,它也被稱作顏色緩沖區(qū)赠尾,因?yàn)樗举|(zhì)上是存儲(chǔ)要顯示的顏色。多個(gè)紋理對(duì)象或多個(gè)渲染緩存對(duì)象毅弧,可通過連接點(diǎn)(attachment points)連接到幀緩存對(duì)象上气嫁。
可以同時(shí)存在很多幀緩存,并且可以通過 OpenGL ES 讓 GPU 把渲染結(jié)果存儲(chǔ)到任意數(shù)量的幀緩存中够坐。但是寸宵,只有將內(nèi)容繪制到視窗體提供的幀緩存中,才能將內(nèi)容輸出到顯示設(shè)備元咙。視圖系統(tǒng)提供的幀緩存通常由兩個(gè)緩存對(duì)象組成梯影,一個(gè)前端緩存,一個(gè)后端緩存庶香。
前幀緩存決定了屏幕上顯示的像素顏色甲棍。程序的渲染結(jié)果通常保存在后幀緩存在內(nèi)的其他幀緩存,當(dāng)渲染后的后幀緩存包含一個(gè)完成的圖像時(shí)赶掖,前后幀緩存會(huì)立即互換感猛,前幀緩存變成新的后幀緩存,后幀緩存變成新的前幀緩存奢赂。