Android图形编程篇--OpenGL实现三角形隧道效果

学了这么久的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比,这个影响到视野的截面有多大;

https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=1140002089,1089941354&fm=26&gp=0.jpg

透视投影原理图

接着就执行到了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);
        //顶点,有 xyz值,所以是 3
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertices_buffer);
        //贴图顶点有两个uv
        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

最后感谢收看,有问题多交流!!!
 

猜你喜欢

转载自blog.csdn.net/s1120080286/article/details/87201377
今日推荐