關(guān)于OpenGL ES Android的介紹這里略過
OpenGL ES世界的基本元素
- 著色器
- 坐標(biāo)系内贮。矩陣
- 紋理
...
本文主要涉及的部分是著色器的使用。
直接開始
創(chuàng)建GLSurfaceView
今天的目標(biāo)是做一個OpenGL ES學(xué)習(xí)的開端伊佃。就是畫一個簡單的三角形情龄。暫時不考慮坐標(biāo)系的矩陣變換和紋理等灭忠。只需要用頂點(diǎn)著色器簡單的來進(jìn)行描述简卧。
這一節(jié)需要使用和認(rèn)識的關(guān)鍵類是
GLSurfaceView
和GLSurfaceView.Render
一句話來描述就是,我們會在GLSurfaceView.Render
上進(jìn)行描繪族跛,在GLSurfaceView
中顯示出來闰挡。
判斷是否支持OpenGL Es2
/**
* 判斷是否支持es2.0
*
* @param context
* @return
*/
public static boolean isSupportEs2(Context context) {
//檢查是否支持2.0
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
if (activityManager != null) {
ConfigurationInfo deviceConfigurationInfo = activityManager.getDeviceConfigurationInfo();
int reqGlEsVersion = deviceConfigurationInfo.reqGlEsVersion;
return reqGlEsVersion >= GLES_VERSION_2 || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1
&& (Build.FINGERPRINT.startsWith("generic")
|| Build.FINGERPRINT.startsWith("unknown")
|| Build.MODEL.contains("google_sdk")
|| Build.MODEL.contains("Emulator")
|| Build.MODEL.contains("Android SDK build for x86")));
} else {
return false;
}
}
創(chuàng)建GLSurfaceView
接著創(chuàng)建一個GLSurfaceView
。并且設(shè)置其版本為2礁哄。同時設(shè)置我們自己的Render
//創(chuàng)建一個GLSurfaceView
glSurfaceView = new GLSurfaceView(this);
glSurfaceView.setEGLContextClientVersion(2);
//設(shè)置自己的Render.Render 內(nèi)進(jìn)行圖形的繪制
glSurfaceView.setRenderer(new TriangleShapeRender(this));
isRenderSet = true;
setContentView(glSurfaceView);
還需要在Activity對應(yīng)的生命周期內(nèi)长酗,來調(diào)用我們的GLSurfaceView
的方法
@Override
protected void onPause() {
super.onPause();
if (isRenderSet) {
glSurfaceView.onPause();
}
}
@Override
protected void onResume() {
super.onResume();
if (isRenderSet) {
glSurfaceView.onResume();
}
}
繪制三角形-Render的實(shí)現(xiàn)類
繪制的基礎(chǔ)知識
GLThread
因?yàn)锳ndroid中的GLSurfaceView
的操作,其實(shí)都是在GLThread
中進(jìn)行桐绒。所以生命周期方法的回調(diào)也都在GLThread
線程中夺脾。所有OpenGL的操作也都需要在該線程中。
基礎(chǔ)的生命周期方法
接下來轉(zhuǎn)到Render
的實(shí)現(xiàn)類里面來茉继。先關(guān)注需要實(shí)現(xiàn)的生命周期方法咧叭。
/**
* 注意:
* 因?yàn)槲覀兪鞘褂胓20來進(jìn)行編程,需要要注意導(dǎo)包
*
* 主要在Render類內(nèi)烁竭,完成對應(yīng)的繪制.
* 對應(yīng)的生命周期的回調(diào)
* -> onSurfaceCreated
* -> onSurfaceChanged
* -> onDrawFrame
*
* Created by a2957 on 2018/5/3.
*/
public class ViewGLRender implements GLSurfaceView.Renderer {
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
//0.簡單的給窗口填充一種顏色
GLES20.glClearColor(0.0f,0.0f,0.0f,0.0f);
//在創(chuàng)建的時候佳簸,去創(chuàng)建這些著色器
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
//在窗口改變的時候調(diào)用
GLES20.glViewport(0,0,width,height);
}
@Override
public void onDrawFrame(GL10 gl) {
//0.glClear()的唯一參數(shù)表示需要被清除的緩沖區(qū)。當(dāng)前可寫的顏色緩沖
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
}
}
如上面代碼所示颖变,對應(yīng)的生命周期方法,對應(yīng)了各個狀態(tài)
- onSurfaceCreated
GLSurfaceView
創(chuàng)建的時機(jī)听想。按照慣用思維腥刹,在這里進(jìn)行一些初始化的操作。 - onSurfaceChanged
GLSurfaceView
改變的時機(jī)汉买。如代碼所示衔峰,初始化GL的ViewPort
- onDrawFrame
這個生命周期方法會不斷的回調(diào)。不斷的繪制。
開始繪制三角形
著色器代碼的套路
我們需要熟悉編寫著色器代碼的套路垫卤。
之所以說是套路威彰,因?yàn)檫@些步驟都是類似的。
1. 編寫著色器的glsl
我們先簡單的寫一下頂點(diǎn)著色器和片元著色器的代碼穴肘。這些代碼具體是為什么這樣寫歇盼。我們在這里先不關(guān)注。我們先熟悉一下流程评抚。
在assets
中創(chuàng)建對應(yīng)的文件豹缀。代碼內(nèi)容如下
-
頂點(diǎn)著色器
//定義一個attribute aPosition ,類型為vec4慨代。4個方向的向量 attribute vec4 aPosition; void main() { //這里的gl_Position是OpenGL內(nèi)置的變量邢笙。 gl_Position = aPosition; }
簡單的描述一下,就是我們自己定義一個屬性
aPosition
來描述位置侍匙。(ps:像是廢話) -
片元著色器
//設(shè)置片元著色器的精度氮惯。這里值要兼容性能和效率。通常都是選擇mediump precision mediump float; //定義個常量 uColor uniform vec4 uColor; void main(){ //同樣想暗。這里的gl_FragColor是內(nèi)置的變量 gl_FragColor = uColor; }
廢話同上妇汗。
2. 在onSurfaceCreated
方法內(nèi)初始化
注意OpenGL的操作,都必須在
GLThread
中進(jìn)行江滨。生命周期方法的回調(diào)也都在這個線程中
-
編譯著色器代碼铛纬,得到代表著色的Id(類似于指針的感覺)
/** * 對ShaderCode進(jìn)行編譯 * * @param type shader的type * @param shaderCode 進(jìn)行編譯的Shader代碼 * @return shaderObjectId */ public static int compileShaderCode(int type, String shaderCode) { //得到一個著色器的ID。主要是對ID進(jìn)行操作 int shaderObjectId = GLES20.glCreateShader(type); //如果著色器的id不為0唬滑,則表示是可以用的 if (shaderObjectId != 0) { //0.上傳代碼 GLES20.glShaderSource(shaderObjectId, shaderCode); //1.編譯代碼.根據(jù)剛剛和代碼綁定的ShaderObjectId進(jìn)行編譯 GLES20.glCompileShader(shaderObjectId); //2.查詢編譯的狀態(tài) int[] status = new int[1]; //調(diào)用getShaderIv 告唆,傳入GL_COMPILE_STATUS進(jìn)行查詢 GLES20.glGetShaderiv(shaderObjectId, GLES20.GL_COMPILE_STATUS, status, 0); if (status[0] == 0) { //等于0。則表示失敗 //失敗的話晶密,需要釋放資源擒悬,就是刪除這個引用 GLES20.glDeleteShader(shaderObjectId); Log.w("OpenGL Utils", "compile failed!"); return 0; } } //最后都會去返回這個shader的引用id return shaderObjectId; }
這段基本的流程就是
根據(jù)著色器的類型,創(chuàng)建一個shaderObjectId=> 使用GLES20將我們的代碼和ID進(jìn)行綁定=> 編譯我們綁定的代碼=> 查詢編譯的狀態(tài)稻艰。如果失敗的話懂牧,就需要釋放資源。delete=> 成功返回我們綁定好編譯后代碼的shaderObjectId
-
創(chuàng)建
program
,將得到的id綁定上尊勿,并鏈接program
@Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { super.onSurfaceCreated(gl, config); //0.先從Asset中得到著色器的代碼 String vertexShaderCode = GLESUtils.readAssetShaderCode(context, VERTEX_SHADER_FILE); String fragmentShaderCode = GLESUtils.readAssetShaderCode(context, FRAGMENT_SHADER_FILE); //1.得到之后僧凤,進(jìn)行編譯。得到id int vertexShaderObjectId = GLESUtils.compileShaderCode(GLES20.GL_VERTEX_SHADER, vertexShaderCode); int fragmentShaderObjectId = GLESUtils.compileShaderCode(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode); //3.繼續(xù)套路元扔。取得到program mProgramObjectId = GLES20.glCreateProgram(); //將shaderId綁定到program當(dāng)中 GLES20.glAttachShader(mProgramObjectId, vertexShaderObjectId); GLES20.glAttachShader(mProgramObjectId, fragmentShaderObjectId); //4.最后躯保,啟動GL link program GLES20.glLinkProgram(mProgramObjectId); }
整體的流程就如上面代碼注釋的一樣。
創(chuàng)建program=> 將shaderId綁定到program當(dāng)中=> 最后澎语,啟動GL link program
這樣著色器的套路就基本確定下來了途事。
三角形的形狀
上面編寫的頂點(diǎn)著色器中验懊,我們定義了aPosition
的屬性。就相當(dāng)于我們將在OpenGL中定義了一個存儲的點(diǎn)尸变。接下來,我們就會將這個點(diǎn)來存儲我們定義的形狀信息义图。來顯示出形狀。
- 三角形的坐標(biāo)系
OpenGL中的坐標(biāo)系是從[-1召烂,1]碱工。
我們先用一組數(shù)組的坐標(biāo)系,來描述我們的三角形
//頂點(diǎn)的坐標(biāo)系
private static float TRIANGLE_COORDS[] = {
//Order of coordinates: X, Y, Z
0.5f, 0.5f, 0.0f, // top
-0.5f, -0.5f, 0.0f, // bottom left
0.5f, -0.5f, 0.0f // bottom right
};
我們還會定義一些常量骑晶,來幫助我們操作痛垛。常量就如注釋說明
//在數(shù)組中曼氛,一個頂點(diǎn)需要3個來描述其位置惑折,需要3個偏移量
private static final int COORDS_PER_VERTEX = 3;
private static final int COORDS_PER_COLOR = 0;
//在數(shù)組中宛琅,描述一個頂點(diǎn)彤钟,總共的頂點(diǎn)需要的偏移量煮仇。這里因?yàn)橹挥形恢庙旤c(diǎn)水醋,所以和上面的值一樣
private static final int TOTAL_COMPONENT_COUNT = COORDS_PER_VERTEX+COORDS_PER_COLOR;
//一個點(diǎn)需要的byte偏移量渐白。
private static final int STRIDE = TOTAL_COMPONENT_COUNT * Constant.BYTES_PER_FLOAT;
-
給三角形分配內(nèi)存空間
需要注意的是借杰,
調(diào)用GLES20的包的方法時碟婆,其實(shí)就是調(diào)用JNI的方法电抚。
所以分配本地的內(nèi)存塊,將java數(shù)據(jù)復(fù)制到本地內(nèi)存中竖共,而本地內(nèi)存可以不受垃圾回收的控制蝙叛。可以使用nio中的ByteBuffer來創(chuàng)建內(nèi)存區(qū)域公给。/* 1. 使用nio中的ByteBuffer來創(chuàng)建內(nèi)存區(qū)域借帘。 2. ByteOrder.nativeOrder()來保證,同一個平臺使用相同的順序 3. 然后可以通過put方法淌铐,將內(nèi)存復(fù)制過去肺然。 因?yàn)檫@里是Float,所以就使用floatBuffer */ mVertexFloatBuffer = ByteBuffer .allocateDirect(TRIANGLE_COORDS.length * Constant.BYTES_PER_FLOAT) .order(ByteOrder.nativeOrder()) .asFloatBuffer() .put(TRIANGLE_COORDS); //因?yàn)槭菑牡谝粋€點(diǎn)開始腿准,就表示三角形的际起,所以將position移動到0 mVertexFloatBuffer.position(0);
-
整體上
- 通過數(shù)組來描述三角形的坐標(biāo)系。因?yàn)槲覀儧]有考慮空間轉(zhuǎn)換吐葱,所以就不需要進(jìn)行矩陣變化街望,暫時就直接使用三角形在OpenGl中的坐標(biāo)系就可以。
- 給定義的數(shù)組弟跑,分配對應(yīng)的本地內(nèi)存的空間它匕。
ByteBuffer .allocateDirect
方法
在onDrawFrame
中調(diào)用繪制
@Override
public void onDrawFrame(GL10 gl) {
super.onDrawFrame(gl);
//0.先使用這個program?這一步應(yīng)該可以放到onCreate中進(jìn)行
GLES20.glUseProgram(mProgramObjectId);
//1.根據(jù)我們定義的取出定義的位置
int vPosition = GLES20.glGetAttribLocation(mProgramObjectId, A_POSITION);
//2.開始啟用我們的position
GLES20.glEnableVertexAttribArray(vPosition);
//3.將坐標(biāo)數(shù)據(jù)放入
GLES20.glVertexAttribPointer(
vPosition, //上面得到的id
COORDS_PER_VERTEX, //告訴他用幾個偏移量來描述一個頂點(diǎn)
GLES20.GL_FLOAT, false,
STRIDE, //一個頂點(diǎn)需要多少個字節(jié)的偏移量
mVertexFloatBuffer);
//取出顏色
int uColor = GLES20.glGetUniformLocation(mProgramObjectId, U_COLOR);
//開始繪制
//設(shè)置繪制三角形的顏色
GLES20.glUniform4fv(
uColor,
1,
TRIANGLE_COLOR,
0
);
//繪制三角形.
//draw arrays的幾種方式 GL_TRIANGLES三角形
//GL_TRIANGLE_STRIP三角形帶的方式(開始的3個點(diǎn)描述一個三角形,后面每多一個點(diǎn)窖认,多一個三角形)
//GL_TRIANGLE_FAN扇形(可以描述圓形)
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, VERTEX_COUNT);
//禁止頂點(diǎn)數(shù)組的句柄
GLES20.glDisableVertexAttribArray(vPosition);
}
-
整體上
- 先
use
我們之前得到的programObjectId
- 啟用我們定義的屬性和變量豫柬,并設(shè)置數(shù)據(jù)
- 用繪制的命令開始對應(yīng)的繪制
- 先
最后的效果
總結(jié)一下,我們從這第一章節(jié)的內(nèi)容了解到了下面這些使用的知識點(diǎn):
- 運(yùn)行在GLThread中
- 著色器編譯和使用的套路
- 使用數(shù)組的方式來描述圖形扑浸,使用本地內(nèi)存分配的方式來分配對應(yīng)的內(nèi)存烧给。
- 繪制圖形的過程中,啟用我們設(shè)置的屬性和變量喝噪,并且繪制的套路
未知道的:
坐標(biāo)矩陣的變化础嫡。和紋理等。
整體的代碼位置:https://github.com/deepsadness/OpenGLDemo5
系列文章地址
Android OpenGL ES(一)-開始描繪一個平面三角形
Android OpenGL ES(二)-正交投影
Android OpenGL ES(三)-平面圖形
Android OpenGL ES(四)-為平面圖添加濾鏡
Android OpenGL ES(五)-結(jié)合相機(jī)進(jìn)行預(yù)覽/錄制及添加濾鏡
Android OpenGL ES(六) - 將輸入源換成視頻
Android OpenGL ES(七) - 生成抖音照片電影