前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >OpenGL ES——一个平平无奇的三角形

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

作者头像
Oceanlong
发布2018-07-03 13:27:44
8090
发布2018-07-03 13:27:44
举报
文章被收录于专栏:移动开发面面观

前言

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

快速开始

判断设备是否支持OpenGL ES

代码语言:javascript
复制
    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

    }

生命周期

代码语言:javascript
复制
    override fun onPause() {
        super.onPause()
        glSurfaceView.let { glSurfaceView.onPause() }
    }

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

用OpenGL渲染Activity

代码语言:javascript
复制
    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最简单的外部框架。

渲染逻辑

代码语言:javascript
复制
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接口。需要实现三个方法:

代码语言:javascript
复制
    @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的大小是不会改变的,那么此函数就只在创建之初被调用一次而已)

原型如下:

代码语言:javascript
复制
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)方法。可以供你设置你需要的刷新模式。

设置背景色

代码语言:javascript
复制
// 设置白色为清屏
gl.glClearColor(1, 1, 1, 1);

设置场景大小

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

设置投影矩阵

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

代码语言:javascript
复制
// 设置投影矩阵
gl.glMatrixMode(GL10.GL_PROJECTION);
// 重置投影矩阵
gl.glLoadIdentity();
// 设置视口的大小
gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);

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

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

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

代码语言:javascript
复制
    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。

如果我们将三角形改为:

代码语言:javascript
复制
    private float[] mTriangleArray = {
            0f, 1f, -1f,
            -1f, -1f, -2f,
            1f, -1f, -2f
    };

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

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

代码语言:javascript
复制
    private float[] mTriangleArray = {
            0f, 1f, -1f,
            -1f, -1f, -2f,
            1f, -1f, -2f
    };

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

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

回归模型

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

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

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

图形和色彩数据

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

代码语言:javascript
复制
    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的生命周期中进行绘制。

代码语言:javascript
复制
    @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();

    }

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

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

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

如有问题,欢迎指正。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018.06.11 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 快速开始
    • 判断设备是否支持OpenGL ES
      • 生命周期
        • 用OpenGL渲染Activity
          • 渲染逻辑
          • 详细介绍
            • GLRender2
              • 生命周期
                • onSurfaceCreated
                • onSurfaceChanged
                • onDrawFrame
              • 设置背景色
                • 设置场景大小
                  • 设置投影矩阵
                    • 回归模型
                      • 图形和色彩数据
                        • 绘制
                        相关产品与服务
                        腾讯云小微
                        腾讯云小微,是一套腾讯云的智能服务系统,也是一个智能服务开放平台,接入小微的硬件可以快速具备听觉和视觉感知能力,帮助智能硬件厂商实现语音人机互动和音视频服务能力。
                        领券
                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档