五颜六色的立方体并算是什么太有意思的事情,看上去太假,没什么感觉。 解决办法就是纹理贴图了。

OpenGL 中使用纹理要先用 glEnable 来启用相关功能


?


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);




 

 



好, 运行一下,看上去还不错

最终代码: android-opengles-2.zip