美文网首页
OpenGL ES——一个平平无奇的三角形

OpenGL ES——一个平平无奇的三角形

作者: oceanLong | 来源:发表于2018-06-11 00:54 被阅读20次

    前言

    随着VR/AR技术的普及,人机交互的模式将产生新的变革。OpenGL ES作为移动端上的图像渲染框架,将变得越来越重要。在此将学习OpenGL ES作为Q3的主要目标。在10月1日前,希望能有阶段性成果。

    快速开始

    判断设备是否支持OpenGL ES

        fun checkSupported() : Boolean{
            var supportsEs2 = false;
            val activityManager = getSystemService(ACTIVITY_SERVICE) as ActivityManager
            val configurationInfo = activityManager.getDeviceConfigurationInfo();
            supportsEs2 = configurationInfo.reqGlEsVersion >= 0x2000;
    
            val isEmulator = Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1
                    && (Build.FINGERPRINT.startsWith("generic")
                    || Build.FINGERPRINT.startsWith("unknown")
                    || Build.MODEL.contains("google_sdk")
                    || Build.MODEL.contains("Emulator")
                    || Build.MODEL.contains("Android SDK built for x86"));
    
            supportsEs2 = supportsEs2 || isEmulator
    
            return supportsEs2
    
        }
    

    生命周期

        override fun onPause() {
            super.onPause()
            glSurfaceView.let { glSurfaceView.onPause() }
        }
    
        override fun onResume() {
            super.onResume()
            glSurfaceView.let { glSurfaceView.onResume() }
        }
    
    

    用OpenGL渲染Activity

    
        lateinit var glSurfaceView: GLSurfaceView
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            if (checkSupported()) {
                glSurfaceView = GLSurfaceView(this);
                glSurfaceView.let { glSurfaceView.setRenderer(GLRender2())
                    setContentView(glSurfaceView); }
            } else {
                Toast.makeText(this, "当前设备不支持OpenGL ES 2.0!", Toast.LENGTH_SHORT).show();
            }
    
        }
    

    我们可以看到,OpenGL实际的渲染逻辑,全部封装在了我自己创建的GLRender2中。

    以上代码,就是OpenGL渲染Activity最简单的外部框架。

    渲染逻辑

    
    public class GLRender2 implements GLSurfaceView.Renderer {
    
    
        private float[] mTriangleArray = {
                0f, 1f, 0f,
                -1f, -1f, 0f,
                1f, -1f, 0f
        };
        //三角形各顶点颜色(三个顶点)
        private float[] mColor = new float[]{
                1, 1, 0, 1,
                0, 1, 1, 1,
                1, 0, 1, 1
        };
        private FloatBuffer mTriangleBuffer;
        private FloatBuffer mColorBuffer;
    
    
        public GLRender2() {
            Log.d("GLRender2" , "call GLRender init");
            //点相关
            //先初始化buffer,数组的长度*4,因为一个float占4个字节
            ByteBuffer bb = ByteBuffer.allocateDirect(mTriangleArray.length * 4);
            //以本机字节顺序来修改此缓冲区的字节顺序
            bb.order(ByteOrder.nativeOrder());
            mTriangleBuffer = bb.asFloatBuffer();
            //将给定float[]数据从当前位置开始,依次写入此缓冲区
            mTriangleBuffer.put(mTriangleArray);
            //设置此缓冲区的位置。如果标记已定义并且大于新的位置,则要丢弃该标记。
            mTriangleBuffer.position(0);
    
    
            //颜色相关
            ByteBuffer bb2 = ByteBuffer.allocateDirect(mColor.length * 4);
            bb2.order(ByteOrder.nativeOrder());
            mColorBuffer = bb2.asFloatBuffer();
            mColorBuffer.put(mColor);
            mColorBuffer.position(0);
        }
    
        @Override
        public void onDrawFrame(GL10 gl) {
    
            Log.d("GLRender2" , "call onDrawFrame");
            // 清除屏幕和深度缓存
            gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
            // 重置当前的模型观察矩阵
            gl.glLoadIdentity();
    
            // 允许设置顶点
            //GL10.GL_VERTEX_ARRAY顶点数组
            gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
            // 允许设置颜色
            //GL10.GL_COLOR_ARRAY颜色数组
            gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
    
            //将三角形在z轴上移动
            gl.glTranslatef(0f, 0.0f, -2.0f);
    
            // 设置三角形
            gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mTriangleBuffer);
            // 设置三角形颜色
            gl.glColorPointer(4, GL10.GL_FLOAT, 0, mColorBuffer);
            // 绘制三角形
            gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 3);
    
    
            // 取消颜色设置
            gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
            // 取消顶点设置
            gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
    
            //绘制结束
            gl.glFinish();
    
        }
    
        @Override
        public void onSurfaceChanged(GL10 gl, int width, int height) {
            Log.d("GLRender2" , "call onSurfaceChanged");
            float ratio = (float) width / height;
            // 设置OpenGL场景的大小,(0,0)表示窗口内部视口的左下角,(w,h)指定了视口的大小
            gl.glViewport(0, 0, width, height);
            // 设置投影矩阵
            gl.glMatrixMode(GL10.GL_PROJECTION);
            // 重置投影矩阵
            gl.glLoadIdentity();
            // 设置视口的大小
            gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
            //以下两句声明,以后所有的变换都是针对模型(即我们绘制的图形)
            gl.glMatrixMode(GL10.GL_MODELVIEW);
            gl.glLoadIdentity();
    
        }
    
        @Override
        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
            Log.d("GLRender2" , "call onSurfaceCreated");
            // 设置白色为清屏
            gl.glClearColor(1, 1, 1, 1);
    
        }
    
    }
    
    

    以上代码,渲染出一个变色的三角形:

    详细介绍

    GLRender2

    这个平平无奇的三角形,它的渲染逻辑究竟是什么样的呢?
    在此之前,我们需要先了解GLRender2是一个怎样的类。

    GLRender2实现了GLSurfaceView.Renderer接口。需要实现三个方法:

    
        @Override
        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
            Log.d("GLRender" , "call onSurfaceCreated");
        }
    
        @Override
        public void onSurfaceChanged(GL10 gl, int width, int height) {
            Log.d("GLRender" , "call onSurfaceChanged");
        }
    
        @Override
        public void onDrawFrame(GL10 gl) {
            Log.d("GLRender" , "call onDrawFrame");
        }
    

    生命周期

    这是GLSurfaceView生命周期的三个环节。

    onSurfaceCreated

    onSurfaceCreatedGLRender2被初始化后首先调用。通常用于初始化伴随GLSurfaceView整个生命周期的数据和设置初始颜色。

    onSurfaceChanged

    onSurfaceChanged
    当GLSurfaceView大小改变时,对应的Surface大小也会改变。值得注意的是,在Surface刚创建的时候,它的size其实是0,也就是说在画第一次图之前它也会被调用一次的。(而且对于很多时候,Surface的大小是不会改变的,那么此函数就只在创建之初被调用一次而已)

    原型如下:

    public abstract void onSurfaceChanged (GL10 gl, int width, int height)
    

    另外值得注意的是,它告诉了我们这张纸有多高多宽。这点很重要。因为在onSurfaceCreated的时候我们是不知道纸的宽高的,所以有一些和长宽相关的初始化工作还得在此函数中来做。

    onDrawFrame

    以后会有两种模式供你选择:

    • RENDERMODE_CONTINUOUSLY
    • RENDERMODE_WHEN_DIRTY

    第一种模式(RENDERMODE_CONTINUOUSLY):
    连续不断的刷,画完一幅图马上又画下一幅。这种模式很明显是用来画动画的;

    第二种模式(RENDERMODE_WHEN_DIRTY):
    只有在需要重画的时候才画下一幅。这种模式就比较节约CPU和GPU一些,适合用来画不经常需要刷新的情况。多说一句,系统如何知道需要重画了呢?当然是你要告诉它……
    调用GLSurfaceView的requestRender ()方法,使其重绘。

    GLSurfaceView的setRenderMode(int renderMode)方法。可以供你设置你需要的刷新模式。

    设置背景色

    // 设置白色为清屏
    gl.glClearColor(1, 1, 1, 1);
    

    设置场景大小

     // 设置OpenGL场景的大小,(0,0)表示窗口内部视口的左下角,(w,h)指定了视口的大小
    gl.glViewport(0, 0, width, height);
    

    设置投影矩阵

    在渲染中,我们只绘制可见的东西。所以我们需要将真实物体转化到可见区域,即谓之投影矩阵。

    // 设置投影矩阵
    gl.glMatrixMode(GL10.GL_PROJECTION);
    // 重置投影矩阵
    gl.glLoadIdentity();
    // 设置视口的大小
    gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
    

    这三句将真实物体映射到坐标系中。这个地方可能有点难以理解。

    虽然在OpenGL中,我们画的是3D物体,但手机屏幕毕竟是一个平面。我们在生活中,看见的也只是一个平面。那么,一个3D物体,我们看到的应该是什么样的,取决于我们的投影矩阵如何设置。

    假设,我们的三角形,三个点分别是:

        private float[] mTriangleArray = {
                0f, 1f, -2f,
                -1f, -1f, -2f,
                1f, -1f, -2f
        };
    

    那么,这个三角形其实是在z轴为-2处的一个平面。我们用下图的方式,进行观察。

    下图,近处的平面,距离视点为1,远处的为10。我们画的三角平面,就在距离视点2的位置。在距离视点1处,我们的视口大小是 2ratio x 2。到距离2处,我们的视口大小一定为2ratio x 2。

    所以此时,我们渲染我们的三角形,它的高一定为画布高度的1/2。

    如果我们将近平面,视点距离改为0.5f。同样的三角形,我们渲染出来高度一定为画布高度的1/4。

    如果我们将三角形改为:

        private float[] mTriangleArray = {
                0f, 1f, -1f,
                -1f, -1f, -2f,
                1f, -1f, -2f
        };
    

    视点距离改为1.0f 。三角形的高度将变为画布高度的3/4。

    而远平面的视点距离,则决定了我们可以看到多远的元素。比如我们还是三角形为:

        private float[] mTriangleArray = {
                0f, 1f, -1f,
                -1f, -1f, -2f,
                1f, -1f, -2f
        };
    

    将远平面视点距离改为1.5f,此时我们将只能看到上个例子中三角形的上半部分。

    其中变化读者可以画立体图,慢慢感受一下。

    回归模型

    完成了对投影的操作后,我们将操作模式设置到模型操作。

            //以下两句声明,以后所有的变换都是针对模型(即我们绘制的图形)
            gl.glMatrixMode(GL10.GL_MODELVIEW);
            gl.glLoadIdentity();
    

    完成了种种矩阵的设置后,我们可以开始进行绘制了。

    图形和色彩数据

    OpenGL并不是对堆里面的数据进行操作,而是在直接内存中(Direct Memory),即操作的数据需要保存到NIO里面的Buffer对象中。而我们上面声明的float[]对象保存在堆中,因此,需要我们将float[]对象转为java.nio.Buffer对象。

        private float[] mTriangleArray = {
                0f, 1f, 1f,
                -1f, -1f, 0f,
                1f, -1f, 0f
        };
        //三角形各顶点颜色(三个顶点)
        private float[] mColor = new float[]{
                1, 1, 0, 1,
                0, 1, 1, 1,
                1, 0, 1, 1
        };
        private FloatBuffer mTriangleBuffer;
        private FloatBuffer mColorBuffer;
    
    
        public GLRender2() {
            Log.d("GLRender2" , "call GLRender init");
            //点相关
            //先初始化buffer,数组的长度*4,因为一个float占4个字节
            ByteBuffer bb = ByteBuffer.allocateDirect(mTriangleArray.length * 4);
            //以本机字节顺序来修改此缓冲区的字节顺序
            bb.order(ByteOrder.nativeOrder());
            mTriangleBuffer = bb.asFloatBuffer();
            //将给定float[]数据从当前位置开始,依次写入此缓冲区
            mTriangleBuffer.put(mTriangleArray);
            //设置此缓冲区的位置。如果标记已定义并且大于新的位置,则要丢弃该标记。
            mTriangleBuffer.position(0);
    
    
            //颜色相关
            ByteBuffer bb2 = ByteBuffer.allocateDirect(mColor.length * 4);
            bb2.order(ByteOrder.nativeOrder());
            mColorBuffer = bb2.asFloatBuffer();
            mColorBuffer.put(mColor);
            mColorBuffer.position(0);
        }
    

    绘制

    我们在onDrawFrame的生命周期中进行绘制。

        @Override
        public void onDrawFrame(GL10 gl) {
    
            Log.d("GLRender2" , "call onDrawFrame");
            // 清除屏幕和深度缓存
            gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
            // 重置当前的模型观察矩阵
            gl.glLoadIdentity();
    
            // 允许设置顶点
            //GL10.GL_VERTEX_ARRAY顶点数组
            gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
            // 允许设置颜色
            //GL10.GL_COLOR_ARRAY颜色数组
            gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
    
            //将画笔在z轴上移动
            gl.glTranslatef(0f, 0.0f, -2.0f);
    
            // 设置三角形
            gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mTriangleBuffer);
            // 设置三角形颜色
            gl.glColorPointer(4, GL10.GL_FLOAT, 0, mColorBuffer);
            // 绘制三角形
            gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 3);
    
            // 取消颜色设置
            gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
            // 取消顶点设置
            gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
    
            //绘制结束
            gl.glFinish();
    
        }
    

    绘制的过程比较模式化,不再赘述。大致包含了:

    • 清除缓存
    • 启动顶点数组模式
    • 启动颜色数组模式
    • 移动画笔
    • 设置图形
    • 设置颜色
    • 关闭顶点数组模式
    • 关闭颜色数组模式
    • 绘制结束

    至此,便完成了一个平平无奇的三角形的绘制过程。

    如有问题,欢迎指正。

    相关文章

      网友评论

          本文标题:OpenGL ES——一个平平无奇的三角形

          本文链接:https://www.haomeiwen.com/subject/nnafeftx.html