Android 绘制 2D、3D 图形

2022-02-27  本文已影响0人  gaookey
image.png

绘制 2D 图形

使用 OpenGL ES 需要三个步骤

  1. 创建 GLSurfaceView 组件,使用 Activity 来显示 GLSurfaceView 组件。
  2. GLSurfaceView 组件创建 GLSurfaceView.Renderer 实例,实现 GLSurfaceView.Renderer 类时需要实现该接口里的三个方法。
  1. 调用 GLSurfaceView 组件的 setRenderer() 方法指定 Renderer 对象,该 Renderer 对象将会完成 GLSurfaceView 里 3D 图形的绘制

实现 Renderer 类时需要实现三个方法,这三个方法都有一个 GL10 形参,它就代表了 OpenGL ES 的“绘制画笔”

SurfaceView 被创建时,系统会回调 Renderer 对象的 onSurfaceCreated() 方法,该方法可以对 OpenGL ES 执行一些无须任何改变的初始化。

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {

        // 关闭抗抖动,可以提高性能
        gl.glDisable(GL10.GL_DITHER);
        // 设置系统对透视进行修正
        gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);
        // 用黑色“清屏”
        gl.glClearColor(0, 0, 0, 0);
        // 设置阴影平滑模式
        gl.glShadeModel(GL10.GL_SMOOTH);
        // 启用深度测试。所谓深度测试,就是让 OpenGL ES 负责跟踪每个物体在 Z 轴上的深度,这样就可避免后面的物体遮挡前面的物体。
        gl.glEnable(GL10.GL_DEPTH_TEST);
        // 设置深度测试的类型
        gl.glDepthFunc(GL10.GL_LEQUAL);
    }

SurfaceView 组件的大小发生改变时,系统会回调 Renderer 对象的 onSurfaceChanged() 方法,因此该方法通常用于初始化 3D场景。

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {

        // 设置 3D 视窗的大小及位置
        gl.glViewport(0, 0, width, height);
        // 将当前矩阵模式设为投影矩阵
        gl.glMatrixMode(GL10.GL_PROJECTION);
        // 初始化单位矩阵
        gl.glLoadIdentity();
        // 计算透视视窗的宽度、高度比
        float ratio = (float) width / height;
        // 调用此方法设置透视视窗的空间大小
        gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
    }

gl.glFrustumf(-0.8f, 0.8f, -1f, 1f, 1f, 10f);
这意味着如果有一个二维矩形,它的4个顶点的坐标分别为 (-0.8, 1)、(0.8, 1)、(0.8. -1)、(-0.8, -1),这个矩形将会占满整个视窗

    @Override
    public void onDrawFrame(GL10 gl) {
        // 清除屏幕缓存和深度缓存
        gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
    }

绘制平面上的多边形

Renderer

public class MyRenderer implements GLSurfaceView.Renderer {
    float[] triangleData = new float[]{
            0.1f, 0.6f, 0.0f, // 上顶点
            -0.3f, 0.0f, 0.0f, // 左顶点
            0.3f, 0.1f, 0.0f  // 右顶点
    };
    int[] triangleColor = new int[]{
            65535, 0, 0, 0, // 上顶点红色
            0, 65535, 0, 0, // 左顶点绿色
            0, 0, 65535, 0 // 右顶点蓝色
    };
    float[] rectData = new float[]{
            0.4f, 0.4f, 0.0f, // 右上顶点
            0.4f, -0.4f, 0.0f, // 右下顶点
            -0.4f, 0.4f, 0.0f, // 左上顶点
            -0.4f, -0.4f, 0.0f // 左下顶点
    };
    int[] rectColor = new int[]{
            0, 65535, 0, 0, // 右上顶点绿色
            0, 0, 65535, 0, // 右下顶点蓝色
            65535, 0, 0, 0, // 左上顶点红色
            65535, 65535, 0, 0 // 左下顶点黄色
    };
    // 依然是正方形的4个顶点,只是顺序交换一下
    float[] rectData2 = new float[]{
            -0.4f, 0.4f, 0.0f, // 左上顶点
            0.4f, 0.4f, 0.0f, // 右上顶点
            0.4f, -0.4f, 0.0f, // 右下顶点
            -0.4f, -0.4f, 0.0f // 左下顶点
    };
    float[] pentacle = new float[]{
            0.4f, 0.4f, 0.0f,
            -0.2f, 0.3f, 0.0f,
            0.5f, 0.0f, 0f,
            -0.4f, 0.0f, 0f,
            -0.1f, -0.3f, 0f
    };
    FloatBuffer triangleDataBuffer;
    IntBuffer triangleColorBuffer;
    FloatBuffer rectDataBuffer;
    IntBuffer rectColorBuffer;
    FloatBuffer rectDataBuffer2;
    FloatBuffer pentacleBuffer;

    public MyRenderer() {
        // 将顶点位置数据数组转换成FloatBuffer
        triangleDataBuffer = floatBufferUtil(triangleData);
        rectDataBuffer = floatBufferUtil(rectData);
        rectDataBuffer2 = floatBufferUtil(rectData2);
        pentacleBuffer = floatBufferUtil(pentacle);
        // 将顶点颜色数据数组转换成IntBuffer
        triangleColorBuffer = intBufferUtil(triangleColor);
        rectColorBuffer = intBufferUtil(rectColor);
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        // 关闭抗抖动
        gl.glDisable(GL10.GL_DITHER);
        // 设置系统对透视进行修正
        gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT
                , GL10.GL_FASTEST);
        gl.glClearColor(0, 0, 0, 0);
        // 设置阴影平滑模式
        gl.glShadeModel(GL10.GL_SMOOTH);
        // 启用深度测试
        gl.glEnable(GL10.GL_DEPTH_TEST);
        // 设置深度测试的类型
        gl.glDepthFunc(GL10.GL_LEQUAL);
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        // 设置3D视窗的大小及位置
        gl.glViewport(0, 0, width, height);
        // 将当前矩阵模式设为投影矩阵
        gl.glMatrixMode(GL10.GL_PROJECTION);
        // 初始化单位矩阵
        gl.glLoadIdentity();
        // 计算透视视窗的宽度、高度比
        float ratio = (float) width / height;
        // 调用此方法设置透视视窗的空间大小
        gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
    }

    // 绘制图形的方法
    @Override
    public void onDrawFrame(GL10 gl) {
        // 清除屏幕缓存和深度缓存
        gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
        // 启用顶点坐标数据
        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
        // 启用顶点颜色数据
        gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
        // 设置当前矩阵堆栈为模型堆栈
        gl.glMatrixMode(GL10.GL_MODELVIEW);
        // --------------------绘制第一个图形---------------------
        // 重置当前的模型视图矩阵
        gl.glLoadIdentity();
        gl.glTranslatef(-0.32f, 0.35f, -1.2f);  // ①
        // 设置顶点的位置数据
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, triangleDataBuffer);
        // 设置顶点的颜色数据
        gl.glColorPointer(4, GL10.GL_FIXED, 0, triangleColorBuffer);
        // 根据顶点数据绘制平面图形。GL_TRIANGLES:绘制三角形
        gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 3);
        // --------------------绘制第二个图形---------------------
        // 重置当前的模型视图矩阵
        gl.glLoadIdentity();
        gl.glTranslatef(0.6f, 0.8f, -1.5f);

        /*
         * 设置顶点的位置数据
         * 这个方法中 pointer 参数用于指定顶点坐标值,但这里并未使用三维数组来指定每个顶点 X、Y、Z 坐标的值,pointer 依然是一个一维数组,其格式为 (x1, y1, z1, x2, y2, z2, x3, y3, z3, ... xN, yN,zN);也就是该数组里将会包含 3N 个数值,每 3 个值指定一个顶点的 X、Y、Z 坐标值。第一个参数 size 指定多少个元素指定一个顶点位置,该 size 参数通常总是 3;type 参数指定顶点坐标值的类型,如果顶点坐标值为 float 类型,则指定为GL10.GL_FLOAT;如果顶点坐标值为整数,则指定为 GL10.GL_FIXED
         * */
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, rectDataBuffer);
        /*
         * 设置顶点的颜色数据
         * 这个方法中 pointer 参数用于指定顶点的颜色值,pointer 依然是一个一维数组,其格式为 (r1, g1, b1, a1, r2, g2, b2, a2, r3, g3, b3, a3, ... rN, gN, bN, aN);也就是该数组里将会包含 4N 个数值,每 4 个值指定一个顶点颜色的红、绿、蓝、透明度的值。第一个参数 size 指定多少个元素指定一个顶点位置,该 size 参数通常总是 4;type 参数指定顶点坐标值的类型,如果顶点坐标值为 foat 类型,则指定为 GL10.GL_FLOAT;如果顶点坐标值为整数,则指定为 GLI0.GL_FIXED
         * */
        gl.glColorPointer(4, GL10.GL_FIXED, 0, rectColorBuffer);
        /*
         * 根据顶点数据绘制平面图形
         * 该方法的第一个参数指定绘制图形类型,第二个参数指定从哪个顶点开始绘制,第三个参数指定总共绘制的顶点数量。
         * GL_TRIANGLE_STRIP:用多个三角形来绘制多边形
         * */
        gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4);
        // --------------------绘制第三个图形---------------------
        // 重置当前的模型视图矩阵
        gl.glLoadIdentity();
        gl.glTranslatef(-0.4f, -0.5f, -1.5f);
        // 设置顶点的位置数据(依然使用之前的顶点颜色)
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, rectDataBuffer2);
        // 根据顶点数据绘制平面图形
        gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4);
        // --------------------绘制第四个图形---------------------
        // 重置当前的模型视图矩阵
        gl.glLoadIdentity();
        gl.glTranslatef(0.4f, -0.5f, -1.5f);
        // 设置使用纯色填充
        gl.glColor4f(1.0f, 0.2f, 0.2f, 0.0f);  // ②
        gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
        // 设置顶点的位置数据
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, pentacleBuffer);
        // 根据顶点数据绘制平面图形
        gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 5);
        // 绘制结束
        gl.glFinish();
        // 停用顶点坐标数据、顶点颜色数据
        gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
    }

    // 定义一个工具方法,将int[]数组转换为OpenGL ES所需的IntBuffer
    private IntBuffer intBufferUtil(int[] arr) {
        IntBuffer mBuffer;
        // 初始化ByteBuffer,长度为arr数组的长度*4,因为一个int占4字节
        ByteBuffer qbb = ByteBuffer.allocateDirect(arr.length * 4);
        // 数组排列用nativeOrder
        qbb.order(ByteOrder.nativeOrder());
        mBuffer = qbb.asIntBuffer();
        mBuffer.put(arr);
        mBuffer.position(0);
        return mBuffer;
    }

    // 定义一个工具方法,将float[]数组转换为OpenGL ES所需的FloatBuffer
    private FloatBuffer floatBufferUtil(float[] arr) {
        FloatBuffer mBuffer;
        // 初始化ByteBuffer,长度为arr数组的长度*4,因为一个int占4字节
        ByteBuffer qbb = ByteBuffer.allocateDirect(arr.length * 4);
        // 数组排列用nativeOrder
        qbb.order(ByteOrder.nativeOrder());
        mBuffer = qbb.asFloatBuffer();
        mBuffer.put(arr);
        mBuffer.position(0);
        return mBuffer;
    }
}

MainActivity

public class MainActivity extends AppCompatActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 创建一个GLSurfaceView,用于显示OpenGL绘制的图形
        GLSurfaceView glView = new GLSurfaceView(this);
        // 创建GLSurfaceView的内容绘制器
        MyRenderer myRender = new MyRenderer();
        // 为GLSurfaceView设置绘制器
        glView.setRenderer(myRender);
        setContentView(glView);
    }
}
image.png

旋转

glRotatef() 控制旋转

public class MyRenderer implements GLSurfaceView.Renderer {
    // 省略定义顶点坐标的代码
    float[] triangleData = new float[]{
            0.1f, 0.6f, 0.0f, // 上顶点
            -0.3f, 0.0f, 0.0f, // 左顶点
            0.3f, 0.1f, 0.0f  // 右顶点
    };
    int[] triangleColor = new int[]{
            65535, 0, 0, 0, // 上顶点红色
            0, 65535, 0, 0, // 左顶点绿色
            0, 0, 65535, 0 // 右顶点蓝色
    };
    float[] rectData = new float[]{
            0.4f, 0.4f, 0.0f, // 右上顶点
            0.4f, -0.4f, 0.0f, // 右下顶点
            -0.4f, 0.4f, 0.0f, // 左上顶点
            -0.4f, -0.4f, 0.0f // 左下顶点
    };
    int[] rectColor = new int[]{
            0, 65535, 0, 0, // 右上顶点绿色
            0, 0, 65535, 0, // 右下顶点蓝色
            65535, 0, 0, 0, // 左上顶点红色
            65535, 65535, 0, 0 // 左下顶点黄色
    };
    // 依然是正方形的4个顶点,只是顺序交换了一下
    float[] rectData2 = new float[]{
            -0.4f, 0.4f, 0.0f, // 左上顶点
            0.4f, 0.4f, 0.0f, // 右上顶点
            0.4f, -0.4f, 0.0f, // 右下顶点
            -0.4f, -0.4f, 0.0f // 左下顶点
    };
    float[] pentacle = new float[]{
            0.4f, 0.4f, 0.0f,
            -0.2f, 0.3f, 0.0f,
            0.5f, 0.0f, 0f,
            -0.4f, 0.0f, 0f,
            -0.1f, -0.3f, 0f
    };

    FloatBuffer triangleDataBuffer;
    IntBuffer triangleColorBuffer;
    FloatBuffer rectDataBuffer;
    IntBuffer rectColorBuffer;
    FloatBuffer rectDataBuffer2;
    FloatBuffer pentacleBuffer;
    // 控制旋转的角度
    private float rotate;

    public MyRenderer() {
        // 将顶点位置数据数组包装成FloatBuffer
        triangleDataBuffer = floatBufferUtil(triangleData);
        rectDataBuffer = floatBufferUtil(rectData);
        rectDataBuffer2 = floatBufferUtil(rectData2);
        pentacleBuffer = floatBufferUtil(pentacle);
        // 将顶点颜色数据数组包装成IntBuffer
        triangleColorBuffer = intBufferUtil(triangleColor);
        rectColorBuffer = intBufferUtil(rectColor);
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        // 关闭抗抖动
        gl.glDisable(GL10.GL_DITHER);
        // 设置系统对透视进行修正
        gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT
                , GL10.GL_FASTEST);
        gl.glClearColor(0, 0, 0, 0);
        // 设置阴影平滑模式
        gl.glShadeModel(GL10.GL_SMOOTH);
        // 启用深度测试
        gl.glEnable(GL10.GL_DEPTH_TEST);
        // 设置深度测试的类型
        gl.glDepthFunc(GL10.GL_LEQUAL);
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        // 设置3D视窗的大小及位置
        gl.glViewport(0, 0, width, height);
        // 将当前矩阵模式设为投影矩阵
        gl.glMatrixMode(GL10.GL_PROJECTION);
        // 初始化单位矩阵
        gl.glLoadIdentity();
        // 计算透视视窗的宽度、高度比
        float ratio = (float) width / height;
        // 调用此方法设置透视视窗的空间大小
        gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
    }

    // 绘制图形的方法
    @Override
    public void onDrawFrame(GL10 gl) {
        // 清除屏幕缓存和深度缓存
        gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
        // 启用顶点坐标数据
        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
        // 启用顶点颜色数据
        gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
        // 设置当前矩阵堆栈为模型堆栈
        gl.glMatrixMode(GL10.GL_MODELVIEW);
        // --------------------绘制第一个图形---------------------
        // 重置当前的模型视图矩阵
        gl.glLoadIdentity();
        gl.glTranslatef(-0.32f, 0.35f, -1.2f);
        // 设置顶点的位置数据
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, triangleDataBuffer);
        // 设置顶点的颜色数据
        gl.glColorPointer(4, GL10.GL_FIXED, 0, triangleColorBuffer);

        // 根据顶点数据绘制平面图形
        gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 3);
        // --------------------绘制第二个图形---------------------
        // 重置当前的模型视图矩阵
        gl.glLoadIdentity();
        gl.glTranslatef(0.6f, 0.8f, -1.5f);
        gl.glRotatef(rotate, 0f, 0f, 0.1f);
        // 设置顶点的位置数据
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, rectDataBuffer);
        // 设置顶点的颜色数据
        gl.glColorPointer(4, GL10.GL_FIXED, 0, rectColorBuffer);
        // 根据顶点数据绘制平面图形
        gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4);
        // --------------------绘制第三个图形---------------------
        // 重置当前的模型视图矩阵
        gl.glLoadIdentity();
        gl.glTranslatef(-0.4f, -0.5f, -1.5f);
        gl.glRotatef(rotate, 0f, 0.2f, 0f);
        // 设置顶点的位置数据(依然使用之前的顶点颜色)
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, rectDataBuffer2);
        // 根据顶点数据绘制平面图形
        gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4);
        // --------------------绘制第四个图形---------------------
        // 重置当前的模型视图矩阵
        gl.glLoadIdentity();
        gl.glTranslatef(0.4f, -0.5f, -1.5f);
        // 设置使用纯色填充
        gl.glColor4f(1.0f, 0.2f, 0.2f, 0.0f);
        gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
        // 设置顶点的位置数据
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, pentacleBuffer);
        // 根据顶点数据绘制平面图形
        gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 5);
        // 绘制结束
        gl.glFinish();
        gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
        // 旋转角度增加1
        rotate += 1;
    }
    // 省略intBufferUtil和floatBufferUtil两个工具方法的代码

    // 定义一个工具方法,将int[]数组转换为OpenGL ES所需的IntBuffer
    private IntBuffer intBufferUtil(int[] arr) {
        IntBuffer mBuffer;
        // 初始化ByteBuffer,长度为arr数组的长度*4,因为一个int占4个字节
        ByteBuffer qbb = ByteBuffer.allocateDirect(arr.length * 4);
        // 数组排列用nativeOrder
        qbb.order(ByteOrder.nativeOrder());
        mBuffer = qbb.asIntBuffer();
        mBuffer.put(arr);
        mBuffer.position(0);
        return mBuffer;
    }

    // 定义一个工具方法,将float[]数组转换为OpenGL ES所需的FloatBuffer
    private FloatBuffer floatBufferUtil(float[] arr) {
        FloatBuffer mBuffer;
        // 初始化ByteBuffer,长度为arr数组的长度*4,因为一个int占4个字节
        ByteBuffer qbb = ByteBuffer.allocateDirect(arr.length * 4);
        // 数组排列用nativeOrder
        qbb.order(ByteOrder.nativeOrder());
        mBuffer = qbb.asFloatBuffer();
        mBuffer.put(arr);
        mBuffer.position(0);
        return mBuffer;
    }
}

绘制 3D 图形

使用 OpenGL ES 绘制 3D 图形的步骤与绘制 2D 图形的步骤大致相同,只是绘制 3D 图形需要定义更多的顶点数据,而且 3D 图形需要绘制更多的三角形。

void glDrawElements(int mode, int count, int type, java.nio.Buffer indices)

根据 indices 指定的索引点来绘制三角形。该方法的第一个参数指定绘制的图形类型,可设为 GL10.GL_TRIANGLESGL10.GL_TRIANGLE_STRIP;第二个参数指定一共包含多少个顶点。indices 参数最重要,它包装了一个长度为 3N 的数组,比如让该参数包装 {0, 2, 3, 1, 4, 5} 数组,这意味着告诉 OpenGL ES 要绘制两个三角形,第一个三角形的三个顶点为 0、2、3 顶点,第二个三角形的三个顶点为 1、4、5 顶点。

public class MyRenderer implements GLSurfaceView.Renderer {
    // 定义三棱椎的4个顶点
    float[] taperVertices = new float[]{
            0.0f, 0.5f, 0.0f,
            -0.5f, -0.5f, -0.2f,
            0.5f, -0.5f, -0.2f,
            0.0f, -0.2f, 0.2f
    };
    // 定义三棱椎的4个顶点的颜色
    int[] taperColors = new int[]{
            65535, 0, 0, 0,  // 红色
            0, 65535, 0, 0,     // 绿色
            0, 0, 65535, 0,  // 蓝色
            65535, 65535, 0, 0 //黄色
    };
    // 定义三棱椎的4个三角面
    private byte[] taperFacets = new byte[]{
            0, 1, 2, // 0、1、2三个顶点组成一个面
            0, 1, 3, // 0、1、3三个顶点组成一个面
            1, 2, 3, // 1、2、3三个顶点组成一个面
            0, 2, 3 // 0、2、3三个顶点组成一个面
    };
    // 定义立方体的8个顶点
    float[] cubeVertices = new float[]{
            // 上顶面正方形的4个顶点
            0.5f, 0.5f, 0.5f,
            0.5f, -0.5f, 0.5f,
            -0.5f, -0.5f, 0.5f,
            -0.5f, 0.5f, 0.5f,
            // 下底面正方形的4个顶点
            0.5f, 0.5f, -0.5f,
            0.5f, -0.5f, -0.5f,
            -0.5f, -0.5f, -0.5f,
            -0.5f, 0.5f, -0.5f
    };
    // 定义立方体所需要的6个面(一共是12个三角形所需的顶点)
    private byte[] cubeFacets = new byte[]{
            0, 1, 2,
            0, 2, 3,
            2, 3, 7,
            2, 6, 7,
            0, 3, 7,
            0, 4, 7,
            4, 5, 6,
            4, 6, 7,
            0, 1, 4,
            1, 4, 5,
            1, 2, 6,
            1, 5, 6
    };
    // 定义Open GL ES绘制所需要的Buffer对象
    FloatBuffer taperVerticesBuffer;
    IntBuffer taperColorsBuffer;
    ByteBuffer taperFacetsBuffer;
    FloatBuffer cubeVerticesBuffer;
    ByteBuffer cubeFacetsBuffer;
    // 控制旋转的角度
    private float rotate;

    public MyRenderer() {
        // 将三棱椎的顶点位置数据数组包装成FloatBuffer
        taperVerticesBuffer = floatBufferUtil(taperVertices);
        // 将三棱椎的4个面的数组包装成ByteBuffer
        taperFacetsBuffer = ByteBuffer.wrap(taperFacets);
        // 将三棱椎的4个定点的颜色数组包装成IntBuffer
        taperColorsBuffer = intBufferUtil(taperColors);
        // 将立方体的顶点位置数据数组包装成FloatBuffer
        cubeVerticesBuffer = floatBufferUtil(cubeVertices);
        // 将立方体的6个面(12个三角形)的数组包装成ByteBuffer
        cubeFacetsBuffer = ByteBuffer.wrap(cubeFacets);
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        // 关闭抗抖动
        gl.glDisable(GL10.GL_DITHER);
        // 设置系统对透视进行修正
        gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);
        gl.glClearColor(0, 0, 0, 0);
        // 设置阴影平滑模式
        gl.glShadeModel(GL10.GL_SMOOTH);
        // 启用深度测试
        gl.glEnable(GL10.GL_DEPTH_TEST);
        // 设置深度测试的类型
        gl.glDepthFunc(GL10.GL_LEQUAL);
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        // 设置3D视窗的大小及位置
        gl.glViewport(0, 0, width, height);
        // 将当前矩阵模式设为投影矩阵
        gl.glMatrixMode(GL10.GL_PROJECTION);
        // 初始化单位矩阵
        gl.glLoadIdentity();
        // 计算透视视窗的宽度、高度比
        float ratio = (float) width / height;
        // 调用此方法设置透视视窗的空间大小。
        gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
    }

    // 绘制图形的方法
    @Override
    public void onDrawFrame(GL10 gl) {
        // 清除屏幕缓存和深度缓存
        gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
        // 启用顶点坐标数据
        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
        // 启用顶点颜色数据
        gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
        // 设置当前矩阵模式为模型视图。
        gl.glMatrixMode(GL10.GL_MODELVIEW);
        // --------------------绘制第一个图形---------------------
        // 重置当前的模型视图矩阵
        gl.glLoadIdentity();
        gl.glTranslatef(-0.6f, 0.0f, -1.5f);
        // 沿着Y轴旋转
        gl.glRotatef(rotate, 0f, 0.2f, 0f);
        // 设置顶点的位置数据
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, taperVerticesBuffer);
        // 设置顶点的颜色数据
        gl.glColorPointer(4, GL10.GL_FIXED, 0, taperColorsBuffer);
        // 按taperFacetsBuffer指定的面绘制三角形
        gl.glDrawElements(GL10.GL_TRIANGLE_STRIP
                , taperFacetsBuffer.remaining(),
                GL10.GL_UNSIGNED_BYTE, taperFacetsBuffer);
        // --------------------绘制第二个图形---------------------
        // 重置当前的模型视图矩阵
        gl.glLoadIdentity();
        gl.glTranslatef(0.7f, 0.0f, -2.2f);
        // 沿着Y轴旋转
        gl.glRotatef(rotate, 0f, 0.2f, 0f);
        // 沿着X轴旋转
        gl.glRotatef(rotate, 1f, 0f, 0f);
        // 设置顶点的位置数据
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, cubeVerticesBuffer);
        // 不设置顶点的颜色数据,还用以前的颜色数据
        // 按cubeFacetsBuffer指定的面绘制三角形
        gl.glDrawElements(GL10.GL_TRIANGLE_STRIP
                , cubeFacetsBuffer.remaining(),
                GL10.GL_UNSIGNED_BYTE, cubeFacetsBuffer);
        // 绘制结束
        gl.glFinish();
        gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
        // 旋转角度增加1
        rotate += 1;
    }

    // 定义一个工具方法,将int[]数组转换为OpenGL ES所需的IntBuffer
    private IntBuffer intBufferUtil(int[] arr) {
        IntBuffer mBuffer;
        // 初始化ByteBuffer,长度为arr数组的长度*4,因为一个int占4个字节
        ByteBuffer qbb = ByteBuffer.allocateDirect(arr.length * 4);
        // 数组排列用nativeOrder
        qbb.order(ByteOrder.nativeOrder());
        mBuffer = qbb.asIntBuffer();
        mBuffer.put(arr);
        mBuffer.position(0);
        return mBuffer;
    }

    // 定义一个工具方法,将float[]数组转换为OpenGL ES所需的FloatBuffer
    private FloatBuffer floatBufferUtil(float[] arr) {
        FloatBuffer mBuffer;
        // 初始化ByteBuffer,长度为arr数组的长度*4,因为一个int占4个字节
        ByteBuffer qbb = ByteBuffer.allocateDirect(arr.length * 4);
        // 数组排列用nativeOrder
        qbb.order(ByteOrder.nativeOrder());
        mBuffer = qbb.asFloatBuffer();
        mBuffer.put(arr);
        mBuffer.position(0);
        return mBuffer;
    }
}
public class MainActivity extends AppCompatActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 创建一个GLSurfaceView,用于显示OpenGL绘制的图形
        GLSurfaceView glView = new GLSurfaceView(this);
        // 创建GLSurfaceView的内容绘制器
        MyRenderer myRender = new MyRenderer();
        // 为GLSurfaceView设置绘制器
        glView.setRenderer(myRender);
        setContentView(glView);
    }
}
image.gif

应用纹理贴图

public class MainActivity extends AppCompatActivity implements GestureDetector.OnGestureListener {
    // 定义旋转角度
    private float anglex = 0f;
    private float angley = 0f;
    static final float ROTATE_FACTOR = 60;
    // 定义手势检测器实例
    GestureDetector detector;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 创建一个GLSurfaceView,用于显示OpenGL绘制的图形
        GLSurfaceView glView = new GLSurfaceView(this);
        // 创建GLSurfaceView的内容绘制器
        MyRenderer myRender = new MyRenderer(this);
        // 为GLSurfaceView设置绘制器
        glView.setRenderer(myRender);
        setContentView(glView);
        // 创建手势检测器
        detector = new GestureDetector(this, this);
    }

    @Override
    public boolean onTouchEvent(MotionEvent me) {
        // 将该Activity上的触碰事件交给GestureDetector处理
        return detector.onTouchEvent(me);
    }

    @Override
    public boolean onFling(MotionEvent event1, MotionEvent event2,
                           float velocityX, float velocityY) {
        velocityX = velocityX > 2000 ? 2000 : velocityX;
        velocityX = velocityX < -2000 ? -2000 : velocityX;
        velocityY = velocityY > 2000 ? 2000 : velocityY;
        velocityY = velocityY < -2000 ? -2000 : velocityY;
        // 根据横向上的速度计算沿Y轴旋转的角度
        angley += velocityX * ROTATE_FACTOR / 4000;
        // 根据纵向上的速度计算沿X轴旋转的角度
        anglex += velocityY * ROTATE_FACTOR / 4000;
        return true;
    }

    @Override
    public boolean onDown(MotionEvent arg0) {
        return false;
    }

    @Override
    public void onLongPress(MotionEvent event) {
    }

    @Override
    public boolean onScroll(MotionEvent event1, MotionEvent event2,
                            float distanceX, float distanceY) {
        return false;
    }

    @Override
    public void onShowPress(MotionEvent event) {
    }

    @Override
    public boolean onSingleTapUp(MotionEvent event) {
        return false;
    }

    public class MyRenderer implements GLSurfaceView.Renderer {
        // 立方体的顶点坐标(一共是36个顶点,组成12个三角形)
        private float[] cubeVertices = {-0.6f, -0.6f, -0.6f, -0.6f, 0.6f,
                -0.6f, 0.6f, 0.6f, -0.6f, 0.6f, 0.6f, -0.6f, 0.6f, -0.6f, -0.6f,
                -0.6f, -0.6f, -0.6f, -0.6f, -0.6f, 0.6f, 0.6f, -0.6f, 0.6f, 0.6f,
                0.6f, 0.6f, 0.6f, 0.6f, 0.6f, -0.6f, 0.6f, 0.6f, -0.6f, -0.6f,
                0.6f, -0.6f, -0.6f, -0.6f, 0.6f, -0.6f, -0.6f, 0.6f, -0.6f, 0.6f,
                0.6f, -0.6f, 0.6f, -0.6f, -0.6f, 0.6f, -0.6f, -0.6f, -0.6f, 0.6f,
                -0.6f, -0.6f, 0.6f, 0.6f, -0.6f, 0.6f, 0.6f, 0.6f, 0.6f, 0.6f,
                0.6f, 0.6f, -0.6f, 0.6f, 0.6f, -0.6f, -0.6f, 0.6f, 0.6f, -0.6f,
                -0.6f, 0.6f, -0.6f, -0.6f, 0.6f, 0.6f, -0.6f, 0.6f, 0.6f, 0.6f,
                0.6f, 0.6f, 0.6f, 0.6f, -0.6f, -0.6f, 0.6f, -0.6f, -0.6f, -0.6f,
                -0.6f, -0.6f, -0.6f, 0.6f, -0.6f, -0.6f, 0.6f, -0.6f, 0.6f, 0.6f,
                -0.6f, 0.6f, -0.6f,};
        // 定义立方体所需要的6个面(一共是12个三角形所需的顶点)
        private byte[] cubeFacets = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
                13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
                30, 31, 32, 33, 34, 35,};
        // 定义纹理贴图的坐标数据
        private float[] cubeTextures = {1.0000f, 1.0000f, 1.0000f, 0.0000f,
                0.0000f, 0.0000f, 0.0000f, 0.0000f, 0.0000f, 1.0000f, 1.0000f,
                1.0000f, 0.0000f, 1.0000f, 1.0000f, 1.0000f, 1.0000f, 0.0000f,
                1.0000f, 0.0000f, 0.0000f, 0.0000f, 0.0000f, 1.0000f, 0.0000f,
                1.0000f, 1.0000f, 1.0000f, 1.0000f, 0.0000f, 1.0000f, 0.0000f,
                0.0000f, 0.0000f, 0.0000f, 1.0000f, 0.0000f, 1.0000f, 1.0000f,
                1.0000f, 1.0000f, 0.0000f, 1.0000f, 0.0000f, 0.0000f, 0.0000f,
                0.0000f, 1.0000f, 0.0000f, 1.0000f, 1.0000f, 1.0000f, 1.0000f,
                0.0000f, 1.0000f, 0.0000f, 0.0000f, 0.0000f, 0.0000f, 1.0000f,
                0.0000f, 1.0000f, 1.0000f, 1.0000f, 1.0000f, 0.0000f, 1.0000f,
                0.0000f, 0.0000f, 0.0000f, 0.0000f, 1.0000f};
        private Context context;
        private FloatBuffer cubeVerticesBuffer;
        private ByteBuffer cubeFacetsBuffer;
        private FloatBuffer cubeTexturesBuffer;
        // 定义本程序所使用的纹理
        private int texture;

        public MyRenderer(Context main) {
            this.context = main;
            // 将立方体的顶点位置数据数组包装成FloatBuffer;
            cubeVerticesBuffer = floatBufferUtil(cubeVertices);
            // 将立方体的6个面(12个三角形)的数组包装成ByteBuffer
            cubeFacetsBuffer = ByteBuffer.wrap(cubeFacets);
            // 将立方体的纹理贴图的坐标数据包装成FloatBuffer
            cubeTexturesBuffer = floatBufferUtil(cubeTextures);
        }

        @Override
        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
            // 关闭抗抖动
            gl.glDisable(GL10.GL_DITHER);
            // 设置系统对透视进行修正
            gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);
            gl.glClearColor(0, 0, 0, 0);
            // 设置阴影平滑模式
            gl.glShadeModel(GL10.GL_SMOOTH);
            // 启用深度测试
            gl.glEnable(GL10.GL_DEPTH_TEST);
            // 设置深度测试的类型
            gl.glDepthFunc(GL10.GL_LEQUAL);
            // 启用2D纹理贴图
            gl.glEnable(GL10.GL_TEXTURE_2D);
            // 装载纹理
            loadTexture(gl);
        }

        @Override
        public void onSurfaceChanged(GL10 gl, int width, int height) {
            // 设置3D视窗的大小及位置
            gl.glViewport(0, 0, width, height);
            // 将当前矩阵模式设为投影矩阵
            gl.glMatrixMode(GL10.GL_PROJECTION);
            // 初始化单位矩阵
            gl.glLoadIdentity();
            // 计算透视视窗的宽度、高度比
            float ratio = (float) width / height;
            // 调用此方法设置透视视窗的空间大小。
            gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
        }

        public void onDrawFrame(GL10 gl) {
            // 清除屏幕缓存和深度缓存
            gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
            // 启用顶点坐标数据
            gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
            // 启用贴图坐标数组数据
            gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);   // ①
            // 设置当前矩阵模式为模型视图。
            gl.glMatrixMode(GL10.GL_MODELVIEW);
            gl.glLoadIdentity();
            // 把绘图中心移入屏幕2个单位
            gl.glTranslatef(0f, 0.0f, -2.0f);
            // 旋转图形
            gl.glRotatef(angley, 0, 1, 0);
            gl.glRotatef(anglex, 1, 0, 0);
            // 设置顶点的位置数据
            gl.glVertexPointer(3, GL10.GL_FLOAT, 0, cubeVerticesBuffer);
            // 设置贴图的坐标数据
            gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, cubeTexturesBuffer);  // ②
            // 执行纹理贴图
            gl.glBindTexture(GL10.GL_TEXTURE_2D, texture);  // ③
            // 按cubeFacetsBuffer指定的面绘制三角形
            gl.glDrawElements(GL10.GL_TRIANGLES, cubeFacetsBuffer.remaining(),
                    GL10.GL_UNSIGNED_BYTE, cubeFacetsBuffer);
            // 绘制结束
            gl.glFinish();
            // 禁用顶点、纹理坐标数组
            gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
            gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
            // 递增角度值以便每次以不同角度绘制
        }

        private void loadTexture(GL10 gl) {
            Bitmap bitmap = null;
            try {
                // 加载位图
                bitmap = BitmapFactory.decodeResource(context.getResources(),
                        R.drawable.sand);
                int[] textures = new int[1];
                // 指定生成N个纹理(第一个参数指定生成一个纹理)
                // textures数组将负责存储所有纹理的代号
                // offset指定从第几个数组元素开始存放纹理代号
                gl.glGenTextures(1, textures, 0);
                // 获取textures纹理数组中的第一个纹理
                texture = textures[0];
                // 通知OpenGL将texture纹理绑定到GL10.GL_TEXTURE_2D目标中
                gl.glBindTexture(GL10.GL_TEXTURE_2D, texture);
                // 设置纹理被缩小(距离视点很远时被缩小)时的滤波方式
                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);
                // 设置在横向、纵向上都是平铺纹理
                gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,
                        GL10.GL_REPEAT);
                gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,
                        GL10.GL_REPEAT);
                // 加载位图生成纹理
                GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
            } finally {
                // 生成纹理之后,回收位图
                if (bitmap != null)
                    bitmap.recycle();
            }
        }
    }

    // 定义一个工具方法,将float[]数组转换为OpenGL ES所需的FloatBuffer
    private FloatBuffer floatBufferUtil(float[] arr) {
        FloatBuffer mBuffer;
        // 初始化ByteBuffer,长度为arr数组的长度*4,因为一个int占4个字节
        ByteBuffer qbb = ByteBuffer.allocateDirect(arr.length * 4);
        // 数组排列用nativeOrder
        qbb.order(ByteOrder.nativeOrder());
        mBuffer = qbb.asFloatBuffer();
        mBuffer.put(arr);
        mBuffer.position(0);
        return mBuffer;
    }
}

摘抄至《疯狂Android讲义(第4版)》

上一篇下一篇

猜你喜欢

热点阅读