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

jerry OpenGL ES 2015年11月25日 收藏

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

创建Bitmap对象

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

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

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

创建材质(Generating a texture)

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

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

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

  1. // Delete a texture.
  2. 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. // Scale up if the texture if smaller.
  2. gl.glTexParameterf(GL10.GL_TEXTURE_2D,
  3. GL10.GL_TEXTURE_MAG_FILTER,
  4. GL10.GL_LINEAR);
  5.  
  6. // scale linearly when image smalled than texture
  7. gl.glTexParameterf(GL10.GL_TEXTURE_2D,
  8. GL10.GL_TEXTURE_MIN_FILTER,
  9. 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. float textureCoordinates[] = {0.0f, 1.0f,
  2. 1.0f, 1.0f,
  3. 0.0f, 0.0f,
  4. 1.0f, 0.0f };

如果使用如下坐标定义:

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

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

  1. float textureCoordinates[] = {0.0f, 2.0f,
  2. 2.0f, 2.0f,
  3. 0.0f, 0.0f,
  4. 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. gl.glTexParameterf(GL10.GL_TEXTURE_2D,
  2. GL10.GL_TEXTURE_WRAP_S,
  3. GL10.GL_REPEAT);
  4. gl.glTexParameterf(GL10.GL_TEXTURE_2D,
  5. GL10.GL_TEXTURE_WRAP_T,
  6. GL10.GL_REPEAT);

然后是将Bitmap资源和Texture绑定起来:

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

使用Texture

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

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

渲染

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

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

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

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

  1. // Our texture id.
  2. private int mTextureId = -1;
  3.  
  4. // The bitmap we want to load as a texture.
  5. private Bitmap mBitmap;
  6.  
  7. /**
  8. * Set the bitmap to load into a texture.
  9. *
  10. * @param bitmap
  11. */
  12. public void loadBitmap(Bitmap bitmap) {
  13. this.mBitmap = bitmap;
  14. mShouldLoadTexture = true;
  15. }
  16.  
  17. /**
  18. * Loads the texture.
  19. *
  20. * @param gl
  21. */
  22. private void loadGLTexture(GL10 gl) {
  23. // Generate one texture pointer...
  24. int[] textures = new int[1];
  25. gl.glGenTextures(1, textures, 0);
  26. mTextureId = textures[0];
  27.  
  28. // ...and bind it to our array
  29. gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureId);
  30.  
  31. // Create Nearest Filtered Texture
  32. gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER,
  33. GL10.GL_LINEAR);
  34. gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER,
  35. GL10.GL_LINEAR);
  36.  
  37. // Different possible texture parameters, e.g. GL10.GL_CLAMP_TO_EDGE
  38. gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,
  39. GL10.GL_CLAMP_TO_EDGE);
  40. gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,
  41. GL10.GL_REPEAT);
  42.  
  43. // Use the Android GLUtils to specify a two-dimensional texture image
  44. // from our bitmap
  45. GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, mBitmap, 0);
  46. }
  47.  

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

  1. // Indicates if we need to load the texture.
  2. private boolean mShouldLoadTexture = false;
  3.  
  4. /**
  5. * Render the mesh.
  6. *
  7. * @param gl
  8. *            the OpenGL context to render to.
  9. */
  10. public void draw(GL10 gl) {
  11. ...
  12.  
  13. // Smooth color
  14. if (mColorBuffer != null) {
  15. // Enable the color array buffer to be used during rendering.
  16. gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
  17. gl.glColorPointer(4, GL10.GL_FLOAT, 0, mColorBuffer);
  18. }
  19.  
  20. if (mShouldLoadTexture) {
  21. loadGLTexture(gl);
  22. mShouldLoadTexture = false;
  23. }
  24. if (mTextureId != -1 && mTextureBuffer != null) {
  25. gl.glEnable(GL10.GL_TEXTURE_2D);
  26. // Enable the texture state
  27. gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
  28.  
  29. // Point to our buffers
  30. gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, mTextureBuffer);
  31. gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureId);
  32. }
  33.  
  34. gl.glTranslatef(x, y, z);
  35.  
  36. ...
  37.  
  38. // Point out the where the color buffer is.
  39. gl.glDrawElements(GL10.GL_TRIANGLES, mNumOfIndices,
  40. GL10.GL_UNSIGNED_SHORT, mIndicesBuffer);
  41.  
  42. ...
  43.  
  44. if (mTextureId != -1 && mTextureBuffer != null) {
  45. gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
  46. }
  47.  
  48. ...
  49.  
  50. }

使用的SimplePlane定义如下:

  1. package se.jayway.opengl.tutorial.mesh;
  2.  
  3. /**
  4. * SimplePlane is a setup class for Mesh that creates a plane mesh.
  5. *
  6. * @author Per-Erik Bergman (per-erik.bergman@jayway.com)
  7. *
  8. */
  9. public class SimplePlane extends Mesh {
  10. /**
  11. * Create a plane with a default with and height of 1 unit.
  12. */
  13. public SimplePlane() {
  14. this(1, 1);
  15. }
  16.  
  17. /**
  18. * Create a plane.
  19. *
  20. * @param width
  21. *            the width of the plane.
  22. * @param height
  23. *            the height of the plane.
  24. */
  25. public SimplePlane(float width, float height) {
  26. // Mapping coordinates for the vertices
  27. float textureCoordinates[] = { 0.0f, 2.0f, //
  28. 2.0f, 2.0f, //
  29. 0.0f, 0.0f, //
  30. 2.0f, 0.0f, //
  31. };
  32.  
  33. short[] indices = new short[] { 0, 1, 2, 1, 3, 2 };
  34.  
  35. float[] vertices = new float[] { -0.5f, -0.5f, 0.0f,
  36. 0.5f, -0.5f, 0.0f,
  37. -0.5f,  0.5f, 0.0f,
  38. 0.5f, 0.5f, 0.0f };
  39.  
  40. setIndices(indices);
  41. setVertices(vertices);
  42. setTextureCoordinates(textureCoordinates);
  43. }
  44. }
  45.  

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