安卓平臺OpenGL ES的調用
- 開發遊戲如果直接使用OpenGL是比較痛苦的,最好的辦法是使用封裝好的引擎,但很有必要了解在安卓java代碼直接調用OpenGL的渲染方法
- 固定渲染管線只可通過配置實現不同的效果,而可編程渲染管線通過一般編程的方式實現,可以實現更加靈活的效果
OpenGL ES
- OpenGL從3.0開始,而OpenGL ES從2.0開始,支持可編程管線
- 下圖中橙色兩塊兒即爲可編程部分,而頂點和片段着色器要相互配合好才能發揮最大性能
- 一般通用編程模式爲寫好兩個shader的代碼,每次運行程序時即時編譯運行
代碼框架
- 在manifest中聲明OpenGL ES
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
- 在入口Acitvity中需要使用GLSurfaceView作爲主視圖
- 在GLSurfaceView中可以擴展出一些其他函數,需要創建一個GLSurfaceView.Renderer的派生類對象負責顯示
- GLSurfaceView.Renderer需要關注三個函數:
- onSurfaceCreated:僅調用一次
- onDrawFrame:每次顯示都調用
- onSurfaceChanged:view大小變化時調用
3D座標變換
- 安卓同樣有對應座標變換的庫
Matrix
- MVP變換的實際乘法順序是PVM
- V可以通過
setLookAtM
得到 - P則是
orthoM frustumM/perspectiveM
生成,其中perspectiveM是對frustumM的封裝,只是在API Level 14後才存在 - M如果不是I的話,表明模型有位置偏移
使用shader畫圖
- 需要創建一個頂點shader、一個片段shader,以及一個program
- 通過
glCreateShader
可以創建一個shader,之後調用glShaderSource
、glCompileShader
分別設置shader代碼和編譯 - progam則通過
glCreateProgram
創建,調用glAttachShader
添加這兩個shader,調用glLinkProgram
進行“鏈接”生成可執行指令,使用時要glUseProgram
示例代碼
OpenGLES20Activity.java
package com.example.androiddeveloper; //import android.support.v7.app.ActionBarActivity; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import javax.microedition.khronos.opengles.GL10; import android.app.Activity; import android.content.Context; import android.opengl.*; public class OpenGLES20Activity extends Activity { private GLSurfaceView mGLView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Create a GLSurfaceView instance and set it // as the ContentView for this Activity. mGLView = new MyGLSurfaceView(this); setContentView(mGLView); } } class MyGLSurfaceView extends GLSurfaceView { private final MyGLRenderer mRenderer; public MyGLSurfaceView(Context context){ super(context); // Create an OpenGL ES 2.0 context setEGLContextClientVersion(2); //setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); mRenderer = new MyGLRenderer(); // Set the Renderer for drawing on the GLSurfaceView setRenderer(mRenderer); } private final float TOUCH_SCALE_FACTOR = 180.0f / 320; private float mPreviousX; private float mPreviousY; @Override public boolean onTouchEvent(MotionEvent e) { // MotionEvent reports input details from the touch screen // and other input controls. In this case, you are only // interested in events where the touch position changed. float x = e.getX(); float y = e.getY(); switch (e.getAction()) { case MotionEvent.ACTION_MOVE: float dx = x - mPreviousX; float dy = y - mPreviousY; // reverse direction of rotation above the mid-line if (y > getHeight() / 2) { dx = dx * -1 ; } // reverse direction of rotation to left of the mid-line if (x < getWidth() / 2) { dy = dy * -1 ; } mRenderer.setAngle( mRenderer.getAngle() + ((dx + dy) * TOUCH_SCALE_FACTOR)); requestRender(); } mPreviousX = x; mPreviousY = y; return true; } }
MyGLRenderer.java
package com.example.androiddeveloper; import javax.microedition.khronos.opengles.GL10; import android.opengl.GLES20; import android.opengl.GLSurfaceView; import android.opengl.Matrix; import android.os.SystemClock; public class MyGLRenderer implements GLSurfaceView.Renderer { private Triangle mTriangle; private final float[] mMVPMatrix = new float[16]; private final float[] mProjectionMatrix = new float[16]; private final float[] mViewMatrix = new float[16]; public void onSurfaceCreated(GL10 unused, javax.microedition.khronos.egl.EGLConfig config) { // Set the background frame color GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); mTriangle = new Triangle(); } private float[] mRotationMatrix = new float[16]; public void onDrawFrame(GL10 unused) { float[] scratch = new float[16]; long time = SystemClock.uptimeMillis() % 4000L; float angle = 0.090f * ((int) time); Matrix.setRotateM(mRotationMatrix, 0, mAngle, 0, 0, -1.0f); GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); Matrix.setLookAtM(mViewMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f); Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0); Matrix.multiplyMM(scratch, 0, mMVPMatrix, 0, mRotationMatrix, 0); mTriangle.draw(scratch); } public void onSurfaceChanged(GL10 unused, int width, int height) { GLES20.glViewport(0, 0, width, height); float ratio = (float) width / height; Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7); } public static int loadShader(int type, String shaderCode){ // create a vertex shader type (GLES20.GL_VERTEX_SHADER) // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER) int shader = GLES20.glCreateShader(type); // add the source code to the shader and compile it GLES20.glShaderSource(shader, shaderCode); GLES20.glCompileShader(shader); return shader; } public volatile float mAngle; public float getAngle() { return mAngle; } public void setAngle(float angle) { mAngle = angle; } }
Triangle.java
package com.example.androiddeveloper; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import android.opengl.GLES20; public class Triangle { private FloatBuffer vertexBuffer; // number of coordinates per vertex in this array static final int COORDS_PER_VERTEX = 3; static float triangleCoords[] = { // in counterclockwise order: 0.0f, 0.622008459f, 0.0f, // top -0.5f, -0.311004243f, 0.0f, // bottom left 0.5f, -0.311004243f, 0.0f // bottom right }; // Set color with red, green, blue and alpha (opacity) values float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f }; private final int mProgram; public Triangle() { // initialize vertex byte buffer for shape coordinates ByteBuffer bb = ByteBuffer.allocateDirect( // (number of coordinate values * 4 bytes per float) triangleCoords.length * 4); // use the device hardware's native byte order bb.order(ByteOrder.nativeOrder()); // create a floating point buffer from the ByteBuffer vertexBuffer = bb.asFloatBuffer(); // add the coordinates to the FloatBuffer vertexBuffer.put(triangleCoords); // set the buffer to read the first coordinate vertexBuffer.position(0); int vertexShader = MyGLRenderer.loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode); int fragmentShader = MyGLRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode); // create empty OpenGL ES Program mProgram = GLES20.glCreateProgram(); // add the vertex shader to program GLES20.glAttachShader(mProgram, vertexShader); // add the fragment shader to program GLES20.glAttachShader(mProgram, fragmentShader); // creates OpenGL ES program executables GLES20.glLinkProgram(mProgram); } private final String vertexShaderCode = "uniform mat4 uMVPMatrix;" + "attribute vec4 vPosition;" + "void main() {" + " gl_Position = uMVPMatrix * vPosition;" + "}"; private final String fragmentShaderCode = "precision mediump float;" + "uniform vec4 vColor;" + "void main() {" + " gl_FragColor = vColor;" + "}"; private int mPositionHandle; private int mColorHandle; private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX; private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex private int mMVPMatrixHandle; public void draw(float[] mvpMatrix) { // Add program to OpenGL ES environment GLES20.glUseProgram(mProgram); // get handle to vertex shader's vPosition member mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition"); // Enable a handle to the triangle vertices GLES20.glEnableVertexAttribArray(mPositionHandle); // Prepare the triangle coordinate data GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, vertexBuffer); // get handle to fragment shader's vColor member mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor"); // Set color for drawing the triangle GLES20.glUniform4fv(mColorHandle, 1, color, 0); mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix"); GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0); // Draw the triangle GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount); // Disable vertex array GLES20.glDisableVertexAttribArray(mPositionHandle); } }