安卓平台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); } }