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

Android中EasyAR+OpenGL 3D立方体添加文字/图片技术咨询

在EasyAR立方体顶部添加“Loading”文字/图片的OpenGL实现方案

刚接触OpenGL不用焦虑,针对你在EasyAR立方体顶部添加“Loading”的需求,我给你准备了两种可行方案,从易到难,你可以根据自己的情况选择:


方案一:渲染带“Loading”的图片(新手友好)

这个方案是在立方体顶部放置一个平面,然后将预先制作好的“Loading”图片作为纹理贴到平面上,实现起来步骤清晰,适合刚接触OpenGL的开发者。

步骤1:新增纹理渲染的Shader

在你的BoxRenderer类中,添加纹理相关的顶点和片段着色器:

// 纹理顶点着色器
private String texture_vert = "uniform mat4 trans;\n" +
        "uniform mat4 proj;\n" +
        "attribute vec4 coord;\n" +
        "attribute vec2 texcoord;\n" +
        "varying vec2 vtexcoord;\n" +
        "\n" +
        "void main(void)\n" +
        "{\n" +
        "   vtexcoord = texcoord;\n" +
        "   gl_Position = proj*trans*coord;\n" +
        "}\n";

// 纹理片段着色器
private String texture_frag = "#ifdef GL_ES\n" +
        "precision highp float;\n" +
        "#endif\n" +
        "uniform sampler2D texture;\n" +
        "varying vec2 vtexcoord;\n" +
        "\n" +
        "void main(void)\n" +
        "{\n" +
        "   gl_FragColor = texture2D(texture, vtexcoord);\n" +
        "}\n";

步骤2:新增纹理渲染相关变量

在类中添加这些变量:

private int program_texture;
private int pos_coord_texture;
private int pos_texcoord_texture;
private int pos_trans_texture;
private int pos_proj_texture;
private int pos_texture_sampler;
private int vbo_coord_texture;
private int vbo_texcoord_texture;
private int textureId;

步骤3:实现纹理加载方法

添加一个加载图片为OpenGL纹理的方法(需要传入Android的Bitmap):

private int loadTexture(Bitmap bitmap) {
    int[] textures = new int[1];
    GLES20.glGenTextures(1, textures, 0);
    textureId = textures[0];

    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);

    GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
    bitmap.recycle();
    return textureId;
}

注意:记得导入android.opengl.GLUtils和android.graphics.Bitmap。

步骤4:修改init方法,初始化纹理程序和顶部平面数据

在init()方法末尾添加以下代码:

// 初始化纹理渲染程序
program_texture = GLES20.glCreateProgram();
int vertShaderTex = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER);
GLES20.glShaderSource(vertShaderTex, texture_vert);
GLES20.glCompileShader(vertShaderTex);
int fragShaderTex = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER);
GLES20.glShaderSource(fragShaderTex, texture_frag);
GLES20.glCompileShader(fragShaderTex);
GLES20.glAttachShader(program_texture, vertShaderTex);
GLES20.glAttachShader(program_texture, fragShaderTex);
GLES20.glLinkProgram(program_texture);
GLES20.glUseProgram(program_texture);

pos_coord_texture = GLES20.glGetAttribLocation(program_texture, "coord");
pos_texcoord_texture = GLES20.glGetAttribLocation(program_texture, "texcoord");
pos_trans_texture = GLES20.glGetUniformLocation(program_texture, "trans");
pos_proj_texture = GLES20.glGetUniformLocation(program_texture, "proj");
pos_texture_sampler = GLES20.glGetUniformLocation(program_texture, "texture");

// 初始化顶部平面的坐标(立方体顶部是z=0.01f/2的面,平面略高于立方体避免遮挡)
vbo_coord_texture = generateOneBuffer();
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vbo_coord_texture);
float plane_vertices[][] = {
        {1.0f/2, 1.0f/2, 0.01f/2 + 0.001f},
        {1.0f/2, -1.0f/2, 0.01f/2 + 0.001f},
        {-1.0f/2, -1.0f/2, 0.01f/2 + 0.001f},
        {-1.0f/2, 1.0f/2, 0.01f/2 + 0.001f}
};
FloatBuffer plane_vertices_buffer = FloatBuffer.wrap(flatten(plane_vertices));
GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, plane_vertices_buffer.limit() * 4, plane_vertices_buffer, GLES20.GL_DYNAMIC_DRAW);

// 初始化纹理坐标
vbo_texcoord_texture = generateOneBuffer();
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vbo_texcoord_texture);
float tex_coords[][] = {
        {1.0f, 0.0f},
        {1.0f, 1.0f},
        {0.0f, 1.0f},
        {0.0f, 0.0f}
};
FloatBuffer tex_coords_buffer = FloatBuffer.wrap(flatten(tex_coords));
GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, tex_coords_buffer.limit() * 4, tex_coords_buffer, GLES20.GL_DYNAMIC_DRAW);

步骤5:添加加载纹理的入口方法

在BoxRenderer中添加一个方法,让外部调用加载“Loading”图片:

public void loadLoadingTexture(Bitmap bitmap) {
    loadTexture(bitmap);
}

你可以在Activity中加载assets里的图片,比如:

Bitmap loadingBitmap = BitmapFactory.decodeStream(getAssets().open("loading.png"));
boxRenderer.loadLoadingTexture(loadingBitmap);

步骤6:修改render方法,先渲染立方体再渲染顶部图片

在你的render方法末尾添加渲染纹理平面的代码:

// 渲染顶部图片平面
GLES20.glUseProgram(program_texture);
GLES20.glEnable(GLES20.GL_BLEND);
GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);

GLES20.glUniformMatrix4fv(pos_trans_texture, 1, false, trans.data(), 0);
GLES20.glUniformMatrix4fv(pos_proj_texture, 1, false, proj.data(), 0);

GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
GLES20.glUniform1i(pos_texture_sampler, 0);

GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vbo_coord_texture);
GLES20.glEnableVertexAttribArray(pos_coord_texture);
GLES20.glVertexAttribPointer(pos_coord_texture, 3, GLES20.GL_FLOAT, false, 0, 0);

GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vbo_texcoord_texture);
GLES20.glEnableVertexAttribArray(pos_texcoord_texture);
GLES20.glVertexAttribPointer(pos_texcoord_texture, 2, GLES20.GL_FLOAT, false, 0, 0);

GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 0, 4);

GLES20.glDisableVertexAttribArray(pos_coord_texture);
GLES20.glDisableVertexAttribArray(pos_texcoord_texture);
GLES20.glDisable(GLES20.GL_BLEND);

方案二:直接渲染“Loading”文字

如果不想用图片,也可以直接渲染文字,核心是把文字生成Bitmap纹理,然后用方案一的方式渲染。这里给你一个生成文字Bitmap的方法:

private Bitmap generateLoadingTextBitmap() {
    int width = 256;
    int height = 64;
    Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(bitmap);
    canvas.drawColor(Color.TRANSPARENT);

    Paint paint = new Paint();
    paint.setColor(Color.WHITE);
    paint.setTextSize(48);
    paint.setTextAlign(Paint.Align.CENTER);
    paint.setAntiAlias(true);

    canvas.drawText("Loading", width/2, height/2 + paint.getTextSize()/2, paint);
    return bitmap;
}

然后调用loadLoadingTexture(generateLoadingTextBitmap())就可以生成文字纹理并渲染。


关键注意事项

  • 确保平面的z坐标略高于立方体顶部,避免两者互相遮挡(代码中已经加了0.001f的偏移)
  • 开启混合模式(GL_BLEND),这样图片/文字的透明区域才会正常显示
  • 图片尺寸尽量是2的幂次方(比如256x256),避免OpenGL纹理兼容问题

附你提供的原立方体渲染代码:

import android.opengl.GLES20;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
import cn.easyar.Vec2F;
import cn.easyar.Matrix44F;

public class BoxRenderer {
    private int program_box;
    private int pos_coord_box;
    private int pos_color_box;
    private int pos_trans_box;
    private int pos_proj_box;
    private int vbo_coord_box;
    private int vbo_color_box;
    private int vbo_color_box_2;
    private int vbo_faces_box;

    private String box_vert = "uniform mat4 trans;\n" +
            "uniform mat4 proj;\n" +
            "attribute vec4 coord;\n" +
            "attribute vec4 color;\n" +
            "varying vec4 vcolor;\n" +
            "\n" +
            "void main(void)\n" +
            "{\n" +
            " vcolor = color;\n" +
            " gl_Position = proj*trans*coord;\n" +
            "}\n" +
            "\n";

    private String box_frag = "#ifdef GL_ES\n" +
            "precision highp float;\n" +
            "#endif\n" +
            "varying vec4 vcolor;\n" +
            "\n" +
            "void main(void)\n" +
            "{\n" +
            " gl_FragColor = vcolor;\n" +
            "}\n" +
            "\n";

    private float[] flatten(float[][] a) {
        int size = 0;
        for (int k = 0; k < a.length; k += 1) {
            size += a[k].length;
        }
        float[] l = new float[size];
        int offset = 0;
        for (int k = 0; k < a.length; k += 1) {
            System.arraycopy(a[k], 0, l, offset, a[k].length);
            offset += a[k].length;
        }
        return l;
    }

    private int[] flatten(int[][] a) {
        int size = 0;
        for (int k = 0; k < a.length; k += 1) {
            size += a[k].length;
        }
        int[] l = new int[size];
        int offset = 0;
        for (int k = 0; k < a.length; k += 1) {
            System.arraycopy(a[k], 0, l, offset, a[k].length);
            offset += a[k].length;
        }
        return l;
    }

    private short[] flatten(short[][] a) {
        int size = 0;
        for (int k = 0; k < a.length; k += 1) {
            size += a[k].length;
        }
        short[] l = new short[size];
        int offset = 0;
        for (int k = 0; k < a.length; k += 1) {
            System.arraycopy(a[k], 0, l, offset, a[k].length);
            offset += a[k].length;
        }
        return l;
    }

    private byte[] flatten(byte[][] a) {
        int size = 0;
        for (int k = 0; k < a.length; k += 1) {
            size += a[k].length;
        }
        byte[] l = new byte[size];
        int offset = 0;
        for (int k = 0; k < a.length; k += 1) {
            System.arraycopy(a[k], 0, l, offset, a[k].length);
            offset += a[k].length;
        }
        return l;
    }

    private byte[] byteArrayFromIntArray(int[] a) {
        byte[] l = new byte[a.length];
        for (int k = 0; k < a.length; k += 1) {
            l[k] = (byte) (a[k] & 0xFF);
        }
        return l;
    }

    private int generateOneBuffer() {
        int[] buffer = {0};
        GLES20.glGenBuffers(1, buffer, 0);
        return buffer[0];
    }

    public void init() {
        program_box = GLES20.glCreateProgram();
        int vertShader = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER);
        GLES20.glShaderSource(vertShader, box_vert);
        GLES20.glCompileShader(vertShader);
        int fragShader = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER);
        GLES20.glShaderSource(fragShader, box_frag);
        GLES20.glCompileShader(fragShader);
        GLES20.glAttachShader(program_box, vertShader);
        GLES20.glAttachShader(program_box, fragShader);
        GLES20.glLinkProgram(program_box);
        GLES20.glUseProgram(program_box);

        pos_coord_box = GLES20.glGetAttribLocation(program_box, "coord");
        pos_color_box = GLES20.glGetAttribLocation(program_box, "color");
        pos_trans_box = GLES20.glGetUniformLocation(program_box, "trans");
        pos_proj_box = GLES20.glGetUniformLocation(program_box, "proj");

        vbo_coord_box = generateOneBuffer();
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vbo_coord_box);
        float cube_vertices[][] = {
                /* +z */{1.0f / 2, 1.0f / 2, 0.01f / 2},
                {1.0f / 2, -1.0f / 2, 0.01f / 2},
                {-1.0f / 2, -1.0f / 2, 0.01f / 2},
                {-1.0f / 2, 1.0f / 2, 0.01f / 2},
                /* -z */{1.0f / 2, 1.0f / 2, -0.01f / 2},
                {1.0f / 2, -1.0f / 2, -0.01f / 2},
                {-1.0f / 2, -1.0f / 2, -0.01f / 2},
                {-1.0f / 2, 1.0f / 2, -0.01f /
相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 08:57:20