android 游戏导引(4. 简单纹理贴图)
这一节主要讲述 opengl 的贴图技术,涉及了简单的纹理知识。临近放年假,忙啊。
源码下载: 点我吧
纹理定义了物体表面的结构,如花纹,图案,皱纹等等。有了纹理,模型世界才会更加丰富多彩。如一个球形模型,我们给其映射足球的纹理,这就是一个足球,给其映射地球纹理,就是一个地球。另外,如果给一个四边形映射一个墙的纹理,这边是墙,否则,我们需要一块砖一块砖的构建在本节中,我们所指的是狭义的纹理: 图像纹理(对应的有函数纹理—用数学函数来定义的纹理)。
纹理实际上是一个二维数组,其元素是一些颜色值,每一元素称之为纹理像素 (texel)。 纹理对象是一个内部数据类型,存储着纹理数据。你不能直接访问纹理对象,但是可以通过一个整数的 ID 来作为其句柄跟踪之。通过此句柄,你可以作为当前使用的纹理(称之为纹理绑定),也可以从内存中删除这个纹理对象,还可以为一的纹理赋值(将一些纹理数据加载到关联的纹理中,称之为指定纹理)。
通常一个纹理映射的步骤是:
在默认设置中,纹理映射是关闭的,启用的参数是 GLTEXTURE2D, 还有其他的参数: GL_TEXTURE_1D, GL_TEXTURE_3D, GL_TEXTURE_CUBE_MAP。我们只用到2D纹理,其他不再赘述。
gl.glEnable(GL_TEXTURE_2D)
创建纹理,用函数 glGenTextures() 完成,函数返回新创建的纹理的 ID。此函数可以创建 n 个纹理,并将纹理ID 放在 textures 中:
void glGenTextures (int n, IntBuffer textures) |
范例:
1
2
3
|
IntBuffer intBuffer = IntBuffer.allocate(
1
);
gl.glGenTextures(
1
, intBuffer);
int
textureId = intBuffer.get();
// 纹理 ID
|
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) |
参数:
1GLUtils.texImage2D (GL10.GL_TEXTURE_2D,
0
, mBitmap,
0
);
删除纹理, 第三个参数指明了第二个参数 textures 数组中纹理ID 的步长,一般是紧凑顺序存放,设为0即可。
void glDeleteTextures (int n, int[] textures, int offset) |
绑定后,此纹理处于活动状态。在第一次绑定一个纹理对象时, 会将一系列初始值来适应你的应用。绑定比较简单,用函数 glBindTexture():
void glBindTexture (int target, int texture) |
第一个参数是纹理类型,我们使用 2D 纹理,参数设为 GL_TEXTURE_2D, 第二个参数是纹理对象的 ID。
有两个版本: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 。
12glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
// 线形滤波
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
// 线形滤波
用函数 glTexCoordPointer 指定纹理坐标数组,
void glTexCoordPointer (int size, int type, int stride, Buffer pointer) |
默认这个功能是关闭的,所以需要打开:
1
2
3
4
|
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
// ...
// 关闭
gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
|
可能的原因:
可能的原因:
先定义一个纹理对象,其基本接口有:
@note: 为了处理 2 的次幂,内部对原始图像不是2的次幂的重新建立了一个图像。详见代码吧。
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
|
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)
{
// mBitmap = bmp;
mWidth = bmp.getWidth();
mHeight = bmp.getHeight();
mPow2Height = pow2(mHeight);
mPow2Width =pow2(mWidth);
maxU = mWidth/(
float
)mPow2Width;
maxV = mHeight/(
float
)mPow2Height;
Bitmap bitmap = Bitmap.createBitmap(mPow2Width, mPow2Height,
bmp.hasAlpha() ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565);
Canvas canvas =
new
Canvas(bitmap);
canvas.drawBitmap(bmp,
0
,
0
,
null
);
mBitmap = bitmap;
}
// 第一次会加载纹理数据
public
void
bind(GL10 gl)
{
if
(textureId ==
0
)
{
int
[] textures =
new
int
[
1
];
gl.glGenTextures(
1
, textures,
0
);
textureId = textures[
0
];
gl.glBindTexture(GL10.GL_TEXTURE_2D, textureId);
gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
GLUtils.texImage2D(GL10.GL_TEXTURE_2D,
0
, mBitmap,
0
);
mBitmap.recycle();
mBitmap =
null
;
}
gl.glBindTexture(GL10.GL_TEXTURE_2D, textureId);
}
// 绘制到屏幕上
public
void
draw(GL10 gl,
float
x,
float
y)
{
gl.glEnable(GL10.GL_TEXTURE_2D);
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
// 绑定
this
.bind(gl);
// 映射
FloatBuffer verticleBuffer = FloatBuffer.wrap(
new
float
[]{
x,y,
x+mWidth,
0
,
x, y+mHeight,
x+mWidth, y+mHeight,
});
FloatBuffer coordBuffer = FloatBuffer.wrap(
new
float
[]{
0
,
0
,
maxU,
0
,
0
,maxV,
maxU,maxV,
});
gl.glTexCoordPointer(
2
, GL10.GL_FLOAT,
0
, coordBuffer);
gl.glVertexPointer(
2
, GL10.GL_FLOAT,
0
, verticleBuffer);
gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP,
0
,
4
);
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
gl.glDisable(GL10.GL_TEXTURE_2D);
}
public
void
draw(GL10 gl,
float
x,
float
y,
float
width,
float
height)
{
gl.glEnable(GL10.GL_TEXTURE_2D);
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
// 绑定
bind(gl);
// 映射
// 映射
FloatBuffer verticleBuffer = FloatBuffer.wrap(
new
float
[]{
x,y,
x+width,
0
,
x, y+height,
x+width, y+height,
});
FloatBuffer coordBuffer = FloatBuffer.wrap(
new
float
[]{
0
,
0
,
maxU,
0
,
0
,maxV,
maxU,maxV,
});
gl.glTexCoordPointer(
2
, GL10.GL_FLOAT,
0
, coordBuffer);
gl.glVertexPointer(
2
, GL10.GL_FLOAT,
0
, verticleBuffer);
gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP,
0
,
4
);
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
gl.glDisable(GL10.GL_TEXTURE_2D);
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
gl.glDisable(GL10.GL_TEXTURE_2D);
}
}
|
代码很简单了,在场景 scene 的 draw() 中绘制一个 texture2D, 具体下载代码看看吧:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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
);
}
}
|
这一节有点枯燥,学习愉快。