五颜六色的立方体并算是什么太有意思的事情,看上去太假,没什么感觉。 解决办法就是纹理贴图了。
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 将纹理绑定到目标中,在接下来的绘制中纹理将自动应用。但在此之前,还需要设置好纹理坐标
|
上坐标是由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