关键字: android OpenGL 移动开发 教程
在这一课,我们讲怎样将纹理映射到立方体的六个面上。
学习纹理映射有很多好处,比方说您想让一颗导弹飞过屏幕。根据前几课的知识,我们最可行的办法可能是很多个多边形来构建导弹的轮廓并加上有趣的颜色。使用纹理映射,您可以使用真实的导弹图像并让它飞过屏幕。您觉得哪个更好看?照片还是一大堆三角形和四边形?使用纹理映射的好处还不止是更好看,而且您的程序运行会更快。导弹贴图可能只是一个飞过窗口的四边形。一个由多边形构建而来的导弹却很可能包括成百上千的多边形。很显然,贴图极大的节省了CPU时间。
这一课,我们采用一种新飞方式来构建立方体,我们只定义一个面的顶点坐标,其它五个面都根据这个面的通过旋转和平移来渲染。同时我们定义了每个顶点的纹理坐标,这样在渲染的时候,纹理就映射到面上。
注意纹理的起始坐标位置为左上角。纹理坐标值归一化到[0,1]。
在TextureCube中,我们为一个面的每个顶点对应一个纹理坐标。
private FloatBuffertexBuffer; // 纹理坐标数据缓冲区
…………
float[]texCoords = {// 定义上面的面的纹理坐标
0.0f, 1.0f, // A. 左-下
1.0f, 1.0f, // B. 右-下
0.0f, 0.0f, // C. 左-上
1.0f, 0.0f // D. 右-上
};
在构造函数中加入
// 设置纹理坐标数组缓冲区,起数据类型为浮点数据
ByteBuffer tbb = ByteBuffer.allocateDirect(texCoords.length * 4);
tbb.order(ByteOrder.nativeOrder());
texBuffer = tbb.asFloatBuffer();
texBuffer.put(texCoords);
texBuffer.position(0);
在draw方法中,使能纹理映射:
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY); // 使能纹理坐标数组
gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0,texBuffer); // 定义纹理坐标数组缓冲区
…………
在图形绘制完成后,调用
gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
在Texture里我们实现了一个新的方法,用于加载纹理
public void loadTexture(GL10 gl, Context context) {
gl.glGenTextures(1, textureIDs, 0); // 生成纹理ID数组
gl.glBindTexture(GL10.GL_TEXTURE_2D, textureIDs[0]); // 绑定到纹理ID
// 设置纹理过滤方式
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);
// 构造一个输入流来加载纹理文件"res/drawable/nehe.bmp"
InputStream ins = context.getResources().openRawResource(R.drawable.nehe);
Bitmap bmp;
try {
// 读取并将输入流解码成位图
bmp = BitmapFactory.decodeStream(ins);
} finally {
try {
ins.close();
}catch(IOException e) {}
}
// 根据加载的位图为当前绑定的纹理ID建立纹理
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bmp, 0);
}
其中第一行glGenTextures告诉OpenGL我们想生成一个生成纹理ID数组,第二行glBindTexture告诉OpenGL将纹理ID textureIDs[0]绑定到纹理目标上。下面两行glTexParameterf告诉OpenGL在显示图像时,当它比放大得原始的纹理大 ( GL_TEXTURE_MAG_FILTER )或缩小得比原始得纹理小( GL_TEXTURE_MIN_FILTER )时OpenGL采用的滤波方式。在近处时使用 GL_LINEAR,远处时 GL_NEAREST。当然我们也可以都设置成GL_LINEAR滤波方式,但这需要需要CPU和显卡做更多的运算。接下来的代码就是加载纹理图像。最后GLUtils.texImage2D根据加载的位图为当前绑定的纹理ID建立纹理。
在MyGLRenderer的onSurfaceCreated中设置纹理和使能纹理
cube.loadTexture(gl,context); // 加载纹理
gl.glEnable(GL10.GL_TEXTURE_2D); // 纹理使能
TextureCube 代码:
package wintop.gllesson06;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import javax.microedition.khronos.opengles.GL10;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLUtils;
// 生成一个带纹理的立方体
// 这里指定义一个面的顶点,立方体的其他面通过平移和旋转这个面来渲染
public class TextureCube {
private FloatBuffer vertexBuffer; // 顶点数组缓冲区
private FloatBuffer texBuffer; // 纹理坐标数据缓冲区
private float[] vertices = { // 定义一个面的顶点坐标
-1.0f, -1.0f, 0.0f, // 0. 左-底-前
1.0f, -1.0f, 0.0f, // 1. 右-底-前
-1.0f, 1.0f, 0.0f, // 2. 左-顶-前
1.0f, 1.0f, 0.0f // 3. 右-顶-前
};
float[] texCoords = { // 定义上面的面的纹理坐标
0.0f, 1.0f, // A. 左-下
1.0f, 1.0f, // B. 右-下
0.0f, 0.0f, // C. 左-上
1.0f, 0.0f // D. 右-上
};
int[] textureIDs = new int[1]; // 纹理-ID数组
// 构造函数,设置缓冲区
public TextureCube()
{
// 设置顶点数组,顶点数据为浮点数据类型。一个浮点类型的数据长度为四个字节
ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);
vbb.order(ByteOrder.nativeOrder()); // 使用原生字节顺序
vertexBuffer = vbb.asFloatBuffer(); // 将字节类型缓冲区转换成浮点类型
vertexBuffer.put(vertices); // 将数据复制进缓冲区
vertexBuffer.position(0); // 定位到初始位置
// 设置纹理坐标数组缓冲区,数据类型为浮点数据
ByteBuffer tbb = ByteBuffer.allocateDirect(texCoords.length * 4);
tbb.order(ByteOrder.nativeOrder());
texBuffer = tbb.asFloatBuffer();
texBuffer.put(texCoords);
texBuffer.position(0);
}
// 绘图
public void draw(GL10 gl){
gl.glFrontFace(GL10.GL_CCW); // 正前面为逆时针方向
gl.glEnable(GL10.GL_CULL_FACE); // 使能剔除面
gl.glCullFace(GL10.GL_BACK); // 剔除背面(不显示)
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY); // 使能纹理坐标数组
gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, texBuffer); // 定义纹理坐标数组缓冲区
// 前
gl.glPushMatrix();
gl.glTranslatef(0.0f, 0.0f, 1.0f);
gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4);
gl.glPopMatrix();
// 左
gl.glPushMatrix();
gl.glRotatef(270.0f, 0.0f, 1.0f, 0.0f);
gl.glTranslatef(0.0f, 0.0f, 1.0f);
gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4);
gl.glPopMatrix();
// 后
gl.glPushMatrix();
gl.glRotatef(180.0f, 0.0f, 1.0f, 0.0f);
gl.glTranslatef(0.0f, 0.0f, 1.0f);
gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4);
gl.glPopMatrix();
// 右
gl.glPushMatrix();
gl.glRotatef(90.0f, 0.0f, 1.0f, 0.0f);
gl.glTranslatef(0.0f, 0.0f, 1.0f);
gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4);
gl.glPopMatrix();
// 顶
gl.glPushMatrix();
gl.glRotatef(270.0f, 1.0f, 0.0f, 0.0f);
gl.glTranslatef(0.0f, 0.0f, 1.0f);
gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4);
gl.glPopMatrix();
// 底
gl.glPushMatrix();
gl.glRotatef(90.0f, 1.0f, 0.0f, 0.0f);
gl.glTranslatef(0.0f, 0.0f, 1.0f);
gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4);
gl.glPopMatrix();
// 恢复原来的状态
gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
gl.glDisable(GL10.GL_CULL_FACE);
}
// 加载一个图像到GL纹理
public void loadTexture(GL10 gl, Context context) {
gl.glGenTextures(1, textureIDs, 0); // 生成纹理ID数组
gl.glBindTexture(GL10.GL_TEXTURE_2D, textureIDs[0]); // 绑定到纹理ID
// 设置纹理过滤方式
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);
// 构造一个输入流来加载纹理文件"res/drawable/nehe.bmp"
InputStream ins = context.getResources().openRawResource(R.drawable.nehe);
Bitmap bmp;
try {
// 读取并将输入流解码成位图
bmp = BitmapFactory.decodeStream(ins);
} finally {
try {
ins.close();
}catch(IOException e) {}
}
// 根据加载的位图为当前绑定的纹理ID建立纹理
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bmp, 0);
}
}
纹理映射后的最终效果如下图所示: