You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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.

Java Implementation with OpenGL ES 2.0

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.

C++ Implementation with Android NDK

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
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 09:22:14