如何在Android中实现带光照材质的无纹理旋转立方体(OpenGL)
Got it, let's figure out how to build your lit, rotating cube on Android! The old fixed-function OpenGL calls like glBegin() and glVertex3f() are deprecated and don't work with Android's modern OpenGL ES stack. Below are two reliable ways to recreate your exact effect—one using Java with OpenGL ES 2.0+, and another using C++ via the Android NDK.
This is the easiest approach if you're comfortable with Android's Java ecosystem. We'll use GLSurfaceView and a custom renderer with programmable shaders (since the fixed pipeline is out the window).
Step 1: Set Up the GLSurfaceView
First, create an activity that uses a GLSurfaceView as its content view:
import android.app.Activity; import android.opengl.GLSurfaceView; import android.os.Bundle; public class CubeActivity extends Activity { private GLSurfaceView glSurfaceView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); glSurfaceView = new GLSurfaceView(this); glSurfaceView.setEGLContextClientVersion(2); // Use OpenGL ES 2.0 glSurfaceView.setRenderer(new CubeRenderer()); setContentView(glSurfaceView); } @Override protected void onPause() { super.onPause(); glSurfaceView.onPause(); } @Override protected void onResume() { super.onResume(); glSurfaceView.onResume(); } }
Step 2: Implement the Custom Renderer
The renderer handles shader compilation, vertex buffer setup, lighting, and rotating cube logic. We use vertex buffer objects (VBOs) to pass cube data to the GPU, and shaders to handle lighting calculations.
import android.opengl.GLES20; import android.opengl.GLSurfaceView; import android.opengl.Matrix; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; public class CubeRenderer implements GLSurfaceView.Renderer { // Matrix variables private final float[] modelMatrix = new float[16]; private final float[] viewMatrix = new float[16]; private final float[] projectionMatrix = new float[16]; private final float[] mvpMatrix = new float[16]; // Shader handles private int programHandle; private int mvpMatrixHandle; private int positionHandle; private int normalHandle; private int lightPosHandle; private int ambientColorHandle; private int diffuseColorHandle; // Cube vertex data (positions + normals) private final float[] cubeData = { // Front face -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, // Back face -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, // Left face -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, // Right face 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, // Top face -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, -1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, // Bottom face -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, -1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f }; // Cube indices for triangle strips private final short[] cubeIndices = { 0, 1, 2, 3, 3, 2, 6, 7, 7, 6, 5, 4, 4, 5, 10, 11, 11, 10, 9, 8, 8, 9, 14, 15, 15, 14, 13, 12, 12, 13, 18, 19, 19, 18, 17, 16 }; private int vertexBuffer; private int indexBuffer; @Override public void onSurfaceCreated(GL10 unused, EGLConfig config) { // Set background color GLES20.glClearColor(0.1f, 0.1f, 0.1f, 1.0f); // Enable depth testing GLES20.glEnable(GLES20.GL_DEPTH_TEST); // Compile shaders int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, getVertexShader()); int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, getFragmentShader()); // Create program and link programHandle = GLES20.glCreateProgram(); GLES20.glAttachShader(programHandle, vertexShader); GLES20.glAttachShader(programHandle, fragmentShader); GLES20.glLinkProgram(programHandle); // Get shader handles mvpMatrixHandle = GLES20.glGetUniformLocation(programHandle, "u_MVPMatrix"); positionHandle = GLES20.glGetAttribLocation(programHandle, "a_Position"); normalHandle = GLES20.glGetAttribLocation(programHandle, "a_Normal"); lightPosHandle = GLES20.glGetUniformLocation(programHandle, "u_LightPos"); ambientColorHandle = GLES20.glGetUniformLocation(programHandle, "u_AmbientColor"); diffuseColorHandle = GLES20.glGetUniformLocation(programHandle, "u_DiffuseColor"); // Setup vertex buffer vertexBuffer = createBuffer(cubeData, GLES20.GL_ARRAY_BUFFER, GLES20.GL_STATIC_DRAW); indexBuffer = createIndexBuffer(cubeIndices, GLES20.GL_ELEMENT_ARRAY_BUFFER, GLES20.GL_STATIC_DRAW); // Set up view matrix (camera position) Matrix.setLookAtM(viewMatrix, 0, 0.0f, 0.0f, 5.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f); } @Override public void onSurfaceChanged(GL10 unused, int width, int height) { GLES20.glViewport(0, 0, width, height); // Set projection matrix float ratio = (float) width / height; Matrix.frustumM(projectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7); } @Override public void onDrawFrame(GL10 unused) { GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); GLES20.glUseProgram(programHandle); // Update model matrix (rotate cube) long time = System.currentTimeMillis() % 4000; float angle = 0.090f * ((int) time); Matrix.setIdentityM(modelMatrix, 0); Matrix.rotateM(modelMatrix, 0, angle, 1.0f, 1.0f, 1.0f); // Calculate MVP matrix Matrix.multiplyMM(mvpMatrix, 0, viewMatrix, 0, modelMatrix, 0); Matrix.multiplyMM(mvpMatrix, 0, projectionMatrix, 0, mvpMatrix, 0); // Pass MVP matrix to shader GLES20.glUniformMatrix4fv(mvpMatrixHandle, 1, false, mvpMatrix, 0); // Pass lighting and material values float[] lightPos = {0.0f, 5.0f, 5.0f, 1.0f}; float[] ambientColor = {0.2f, 0.2f, 0.2f, 1.0f}; // Ambient material color float[] diffuseColor = {0.8f, 0.8f, 0.8f, 1.0f}; // Diffuse material color GLES20.glUniform4fv(lightPosHandle, 1, lightPos, 0); GLES20.glUniform4fv(ambientColorHandle, 1, ambientColor, 0); GLES20.glUniform4fv(diffuseColorHandle, 1, diffuseColor, 0); // Bind vertex buffer GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vertexBuffer); // Enable position attribute GLES20.glEnableVertexAttribArray(positionHandle); GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 6 * Float.SIZE / 8, 0); // Enable normal attribute GLES20.glEnableVertexAttribArray(normalHandle); GLES20.glVertexAttribPointer(normalHandle, 3, GLES20.GL_FLOAT, false, 6 * Float.SIZE / 8, 3 * Float.SIZE / 8); // Bind index buffer and draw GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, indexBuffer); GLES20.glDrawElements(GLES20.GL_TRIANGLE_STRIP, cubeIndices.length, GLES20.GL_UNSIGNED_SHORT, 0); // Clean up GLES20.glDisableVertexAttribArray(positionHandle); GLES20.glDisableVertexAttribArray(normalHandle); } // Helper to load and compile shaders private int loadShader(int type, String shaderCode) { int shader = GLES20.glCreateShader(type); GLES20.glShaderSource(shader, shaderCode); GLES20.glCompileShader(shader); return shader; } // Helper to create vertex buffer private int createBuffer(float[] data, int bufferType, int usage) { int[] buffer = new int[1]; GLES20.glGenBuffers(1, buffer, 0); GLES20.glBindBuffer(bufferType, buffer[0]); GLES20.glBufferData(bufferType, data.length * Float.SIZE / 8, java.nio.FloatBuffer.wrap(data), usage); return buffer[0]; } // Helper to create index buffer private int createIndexBuffer(short[] data, int bufferType, int usage) { int[] buffer = new int[1]; GLES20.glGenBuffers(1, buffer, 0); GLES20.glBindBuffer(bufferType, buffer[0]); GLES20.glBufferData(bufferType, data.length * Short.SIZE / 8, java.nio.ShortBuffer.wrap(data), usage); return buffer[0]; } // Vertex shader for lighting private String getVertexShader() { return "uniform mat4 u_MVPMatrix;\n" + "uniform vec4 u_LightPos;\n" + "attribute vec4 a_Position;\n" + "attribute vec3 a_Normal;\n" + "varying vec3 v_Normal;\n" + "varying vec3 v_LightDir;\n" + "void main() {\n" + " gl_Position = u_MVPMatrix * a_Position;\n" + " v_Normal = a_Normal;\n" + " v_LightDir = vec3(u_LightPos - a_Position);\n" + "}"; } // Fragment shader for lighting and material private String getFragmentShader() { return "precision mediump float;\n" + "uniform vec4 u_AmbientColor;\n" + "uniform vec4 u_DiffuseColor;\n" + "varying vec3 v_Normal;\n" + "varying vec3 v_LightDir;\n" + "void main() {\n" + " vec3 normal = normalize(v_Normal);\n" + " vec3 lightDir = normalize(v_LightDir);\n" + " float diff = max(dot(normal, lightDir), 0.0);\n" + " vec4 diffuse = diff * u_DiffuseColor;\n" + " gl_FragColor = u_AmbientColor + diffuse;\n" + "}"; } }
Key Notes for Java Implementation
- We use vertex buffer objects (VBOs) instead of
glBegin()to send vertex data to the GPU—this is the modern, efficient way. - The shaders handle lighting calculations: ambient light (constant low-level illumination) and diffuse light (depends on surface angle relative to the light source).
- The cube rotates using matrix transformations (model matrix) updated each frame.
If you prefer to stick with C++, you can use the Android NDK to create an OpenGL ES 2.0+ app. This requires setting up JNI bindings and managing the EGL context manually.
Step 1: NDK Setup
First, add the NDK plugin to your Android project, then create a jni directory with these files:
1.1 cube_renderer.cpp
#include <jni.h> #include <android/log.h> #include <GLES2/gl2.h> #include <GLES2/gl2ext.h> #include <cmath> #include <cstring> #define LOG_TAG "CubeRenderer" #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) // Shader handles GLuint programHandle; GLint mvpMatrixHandle; GLint positionHandle; GLint normalHandle; GLint lightPosHandle; GLint ambientColorHandle; GLint diffuseColorHandle; // Cube data GLfloat cubeData[] = { // Front face

