寫在前面的話
<p>
上一篇文章對Android端的ColorMatrix進(jìn)行了講解庸诱,雖然說可以滿足我們做濾鏡的簡單需求,但是對于視頻和相機這些更多方面的濾鏡需求就不能夠滿足需求了鲤妥,所以為了濾鏡可以在更多場景使用,選擇用OpenGL來實現(xiàn)濾鏡效果贮勃,由于OpenGL是通過GPU來計算的,所以處理起來速度會更快苏章,也會避免由于圖像計算較復(fù)雜帶來的OOM問題寂嘉。
但是對于廣大的開發(fā)者來說,OpenGl是一個相對來說比較陌生的東西枫绅,可能大家都聽但是不是特別了解泉孩,所以這篇文章就對OpenGL進(jìn)行簡單的介紹,并實現(xiàn)圖片用OpenGL顯示出來并淋。
一.OpenGl基礎(chǔ)知識
<p>
- OpenGL 繪制的都是圖形棵譬,包括形狀和填充,基本形狀是三角形预伺。
- 每個形狀都有頂點,Vertix曼尊,頂點的序列就是一個圖形酬诀。
- Shader,著色器骆撇,用來描述如何繪制(渲染)瞒御,GLSL 是 OpenGL 的編程語言,全稱就叫 OpenGL Shader Language神郊。OpenGL 渲染需要兩種 shader肴裙,vertex 和 fragment。
- Vertex shader(頂點著色器)涌乳,控制頂點的繪制蜻懦,指定坐標(biāo)、變換等夕晓。
- Fragment shader(片段著色器)宛乃,控制形狀內(nèi)區(qū)域渲染,紋理填充內(nèi)容蒸辆。
OpenGl的坐標(biāo)系與Android的坐標(biāo)系不同征炼,是三維坐標(biāo)系,原點在中間躬贡,x 軸向右谆奥,y 軸向上,z 軸朝向我們拂玻,x y z 取值范圍都是 [-1, 1]
如圖所示
OpenGL 紋理坐標(biāo)系為二維坐標(biāo)系酸些,原點在左下角宰译,s(x)軸向右,t(y)軸向上擂仍,x y 取值范圍都是 [0, 1]:
如圖所示
其實通過介紹這些基礎(chǔ)知識囤屹,大家應(yīng)該可以對OpenGl基本的實現(xiàn)有點思路了。
注意:OpenGL ES 2.0需要Android2.2 (API Level 8) 及以上版本逢渔,所以請確保你的Android項目的運行目標(biāo)的API等級不低于8或更高肋坚。
二.OpenGl簡單構(gòu)造
<p>
與我們創(chuàng)建界面時候添加的View一樣,實現(xiàn)OpenGl則需要GLSurfaceView肃廓,這是讓我們渲染的"畫布"智厌。
1.構(gòu)造一個GLSurfaceView對象
事實上GLSurfaceView并沒有提供很多功能,實際上繪制對象的任務(wù)都在GLSurfaceView.Renderer中進(jìn)行盲赊。所以GLSurfaceView中代碼也非常少铣鹏,甚至可以直接使用GLSurfaceView。但最好別這樣做哀蘑,因為你需要擴(kuò)展這個類來響應(yīng)觸摸事件诚卸。
public class MyGLSurfaceView extends GLSurfaceView{
public MyGLSurfaceView(Context context) {
this(context,null);
}
public MyGLSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
}
}
2.構(gòu)造一個Renderer類
Renderer類負(fù)責(zé)控制在GLSurfaceView中繪制任務(wù),并提供三個回調(diào)方法供Android系統(tǒng)調(diào)用绘迁,用來計算在GLSurfaceView中繪制什么以及如何繪制合溺。
- onSurfaceCreated():僅調(diào)用一次,用于設(shè)置view的OpenGL ES環(huán)境缀台。
- onDrawFrame():每次重繪view時調(diào)用棠赛。
- onSurfaceChanged():當(dāng)view的幾何形狀發(fā)生變化時調(diào)用,比如設(shè)備從豎屏變?yōu)闄M屏膛腐。
public class MyRender implements GLSurfaceView.Renderer{
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// 設(shè)置背景色
GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES20.glViewport(0, 0, width, height);
}
@Override
public void onDrawFrame(GL10 gl) {
// 重繪背景色
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
}
}
這里只是創(chuàng)建了一個帶顏色的顯示睛约,并沒有做任何事情
3.GLSurfaceView與Renderer關(guān)聯(lián)
設(shè)置渲染對象,用于控制在GLSurfaceView中的繪制工作
setRenderer(new MyRenderer());
指定使用的OpenGl版本
setEGLContextClientVersion(2);
指定刷新方式(刷新方式有兩種哲身, RENDERMODE_WHEN_DIRTY 和 RENDERMODE_CONTINUOUSLY 辩涝,前者是懶惰渲染,需要手動調(diào)用 glSurfaceView.requestRender() 才會進(jìn)行更新勘天,而后者則是不停渲染膀值。)
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
4.Activity中顯示
public class OpenGLActivity extends Activity {
private GLSurfaceView mGLSurfaceView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mGLSurfaceView = new MyGLSurfaceView(this);
setContentView(mGLSurfaceView);
}
}
這里就是創(chuàng)建一個GLSurfaceView對象,并將其設(shè)置為當(dāng)前Activity的ContentView
運行如下
三.OpenGl實現(xiàn)Hello World
<p>
OpenGl的Hello World當(dāng)然不是我們認(rèn)知的Hello World误辑,而是實現(xiàn)一個最基礎(chǔ)的圖形顯示
通過上面的簡單構(gòu)造我們可以知道沧踏,渲染這部分其實是在Renderer里面去實現(xiàn)的,所以其實我們需要改動的地方就是Renderer這部分
接下來我們一步一步來實現(xiàn)顯示一個三角形圖像
1.變量
<p>
前面最開始提到的兩個著色器并沒有在上個部分有體現(xiàn)巾钉,而當(dāng)我們實現(xiàn)真正的圖像和圖形翘狱,時候則需要用頂點著色器對圖像定位,片段著色器對圖像進(jìn)行渲染
所以首先我們聲明兩個著色器程序
著色器程序則是用著色器語言來寫的砰苍,至于著色器語言的知識潦匈,這里我就不介紹了阱高,大家可以自行搜索,簡單的了解下就可以繼續(xù)后面的流程了
頂點著色器程序
private static final String VERTEX_SHADER = "attribute vec4 vPosition;\n"
+ "void main() {\n"
+ " gl_Position = vPosition;\n"
+ "}";
這里做的事情是將傳過來的坐標(biāo)作為頂點的位置
片段著色器程序
private static final String FRAGMENT_SHADER = "precision mediump float;\n"
+ "void main() {\n"
+ " gl_FragColor = vec4(0,0,1,1);\n"
+ "}";
這里做的事情是將紋理的顏色設(shè)置為藍(lán)色
頂點坐標(biāo)數(shù)組
private static final float[] VERTEX = {
0.0f, 1.0f, 0.0f, // top
-1.0f, -1.0f, 0.0f, // bottom left
1.0ff, -1.0f, 0.0f, // bottom right
};
FloatBuffer存儲頂點坐標(biāo)數(shù)組/片段坐標(biāo)數(shù)組
private final FloatBuffer mVertexBuffer;
這里我們只需要把頂點坐標(biāo)傳給著色器茬缩,所以只聲明了頂點所用的FloatBuffer赤惊,由于不能直接把上面的頂點坐標(biāo)數(shù)組直接傳給著色器,所以需要FloatBuffer來進(jìn)行傳遞
接下來初始化FloatBuffer凰锡,將數(shù)組傳遞給FloatBuffer
mVertexBuffer = ByteBuffer.allocateDirect(VERTEX.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(VERTEX);
mVertexBuffer.position(0);
定義需要傳遞的變量
前面的頂點著色器需要將外部的頂點坐標(biāo)傳入未舟,所以我們要先定義一個變量,并將這個變量與著色器語言中需要傳入的變量關(guān)聯(lián)起來
private int mPositionHandle;
定義GLSL程序
private int mProgram;
2.構(gòu)建GLSL程序
<p>
構(gòu)建步驟如下
- 創(chuàng)建 GLSL 程序:
- 加載 shader 代碼
- attatch shader 代碼
- 鏈接 shader 代碼
- 獲取 shader 代碼中的變量索引
和普通的 view 利用 canvas 來繪制不一樣掂为,OpenGL 需要加載 GLSL 程序裕膀,讓 GPU 進(jìn)行繪制。所以我們需要定義 shader 代碼勇哗,并在 onSurfaceChanged回調(diào)中加載:
@Override
public void onSurfaceChanged(GL10 unused, int width, int height) {
mProgram = GLES20.glCreateProgram();
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER);
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER);
GLES20.glAttachShader(mProgram, vertexShader);
GLES20.glAttachShader(mProgram, fragmentShader);
GLES20.glLinkProgram(mProgram);
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
}
//加載Shader代碼
static int loadShader(int type, String shaderCode) {
int shader = GLES20.glCreateShader(type);
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);
return shader;
}
就不對代碼進(jìn)行分析了昼扛,按照上面的步驟基本是對應(yīng)的
3.繪制
<p>
繪制則是在onDrawFrame回調(diào)中加載
@Override
public void onDrawFrame(GL10 unused) {
//清除指定的buffer到預(yù)設(shè)值∮担可清除以下四類buffer:
//1)GL_COLOR_BUFFER_BIT
//2)GL_DEPTH_BUFFER_BIT
//3)GL_ACCUM_BUFFER_BIT
//4)GL_STENCIL_BUFFER_BIT
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
//安裝一個program object抄谐,并把它作為當(dāng)前rendering state的一部分。
GLES20.glUseProgram(mProgram);
//Enable由索引index指定的通用頂點屬性數(shù)組扰法。
GLES20.glEnableVertexAttribArray(mPositionHandle);
//定義一個通用頂點屬性數(shù)組蛹含。當(dāng)渲染時,它指定了通用頂點屬性數(shù)組從索引index處開始的位置和數(shù)據(jù)格式
GLES20.glVertexAttribPointer(mPositionHandle, 3, GLES20.GL_FLOAT, false,
12, mVertexBuffer);
//三個成員變量mode,first,count
//1) mode:指明render原語迹恐,如:GL_POINTS, GL_LINE_STRIP, GL_LINE_LOOP, GL_LINES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, GL_TRIANGLES, GL_QUAD_STRIP, GL_QUADS, 和 GL_POLYGON。
//2) first: 指明Enable數(shù)組中起始索引卧斟。
//3) count: 指明被render的原語個數(shù)殴边。
//可以預(yù)先使用單獨的數(shù)據(jù)定義vertex、normal和color珍语,然后通過一個簡單的glDrawArrays構(gòu)造一系列原語锤岸。當(dāng)調(diào)用 glDrawArrays時,它使用每個enable的數(shù)組中的count個連續(xù)的元素板乙,來構(gòu)造一系列幾何原語是偷,從第first個元素開始
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);
//Disable由索引index指定的通用頂點屬性數(shù)組。
GLES20.glDisableVertexAttribArray(mPositionHandle);
}
注釋已經(jīng)寫的很詳細(xì)了募逞,就不詳細(xì)分析了
接下來運行如圖
四.OpenGl實現(xiàn)圖像顯示
<p>
經(jīng)過上面的講解大家應(yīng)該對OpenGl簡單使用有一定的了解了吧蛋铆,OK,接下來回到我們這個系統(tǒng)最初的目的放接,首先我們來實現(xiàn)用OpenGl展示圖片
其實將上面的代碼進(jìn)行簡單的修改就可以實現(xiàn)需要的效果
接下來一步一步實現(xiàn)
1.繪制矩形
<p>
這一步其實很簡單刺啦,前面的三角形是繪制了三個點,那么這里繪制矩形的話纠脾,我們繪制兩個三角形玛瘸,六個點蜕青,自然就實現(xiàn)了
代碼如下
private static final float[] VERTEX = {
-1.0f, -1.0f, 0.0f, // bottom left
1.0f, -1.0f, 0.0f, // bottom right
1.0f, 1.0f, 0.0f, // top right
-1.0f, -1.0f, 0.0f,
1.0f, 1.0f, 0.0f,
-1f, 1.0f, 0.0f,
};
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 6);
當(dāng)然除了這樣以為,我們注意一下glDrawArrays這個第一個參數(shù)糊渊,我們是使用的GLES20.GL_TRIANGLES右核,當(dāng)然除了這個參數(shù),還有另外兩個與TRIANGLES的參數(shù)渺绒,我們來看下區(qū)分
- GL_TRIANGLES:每三個頂之間繪制三角形贺喝,之間不連接
- GL_TRIANGLE_FAN:以V0V1V2,V0V2V3,V0V3V4,……的形式繪制三角形
- GL_TRIANGLE_STRIP:順序在每三個頂點之間均繪制三角形芒篷。這個方法可以保證從相同的方向上所有三角形均被繪制搜变。以V0V1V2,V1V2V3,V2V3V4……的形式繪制三角形
所以其實我們并不需要6個點那么多,四個點就足夠了
如下
private static final float[] VERTEX = {
-1.0f, -1.0f, 0.0f,
1.0f, -1.0f, 0.0f,
-1.0f, 1.0f, 0.0f,
1.0f, 1.0f, 0.0f,
};
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
運行如下
2.著色器程序修改
<p>
因為我們要顯示圖片针炉,自然之前的著色器程序是不能滿足需求的挠他,那么我們簡單分析一下,怎么才能把圖片渲染上去呢篡帕?首先圖片紋理要傳進(jìn)去殖侵,其次還得知道什么地方繪制
所以對上面的著色器程序進(jìn)行修改
如下:
private static final String VERTEX_SHADER = "attribute vec4 vPosition;" +
"attribute vec2 a_texCoord;" +
"varying vec2 v_texCoord;" +
"void main() {" +
" gl_Position = vPosition;" +
" v_texCoord = a_texCoord;" +
"}";
private static final String FRAGMENT_SHADER = "precision mediump float;" +
"varying vec2 v_texCoord;" +
"uniform sampler2D s_texture;" +
"void main() {" +
" gl_FragColor = texture2D( s_texture, v_texCoord );" +
"}";
我們可以看到增加的東西是varying vec2 v_texCoord與uniform sampler2D s_texture,第一是代表紋理坐標(biāo)點镰烧,第二個代表圖片紋理拢军,通過GLSL的內(nèi)建函數(shù)texture2D來獲取對應(yīng)位置紋理的顏色RGBA值
這里出現(xiàn)了更多的關(guān)鍵字, uniform 怔鳖, attribute 茉唉, varying ,包括上面的內(nèi)建函數(shù)结执,這里并不做講解度陆,有興趣的同學(xué)去搜索一下就可以了解了解
3.繪制圖片紋理
<p>
我們已經(jīng)對著色器程序修改,接下來自然就是去將需要的坐標(biāo)和紋理傳給著色器程序
紋理坐標(biāo)系文章最開始有提到献幔,所以我們先創(chuàng)建紋理坐標(biāo)數(shù)組懂傀,并初始化
(1)建紋理坐標(biāo)數(shù)組,并初始化
private static final float[] UV_TEX_VERTEX = { // in clockwise order:
0.0f, 1.0f,
1.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f,
};
FloatBuffer mUvTexVertexBuffer;
mUvTexVertexBuffer = ByteBuffer.allocateDirect(UV_TEX_VERTEX.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(UV_TEX_VERTEX);
mUvTexVertexBuffer.position(0);
接下來生成圖片紋理
(2)生成圖片紋理
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
...
int[] mTexNames = new int[1];
GLES20.glGenTextures(1, mTexNames, 0);
Bitmap bitmap = BitmapFactory.decodeResource(mResources, R.drawable.p_300px);
//選擇當(dāng)前活躍的紋理源
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
//將生成的紋理的名稱綁定到指定的紋理上
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTexNames[0]);
//設(shè)置紋理被縮欣小(距離視點很遠(yuǎn)時被縮械乓稀)時候的濾波方式
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,
GLES20.GL_LINEAR);
//設(shè)置紋理被放大(距離視點很近時被方法)時候的濾波方式
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,
GLES20.GL_LINEAR);
//設(shè)置在橫向、縱向上都是位于紋理邊緣或者靠近紋理邊緣的紋理單元將用于紋理計算郑兴,但不使用紋理邊框上的紋理單元犀斋。
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,
GLES20.GL_REPEAT);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,
GLES20.GL_REPEAT);
// 加載位圖生成紋理
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
bitmap.recycle();
...
}
大家要注意 GLES20.glActiveTexture(GLES20.GL_TEXTURE0)這個方法,這是生成紋理情连,在OpenGl里面有很多個這種GL_TEXTURE表示不同紋理
(3)獲取 shader 代碼中的新的變量索引
private int mTexCoordHandle;
private int mTexSamplerHandle;
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
...
mTexCoordHandle = GLES20.glGetAttribLocation(mProgram, "a_texCoord");
mTexSamplerHandle = GLES20.glGetUniformLocation(mProgram, "s_texture");
...
}
(4)繪制
@Override
public void onDrawFrame(GL10 gl) {
...
GLES20.glEnableVertexAttribArray(mTexCoordHandle);
GLES20.glVertexAttribPointer(mTexCoordHandle, 2, GLES20.GL_FLOAT, false, 0,
mUvTexVertexBuffer);
GLES20.glUniform1i(mTexSamplerHandle, 0);
GLES20.glDisableVertexAttribArray(mTexCoordHandle);
...
}
注意這里的glUniform1i方法闪水,這里的第二個參數(shù)和前面提到的glActiveTexture方法的后綴要一致
到這里基本就結(jié)束了,運行如下
寫在后面的話
<p>
通過這么一大串的內(nèi)容,我相信大家應(yīng)該都對OpenGl在安卓上面的實現(xiàn)有一定了解了吧球榆,關(guān)于GLSL語言呢朽肥,大家可以自行去了解一下,這會對后面的文章有一定幫助持钉,下一篇則是介紹如何用OpenGl實現(xiàn)攝像機預(yù)覽與音頻播放衡招,peace~~