关键字: android OpenGL 移动开发 教程
在这一课,我们将添加光照和输入控制,它可以是我们的应用看起来更美观,在这一课我们将演示三种不同的纹理滤波方式。演示如何通过键盘和触屏操作来移动场景中的对象,同时将演示在OpenGL场景中应用简单的光照。
为了演示用户输入,捕获用户输入事件,我们需要从GLSurfaceView导出一个子类来重载事件处理方法(比如重载onKeyUp(), onTouchEvent())。该子类的命名为MyGLSurfaceView,我们重载了onKeyUp和onTouchEvent方法,分别用来处理键盘和触屏事件。
在第六课的基础上,TextureCube处理更换了纹理,其他都没做改变。在MyGLRenderer中加了几个控制变量:
floatangleX = 0.0f;
floatangleY = 0.0f;
floatspeedX = 0.0f;
floatspeedY = 0.0f;
floatz = -6.0f;
用于控制立方体的在Z方向的位置,在x和y方向旋转的角度和速度。对其中onDrawFrame方法中的内容做了相应调整,方便我们演示用户输入的效果。
public void onDrawFrame(GL10 gl) { // 清除屏幕和深度缓存 gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); // 启动和关闭光源 if (lightingEnabled) { gl.glEnable(GL10.GL_LIGHTING); } else { gl.glDisable(GL10.GL_LIGHTING); } // -------- 渲染彩色立方体 ------ // 重置当前的模型观察矩阵 gl.glLoadIdentity(); // 纵深向里移动z gl.glTranslatef(0.0f, 0.0f, z); // 绕X轴旋转立方体 gl.glRotatef(angleX, 1.0f, 0.0f, 0.0f); // 绕Y轴旋转立方体 gl.glRotatef(angleY, 0.0f, 1.0f, 0.0f); // 画立方体 cube.draw(gl, currentTextureFilter); // 每次刷新之后更新旋转角度 angleX += speedX; angleY += speedY; }
MyGLSurfaceView实现了用户输入控制。
相应的,GLLesson07Activity.java中onCreate方法的代码调整为:
publicvoid onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
glView =new MyGLSurfaceView(this);//创建一个定制的GLSurfaceView
setContentView(glView);
}。
编译运行,我们就可以通过输入一些控制来看立方体空间位置的变化。
现在我们演示不同的纹理映射滤波方式,首先对TextureCube中的代码进行调整textureIDs现在数组的的长度为3。
int[]textureIDs =newint[3];//纹理-ID数组
draw方法添加了一个新的输入变量inttextureFilter用于选择滤波方式。
public class TextureCube {
…………
//通过纹理坐标ID选择纹理的滤波方式
gl.glBindTexture(GL10.GL_TEXTURE_2D,textureIDs[textureFilter]);
…………
gl.glGenTextures(3, textureIDs, 0);//为3个纹理生成纹理ID数组
//生成 Nearest方式滤波的纹理并绑定到纹理0
gl.glBindTexture(GL10.GL_TEXTURE_2D,textureIDs[0]);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_NEAREST);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bmp, 0);
//生成Linear方式滤波的纹理并绑定到纹理1
gl.glBindTexture(GL10.GL_TEXTURE_2D,textureIDs[1]);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bmp, 0);
//生成mipmapped方式滤波的纹理并绑定到纹理2
gl.glBindTexture(GL10.GL_TEXTURE_2D,textureIDs[2]);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR_MIPMAP_NEAREST);
if(glinstanceof GL11) {
gl.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_GENERATE_MIPMAP, GL11.GL_TRUE);
}
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bmp, 0);
bmp.recycle();
}
MyGLRenderer中的代码修还为:
…………
intcurrentTextureFilter = 0; //当前纹理滤波方式
…………
cube.draw(gl,currentTextureFilter);
…………
在MyGLSurfaceView的onKeyUp方法,添加
case KeyEvent.KEYCODE_DPAD_CENTER: //选择纹理滤波方式
renderer.currentTextureFilter = (renderer.currentTextureFilter + 1) % 3;
break;
然后编译运行。选择DPAD_CENTER按钮点击,观察纹理的效果,就会发现NEAEST效果差一些,其他两种方式的效果要好些。
最后一步,我们演示光照效果。在MyGLRenderer中加入灯光控制的代码
声明几个控制光照的变量
booleanlightingEnabled =false; //是否打开光照
privatefloat[]lightAmbient = {0.5f, 0.5f, 0.5f, 1.0f};
privatefloat[]lightDiffuse = {1.0f, 1.0f, 1.0f, 1.0f};
privatefloat[]lightPosition = {0.0f, 0.0f, 2.0f, 1.0f};
在onSurfaceCreated中添加设置光源的代码:
// 设置光源 GL_LIGHT1的环境光(ambient)和漫射光(diffuse)
gl.glLightfv(GL10.GL_LIGHT1, GL10.GL_AMBIENT,lightAmbient, 0);
gl.glLightfv(GL10.GL_LIGHT1, GL10.GL_DIFFUSE,lightDiffuse, 0);
gl.glLightfv(GL10.GL_LIGHT1, GL10.GL_POSITION,lightPosition, 0);
gl.glEnable(GL10.GL_LIGHT1); //使能光源1
gl.glEnable(GL10.GL_LIGHT0); //使能缺省光源0
在onDramFrame中加入启动和关闭光源的代码
// 启动和关闭光源
if (lightingEnabled) {
gl.glEnable(GL10.GL_LIGHTING);
} else {
gl.glDisable(GL10.GL_LIGHTING);
}
最后在MyGLSurfece.java中的onKey方法中加入用户控制代码,用于打开个关闭光源。
case KeyEvent.KEYCODE_L: //光源开关
renderer.lightingEnabled = !renderer.lightingEnabled;
break;
代码:
GLLesson07Activity.java
package wintop.gllesson07; import android.app.Activity; import android.opengl.GLSurfaceView; import android.os.Bundle; public class GLLesson07Activity extends Activity { private GLSurfaceView glView; // 使用GLSurfaceView 导出类 /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); glView = new MyGLSurfaceView(this); // 创建一个定制的GLSurfaceView setContentView(glView); } // Call back when the activity is going into the background @Override protected void onPause(){ super.onPause(); glView.onPause(); } // Call back after onPause() @Override protected void onResume() { super.onResume(); glView.onResume(); } }
MyGLRenderer.java
package wintop.gllesson07; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; import android.content.Context; import android.opengl.GLSurfaceView; import android.opengl.GLU; public class MyGLRenderer implements GLSurfaceView.Renderer { private Context context; // 应用的上下文句柄 private TextureCube cube; // 下面的变量控制立方体的在Z方向的位置,在x和y方向旋转的角度和速度 float angleX = 0.0f; float angleY = 0.0f; float speedX = 0.0f; float speedY = 0.0f; float z = -6.0f; int currentTextureFilter = 0; // 当前纹理滤波方式 // 光照 boolean lightingEnabled = false; // 是否打开光照 private float[] lightAmbient = {0.5f, 0.5f, 0.5f, 1.0f}; private float[] lightDiffuse = {1.0f, 1.0f, 1.0f, 1.0f}; private float[] lightPosition = {0.0f, 0.0f, 2.0f, 1.0f}; // Constructor with global application context public MyGLRenderer(Context context) { this.context = context; // 设置所用图形的数据数组缓冲区 cube = new TextureCube(); } // Call back when the surface is first created or re-created @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { // 启用阴影平滑 gl.glShadeModel(GL10.GL_SMOOTH); // 设置背景颜色 gl.glClearColor(0.2f, 0.4f, 0.52f, 1.0f); // 设置深度缓存 gl.glClearDepthf(1.0f); // 启用深度测试 gl.glEnable(GL10.GL_DEPTH_TEST); // 所作深度测试的类型 gl.glDepthFunc(GL10.GL_LEQUAL); // 告诉系统对透视进行修正 gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST); // 禁止抖动以取得更好的性能 gl.glDisable(GL10.GL_DITHER); // 设置纹理 cube.loadTexture(gl, context); // 加载纹理 gl.glEnable(GL10.GL_TEXTURE_2D); // 纹理使能 // 设置光源 GL_LIGHT1 的环境光(ambient)和 漫射光(diffuse) gl.glLightfv(GL10.GL_LIGHT1, GL10.GL_AMBIENT, lightAmbient, 0); gl.glLightfv(GL10.GL_LIGHT1, GL10.GL_DIFFUSE, lightDiffuse, 0); gl.glLightfv(GL10.GL_LIGHT1, GL10.GL_POSITION, lightPosition, 0); gl.glEnable(GL10.GL_LIGHT1); // 使能光源1 gl.glEnable(GL10.GL_LIGHT0); // 使能缺省光源0 } // Call back after onSurfaceCreated() or whenever the window's size changes @Override public void onSurfaceChanged(GL10 gl, int width, int height) { if(height == 0) // 防止被零除 { height = 1; } // 重置当前的视图区域 gl.glViewport(0, 0, width, height); // 选择投影矩阵 gl.glMatrixMode(GL10.GL_PROJECTION); // 重置投影矩阵 gl.glLoadIdentity(); // 设置视图区域的大小 GLU.gluPerspective(gl, 45.0f, (float)width/(float)height,0.1f,100.0f); // 选择模型观察矩阵 gl.glMatrixMode(GL10.GL_MODELVIEW); // 重置模型观察矩阵 gl.glLoadIdentity(); } // Call back to draw the current frame. @Override public void onDrawFrame(GL10 gl) { // 清除屏幕和深度缓存 gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); // 启动和关闭光源 if (lightingEnabled) { gl.glEnable(GL10.GL_LIGHTING); } else { gl.glDisable(GL10.GL_LIGHTING); } // -------- 渲染彩色立方体 ------ // 重置当前的模型观察矩阵 gl.glLoadIdentity(); // 纵深向里移动z gl.glTranslatef(0.0f, 0.0f, z); // 绕X轴旋转立方体 gl.glRotatef(angleX, 1.0f, 0.0f, 0.0f); // 绕Y轴旋转立方体 gl.glRotatef(angleY, 0.0f, 1.0f, 0.0f); // 画立方体 cube.draw(gl, currentTextureFilter); // 每次刷新之后更新旋转角度 angleX += speedX; angleY += speedY; } }
MyGLSurfaceView.java
package wintop.gllesson07; import android.opengl.GLSurfaceView; import android.content.Context; import android.view.KeyEvent; import android.view.MotionEvent; // 为了捕获用户输入事件,我们需要从GLSurfaceView导出一个子类来重载事件处理 // 方法(比如重载onKeyUp(), onTouchEvent()) public class MyGLSurfaceView extends GLSurfaceView { MyGLRenderer renderer; // 定制的 GL 渲染器 // 用于触屏事件 private final float TOUCH_SCALE_FACTOR = 180.0f / 320.0f; private float previousX; private float previousY; // 构造函数 - 创建和设置渲染器 public MyGLSurfaceView(Context context) { super(context); renderer = new MyGLRenderer(context); this.setRenderer(renderer); // 设置焦点,否则键盘/按钮可能无响应 this.requestFocus(); this.setFocusableInTouchMode(true); } // 处理键盘事件 @Override public boolean onKeyUp(int keyCode, KeyEvent evt) { switch(keyCode) { case KeyEvent.KEYCODE_DPAD_LEFT: // 减少Y向旋转速度 renderer.speedY -= 0.1f; break; case KeyEvent.KEYCODE_DPAD_RIGHT: // 增加Y向旋转速度 renderer.speedY += 0.1f; break; case KeyEvent.KEYCODE_DPAD_UP: // 减少X向旋转速度 renderer.speedX -= 0.1f; break; case KeyEvent.KEYCODE_DPAD_DOWN: // 增加X向旋转速度 renderer.speedX += 0.1f; break; case KeyEvent.KEYCODE_A: // 缩小 (减少 z) renderer.z -= 0.2f; break; case KeyEvent.KEYCODE_Z: // 放大 (增加 z) renderer.z += 0.2f; break; case KeyEvent.KEYCODE_DPAD_CENTER: // 选择纹理滤波方式 renderer.currentTextureFilter = (renderer.currentTextureFilter + 1) % 3; break; case KeyEvent.KEYCODE_L: // 光源开关 renderer.lightingEnabled = !renderer.lightingEnabled; break; default: return super.onKeyUp(keyCode, evt); } return true; // 事件处理后返回 } // 处理触屏事件 @Override public boolean onTouchEvent(final MotionEvent evt) { float currentX = evt.getX(); float currentY = evt.getY(); float deltaX, deltaY; switch (evt.getAction()) { case MotionEvent.ACTION_MOVE: // 根据物体的移动调整合理的旋转角度 deltaX = currentX - previousX; deltaY = currentY - previousY; renderer.angleX += deltaY * TOUCH_SCALE_FACTOR; renderer.angleY += deltaX * TOUCH_SCALE_FACTOR; } // Save current x, y previousX = currentX; previousY = currentY; return true; // 事件处理后返回 } }
TextureCube.java
package wintop.gllesson07; 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 javax.microedition.khronos.opengles.GL11; 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[3]; // 纹理-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, int textureFilter){ // 选择滤波方式 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); // 定义纹理坐标数组缓冲区 // 通过纹理坐标ID选择纹理的滤波方式 gl.glBindTexture(GL10.GL_TEXTURE_2D, textureIDs[textureFilter]); // 前 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/crate.bmp" InputStream ins = context.getResources().openRawResource(R.drawable.crate); Bitmap bmp; try { // 读取并将输入流解码成位图 bmp = BitmapFactory.decodeStream(ins); } finally { try { ins.close(); }catch(IOException e) {} } gl.glGenTextures(3, textureIDs, 0); // 为3个纹理生成纹理ID数组 // 生成 Nearest方式滤波的纹理并绑定到纹理0 gl.glBindTexture(GL10.GL_TEXTURE_2D, textureIDs[0]); gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_NEAREST); gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST); GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bmp, 0); // 生成Linear方式滤波的纹理并绑定到纹理1 gl.glBindTexture(GL10.GL_TEXTURE_2D, textureIDs[1]); gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR); GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bmp, 0); // 生成 mipmapped方式滤波的纹理并绑定到纹理2 gl.glBindTexture(GL10.GL_TEXTURE_2D, textureIDs[2]); gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR); gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR_MIPMAP_NEAREST); if(gl instanceof GL11) { gl.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_GENERATE_MIPMAP, GL11.GL_TRUE); } GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bmp, 0); bmp.recycle(); } }
程序的运行结果如下图所示:
代码下载地址:http://download.csdn.net/detail/seniorwizard/4470542