原址:http://www.j2megame.com/html/xwzx/ty/2390.html
1 纹理 Texture
纹理定义了物体表面的结构,如花纹,图案,皱纹等等。有了纹理,模型世界才会更加丰富多彩。如一个球形模型,我们给其映射足球的纹理,这就是一个足球,给其映射地球纹理,就是一个地球。另外,如果给一个四边形映射一个墙的纹理,这边是墙,否则,我们需要一块砖一块砖的构建在本节中,我们所指的是狭义的纹理: 图像纹理(对应的有函数纹理—用数学函数来定义的纹理)。
纹理实际上是一个二维数组,其元素是一些颜色值,每一元素称之为纹理像素 (texel)。 纹理对象是一个内部数据类型,存储着纹理数据。你不能直接访问纹理对象,但是可以通过一个整数的 ID 来作为其句柄跟踪之。通过此句柄,你可以作为当前使用的纹理(称之为纹理绑定),也可以从内存中删除这个纹理对象,还可以为一的纹理赋值(将一些纹理数据加载到关联的纹理中,称之为指定纹理)。
通常一个纹理映射的步骤是:
- 创建纹理对象。就是获得一个新的纹理句柄 ID.
- 指定纹理。就是将数据赋值给 ID 的纹理对象,在这一步,图像数据正式加载到了 ID 的纹理对象中。
- 设定过滤器。定义了opengl现实图像的效果,如纹理放大时的马赛克消除。
- 绑定纹理对象。就是将 ID 的纹理作为下面操作的纹理。
- 纹理映射。将已绑定纹理的数据绘制到屏幕上去,在这一步,就能看到贴图的效果了。
1.1 纹理坐标 和 纹理映射
1.2 opengl 中启用纹理映射功能
在默认设置中,纹理映射是关闭的,启用的参数是 GLTEXTURE2D, 还有其他的参数: GL_TEXTURE_1D, GL_TEXTURE_3D, GL_TEXTURE_CUBE_MAP。我们只用到2D纹理,其他不再赘述。
gl.glEnable(GL_TEXTURE_2D)
1.3 创建纹理
创建纹理,用函数 glGenTextures() 完成,函数返回新创建的纹理的 ID。此函数可以创建 n 个纹理,并将纹理ID 放在 textures 中:
void glGenTextures (int n, IntBuffer textures) |
范例:
1 | IntBuffer intBuffer = IntBuffer.allocate(1); |
2 | gl.glGenTextures(1, intBuffer); |
3 | int textureId = intBuffer.get(); // 纹理 ID |
1.4 指定纹理
OpenGL 提供了三个函数来指定纹理: glTexImage1D(), glTexImage2D(), glTexImage3D(). 这三个版本用于相应维数的纹理,我们用到的是 2D 版本: glTexImage2D().
void glTexImage2D (int target, int level, int internalformat, int width, int height, int border, int format, int type, Buffer pixels) |
参数过多,可以使用 GLUtils 中的 texImage2D() 函数,好处是直接将 Bitmap 数据作为参数:
void texImage2D (int target, int level, Bitmap bitmap, int border) |
参数:
-
target
- 操作的目标类型,设为 GL_TEXTURE_2D 即可 level
- 纹理的级别,本节不涉及,设为 0 即可 bitmap
- 图像 border
- 边框,一般设为0
GLUtils.texImage2D (GL10.GL_TEXTURE_2D, 0, mBitmap, 0);
1.5 删除纹理
删除纹理, 第三个参数指明了第二个参数 textures 数组中纹理ID 的步长,一般是紧凑顺序存放,设为0即可。
void glDeleteTextures (int n, int[] textures, int offset) |
1.6 绑定纹理
绑定后,此纹理处于活动状态。在第一次绑定一个纹理对象时, 会将一系列初始值来适应你的应用。绑定比较简单,用函数 glBindTexture():
void glBindTexture (int target, int texture) |
第一个参数是纹理类型,我们使用 2D 纹理,参数设为 GL_TEXTURE_2D, 第二个参数是纹理对象的 ID。
1.7 设置过滤器
有两个版本:float版和int版本。
void glTexParameterf (int target, int pname, float param) | |
void glTexParameterx (int target, int pname, int param) |
一般我们设置两个, 一个放大器的: GL_TEXTURE_MAG_FILTER, 一个缩小器的: GL_TEXTURE_MIN_FILTER.
下面的两行告诉OpenGL在显示图像时,当它比放大得原始的纹理大 ( GL_TEXTURE_MAG_FILTER )或缩小得比原始得纹理小( GL_TEXTURE_MIN_FILTER )时OpenGL采用的滤波方式。通常这两种情况下我都采用 GL_LINEAR 。这使得纹理从很远处到离屏幕很近时都平滑显示。使用 GL_LINEAR 需要CPU和显卡做更多的运算。如果您的机器很慢,您也许应该采用 GL_NEAREST 。过滤的纹理在放大的时候,看起来斑驳的很(马赛克)。您也可以结合这两种滤波方式。在近处时使用 GL_LINEAR ,远处时 GL_NEAREST 。
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); // 线形滤波
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); // 线形滤波
1.8 纹理映射
用函数 glTexCoordPointer 指定纹理坐标数组,
void glTexCoordPointer (int size, int type, int stride, Buffer pointer) |
默认这个功能是关闭的,所以需要打开:
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY); |
// ... |
// 关闭 |
gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY); |
2 常见的几个问题
2.1 贴图呈现白色
可能的原因:
- 未启用 GL_TEXTURE_2D 选项。请使用 glEnable() 和 glDisable() 函数进行开启和关闭。
- 纹理对象无数据。 使用 GLUtils.texImage2D() 来指定,指定前需 glBindTexture() 激活当前纹理。
2.2 图像扭曲
可能的原因:
- 纹理坐标和顶点坐标对应关系是否正确,调整之
- 图像的大小不是 2 的次幂, 解决: 内部重新生成一张 2 的次幂的image,调整uv坐标
3 代码实现
先定义一个纹理对象,其基本接口有:
- 创建+指定。 构造函数完成
- 绑定。
- 绘制。
@note: 为了处理 2 的次幂,内部对原始图像不是2的次幂的重新建立了一个图像。详见代码吧。
public class Texture2D { |
private int mWidth; |
private int mHeight; |
private int mPow2Width; |
private int mPow2Height; |
private float maxU = 1.0f; |
private float maxV = 1.0f; |
private Bitmap mBitmap = null; |
private int textureId = 0; |
// 删除纹理数据 |
public void delete(GL10 gl) |
{ |
if (textureId != 0){ |
gl.glDeleteTextures(1, new int[]{textureId}, 0); |
textureId = 0; |
} |
// bitmap |
if (mBitmap != null) |
{ |
if (mBitmap.isRecycled()) |
mBitmap.recycle(); |
mBitmap = null; |
} |
} |
public static int pow2(int size) |
{ |
int small = (int)(Math.log((double)size)/Math.log(2.0f)) ; |
if ( (1 << small) >= size) |
return 1 << small; |
else |
return 1 << (small + 1); |
} |
// 构建,推迟到第一次绑定时 |
public Texture2D(Bitmap bmp) |
043 | { |
044 | // mBitmap = bmp; |
045 | mWidth = bmp.getWidth(); |
046 | mHeight = bmp.getHeight(); |
047 |
048 | mPow2Height = pow2(mHeight); |
049 | mPow2Width =pow2(mWidth); |
050 |
051 | maxU = mWidth/(float)mPow2Width; |
052 | maxV = mHeight/(float)mPow2Height; |
053 |
054 | Bitmap bitmap = Bitmap.createBitmap(mPow2Width, mPow2Height, |
055 | bmp.hasAlpha() ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565); |
056 | Canvas canvas = new Canvas(bitmap); |
057 | canvas.drawBitmap(bmp, 0, 0, null); |
058 | mBitmap = bitmap; |
059 | } |
060 |
061 | // 第一次会加载纹理数据 |
062 | public void bind(GL10 gl) |
063 | { |
064 | if (textureId ==0) |
065 | { |
066 | int[] textures = new int[1]; |
067 | gl.glGenTextures(1, textures, 0); |
068 | textureId = textures[0]; |
069 |
070 | gl.glBindTexture(GL10.GL_TEXTURE_2D, textureId); |
071 |
072 | gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR); |
073 | gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR); |
074 |
075 | GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, mBitmap, 0); |
076 |
077 | mBitmap.recycle(); |
078 | mBitmap = null; |
079 | } |
080 |
081 | gl.glBindTexture(GL10.GL_TEXTURE_2D, textureId); |
082 | } |
083 |
084 | // 绘制到屏幕上 |
085 | public void draw(GL10 gl, float x, float y) |
086 | { |
087 | gl.glEnable(GL10.GL_TEXTURE_2D); |
088 | gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY); |
089 | gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); |
090 |
091 | // 绑定 |
092 | this.bind(gl); |
093 |
094 | // 映射 |
095 | FloatBuffer verticleBuffer = FloatBuffer.wrap(new float[]{ |
096 | x,y, |
097 | x+mWidth, 0, |
098 | x, y+mHeight, |
099 | x+mWidth, y+mHeight, |
100 | }); |
101 | FloatBuffer coordBuffer = FloatBuffer.wrap(new float[]{ |
102 | 0,0, |
103 | maxU,0, |
104 | 0,maxV, |
105 | maxU,maxV, |
106 | }); |
107 |
108 | gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, coordBuffer); |
109 | gl.glVertexPointer(2, GL10.GL_FLOAT, 0, verticleBuffer); |
110 | gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP,0,4); |
111 |
112 | gl.glDisableClientState(GL10.GL_VERTEX_ARRAY); |
113 | gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY); |
114 | gl.glDisable(GL10.GL_TEXTURE_2D); |
115 | } |
116 |
117 | public void draw(GL10 gl, float x, float y, float width, float height) |
118 | { |
119 | gl.glEnable(GL10.GL_TEXTURE_2D); |
120 | gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY); |
121 | gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); |
122 |
123 | // 绑定 |
124 | bind(gl); |
125 |
126 | // 映射 |
127 | // 映射 |
128 | FloatBuffer verticleBuffer = FloatBuffer.wrap(new float[]{ |
129 | x,y, |
130 | x+width, 0, |
131 | x, y+height, |
132 | x+width, y+height, |
133 | }); |
134 | FloatBuffer coordBuffer = FloatBuffer.wrap(new float[]{ |
135 | 0,0, |
136 | maxU,0, |
137 | 0,maxV, |
138 | maxU,maxV, |
139 | }); |
140 |
141 | gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, coordBuffer); |
142 | gl.glVertexPointer(2, GL10.GL_FLOAT, 0, verticleBuffer); |
143 | gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP,0,4); |
144 |
145 | gl.glDisableClientState(GL10.GL_VERTEX_ARRAY); |
146 | gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY); |
147 | gl.glDisable(GL10.GL_TEXTURE_2D); |
148 |
149 | gl.glDisableClientState(GL10.GL_VERTEX_ARRAY); |
150 | gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY); |
151 | gl.glDisable(GL10.GL_TEXTURE_2D); |
152 |
153 | } |
154 |
155 | } |
4 贴图一个机器人
代码很简单了,在场景 scene 的 draw() 中绘制一个 texture2D, 具体下载代码看看吧:
public class AndroidScene extends GlObject{ |
Texture2D texture; |
public AndroidScene() |
{ |
super(); |
// 使用 assets 文件夹下的 androida.jpg |
Bitmap androidBitmap = GameSystem.getInstance().getBitmapFromAssets("androida.jpg"); |
texture = new Texture2D(androidBitmap); |
} |
public void draw(GL10 gl) |
{ |
texture.draw(gl, 0, 0); |
} |
} |
这一节有点枯燥,学习愉快。