五颜六色的立方体并算是什么太有意思的事情,看上去太假,没什么感觉。 解决办法就是纹理贴图了。
OpenGL 中使用纹理要先用 glEnable 来启用相关功能
1 | gl.glEnable(GL10.GL_TEXTURE_2D); |
然后先准备一张图片作为纹理贴图,需要注意的是,有些设备对图片的尺寸有要求,我手上这个G7就只支持方形的纹理图片,其它可能的限制还有长宽必须是 2 的 n 次幂,最大尺寸不能超过256或1024等等。弄好图片之后,把它放到 res/drawable 文件夹中,比如 leftcode.png,然后通过资源加载到纹理
private void loadTexture(GL10 gl) { InputStream bitmapStream = null; Bitmap bitmap = null; try { // 打开图片资源流 bitmapStream = context.getResources().openRawResource( R.drawable.leftcode); // 解码图片生成 Bitmap 实例 bitmap = BitmapFactory.decodeStream(bitmapStream); // 生成一个纹理对象,并将其ID保存到成员变量 texture 中 int[] textures = new int[1]; gl.glGenTextures(1, textures, 0); texture = textures[0]; // 将生成的空纹理绑定到当前2D纹理通道 gl.glBindTexture(GL10.GL_TEXTURE_2D, texture); // 设置2D纹理通道当前绑定的纹理的属性 gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST); gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR); gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_REPEAT); gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_REPEAT); // 将bitmap应用到2D纹理通道当前绑定的纹理中 GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0); } finally { // 释放资源 // BTW: 期待 android 早日支持 Java 新的 try-with-resource 语法 if (bitmap != null) bitmap.recycle(); if (bitmapStream != null) { try { bitmapStream.close(); } catch (IOException e) { } } }}
BitmapFactory.decodeStream 从流中加载并解码图片并生成Bitmap对象。令人不解的是更简单的方法的 decodeResource 方法在虚拟机中工作良好,但到我的手机中就不行了,只好退而求其次了。
glGenTextures 生成一组纹理并把纹理的ID存入数组参数中。这里只生成了一个。
glBindTexture 将指定ID的纹理绑定到指定的目标中去,接下来对目录所作的操作将针对该纹理进行。
glTexParameterf 设置纹理参数,这里设置了4个参数:
GL_TEXTURE_MIN_FILTER 和 GL_TEXTURE_MAG_FILTER 指定纹理在被缩小或放大时使用的过滤方式,LINEAR (线性插值?)效果要比 NEAREST(最近点?)好但也更需要更多运算。
GL_TEXTURE_WRAP_S 和 GL_TEXTURE_WRAP_T 表示当贴图坐标不在 0.0-1.0 之间时如何处理,这里使用 REPEAT 即平铺贴图。
GLUtils.texImage2D 辅助方法用于将 Bitmap 对象设置到纹理中,设置完后 Bitmap 对象即不再需要,可以丢弃。
最后,将在 HelloWorldRenderer 构造方法中将参数 Main Activity 存入成员变量 context 中以便在 loadTexture 中用于访问资源。并在 onSurfaceCreated 中调用 loadTexture 加载纹理。
在绘制图元之前,使用 glBindTexture 将纹理绑定到目标中,在接下来的绘制中纹理将自动应用。但在此之前,还需要设置好纹理坐标
private float[] data_tvertices = { 1.0000f, 1.0000f, 1.0000f, 0.0000f, 0.0000f, 0.0000f, 0.0000f, 0.0000f, 0.0000f, 1.0000f, 1.0000f, 1.0000f, 0.0000f, 1.0000f, 1.0000f, 1.0000f, 1.0000f, 0.0000f, 1.0000f, 0.0000f, 0.0000f, 0.0000f, 0.0000f, 1.0000f, 0.0000f, 1.0000f, 1.0000f, 1.0000f, 1.0000f, 0.0000f, 1.0000f, 0.0000f, 0.0000f, 0.0000f, 0.0000f, 1.0000f, 0.0000f, 1.0000f, 1.0000f, 1.0000f, 1.0000f, 0.0000f, 1.0000f, 0.0000f, 0.0000f, 0.0000f, 0.0000f, 1.0000f, 0.0000f, 1.0000f, 1.0000f, 1.0000f, 1.0000f, 0.0000f, 1.0000f, 0.0000f, 0.0000f, 0.0000f, 0.0000f, 1.0000f, 0.0000f, 1.0000f, 1.0000f, 1.0000f, 1.0000f, 0.0000f, 1.0000f, 0.0000f, 0.0000f, 0.0000f, 0.0000f, 1.0000f, }; |
上坐标是由3ds max 场景中导出,除此之外将原有的顶点坐标及顶点索引数组的内容也一并更新,也是从同一 3ds max 场景中导出(该场景只有一个立方体)
private float[] data_vertices = { -5.0f, -5.0f, -5.0f, -5.0f, 5.0f, -5.0f, 5.0f, 5.0f, -5.0f, 5.0f, 5.0f, -5.0f, 5.0f, -5.0f, -5.0f, -5.0f, -5.0f, -5.0f, -5.0f, -5.0f, 5.0f, 5.0f, -5.0f, 5.0f, 5.0f, 5.0f, 5.0f, 5.0f, 5.0f, 5.0f, -5.0f, 5.0f, 5.0f, -5.0f, -5.0f, 5.0f, -5.0f, -5.0f, -5.0f, 5.0f, -5.0f, -5.0f, 5.0f, -5.0f, 5.0f, 5.0f, -5.0f, 5.0f, -5.0f, -5.0f, 5.0f, -5.0f, -5.0f, -5.0f, 5.0f, -5.0f, -5.0f, 5.0f, 5.0f, -5.0f, 5.0f, 5.0f, 5.0f, 5.0f, 5.0f, 5.0f, 5.0f, -5.0f, 5.0f, 5.0f, -5.0f, -5.0f, 5.0f, 5.0f, -5.0f, -5.0f, 5.0f, -5.0f, -5.0f, 5.0f, 5.0f, -5.0f, 5.0f, 5.0f, 5.0f, 5.0f, 5.0f, 5.0f, 5.0f, -5.0f, -5.0f, 5.0f, -5.0f, -5.0f, -5.0f, -5.0f, -5.0f, -5.0f, 5.0f, -5.0f, -5.0f, 5.0f, -5.0f, 5.0f, 5.0f, -5.0f, 5.0f, -5.0f, };private byte[] data_triangles = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, };
在 createBuffers 中添加代码创建纹理坐标缓冲对象
// 创建纹理坐标缓冲tvertices = ByteBuffer.allocateDirect(data_tvertices.length * 4);tvertices.order(ByteOrder.nativeOrder());tvertices.asFloatBuffer().put(data_tvertices);tvertices.position(0);
最后在绘制代码中添加纹理及纹理坐标设置的代码
// 启用顶点数组、纹理坐标数组gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY); // 设置正面gl.glFrontFace(GL10.GL_CW); // 设置顶点数组指针为 ByteBuffer 对象 vertices// 第一个参数为每个顶点包含的数据长度(以第二个参数表示的数据类型为单位)gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertices);gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, tvertices); // 绑定纹理gl.glBindTexture(GL10.GL_TEXTURE_2D, texture); // 绘制 triangles 表示的三角形gl.glDrawElements(GL10.GL_TRIANGLES, triangles.remaining(), GL10.GL_UNSIGNED_BYTE, triangles); // 禁用顶点、纹理坐标数组gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
修改投景变换矩阵以显示这个稍大些的立方体
GLU.gluLookAt(gl, 30f, 30f, 30f, 0f, 0f, 0f, 0, 1, 0);
好, 运行一下,看上去还不错
最终代码: