Android OpenGL ES 简明开发教程七:材质渲染

前面讨论了如何给3D图形染色,更一般的情况是使用位图来给Mesh上色(渲染材质)。主要步骤如下:

创建Bitmap对象

使用材质渲染,首先需要构造用来渲染的Bitmap对象,Bitmap对象可以从资源文件中读取或是从网络下载或是使用代码构造。为简单起见,本例从资源中读取:

1
2
Bitmap bitmap = BitmapFactory.decodeResource(contect.getResources(),
  R.drawable.icon);

要注意的是,有些设备对使用的Bitmap的大小有要求,要求Bitmap的宽度和长度为2的几次幂(1,2,4,8,16,32,64.。。。),如果使用不和要求的Bitmap来渲染,可能只会显示白色。

创建材质(Generating a texture)

下一步使用OpenGL库创建一个材质(Texture),首先是获取一个Texture Id。

1
2
3
4
5
// Create an int array with the number of textures we want,
// in this case 1.
int [] textures = new int [ 1 ];
// Tell OpenGL to generate textures.
gl.glGenTextures( 1 , textures, 0 );

textures中存放了创建的Texture ID,使用同样的Texture Id ,也可以来删除一个Texture:

1
2
// Delete a texture.
gl.glDeleteTextures( 1 , textures, 0 )

有了Texture Id之后,就可以通知OpenGL库使用这个Texture:

1
gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[ 0 ]);

设置Texture参数glTexParameter

下一步需要给Texture填充设置参数,用来渲染的Texture可能比要渲染的区域大或者小,这是需要设置Texture需要放大或是缩小时OpenGL的模式:

1
2
3
4
5
6
7
8
9
// Scale up if the texture if smaller.
gl.glTexParameterf(GL10.GL_TEXTURE_2D,
  GL10.GL_TEXTURE_MAG_FILTER,
  GL10.GL_LINEAR);
  
// scale linearly when image smalled than texture
gl.glTexParameterf(GL10.GL_TEXTURE_2D,
  GL10.GL_TEXTURE_MIN_FILTER,
  GL10.GL_LINEAR);

常用的两种模式为GL10.GL_LINEAR和GL10.GL_NEAREST。

需要比较清晰的图像使用GL10.GL_NEAREST:

而使用GL10.GL_LINEAR则会得到一个较模糊的图像:

UV Mapping

下一步要告知OpenGL库如何将Bitmap的像素映射到Mesh上。这可以分为两步来完成:

定义UV坐标

UV Mapping指将Bitmap的像素映射到Mesh上的顶点。UV坐标定义为左上角(0,0),右下角(1,1)(因为使用的2D Texture),下图坐标显示了UV坐标,右边为我们需要染色的平面的顶点顺序:

为了能正确的匹配,需要把UV坐标中的(0,1)映射到顶点0,(1,1)映射到顶点2等等。

1
2
3
4
float textureCoordinates[] = { 0 .0f, 1 .0f,
  1 .0f, 1 .0f,
  0 .0f, 0 .0f,
  1 .0f, 0 .0f };

如果使用如下坐标定义:

1
2
3
4
float textureCoordinates[] = { 0 .0f, 0 .5f,
  0 .5f, 0 .5f,
  0 .0f, 0 .0f,
  0 .5f, 0 .0f };

Texture匹配到Plane的左上角部分。

1
2
3
4
float textureCoordinates[] = { 0 .0f, 2 .0f,
  2 .0f, 2 .0f,
  0 .0f, 0 .0f,
  2 .0f, 0 .0f };

将使用一些不存在的Texture去渲染平面(UV坐标为0,0-1,1 而 (0,0)-(2,2)定义超过UV定义的大小),这时需要告诉OpenGL库如何去渲染这些不存在的Texture部分。

有两种设置

  • GL_REPEAT 重复Texture。
  • GL_CLAMP_TO_EDGE 只靠边线绘制一次。

下面有四种不同组合:

本例使用如下配置:

1
2
3
4
5
6
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资源和Texture绑定起来:

1
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0 , bitmap, 0 );

使用Texture

为了能够使用上面定义的Texture,需要创建一Buffer来存储UV坐标:

1
2
3
4
5
FloatBuffer byteBuf = ByteBuffer.allocateDirect(texture.length * 4 );
byteBuf.order(ByteOrder.nativeOrder());
textureBuffer = byteBuf.asFloatBuffer();
textureBuffer.put(textureCoordinates);
textureBuffer.position( 0 );

渲染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Telling OpenGL to enable textures.
gl.glEnable(GL10.GL_TEXTURE_2D);
// Tell OpenGL where our texture is located.
gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[ 0 ]);
// Tell OpenGL to enable the use of UV coordinates.
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
// Telling OpenGL where our UV coordinates are.
gl.glTexCoordPointer( 2 , GL10.GL_FLOAT, 0 , textureBuffer);
  
// ... here goes the rendering of the mesh ...
  
// Disable the use of UV coordinates.
gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
// Disable the use of textures.
gl.glDisable(GL10.GL_TEXTURE_2D);

本例代码是在一个平面上(SimplePlane)下使用Texture来渲染,首先是修改Mesh基类,使它能够支持定义UV 坐标:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Our UV texture buffer.
private FloatBuffer mTextureBuffer;
  
/**
  * Set the texture coordinates.
  *
  * @param textureCoords
  */
protected void setTextureCoordinates( float [] textureCoords) {
  // float is 4 bytes, therefore we multiply the number if
  // vertices with 4.
  ByteBuffer byteBuf = ByteBuffer.allocateDirect(
  textureCoords.length * 4 );
  byteBuf.order(ByteOrder.nativeOrder());
  mTextureBuffer = byteBuf.asFloatBuffer();
  mTextureBuffer.put(textureCoords);
  mTextureBuffer.position( 0 );
}

并添加设置Bitmap和创建Texture的方法:

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
// Our texture id.
private int mTextureId = - 1 ;
  
// The bitmap we want to load as a texture.
private Bitmap mBitmap;
  
/**
  * Set the bitmap to load into a texture.
  *
  * @param bitmap
  */
public void loadBitmap(Bitmap bitmap) {
  this .mBitmap = bitmap;
  mShouldLoadTexture = true ;
}
  
/**
  * Loads the texture.
  *
  * @param gl
  */
private void loadGLTexture(GL10 gl) {
  // Generate one texture pointer...
  int [] textures = new int [ 1 ];
  gl.glGenTextures( 1 , textures, 0 );
  mTextureId = textures[ 0 ];
  
  // ...and bind it to our array
  gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureId);
  
  // Create Nearest Filtered Texture
  gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER,
  GL10.GL_LINEAR);
  gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER,
  GL10.GL_LINEAR);
  
  // Different possible texture parameters, e.g. GL10.GL_CLAMP_TO_EDGE
  gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,
  GL10.GL_CLAMP_TO_EDGE);
  gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,
  GL10.GL_REPEAT);
  
  // Use the Android GLUtils to specify a two-dimensional texture image
  // from our bitmap
  GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0 , mBitmap, 0 );
}

最后修改draw方法来渲染材质:

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
// Indicates if we need to load the texture.
private boolean mShouldLoadTexture = false ;
  
/**
  * Render the mesh.
  *
  * @param gl
  *            the OpenGL context to render to.
  */
public void draw(GL10 gl) {
  ...
  
  // Smooth color
  if (mColorBuffer != null ) {
  // Enable the color array buffer to be used during rendering.
  gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
  gl.glColorPointer( 4 , GL10.GL_FLOAT, 0 , mColorBuffer);
  }
  
  if (mShouldLoadTexture) {
  loadGLTexture(gl);
  mShouldLoadTexture = false ;
  }
  if (mTextureId != - 1 && mTextureBuffer != null ) {
  gl.glEnable(GL10.GL_TEXTURE_2D);
  // Enable the texture state
  gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
  
  // Point to our buffers
  gl.glTexCoordPointer( 2 , GL10.GL_FLOAT, 0 , mTextureBuffer);
  gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureId);
  }
  
  gl.glTranslatef(x, y, z);
  
  ...
  
  // Point out the where the color buffer is.
  gl.glDrawElements(GL10.GL_TRIANGLES, mNumOfIndices,
  GL10.GL_UNSIGNED_SHORT, mIndicesBuffer);
  
  ...
  
  if (mTextureId != - 1 && mTextureBuffer != null ) {
  gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
  }
  
  ...
  
}

本例使用的SimplePlane定义如下:

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
package se.jayway.opengl.tutorial.mesh;
  
/**
  * SimplePlane is a setup class for Mesh that creates a plane mesh.
  *
  * @author Per-Erik Bergman (per-erik.bergman@jayway.com)
  *
  */
public class SimplePlane extends Mesh {
  /**
  * Create a plane with a default with and height of 1 unit.
  */
  public SimplePlane() {
  this ( 1 , 1 );
  }
  
  /**
  * Create a plane.
  *
  * @param width
  *            the width of the plane.
  * @param height
  *            the height of the plane.
  */
  public SimplePlane( float width, float height) {
  // Mapping coordinates for the vertices
  float textureCoordinates[] = { 0 .0f, 2 .0f, //
  2 .0f, 2 .0f, //
  0 .0f, 0 .0f, //
  2 .0f, 0 .0f, //
  };
  
  short [] indices = new short [] { 0 , 1 , 2 , 1 , 3 , 2 };
  
  float [] vertices = new float [] { - 0 .5f, - 0 .5f, 0 .0f,
  0 .5f, - 0 .5f, 0 .0f,
  - 0 .5f,  0 .5f, 0 .0f,
  0 .5f, 0 .5f, 0 .0f };
  
  setIndices(indices);
  setVertices(vertices);
  setTextureCoordinates(textureCoordinates);
  }
}

本例示例代码下载 ,到本篇为止介绍了OpenGL ES开发的基本方法,更详细的教程将在以后发布,后面先回到Android ApiDemos中OpenGL ES的示例。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值