OpenGL ES 定义形状

本篇文章属于 使用 OpenGL ES 进行图形绘制 这个系列的第二篇文章,主要内容是介绍在如何在 Android 应用中利用定义 OpenGL 中图形的形状。文章中所有的代码示例都已放在 Github 上,可以去项目 OpenGL-ES-Learning 中查看 。

如同学习绘制自定义 View 一样,定义图形的形状是实现各种复杂的图形的基础,下面将介绍 OpenGL ES 相对于 Android 设备屏幕的坐标系、定义形状和形状绘制等基础知识。

定义一个三角形

OpenGL ES 允许我们使用三维空间的坐标来定义绘画对象。所以在我们能画三角形之前,必须先定义它的坐标。在 OpenGL 中,典型的办法是为坐标定义一个 Float 类型的顶点数组。为了效率最大化,我们可以将坐标写入一个 ByteBuffer,它将会传入 OpenGl ES 的 pipeline 来处理。

ByteBuffer 俗称缓冲器,在 NIO 中,数据的读写操作始终是与缓冲区相关联的。读取时信道 (SocketChannel) 将数据读入缓冲区,写入时首先要将发送的数据按顺序填入缓冲区。缓冲区是定长的,基本上它只是一个列表,它的所有元素都是基本数据类型。ByteBuffer 是最常用的缓冲区,它提供了读写其他数据类型的方法,且信道的读写方法只接收 ByteBuffer。关于 ByteBuffer 的更多内容,推荐一篇文章 Android中直播视频技术探究之—基础核心类ByteBuffer解析

public class Triangle {

    /**
     * 定义三角形顶点的坐标数据的浮点型缓冲区
     */
    private FloatBuffer vertexBuffer;

    // 坐标数组中的顶点坐标个数
    static final int COORDINATES_PRE_VERTEX = 3;
    static float triangleCoords[] = {   // 以逆时针顺序;
            0.0f,  0.622008459f, 0.0f,  // top
            -0.5f, -0.311004243f, 0.0f, // bottom left
            0.5f, -0.311004243f, 0.0f   // bottom right
    };

    // Set color with red, green, blue and alpha (opacity) values
    float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };

    public Triangle(){
        // 初始化形状中顶点坐标数据的字节缓冲区
        // 通过 allocateDirect 方法获取到 DirectByteBuffer 实例
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(
                // 顶点坐标个数 * 坐标数据类型 float 一个是 4 bytes
                triangleCoords.length * 4
        );

        // 设置缓冲区使用设备硬件的原本字节顺序进行读取;
        byteBuffer.order(ByteOrder.nativeOrder());

        // 因为 ByteBuffer 是将数据移进移出通道的唯一方式使用,这里使用 “as” 方法从 ByteBuffer 中获得一个基本类型缓冲区(浮点缓冲区)
        vertexBuffer = byteBuffer.asFloatBuffer();
        // 把顶点坐标信息数组存储到 FloatBuffer
        vertexBuffer.put(triangleCoords);
        // 设置从缓冲区的第一个位置开始读取顶点坐标信息
        vertexBuffer.position(0);
    }
}

跟 View 的坐标系原点位于屏幕左上角不同,默认情况下 OpenGL ES 会假定一个坐标系,在这个坐标系中,[0, 0, 0](分别对应X轴坐标, Y轴坐标, Z轴坐标)对应的是GLSurfaceView 的中心。如 [1, 1, 0] 对应的是右上角,[-1, -1, 0] 对应的则是左下角。

在 OpenGL 里,我们要渲染的一切物体都要映射到 X 轴和 Y 轴上 [-1,1] 的范围内,对于Z轴也一样。这个范围内的坐标被称为归一化设备坐标,其独立于屏幕实际尺寸或形状。也就是说在 Android 设备上显示图形时屏幕的尺寸和形状虽然会有所不同,但是 OpenGL 假设了一个平方均匀的坐标系,默认情况下将这些坐标按比例绘制非正方形屏幕上,就好像它是完全正方形一样。

OpenGL 坐标系

如上图所示的坐标系,左图是默认的 OpenGL 坐标系,右图是实际展示 Android 设备屏幕时的坐标系,会看到三角形会有一个明显的拉伸。默认情况下,对于 OpenGL 而言不管硬件设备屏幕是不是正方形,都把它当作一个正方形来处理,三维坐标都限定在 [-1, 1]内。 所以 Open GL 的坐标体系独立于实际的屏幕尺寸。

要处理画面被拉伸的问题,可以考虑调整坐标空间,把屏幕的形状考虑在内,可行的一个方法是把较小的范围固定在 [-1,1] 内,而按屏幕尺寸的比例调整较大的范围。这里推荐一篇文章:Android OpenGL ES 调整屏幕的宽高比,文章中提到了如何处理 Open GL 在实际展示时宽高比控制。而这篇文章:OpenGL ES 透视投影则是对归一化设备坐标到视口(视口:OpenGL 渲染操作最终显示窗口)的窗口坐标转化说明。这些内容建议暂时放一下,遇到相关问题时在深入了解。

三角形顶点坐标

注意到上面这个形状的坐标是以逆时针顺序定义的。绘制的顺序非常关键,因为它定义了哪一面是形状的正面(希望绘制的一面),以及背面(使用 OpenGL ES 的 Cull Face 功能可以让背面不要绘制)。更多关于该方面的信息,可以阅读 OpenGL ES 开发手册。

定义一个矩形

在 OpenGL 中定义三角形非常简单,那定义一个矩形呢?有很多方法可以用来定义矩形,不过在 OpenGL ES 中最典型的办法是使用两个三角形拼接在一起:


绘制矩形

同样的我们通过按逆时针顺序为三角形顶点定义坐标来表示这个图形,并将值放入一个 ByteBuffer 中。为了避免由两个三角形重合的那条边的顶点被重复定义,可以使用一个绘制列表(drawing list)来告诉 OpenGL ES 绘制顺序。下面是代码样例:

public class Square {

    /**
     * 顶点坐标数据缓冲区(float 类型)
     */
    private FloatBuffer vertexBuffer;

    /**
     * 绘制顺序数据缓冲区(short类型)
     */
    private ShortBuffer drawListBuffer;


    /**
     * 顶点坐标数据的数组
     */
    static float squareCoords[] = {
            -0.5f,  0.5f, 0.0f,   // top left
            -0.5f, -0.5f, 0.0f,   // bottom left
            0.5f, -0.5f, 0.0f,   // bottom right
            0.5f,  0.5f, 0.0f }; // top right

    /**
     * 绘制顶点顺序
     */
    private short drawOrder[] = { 0, 1, 2, 0, 2, 3 }; // order to draw vertices


    public Square() {
        // initialize vertex byte buffer for shape coordinates
        ByteBuffer bb = ByteBuffer.allocateDirect(
                // (# of coordinate values * 4 bytes per float)
                squareCoords.length * 4);
        bb.order(ByteOrder.nativeOrder());
        vertexBuffer = bb.asFloatBuffer();
        vertexBuffer.put(squareCoords);
        vertexBuffer.position(0);

        // initialize byte buffer for the draw list
        ByteBuffer dlb = ByteBuffer.allocateDirect(
                // (# of coordinate values * 2 bytes per short)
                drawOrder.length * 2);
        dlb.order(ByteOrder.nativeOrder());
        drawListBuffer = dlb.asShortBuffer();
        drawListBuffer.put(drawOrder);
        drawListBuffer.position(0);
    }
}

该样例可以看作是一个如何使用 OpenGL 创建复杂图形的启发,通常来说我们需要使用三角形的集合来绘制对象。

文章中所有的代码示例都已放在 Github 上,可以去项目 OpenGL-ES-Learning 中查看 。

上述内容主要是对如何定义简单形状进行一个说明,了解 OpenGL 坐标体系规则,下面将了解如何在屏幕上画这些形状。

>>>>Next>>>> : 绘制形状

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容