在这一课,我们将添加光照和输入控制,它可以是我们的应用看起来更美观,在这一课我们将演示三种不同的纹理滤波方式。演示如何通过键盘和触屏操作来移动场景中的对象,同时将演示在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();
}
}
程序的运行结果如下图所示: