学了这么久的Android OpenGL的知识,今天花一部分时间实现一个三角形隧道的效果,这个案例结合了一些基础的Android OpenGL ES的知识点,效果图如下图所示。
三角隧道效果示图首先,在GLSurfaceView的渲染器Render中实现onSurfaceCreated方法,该方法主要完成屏幕的初始化操作等,包括屏幕颜色清空,允许深度缓存测试,设置灯光光效等。这里解释下深度缓存测试,启用了深度缓存之后,OpenGL在绘制的时候就会检查,当前像素前面是否有别的像素,如果别的像素挡道了它,那它就不会绘制,也就是说,OpenGL就只绘制最前面的一层,所以需要绘制透明图片时,就需要关闭它。
@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);
//初始化app
initApp(gl);
//设置灯光光效
setUpLight(gl);
}
setUpLight(gl)用于设置光效,执行步骤为:开启光效à使用0号光源à设置环境光à设置散射光à设置聚焦光。
private void setUpLight(GL10 gl) {
//开启光效
gl.glEnable(GL10.GL_LIGHTING);
//使用0号光源
gl.glEnable(GL10.GL_LIGHT0);
//环境光颜色
FloatBuffer lightAmbient = FloatBuffer.wrap(new float[]{0.4f, 0.4f, 0.4f, 1});
//设置环境光
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, lightAmbient);
//设置散射光
FloatBuffer lightDiffuse = FloatBuffer.wrap(new float[]{0.8f, 0.8f, 0.8f, 1});
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, lightDiffuse);
//设置聚焦光
FloatBuffer lightPosition = FloatBuffer.wrap(new float[]{10f, 10f, 10f});
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, lightPosition);
}
接着当程序执行到onSurfaceChanged方法时,在这个方法中设置视口大小,设置透视投影,创建初始的透视投影矩阵。gl.glLoadIdentity()的目的是将居住重置,这样操作以后之前的矩阵状态都会失效。
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
float radio=(float)width/(float)height;
gl.glViewport(0,0,width,height);
//设置透视投影
gl.glMatrixMode(GL10.GL_PROJECTION);
//glLoadIdentity()的作用就是把矩阵堆栈中的在栈顶的那个矩阵置为单位矩阵,好让之前的任何变换都不影响后面的变化。
gl.glLoadIdentity();
//创建一个对称的透视投影矩阵
GLU.gluPerspective(gl,45.0f,radio,1f,10f);
}
OpenGL有两种投影:正射投影(垂直投影)和透视投影。透视投影通过指定一个平截头体来定义视见体的范围(如下图所示,人的位置相当于摄像机,中间物体为拍摄物体位置);
void gluPerspective (GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar)
fovy是眼睛上下睁开的幅度,角度值,值越小,视野范围越狭小(眯眼),值越大,视野范围越宽阔(睁开铜铃般的大眼),在我们的代码这个参数是45.0f;
zNear表示近裁剪面到眼睛的距离,zFar表示远裁剪面到眼睛的距离,注意zNear和zFar不能设置设置为负值(你怎么看到眼睛后面的东西);
aspect表示裁剪面的宽w高h比,这个影响到视野的截面有多大;
透视投影原理图
接着就执行到了onDrawFrame的方法了,每次执行都要清除颜色和深度缓存,设置摄像机观测点矩阵,位置在(0,0,1)位置,开启顶点,颜色,纹理绘制功能,执行绘制,关闭顶点,颜色,纹理贴图等功能。
@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(); //视点变换,eyex, eyey,eyez 相机在世界坐标的位置,centerx,centery,centerz 相机镜头对准的物体在世界坐标的位置 //第一组数据就是脑袋的位置 //第二组数据就是眼睛看的物体的位置 //第三组就是头顶朝向的方向(因为你可以歪着头看同一个物体)。 GLU.gluLookAt(gl, 0, 0, 1, centerX, centerY, 0, 0, 1, 0); //设置平滑的渲染模式 gl.glShadeModel(GL10.GL_SMOOTH); //允许绘制顶点 gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); gl.glEnableClientState(GL10.GL_COLOR_ARRAY); gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY); mTunnel3D.render(gl,-0.6f); mTunnel3D.nextFrame(); gl.glDisableClientState(GL10.GL_VERTEX_ARRAY); gl.glDisableClientState(GL10.GL_COLOR_ARRAY); gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY); } 具体的绘制操作被放在试题类Tunnel3D中完成,下面来看下Tunnel3D的一些操作。Tunnel3D执行初始化变量操作, public Tunnel3D(int revolution, int depth) { start_a = 0; start_v = 0; nx = revolution; ny = depth; nv = nx * ny; colors = new byte[nv * 3]; vertics = new float[nv * 3]; faces = new short[((nx + 1) * (ny - 1)) << 1]; texture = new float[nv * 2]; //生成数据 genVertex(); genFaces(); genColors(); genTexture(); buildBuffers(); fillVertex(); fillFaces(); fillColors(); fillTexture(); } genVertex函数用于生成顶点的坐标数组,这里采用三角变化函数的用法进行生成。 //产生顶点坐标 private void genVertex() { int i = 0; double delta_x = 360.0 / (double) nx; double delta_y = 1.0; for (int y = 0; y < ny; y++) { for (int x = 0; x < nx; x++) { //sin度的计算值 vertics[i + 0] = (float) Math.sin(Math.toRadians((double) x * delta_x)); vertics[i + 1] = (float) Math.cos(Math.toRadians((double) x * delta_x)); //todo vertics[i + 2] = (float) (-y * delta_y); i += 3; } } Log.v("Yu","vertics:"+vertics); } buildBuffers函数用于数组转buffer的操作,对于openGL来说操作都是通过buffer来进行的。 private void buildBuffers() { vertices_direct = ByteBuffer.allocateDirect(vertics.length * (Float.SIZE >> 3)); vertices_direct.order(ByteOrder.nativeOrder()); vertices_buffer = vertices_direct.asFloatBuffer(); faces_direct = ByteBuffer.allocateDirect(faces.length * (Short.SIZE >> 3)); faces_direct.order(ByteOrder.nativeOrder()); faces_buffer = faces_direct.asShortBuffer(); // colors_direct = ByteBuffer.allocateDirect(colors.length * (Float.SIZE >> 3)); // colors_direct.order(ByteOrder.nativeOrder()); // colors_buffer = colors_direct.asFloatBuffer(); colors_buffer = ByteBuffer.allocateDirect(colors.length); texture_direct = ByteBuffer.allocateDirect(texture.length * (Float.SIZE >> 3)); texture_direct.order(ByteOrder.nativeOrder()); texture_buffer = texture_direct.asFloatBuffer(); } //填充vertex private void fillVertex() { vertices_buffer.clear(); vertices_buffer.put(vertics); vertices_buffer.position(0); } 渲染render函数用于执行三角隧道的渲染,最后通过gl.glDrawArrays绘制三角形。 //渲染隧道 public void render(GL10 gl, float depth) { //gl.glTranslatef(-px, -py, depth); //顶点,有 x、y、z值,所以是 3 gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertices_buffer); //贴图顶点有两个u和v gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, texture_buffer); //颜色有3个值r,g,b gl.glColorPointer(3, GL10.GL_UNSIGNED_BYTE, 0, colors_buffer); // int dy = 0; // int nf = (nx + 1) << 1; // faces_buffer.position(0); // for (int y = 0; y < (ny - 1); y++) { // //Log.v("Yu","标记:"+y); // gl.glDrawElements(GL10.GL_TRIANGLE_STRIP, nf, GL10.GL_UNSIGNED_BYTE, faces_buffer); // dy += nf; // faces_buffer.position(dy); // } //绘制三角形带 gl.glDrawArrays(GL10.GL_TRIANGLES, 0, vertics.length); }
nextFrame函数用于下一帧坐标位置变化的操作。
public void nextFrame() {
int i = 0;
double delta_x = 360 / nx;
double delta_y = 1.0;
double delta_z = 220 / ny;
for (int y = 0; y < ny; y++) {
double sa = start_a + (ny - y) * delta_z;
float sx = (float) Math.cos(Math.toRadians(sa));
float sy = (float) Math.sin(Math.toRadians(sa));
if (y == 0) {
px = sx;
py = sy;
}
for (int x = 0; x < nx; x++) {
vertics[i + 0] = sx + (float) Math.sin(Math.toRadians((double) x * delta_x));
vertics[i + 1] = sy + (float) Math.cos(Math.toRadians((double)x * delta_x));
vertics[i + 2] = (float) (-y * delta_y);
i += 3;
}
}
start_a += 2;
fillVertex();
i = 0;
delta_x = 1 / nx;
delta_y = 1 / ny;
for (int y = 0; y < ny; y++) {
for (int x = 0; x < nx; x++) {
texture[i + 0] = (float) x * (float) delta_x;
texture[i + 1] = start_v + (float) y * (float) delta_y;
}
}
start_v += 0.05f;
fillTexture();
}
以上就是三角隧道效果的代码,完整代码可以到本人的GitHub上下载
地址:https://github.com/HOTFIGHTER/OpenGL-for-Android
最后感谢收看,有问题多交流!!!