這節(jié)要說(shuō)的是Opengles的渲染流程和程序流程,都是一些非惩希基礎(chǔ)的東西野舶,覺(jué)得已經(jīng)熟悉的同學(xué)可以自行忽略。
以下是一幅經(jīng)典的Opengl渲染管線流程圖
1.頂點(diǎn)數(shù)據(jù)
2.基本圖元
Opengles中包含三種圖元方式宰衙,點(diǎn)愕贡,線讽挟,三角形
3.頂點(diǎn)著色器
包含對(duì)頂點(diǎn)數(shù)據(jù)的處理和轉(zhuǎn)換
4.圖元裝配
把所有輸入的頂點(diǎn)數(shù)據(jù)作為輸入,將所有點(diǎn)配裝成指定圖元的形狀
5.幾何著色器
(Opengl特有) 把基本圖元形式的頂點(diǎn)的幾何作為輸入,可以通過(guò)產(chǎn)生新頂點(diǎn)構(gòu)造出新的基本圖元來(lái)生成其它形狀
6.細(xì)分著色器
(Opengl特有)可以把基本圖元細(xì)分為更多的基本圖形菌湃,創(chuàng)建出更加平滑的視覺(jué)效果
7.光柵化
像素化,圖形映射到屏幕上網(wǎng)絡(luò)的像素點(diǎn)逐抑,生成提供片段給片元著色器處理刚夺,光柵化包含一個(gè)剪裁處理,會(huì)計(jì)算舍棄超出定義視窗外的像素
8.片元著色器
為每一個(gè)像素點(diǎn)提供最終的顏色涛贯,這里會(huì)可以提供紋理題圖诽嘉,如果是3D場(chǎng)景其可以包含3D場(chǎng)景的一些額外數(shù)據(jù),例如光線弟翘,陰影
9.測(cè)試和混合
對(duì)每個(gè)像素點(diǎn)進(jìn)行深度測(cè)試虫腋,Alpha測(cè)試并進(jìn)行顏色混合操作,進(jìn)一步合成整個(gè)視窗每一個(gè)像素點(diǎn)最終要顯示的顏色和透明度
如果從API的角度來(lái)分析衅胀,你會(huì)發(fā)現(xiàn)有更多的操作岔乔。
0.頂點(diǎn)緩沖
在輸入頂點(diǎn)數(shù)據(jù)的時(shí)候需要線做頂點(diǎn)緩沖,這里可以使用頂點(diǎn)緩沖去對(duì)象(VBO)滚躯,頂點(diǎn)數(shù)組對(duì)象(VAO)雏门。VBO可以減少IO開(kāi)銷嘿歌,并使用數(shù)組記錄每一個(gè)快數(shù)據(jù)對(duì)應(yīng)綁定的類型(頂點(diǎn)位置、法向量)茁影,這些數(shù)據(jù)會(huì)存放在GPU上宙帝。VAO是使用一個(gè)數(shù)組來(lái)存每一個(gè)VBO儲(chǔ)存的數(shù)據(jù)、類型募闲,每次回執(zhí)時(shí)就不需要一個(gè)一個(gè)傳遞了步脓。
經(jīng)過(guò)片元著色之后,測(cè)試和混合也是分很多種
10.剪裁測(cè)試
每一個(gè)片元在幀緩沖中對(duì)應(yīng)的位置浩螺,若干對(duì)應(yīng)的位置裁剪窗口中則將此片元送入下一個(gè)階段靴患,否則會(huì)丟棄此片元,可以在屏幕上指定區(qū)域繪制要出,不在這片區(qū)域不進(jìn)行繪制
11.深度測(cè)試和模版測(cè)試
深度測(cè)試是用片元的深度值和幀緩沖中儲(chǔ)存的對(duì)應(yīng)位置的片元的深度值進(jìn)行比較鸳君,深度值小的片元會(huì)覆蓋或混合深度值大得片元。
模板測(cè)試 講回執(zhí)區(qū)域限定在一定的范圍內(nèi)患蹂,一般用于湖面倒影或颊,鏡像等場(chǎng)合
12.顏色緩沖和混合
如果程序開(kāi)啟了Alpha混合,則可以根據(jù)上一階段對(duì)應(yīng)的片元和幀緩沖的位置片元進(jìn)行alpha混合
13.抖動(dòng)
抖動(dòng)可以模擬更大的色深传于,需要自己編寫算法實(shí)現(xiàn)囱挑,通過(guò)GL_DITHER來(lái)控制
14.幀緩沖
opengles并不是直接在屏幕上進(jìn)行繪制,是預(yù)先在幀緩沖區(qū)進(jìn)行繪制沼溜,當(dāng)繪制完之后再下將繪制的結(jié)果交換到屏幕上平挑,因此每繪制新的一幀是都需要清除緩沖區(qū)的相關(guān)數(shù)據(jù),否則會(huì)產(chǎn)生不正確的繪制效果盛末。
這些都是基本的渲染流程弹惦,接下來(lái)說(shuō)一下程序流程,以Android程序?yàn)槔?br>
這個(gè)之前需要了解一下Android中屏幕顯示對(duì)Opengles的承載悄但,
SurfaceTexture棠隐,TextureView, SurfaceView和GLSurfaceView
值得注意的是Android直接內(nèi)置了Opengles,并內(nèi)置了GL10檐嚣,GL20助泽,GL30的類,封裝了Opengles的Android API嚎京,當(dāng)然其中也屏蔽了一些細(xì)節(jié)嗡贺,對(duì)于真正去理解opengles實(shí)現(xiàn)有一定的差距。
初學(xué)者很多會(huì)選用GLSurfaceView來(lái)做實(shí)現(xiàn)鞍帝,例如簡(jiǎn)單繪制圖形是沒(méi)問(wèn)題的诫睬。但是我們?nèi)绻钊胍稽c(diǎn)學(xué)習(xí),例如濾鏡帕涌,例如錄制播放摄凡,還是需要使用SurfaceView來(lái)做的续徽,因?yàn)镾urfaceView可以控制繪制的線程,需要自己定義EGL環(huán)境亲澡,還有SurfaceTexture綁定钦扭,同樣這也是初學(xué)者使用時(shí)的難點(diǎn)。
使用一個(gè)GLSurfaceView來(lái)顯示一個(gè)三角形為例床绪,這里就屏蔽了EGL客情、GLThread和SurfaceTexture使用的細(xì)節(jié),重點(diǎn)關(guān)注在Opengles中的實(shí)現(xiàn)癞己。
GLSurfaceView.Render提供三個(gè)回調(diào)接口
onSurfaceCreated 紋理窗口創(chuàng)建
onSurfaceChanged 視口大小更改
onDrawFrame 繪制
初始化的時(shí)候膀斋,準(zhǔn)備好頂點(diǎn)著色器和片元著色器內(nèi)容,這里面頂點(diǎn)做色器和片元著色器可以使用字符串讀取末秃,也可以使用glsl的shader文件來(lái)讀取概页。
//頂點(diǎn)著色器內(nèi)容
char vShaderStr[] =
"#version 300 es \n"
"layout(location = 0)in vec4 vPosition; \n"
"void main() \n"
"{ \n"
" gl_Position = vPosition; \n"
"} \n";
//片元著色器內(nèi)容
char fShaderStr[] =
"#version 300 es \n"
"precision mediump float; \n"
"out vec4 fragColor; \n"
"void main() \n"
"{ \n"
" fragColor = vec4(1.0,0.0,0.0,1.0); \n"
"} \n";
加載紋理
GLuint LoadShader(GLenum type,const char *shaderSrc){
GLuint shader;
GLint compiled;
//通過(guò)shader類型來(lái)獲取
shader = glCreateShader(type);
if(shader ==0){
return 0;
}
//加載shader資源
glShaderSource(shader,1,&shaderSrc,NULL);
//編譯shader資源
glCompileShader(shader);
//獲取shader信息
glGetShaderiv(shader,GL_COMPILE_STATUS,&compiled);
//編譯不成功籽御,打印日志
if(!compiled){
GLint infoLen = 0;
glGetShaderiv(shader,GL_INFO_LOG_LENGTH,&infoLen);
if(infoLen >1){
char *infoLog= (char*)malloc(sizeof(char*) *infoLen);
glGetShaderInfoLog(shader,infoLen,NULL,infoLog);
LOGE("Error compiling shader:[%s]",infoLog);
free(infoLog);
}
glDeleteShader(shader);
return 0;
}
//返回編譯成功的shader
return shader;
}
創(chuàng)建紋理空間->加載紋理資源->編譯紋理
加載紋理綁定到程序
GLuint vertexShader;
GLuint fragmentShader;
GLuint programObject;
GLint linked;
//加載頂點(diǎn)著色器
vertexShader = LoadShader(GL_VERTEX_SHADER, vShaderStr);
//加載片元著色器
fragmentShader = LoadShader(GL_FRAGMENT_SHADER, fShaderStr);
//創(chuàng)建程序
programObject = glCreateProgram();
if (programObject == 0) {
return;
}
//綁定頂點(diǎn)著色器
glAttachShader(programObject, vertexShader);
//綁定片元著色器
glAttachShader(programObject, fragmentShader);
//關(guān)聯(lián)到程序
glLinkProgram(programObject);
//獲取程序狀態(tài)
glGetProgramiv(programObject, GL_LINK_STATUS, &linked);
//如果沒(méi)有鏈接成功
if (!linked) {
GLint infoLen = 0;
//獲取錯(cuò)誤日志
glGetProgramiv(programObject, GL_INFO_LOG_LENGTH, &infoLen);
//錯(cuò)誤日志有內(nèi)容
if (infoLen > 1) {
char *infoLog = (char *) malloc(sizeof(char *) * infoLen);
//讀取對(duì)應(yīng)長(zhǎng)度內(nèi)容
glGetProgramInfoLog(programObject, infoLen, NULL, infoLog);
//打印
LOGE("Error compiling shader:[%s]", infoLog);
//釋放log
free(infoLog);
}
//關(guān)閉程序
glDeleteProgram(programObject);
return;
}
//轉(zhuǎn)為全局地址
g_programObject = programObject;
//清屏幕
glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
加載紋理->綁定程序
在GLsurfaceView繪制的時(shí)候练慕,調(diào)用繪制渲染
//頂點(diǎn)
GLfloat vVertices[] = {
0.0f, 0.5f, 0.0f,
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f
};
//清屏
glViewport(0, 0, g_width, g_height);
glClear(GL_COLOR_BUFFER_BIT);
//指定使用程序
glUseProgram(g_programObject);
//綁定頂點(diǎn)數(shù)據(jù)到shader
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vVertices);
//允許頂點(diǎn)著色器讀取GPU數(shù)據(jù)
glEnableVertexAttribArray(0);
//畫三角形
glDrawArrays(GL_TRIANGLES, 0, 3);
清屏->指定使用程序->傳入?yún)?shù)到著色器->允許GPU讀取->繪制圖形
日志打印
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
Opengles2.0與Opengles3.0區(qū)別
Opengles2.0對(duì)應(yīng)的是Android 2.2(API 8)
Opengles3.0對(duì)應(yīng)的是Android 4.3(API 18)
Opengles3.1對(duì)應(yīng)的是Android 5.0(API 21)
先將頂點(diǎn)著色器和片段著色器文件貼出來(lái)(這是用來(lái)渲染普通視頻用的),這是使用的OpenGLES3.0版本技掏。(存在兼容性問(wèn)題)铃将,下面只是一部分問(wèn)題,且這里就不將bug的log寫出來(lái)了哑梳,這是提示大家正確的寫法劲阎。
頂點(diǎn)著色器
#version 300 es
in vec4 aPosition;//頂點(diǎn)位置
in vec2 aTexCoord;//S T 紋理坐標(biāo)
out vec2 vTexCoord;
void main() {
vTexCoord = aTexCoord;
gl_Position = aPosition;
}
片段著色器
#version 300 es
#extension GL_OES_EGL_image_external_essl3 : require
precision mediump float;
in vec2 vTexCoord;
uniform samplerExternalOES sTexture;
out vec4 vFragColor;
void main() {
vFragColor=texture(sTexture, vTexCoord);
}
1.沒(méi)有在著色器文件中標(biāo)明使用版本的時(shí)候默認(rèn)使用2.0版本。
在上面的著色器文件中添加#version 300 es即表明使用3.0版本鸠真,如果不添加則使用默認(rèn)2.0版本(注意此行必須放在第一行)悯仙。同時(shí)注意使用3.0的api的時(shí)候必須添加此行。
2.3.0中attribute變成了in和out
OpenGL ES 3.0中將2.0的attribute改成了in吠卷,頂點(diǎn)著色器的varying改成out锡垄,片段著色器的varying改成了in,也就是說(shuō)頂點(diǎn)著色器的輸出就是片段著色器的輸入祭隔,另外uniform跟2.0用法一樣货岭。
3.3.0中使用內(nèi)置參數(shù)gl_FragColor會(huì)出問(wèn)題
這里我們只能自定義顏色向量out vec4 vFragColor;
4.3.0中將GL_OES_EGL_image_external變?yōu)榱薌L_OES_EGL_image_external_essl3
在使用紋理擴(kuò)展的時(shí)候,也就是uniform samplerExternalOES sTexture的時(shí)候疾渴。在3.0中我們使用GL_OES_EGL_image_external_essl3而不是GL_OES_EGL_image_external千贯。使用相機(jī)采集紋理的時(shí)候就知道了
5.3.0中將紋理的處理方法統(tǒng)一為texture
在2.0中2D紋理和3D紋理處理分別使用texture2D和texture3D方法,而在3.0后使用texture統(tǒng)一處理搞坝。
6.in或者out變量等不能在函數(shù)內(nèi)(如main函數(shù)內(nèi))聲明