原创内容,转载请注明EOE论坛。 声明:因为本人的学习都是居于杨丰盛老师的《Android应用开发揭秘》一书,因此,本内容教程代码和大部分的内容都来自公布在网络上的杨老师的代码。C/C++部分可以看NeHe的OpenGL教程:http://yarin.blog.51cto.com/1130898/380974
效果图片
1 . 局部顶点:我们将这张图片由X、Y轴平均分割成44份,也就是有45个分割点,这些构成小方格的点就是局部顶点了。
2. 局部纹理:因为顶点是局部的,也就是说,要对局部顶点构成的每一个小方格进行纹理(贴图)。纹理的单位也整数“1”,所以,每个小方格就表示纹理的1/44部分了。
3. 三角函数:因为三角函数“正弦”或者“余弦”在坐标轴上是波动的,那么我们将图片平躺在以X、Y轴的平面上,Z轴作为表示“正弦”或者“余弦”的波动轴。那么就会有我们看到的波动的效果了。
上主要代码:
package com.geolo.android;
/*
* 代码大部分注释源于杨丰盛的《Android应用开发揭秘》
* */
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLSurfaceView.Renderer;
import android.opengl.GLU;
import android.opengl.GLUtils;
public class MyRenderer implements Renderer {
private float vertex[][][]=new float[45][45][3];//局部顶点数组(45,45,3)
private float hold;// 局部顶点数据的临时变量
private float angleX,angleY = 0;
public float getAngleY() {
return angleY;
}
public void setAngleY(float angleY) {
this.angleY = angleY;
}
private Bitmap mBitmap = null;
private int textrueId = 0;
private FloatBuffer texCoord = FloatBuffer.allocate(8);
private FloatBuffer points = FloatBuffer.allocate(12);
int wiggle_count = 0;// 指定旗形波浪的运动速度
public MyRenderer(Context context){//构造函数,用来初始化纹理用的图片
mBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.img);
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST);//告诉系统需要对透视进行修正
gl.glClearColor(0, 0, 0, 1);//设置清理屏幕的颜色
gl.glEnable(GL10.GL_DEPTH_TEST);//启用深度缓存
gl.glClearDepthf(10.0f);//设置深度测试
gl.glDepthFunc(GL10.GL_LEQUAL);//深度测试的类型(小于或者等于时我们都渲染)
setupTexture(gl);//加载纹理
initData();//初始化局部顶点的数据
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
float radio = (float)width/height;
gl.glViewport(0, 0, width, height);//设置视口(OpenGL场景的大小)
gl.glMatrixMode(GL10.GL_PROJECTION);//设置投影矩阵为透视投影
gl.glLoadIdentity();//重置投影矩阵(置为单位矩阵)
gl.glFrustumf(-radio, radio, -1, 1, 1, 30);//创建一个透视投影矩阵(设置视口大小)
}
@Override
public void onDrawFrame(GL10 gl) {
// 首先清理屏幕
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
// 设置模型视图矩阵
gl.glMatrixMode(GL10.GL_MODELVIEW);
//重置矩阵
gl.glLoadIdentity();
// 视点变换
GLU.gluLookAt(gl, 0, 0, 3, 0, 0, 0, 0, 1, 0);
//绘制旗帜
drawFlag(gl);
}
/**装载纹理*/
private void setupTexture(GL10 gl){
gl.glEnable(GL10.GL_TEXTURE_2D);// 开启2D纹理贴图
IntBuffer intBuffer = IntBuffer.allocate(1);//创建一个int[]单位的缓冲区,用来存储该纹理的名称ID
gl.glGenTextures(1, intBuffer);//创建纹理,并将纹理ID值写入缓冲区。--不知道我的理解对不对。。。。
textrueId = intBuffer.get();//保存ID名称
//根据纹理ID绑定纹理
gl.glBindTexture(GL10.GL_TEXTURE_2D, textrueId);
// 生成纹理或者说载入纹理
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, mBitmap, 0);
// 配置图像也可以说是设置滤波
gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR_MIPMAP_NEAREST);
gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
//因为已经载入了纹理,因此,Bitmap可以被清除。
mBitmap.recycle();
mBitmap = null;
}
/**初始化顶点数据*/
private void initData(){
// 沿X平面循环
for(int x=0; x<45; x++){
// 沿Y平面循环
for(int y=0; y<45; y++){
// 向表面添加波浪效果
vertex[x][y][0]=((float)x/5.0f)-4.5f;//x
vertex[x][y][1]=(((float)y/5.0f)-4.5f); //y
vertex[x][y][2]=(float)(Math.sin(((((float)x/5.0f)*40.0f)/360.0f)*3.141592654*2.0f));//z
}
}
}
private void drawFlag(GL10 gl){
int x, y;// 循环变量
float float_x, float_y, float_xb, float_yb; //用来将旗形的波浪分割成很小的四边形
gl.glTranslatef(0.0f,0.0f,-12.0f);//平移操作
// 旋转操作
gl.glRotatef(angleX,1.0f,0.0f,0.0f);
gl.glRotatef(angleY,0.0f,1.0f,0.0f);
// 允许设置顶点数组和纹理坐标数组
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
// 设置顶点数组、纹理坐标数组
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, points);
gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, texCoord);
gl.glBindTexture(GL10.GL_TEXTURE_2D, textrueId);//绑定纹理
for (x = 0; x < 44; x++){
for (y = 0; y < 44; y++){
float_x = (float) (x) / 44.0f; // 生成X浮点值
float_y = (float) (y) / 44.0f; // 生成Y浮点值
float_xb = (float) (x + 1) / 44.0f; // X浮点值+0.0227f
float_yb = (float) (y + 1) / 44.0f; // Y浮点值+0.0227f
// 保存纹理坐标数组
texCoord.clear();
texCoord.put(float_x);
texCoord.put(float_y);
texCoord.put(float_x);
texCoord.put(float_yb);
texCoord.put(float_xb);
texCoord.put(float_yb);
texCoord.put(float_xb);
texCoord.put(float_y);
// 保存顶点数组
points.clear();
points.put(vertex[x][y][0]);
points.put(vertex[x][y][1]);
points.put(vertex[x][y][2]);
points.put(vertex[x][y + 1][0]);
points.put(vertex[x][y + 1][1]);
points.put(vertex[x][y + 1][2]);
points.put(vertex[x + 1][y + 1][0]);
points.put(vertex[x + 1][y + 1][1]);
points.put(vertex[x + 1][y + 1][2]);
points.put(vertex[x + 1][y][0]);
points.put(vertex[x + 1][y][1]);
points.put(vertex[x + 1][y][2]);
// 绘制
gl.glDrawArrays(GL10.GL_TRIANGLE_FAN, 0, 4);
}
}
// 禁止设置顶点数组、纹理坐标数组
gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
// 用来降低波浪速度(每隔2帧一次)
if( wiggle_count == 2 ){
// 沿Y平面循环
for( y = 0; y < 45; y++ ){
// 存储当前左侧波浪值
hold=vertex[0][y][2];
// 沿X平面循环
for( x = 0; x < 44; x++){
// 当前波浪值等于其右侧的波浪值
vertex[x][y][2] = vertex[x+1][y][2];
}
// 刚才的值成为最左侧的波浪值
vertex[44][y][2]=hold;
}
// 计数器清零
wiggle_count = 0;
}
wiggle_count++;
}
public float getAngleX() {
return angleX;
}
public void setAngleX(float angleX) {
this.angleX = angleX;
}
}
--> 分析下代码的主要部分:
for(int x=0; x<45; x++){
for(int y=0; y<45; y++) {
// 向表面添加波浪效果
points[x][y][0]=float((x/5.0f)-4.5f);
points[x][y][1]=float((y/5.0f)-4.5f);
points[x][y][2]=float(sin((((x/5.0f)*40.0f)/360.0f)*3.141592654*2.0f));
}
}
上面的两个循环初始化网格上的点。使用整数循环可以消除由于浮点运算取整造成的脉冲锯齿的出现。我们将x和y变量都除以5,再减去4.5。这样使得我们的波浪可以“居中”(译者:这样计算所得结果将落在区间[-4.5,4.5]之间)。(NeHe文章翻译)
点[x][y][2]最后的值就是一个sine函数计算的结果。Sin()函数需要一个弧度参变量。将float_x乘以40.0f,得到角度值。然后除以360.0f再乘以PI,乘以2,就转换为弧度了。(NeHe文章翻译)
for( y = 0; y < 45; y++ ){
// 存储当前左侧波浪值
hold=vertex[0][y][2];
// 沿X平面循环
for( x = 0; x < 44; x++){
// 当前波浪值等于其右侧的波浪值
vertex[x][y][2] = vertex[x+1][y][2];
}
// 刚才的值成为最左侧的波浪值
vertex[44][y][2]=hold;
}
我们每次要将当前的小方格的局部坐标保存起来,在下一次将该坐标传递给下一个小方格---物理学上,粒子波动的效果。比如跳绳。
如果疑问,我会在补充内容。
效果图: