android-opengles3.0開發【2】繪製圖形
簡介
android 下 opengles 的繪製圖形簡單來說步驟如下:
- 定義圖形頂點資料
- 編寫/編譯 頂點著色器 和 片段著色器。
- 建立程式,將著色器繫結到程式上,然後連線程式。如果著色器中沒有定義屬性的位置,則在繫結著色器之後、連線程式之前,將屬性名稱和位置進行繫結。
- 使用程式,將圖形頂點資料放到相應的屬性位置上,然後進行繪製。
定義圖形頂點資料
浮點型陣列,頂點的順序按逆時針排列。
android 平臺上,app 執行在 jvm 中,記憶體由 jvm 管理,而 opengles 執行在 native 環境,所以為了式 opengles 能夠訪問圖形頂點資料,需要把頂點拷貝到 native 記憶體中。
另外,為了方便操作 native 中的頂點位元組,將其對映到 FloatBuffer 中,然後就可以像使用陣列一樣使用了。
//頂點,按逆時針順序排列 private static final float[] vertices = { 0.0f, 0.5f, 0.0f, -0.5f, -0.5f, 0.0f, 0.5f, -0.5f, 0.0f}; public TriangleRender() { //將頂點資料拷貝對映到 native 記憶體中,以便opengl能夠訪問 verticesBuffer = ByteBuffer .allocateDirect(vertices.length * BYTES_PER_FLOAT)//直接分配 native 記憶體,不會被gc .order(ByteOrder.nativeOrder())//和本地平臺保持一致的位元組序(大/小頭) .asFloatBuffer();//將底層位元組對映到FloatBuffer例項,方便使用 verticesBuffer .put(vertices)//將頂點拷貝到 native 記憶體中 .position(0);//每次 put position 都會 + 1,需要在繪製前重置為0 }
編寫/編譯著色器
著色器的原始碼可以寫到單獨的檔案中,也可以用字串拼接(本文使用的方式,為了方便)
頂點著色器
layout (location = 0)
簡單來說,該著色器的內容就是:使用 opengles3.0 版本,將圖形頂點資料採用4分量向量的資料結構繫結到著色器的第 0 個屬性上,屬性的名字叫 vPosition,著色器執行時,將 vPosition 的值傳給用來表示頂點最終位置的內建變數 gl_Position。
需要注意的是layout (location = 0)
不是必須要寫,如果不寫的話,需要在後面著色器繫結到程式之後,程式連線之前使用glBindAttribLocation
方法繫結。
//頂點著色器 private static final String vertextShaderSource = "#version 300 es\n" + "layout (location = 0) in vec4 vPosition;\n" + "void main()\n" + "{\n" + "gl_Position = vPosition;\n" + "}\n";
片段著色器
- 第一行指定opengles版本,不指定預設為 opengles2.0
- 第二行指明浮點變數的精度
- 第三行宣告一個輸出變數 fragColor ,4分量向量
- 給輸出變數賦值
//片段著色器 private static final String fragmentShaderSource = "#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";
著色器內容編輯完之後,想要使用還要經過編譯。
/** * 載入著色器源,並編譯 * * @param type頂點著色器(GL_VERTEX_SHADER)/片段著色器(GL_FRAGMENT_SHADER) * @param shaderSource 著色器源(上面編輯的內容) * @return 著色器 */ private int loadShader(int type, String shaderSource) { //建立著色器物件 int shader = GLES30.glCreateShader(type); if (shader == 0) return 0;//建立失敗 //載入著色器源 GLES30.glShaderSource(shader, shaderSource); //編譯著色器 GLES30.glCompileShader(shader); //檢查編譯狀態 int[] compiled = new int[1]; GLES30.glGetShaderiv(shader, GLES30.GL_COMPILE_STATUS, compiled, 0); if (compiled[0] == 0) { Log.e(TAG, GLES30.glGetShaderInfoLog(shader)); GLES30.glDeleteShader(shader); return 0;//編譯失敗 } return shader; }
初始化程式
具體的流程下面程式碼很清楚,需要注意的就是如果著色器中沒有指定屬性的位置,則需要呼叫glBindAttribLocation
進行指定。
@Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { //獲取頂點著色器 int vertextShader = loadShader(GLES30.GL_VERTEX_SHADER, vertextShaderSource); //獲取片段著色器 int fragmentShader =loadShader(GLES30.GL_FRAGMENT_SHADER, fragmentShaderSource); //建立程式 int tmpProgram = GLES30.glCreateProgram(); if (tmpProgram == 0) return;//建立失敗 //繫結著色器到程式 GLES30.glAttachShader(tmpProgram, vertextShader); GLES30.glAttachShader(tmpProgram, fragmentShader); //繫結屬性位置 vPosition :0 著色器中沒有設定屬性位置時使用 //GLES30.glBindAttribLocation(tmpProgram, 0, "vPosition"); //連線程式 GLES30.glLinkProgram(tmpProgram); //檢查連線狀態 int[] linked = new int[1]; GLES30.glGetProgramiv(tmpProgram,GLES30.GL_LINK_STATUS, linked, 0); if (linked[0] == 0){ Log.e(TAG, "tmpProgram linked error"); Log.e(TAG, GLES30.glGetProgramInfoLog(tmpProgram)); GLES30.glDeleteProgram(tmpProgram); return;//連線失敗 } //儲存程式,後面使用 program = tmpProgram; //設定清除渲染時的顏色 GLES30.glClearColor(1.0f, 1.0f, 1.0f, 0.0f); }
使用程式繪製
@Override public void onDrawFrame(GL10 gl) { //擦除螢幕 GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT); //使用程式 GLES30.glUseProgram(program); //獲取 vPosition 屬性的位置 int vposition = GLES30.glGetAttribLocation(program, "vPosition"); //載入頂點資料到 vPosition 屬性位置 GLES30.glVertexAttribPointer(vposition,3,GLES30.GL_FLOAT,false,0,verticesBuffer); GLES30.glEnableVertexAttribArray(vposition); //繪製 GLES30.glDrawArrays(GLES30.GL_TRIANGLES,0,3); }
總結
本文通過繪製一個三角形,梳理了 opengles 繪製圖形的流程,著色器、程式的使用方法。