編輯:關於Android編程
Github項目地址,歡迎star~!
OpenGL ES的使用,一般包括如下幾個步驟:
1. EGL Context初始化
2. OpenGL ES初始化
3. OpenGL ES設置選項與繪制
4. OpenGL ES資源釋放(可選)
5. EGL資源釋放
Android平台提供了一個GLSurfaceView,來幫助使用者完成第一步和第五步,由於釋放EGL資源時會自動釋放之前申請的OpenGL ES資源,所以需要我們自己做的就只有2和3。
首先,我們在主布局中引入一個GLSurfaceView,並讓他充滿整個布局,並在Activity中獲取他的實例
public class MainActivity extends AppCompatActivity { private GLSurfaceView glSurfaceView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); glSurfaceView= (GLSurfaceView) findViewById(R.id.surface_view); } }
獲取實例以後,我們就可以對於這個GLSurfaceView進行配置:
glSurfaceView.setEGLContextClientVersion(2); glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); glSurfaceView.setRenderer(new GLRenderer());
第一行是設置EGL上下文的客戶端版本,因為我們使用的是OpenGL ES 2.0,所以設置為2
第二行代表渲染模式,選項有兩種(大家應該能看懂英文的介紹),一個是需要渲染(觸控事件,渲染請求)才渲染,一個是不斷渲染。
/** * The renderer only renders * when the surface is created, or when {@link #requestRender} is called. * * @see #getRenderMode() * @see #setRenderMode(int) * @see #requestRender() */ public final static int RENDERMODE_WHEN_DIRTY = 0; /** * The renderer is called * continuously to re-render the scene. * * @see #getRenderMode() * @see #setRenderMode(int) */ public final static int RENDERMODE_CONTINUOUSLY = 1;
這個GLRenderer是個什麼鬼?其實這是我們自定義的一個類,實現了GLSurfaceView.Renderer這個接口,用來完成繪制操作。現在我們來看看這個類的定義:
public class GLRenderer implements GLSurfaceView.Renderer { @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { } @Override public void onSurfaceChanged(GL10 gl, int width, int height) { } @Override public void onDrawFrame(GL10 gl) { } }
可以看到,我們只是簡單的實現了GLSurfaceView.Renderer這個接口,還沒有寫任何操作,現在讓我們來看看程序運行的效果:
一片漆黑(白色邊是因為布局的Padding)。。現在我們來分別看一下這三個函數<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPnB1YmxpYyB2b2lkIG9uU3VyZmFjZUNyZWF0ZWQoR0wxMCBnbCwgRUdMQ29uZmlnIGNvbmZpZyk8YnIgLz4NCrTTw/vX1r/J0tS/tLP2o6zV4rj2uq/K/dTaU3VyZmFjZbG7tLS9qLXEyrG68rX308OjrMO/tM7O0sPHvavTptPDx9C7u7W9xuTL+7XYt72jrNTZx9C7u7vYwLS1xMqxuvK2vNPQv8nE3LG7tffTw6Os1NrV4rj2uq/K/dbQo6zO0sPH0OjSqs3qs8nSu9CpT3BlbkdMIEVTz+C52LHkwb+1xLP1yry7rzwvcD4NCjxwPnB1YmxpYyB2b2lkIG9uU3VyZmFjZUNoYW5nZWQoR0wxMCBnbCwgaW50IHdpZHRoLCBpbnQgaGVpZ2h0KTxiciAvPg0Kw7+1scbBxLuz37Tnt6LJ+rHku6/KsaOs1eK49rqvyv274bG7tffTw6OosPzAqLjVtPK/qsqx0tS8sLrhxsGhosr6xsHH0Lu7o6mjrHdpZHRous1oZWlnaHS+zcrHu+bWxsf40/K1xL/tus2436Ooyc/NvLrayavH+NPyo6k8L3A+DQo8cD5wdWJsaWMgdm9pZCBvbkRyYXdGcmFtZShHTDEwIGdsKTxiciAvPg0K1eK49srH1vfSqrXEuq/K/aOsztLDx7XEu+bWxrK/t9a+zdTa1eLA76Osw7/Su7TOu+bWxsqx1eK49rqvyv22vLvhsbu199PDo6zWrsewyejWw8HLR0xTdXJmYWNlVmlldy5SRU5ERVJNT0RFX0NPTlRJTlVPVVNMWaOs0rK+zcrHy7WwtNXV1f2zo7XEy9m2yKOsw7/D69XiuPa6r8r9u+Gxu7X308M2MLTOo6zL5Mi7ztLDx7u5yrLDtLa8w7vX9jwvcD4NCjxwPs6qwcu9+NDQz8LSu7K9uaTX96Os1NrO0sPHtLS9qNXiuPbA4Mqxo6y0q8jr0ru49snPz8LOxLKisaO05sbwwLQ8L3A+DQo8cHJlIGNsYXNzPQ=="brush:java;"> private Context context; public GLRenderer(Context context) { this.context = context; }
glSurfaceView.setRenderer(new GLRenderer(this));
好了,現在我們開始繪制操作,先創建一個工具類ShaderUtils,因為這裡介紹的功能我們會多次用到。
其中的一個函數用來讀取raw中的文本文件,並且以String的形式返回,這個其實和OpenGL ES的關系並不大,代碼如下:
public static String readRawTextFile(Context context, int resId) { InputStream inputStream = context.getResources().openRawResource(resId); try { BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); StringBuilder sb = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { sb.append(line).append("\n"); } reader.close(); return sb.toString(); } catch (IOException e) { e.printStackTrace(); } return null; }
我們再在raw文件夾中創建兩個文件,fragment_shader.glsl和vertex_shader.glsl,他們分別是片元著色器和頂點著色器的腳本,之前說的可編程管線,就是指OpenGL ES 2.0可以即時編譯這些腳本,來實現豐富的功能,兩個文件的內容如下:
vertex_shader.glsl
attribute vec4 aPosition; void main() { gl_Position = aPosition; }
vec4是一個包含4個浮點數(float,我們約定,在OpenGL中提到的浮點數都是指float類型)的向量,attribute表示變元,用來在Java程序和OpenGL間傳遞經常變化的數據,gl_Position 是OpenGL ES的內建變量,表示頂點坐標(xyzw,w是用來進行投影變換的歸一化變量),我們會通過aPosition把要繪制的頂點坐標傳遞給gl_Position
fragment_shader.glsl
precision mediump float; void main() { gl_FragColor = vec4(0,0.5,0.5,1); }
precision mediump float用來指定運算的精度以提高效率(因為運算量還是蠻大的),gl_FragColor 也是一個內建的變量,表示顏色,以rgba的方式排布,范圍是[0,1]的浮點數
使用readRawTextFile把文件讀進來,然後創建一個OpenGL ES程序
String vertexShader = ShaderUtils.readRawTextFile(context, R.raw.vertex_shader); String fragmentShader= ShaderUtils.readRawTextFile(context, R.raw.fragment_shader); programId=ShaderUtils.createProgram(vertexShader,fragmentShader);
讀取文件應該好理解,創建程序就比較復雜了,具體的步驟是這樣的,我們先看創建程序之前要做的事情:
1. 創建一個新的著色器對象
2. 上傳和編譯著色器代碼,就是我們之前讀進來的String
3. 讀取編譯狀態(可選)
public static int loadShader(int shaderType, String source) { int shader = GLES20.glCreateShader(shaderType); if (shader != 0) { GLES20.glShaderSource(shader, source); GLES20.glCompileShader(shader); int[] compiled = new int[1]; GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); if (compiled[0] == 0) { Log.e(TAG, "Could not compile shader " + shaderType + ":"); Log.e(TAG, GLES20.glGetShaderInfoLog(shader)); GLES20.glDeleteShader(shader); shader = 0; } } return shader; }
shaderType用來指定著色器類型,取值有GLES20.GL_VERTEX_SHADER和GLES20.GL_FRAGMENT_SHADER,source就是剛才讀入的代碼,如果創建成功,那麼shader會是一個非零的值,我們用GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);來獲取編譯的狀態,如果創建失敗,就刪除這個著色器:GLES20.glDeleteShader(shader);
public static int createProgram(String vertexSource, String fragmentSource) { int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource); if (vertexShader == 0) { return 0; } int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource); if (pixelShader == 0) { return 0; } int program = GLES20.glCreateProgram(); if (program != 0) { GLES20.glAttachShader(program, vertexShader); checkGlError("glAttachShader"); GLES20.glAttachShader(program, pixelShader); checkGlError("glAttachShader"); GLES20.glLinkProgram(program); int[] linkStatus = new int[1]; GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); if (linkStatus[0] != GLES20.GL_TRUE) { Log.e(TAG, "Could not link program: "); Log.e(TAG, GLES20.glGetProgramInfoLog(program)); GLES20.glDeleteProgram(program); program = 0; } } return program; }
我們先創建頂點著色器和片元著色器,然後用GLES20.glCreateProgram()創建程序,同樣地,如果創建成功,會返回一個非零的值,我們用GLES20.glAttachShader(program, shaderID)這個函數把程序和著色器綁定起來,然後用GLES20.glLinkProgram(program)鏈接程序(編譯鏈接,好有道理的樣子。。)GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);和之前的類似,是用來獲取鏈接狀態的。
另外還有一個打印錯誤日志的功能函數:
public static void checkGlError(String label) { int error; while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { Log.e(TAG, label + ": glError " + error); throw new RuntimeException(label + ": glError " + error); } }
創建好了程序之後,我們獲取之前頂點著色器中,aPosition的引用,以便於傳送頂點數據
aPositionHandle= GLES20.glGetAttribLocation(programId,"aPosition");
OpenGL ES工作在native層(C、C++),如果要傳送數據,我們需要使用特殊的方法把數據復制過去。首先定義一個頂點數組,這是我們要繪制的三角形的三個頂點坐標(逆時針),三個浮點數分別代表xyz,因為是在平面上繪制,我們把z設置為0
private final float[] vertexData = { 0f,0f,0f, 1f,-1f,0f, 1f,1f,0f };
如果程序正常工作,那麼我們的三角形應該出現在這個區域(見下圖):
我們使用一個FloatBuffer將數據傳遞到本地內存,目前這個類中的全局變量如下:
private Context context; private int aPositionHandle; private int programId; private FloatBuffer vertexBuffer;
我們在類的構造函數中把頂點數據傳遞過去:
vertexBuffer = ByteBuffer.allocateDirect(vertexData.length * 4) .order(ByteOrder.nativeOrder()) .asFloatBuffer() .put(vertexData); vertexBuffer.position(0);
一個float是4個字節,ByteBuffer用來在本地內存分配足夠的大小,並設置存儲順序為nativeOrder(關於存儲序的更多資料可以在維基百科上找到),最後把vertexData放進去,當然,不要忘了設定索引位置vertexBuffer.position(0);
完成了上述工作以後,我們就可以來畫三角形了(好辛苦。。)
@Override public void onDrawFrame(GL10 gl) { GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT); GLES20.glUseProgram(programId); GLES20.glEnableVertexAttribArray(aPositionHandle); GLES20.glVertexAttribPointer(aPositionHandle, 3, GLES20.GL_FLOAT, false, 12, vertexBuffer); GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3); }
第一行用來清空顏色緩沖區和深度緩沖區,然後我們指定使用剛才創建的那個程序。GLES20.glEnableVertexAttribArray(aPositionHandle);的作用是啟用頂點數組,aPositionHandle就是我們傳送數據的目標位置。
GLES20.glVertexAttribPointer的原型是這樣的:
glVertexAttribPointer( int indx, int size, int type, boolean normalized, int stride, java.nio.Buffer ptr )
stride表示步長,因為一個頂點三個坐標,一個坐標是float(4字節),所以步長是12字節
最後,我們用GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);把三角形畫出來,glDrawArrays的原型如下,我就不解釋了
public static native void glDrawArrays( int mode, int first, int count );
編譯運行,效果應該是這樣的:
咦,畫出來的不是等腰三角形嘛,就像我們之前說的,OpenGL會把整個屏幕(其實是整個可以繪制的區域,也就是前面黑色的區域)當成輸出,所以我們畫出來的三角形出現了變形。那麼橫屏的情況下是什麼樣的呢? 來看一下:
在下一節,我們會學習如何來處理這種情況,並且學習如何用OpenGL繪制一張圖片。
PS:三角形是OpenGL的基本形狀,很多復雜的幾何體,都是通過切分成三角形來繪制的,後面我們會越來越深刻的感受到這一點。
JSON數據是一種輕量級的數據交換格式,在Android中通常應用於客戶端與服務器交互之間的數據傳輸。像現在在網上有很多解析JSON數據的jar包,但是歸根到底用的都是A
用戶界面的概觀 所有的Android應用程序的用戶界面元素都是用View和ViewGroup對象構建的。View就是在手機屏幕上描繪一個可以與用戶交互
圓環交替、等待效果效果就這樣,分析了一下,大概有這幾個屬性,兩個顏色,一個速度,一個圓環的寬度。自定View的幾個步驟:1、自定義View的屬性2、在View的構造方法中
如今的人們幾乎每天都會用手機拍攝無數張照片,但是照片一多管理就是一件麻煩事。一般來說,用戶管理照片不是系統的圖庫就是快圖應用,後者雖然浏覽速度快但管理功能卻