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 /
相关产品推荐
相关产品推荐

