首先新建一个activity用于屏幕的view
package com.example.wang.airhockey3d; import android.opengl.GLSurfaceView; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; public class AirHockeyActivity extends AppCompatActivity { private GLSurfaceView glSurfaceView; private boolean rendererSet = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); glSurfaceView = new GLSurfaceView(this); glSurfaceView.setEGLContextClientVersion(2); glSurfaceView.setRenderer(new AirHockeyRenderer(this)); setContentView(glSurfaceView); } @Override protected void onPause() { super.onPause(); if (rendererSet) { glSurfaceView.onPause(); } } @Override protected void onResume() { super.onResume(); if (rendererSet) { glSurfaceView.onResume(); } } } 最重要的是有一个新渲染类, new AirHockeyRenderer(this) package com.example.wang.airhockey3d; import static android.opengl.GLES20.GL_COLOR_BUFFER_BIT; import static android.opengl.GLES20.GL_FLOAT; import static android.opengl.GLES20.GL_LINES; import static android.opengl.GLES20.GL_POINTS; import static android.opengl.GLES20.GL_TRIANGLE_FAN; import static android.opengl.GLES20.glClear; import static android.opengl.GLES20.glClearColor; import static android.opengl.GLES20.glDrawArrays; import static android.opengl.GLES20.glEnableVertexAttribArray; import static android.opengl.GLES20.glGetAttribLocation; import static android.opengl.GLES20.glGetUniformLocation; import static android.opengl.GLES20.glUniformMatrix4fv; import static android.opengl.GLES20.glUseProgram; import static android.opengl.GLES20.glVertexAttribPointer; import static android.opengl.GLES20.glViewport; import static android.opengl.Matrix.multiplyMM; import static android.opengl.Matrix.rotateM; import static android.opengl.Matrix.setIdentityM; import static android.opengl.Matrix.translateM; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; import android.content.Context; import android.opengl.GLSurfaceView.Renderer; public class AirHockeyRenderer implements Renderer { private static final String U_MATRIX = "u_Matrix"; private static final String A_POSITION = "a_Position"; private static final String A_COLOR = "a_Color"; /* private static final int POSITION_COMPONENT_COUNT = 4; */ private static final int POSITION_COMPONENT_COUNT = 2; private static final int COLOR_COMPONENT_COUNT = 3; private static final int BYTES_PER_FLOAT = 4; private static final int STRIDE = (POSITION_COMPONENT_COUNT + COLOR_COMPONENT_COUNT) * BYTES_PER_FLOAT; private final FloatBuffer vertexData; private final Context context; private final float[] projectionMatrix = new float[16]; private final float[] modelMatrix = new float[16]; private int program; private int uMatrixLocation; private int aPositionLocation; private int aColorLocation; public AirHockeyRenderer(Context context) { this.context = context; /* float[] tableVerticesWithTriangles = { // Order of coordinates: X, Y, Z, W, R, G, B // Triangle Fan 0f, 0f, 0f, 1.5f, 1f, 1f, 1f, -0.5f, -0.8f, 0f, 1f, 0.7f, 0.7f, 0.7f, 0.5f, -0.8f, 0f, 1f, 0.7f, 0.7f, 0.7f, 0.5f, 0.8f, 0f, 2f, 0.7f, 0.7f, 0.7f, -0.5f, 0.8f, 0f, 2f, 0.7f, 0.7f, 0.7f, -0.5f, -0.8f, 0f, 1f, 0.7f, 0.7f, 0.7f, // Line 1 -0.5f, 0f, 0f, 1.5f, 1f, 0f, 0f, 0.5f, 0f, 0f, 1.5f, 1f, 0f, 0f, // Mallets 0f, -0.4f, 0f, 1.25f, 0f, 0f, 1f, 0f, 0.4f, 0f, 1.75f, 1f, 0f, 0f }; */ float[] tableVerticesWithTriangles = { // Order of coordinates: X, Y, R, G, B // Triangle Fan 0f, 0f, 1f, 1f, 1f, -0.5f, -0.8f, 0.7f, 0.7f, 0.7f, 0.5f, -0.8f, 0.7f, 0.7f, 0.7f, 0.5f, 0.8f, 0.7f, 0.7f, 0.7f, -0.5f, 0.8f, 0.7f, 0.7f, 0.7f, -0.5f, -0.8f, 0.7f, 0.7f, 0.7f, // Line 1 -0.5f, 0f, 1f, 0f, 0f, 0.5f, 0f, 1f, 0f, 0f, // Mallets 0f, -0.4f, 0f, 0f, 1f, 0f, 0.4f, 1f, 0f, 0f }; vertexData = ByteBuffer .allocateDirect(tableVerticesWithTriangles.length * BYTES_PER_FLOAT) .order(ByteOrder.nativeOrder()).asFloatBuffer(); vertexData.put(tableVerticesWithTriangles); } @Override public void onSurfaceCreated(GL10 glUnused, EGLConfig config) { glClearColor(0.0f, 0.0f, 0.0f, 0.0f); String vertexShaderSource = TextResourceReader .readTextFileFromResource(context, R.raw.simple_vertex_shader); String fragmentShaderSource = TextResourceReader .readTextFileFromResource(context, R.raw.simple_fragment_shader); int vertexShader = ShaderHelper.compileVertexShader(vertexShaderSource); int fragmentShader = ShaderHelper .compileFragmentShader(fragmentShaderSource); program = ShaderHelper.linkProgram(vertexShader, fragmentShader); if (LoggerConfig.ON) { ShaderHelper.validateProgram(program); } glUseProgram(program); uMatrixLocation = glGetUniformLocation(program, U_MATRIX); aPositionLocation = glGetAttribLocation(program, A_POSITION); aColorLocation = glGetAttribLocation(program, A_COLOR); // Bind our data, specified by the variable vertexData, to the vertex // attribute at location A_POSITION_LOCATION. vertexData.position(0); glVertexAttribPointer(aPositionLocation, POSITION_COMPONENT_COUNT, GL_FLOAT, false, STRIDE, vertexData); glEnableVertexAttribArray(aPositionLocation); // Bind our data, specified by the variable vertexData, to the vertex // attribute at location A_COLOR_LOCATION. vertexData.position(POSITION_COMPONENT_COUNT); glVertexAttribPointer(aColorLocation, COLOR_COMPONENT_COUNT, GL_FLOAT, false, STRIDE, vertexData); glEnableVertexAttribArray(aColorLocation); } /** * onSurfaceChanged is called whenever the surface has changed. This is * called at least once when the surface is initialized. Keep in mind that * Android normally restarts an Activity on rotation, and in that case, the * renderer will be destroyed and a new one created. * * @param width * The new width, in pixels. * @param height * The new height, in pixels. */ @Override public void onSurfaceChanged(GL10 glUnused, int width, int height) { // Set the OpenGL viewport to fill the entire surface. glViewport(0, 0, width, height); /* final float aspectRatio = width > height ? (float) width / (float) height : (float) height / (float) width; if (width > height) { // Landscape orthoM(projectionMatrix, 0, -aspectRatio, aspectRatio, -1f, 1f, -1f, 1f); } else { // Portrait or square orthoM(projectionMatrix, 0, -1f, 1f, -aspectRatio, aspectRatio, -1f, 1f); } */ MatrixHelper.perspectiveM(projectionMatrix, 45, (float) width / (float) height, 1f, 10f); /* setIdentityM(modelMatrix, 0); translateM(modelMatrix, 0, 0f, 0f, -2f); */ setIdentityM(modelMatrix, 0); translateM(modelMatrix, 0, 0f, 0f, -2.5f); rotateM(modelMatrix, 0, -60f, 1f, 0f, 0f); final float[] temp = new float[16]; multiplyMM(temp, 0, projectionMatrix, 0, modelMatrix, 0); System.arraycopy(temp, 0, projectionMatrix, 0, temp.length); } /** * OnDrawFrame is called whenever a new frame needs to be drawn. Normally, * this is done at the refresh rate of the screen. */ @Override public void onDrawFrame(GL10 glUnused) { // Clear the rendering surface. glClear(GL_COLOR_BUFFER_BIT); // Assign the matrix glUniformMatrix4fv(uMatrixLocation, 1, false, projectionMatrix, 0); // Draw the table. glDrawArrays(GL_TRIANGLE_FAN, 0, 6); // Draw the center dividing line. glDrawArrays(GL_LINES, 6, 2); // Draw the first mallet. glDrawArrays(GL_POINTS, 8, 1); // Draw the second mallet. glDrawArrays(GL_POINTS, 9, 1); } } 剩下的是一些辅助类,这个编译,链接shader时用的。 package com.example.wang.airhockey3d; /** * Created by wang on 17-5-4. */ import static android.opengl.GLES20.GL_COMPILE_STATUS; import static android.opengl.GLES20.GL_FRAGMENT_SHADER; import static android.opengl.GLES20.GL_LINK_STATUS; import static android.opengl.GLES20.GL_VALIDATE_STATUS; import static android.opengl.GLES20.GL_VERTEX_SHADER; import static android.opengl.GLES20.glAttachShader; import static android.opengl.GLES20.glCompileShader; import static android.opengl.GLES20.glCreateProgram; import static android.opengl.GLES20.glCreateShader; import static android.opengl.GLES20.glDeleteProgram; import static android.opengl.GLES20.glDeleteShader; import static android.opengl.GLES20.glGetProgramInfoLog; import static android.opengl.GLES20.glGetProgramiv; import static android.opengl.GLES20.glGetShaderInfoLog; import static android.opengl.GLES20.glGetShaderiv; import static android.opengl.GLES20.glLinkProgram; import static android.opengl.GLES20.glShaderSource; import static android.opengl.GLES20.glValidateProgram; import android.util.Log; public class ShaderHelper { private static final String TAG = "ShaderHelper"; /** * Loads and compiles a vertex shader, returning the OpenGL object ID. */ public static int compileVertexShader(String shaderCode) { return compileShader(GL_VERTEX_SHADER, shaderCode); } /** * Loads and compiles a fragment shader, returning the OpenGL object ID. */ public static int compileFragmentShader(String shaderCode) { return compileShader(GL_FRAGMENT_SHADER, shaderCode); } /** * Compiles a shader, returning the OpenGL object ID. */ private static int compileShader(int type, String shaderCode) { // Create a new shader object. final int shaderObjectId = glCreateShader(type); if (shaderObjectId == 0) { if (LoggerConfig.ON) { Log.w(TAG, "Could not create new shader."); } return 0; } // Pass in the shader source. glShaderSource(shaderObjectId, shaderCode); // Compile the shader. glCompileShader(shaderObjectId); // Get the compilation status. final int[] compileStatus = new int[1]; glGetShaderiv(shaderObjectId, GL_COMPILE_STATUS, compileStatus, 0); if (LoggerConfig.ON) { // Print the shader info log to the Android log output. Log.v(TAG, "Results of compiling source:" + "\n" + shaderCode + "\n:" + glGetShaderInfoLog(shaderObjectId)); } // Verify the compile status. if (compileStatus[0] == 0) { // If it failed, delete the shader object. glDeleteShader(shaderObjectId); if (LoggerConfig.ON) { Log.w(TAG, "Compilation of shader failed."); } return 0; } // Return the shader object ID. return shaderObjectId; } /** * Links a vertex shader and a fragment shader together into an OpenGL * program. Returns the OpenGL program object ID, or 0 if linking failed. */ public static int linkProgram(int vertexShaderId, int fragmentShaderId) { // Create a new program object. final int programObjectId = glCreateProgram(); if (programObjectId == 0) { if (LoggerConfig.ON) { Log.w(TAG, "Could not create new program"); } return 0; } // Attach the vertex shader to the program. glAttachShader(programObjectId, vertexShaderId); // Attach the fragment shader to the program. glAttachShader(programObjectId, fragmentShaderId); // Link the two shaders together into a program. glLinkProgram(programObjectId); // Get the link status. final int[] linkStatus = new int[1]; glGetProgramiv(programObjectId, GL_LINK_STATUS, linkStatus, 0); if (LoggerConfig.ON) { // Print the program info log to the Android log output. Log.v(TAG, "Results of linking program:\n" + glGetProgramInfoLog(programObjectId)); } // Verify the link status. if (linkStatus[0] == 0) { // If it failed, delete the program object. glDeleteProgram(programObjectId); if (LoggerConfig.ON) { Log.w(TAG, "Linking of program failed."); } return 0; } // Return the program object ID. return programObjectId; } /** * Validates an OpenGL program. Should only be called when developing the * application. */ public static boolean validateProgram(int programObjectId) { glValidateProgram(programObjectId); final int[] validateStatus = new int[1]; glGetProgramiv(programObjectId, GL_VALIDATE_STATUS, validateStatus, 0); Log.v(TAG, "Results of validating program: " + validateStatus[0] + "\nLog:" + glGetProgramInfoLog(programObjectId)); return validateStatus[0] != 0; } } package com.example.wang.airhockey3d; /** * Created by wang on 17-5-4. */ public class MatrixHelper { public static void perspectiveM(float[] m, float yFovInDegrees, float aspect, float n, float f) { final float angleInRadians = (float) (yFovInDegrees * Math.PI / 180.0); final float a = (float) (1.0 / Math.tan(angleInRadians / 2.0)); m[0] = a / aspect; m[1] = 0f; m[2] = 0f; m[3] = 0f; m[4] = 0f; m[5] = a; m[6] = 0f; m[7] = 0f; m[8] = 0f; m[9] = 0f; m[10] = -((f + n) / (f - n)); m[11] = -1f; m[12] = 0f; m[13] = 0f; m[14] = -((2f * f * n) / (f - n)); m[15] = 0f; } } 这个应该是在读取shader程序 package com.example.wang.airhockey3d; /** * Created by wang on 17-5-4. */ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import android.content.Context; import android.content.res.Resources; public class TextResourceReader { /** * Reads in text from a resource file and returns a String containing the * text. */ public static String readTextFileFromResource(Context context, int resourceId) { StringBuilder body = new StringBuilder(); try { InputStream inputStream = context.getResources() .openRawResource(resourceId); InputStreamReader inputStreamReader = new InputStreamReader( inputStream); BufferedReader bufferedReader = new BufferedReader( inputStreamReader); String nextLine; while ((nextLine = bufferedReader.readLine()) != null) { body.append(nextLine); body.append('\n'); } } catch (IOException e) { throw new RuntimeException( "Could not open resource: " + resourceId, e); } catch (Resources.NotFoundException nfe) { throw new RuntimeException("Resource not found: " + resourceId, nfe); } return body.toString(); } } package com.example.wang.airhockey3d; /** * Created by wang on 17-5-4. */ public class LoggerConfig { public static final boolean ON = true; } 资源文件 precision mediump float; varying vec4 v_Color; void main() { gl_FragColor = v_Color; } uniform mat4 u_Matrix; attribute vec4 a_Position; attribute vec4 a_Color; varying vec4 v_Color; void main() { v_Color = a_Color; gl_Position = u_Matrix * a_Position; gl_PointSize = 10.0; } 生成的效果