OpenGLES 之 绘制三角形 (一)

一、OpenGL ES

OpenGL是一个供运行在Linux、Unix、Mac和Windows的桌面系统使用的跨平台标准的API,OpenGL ES是以手持和嵌入式设备为目标的高级3D图形应用程序编程接口。即OpenGLES是OpenGL的子集,去除了复杂的不常用的功能,专门为PDA等为目标的图形API。

OpenGLES经常用在对图片、视频、相机、游戏等方面。它是使用了GPU的计算的,从而解放CPU的使用,加速渲染能力。

二、前言

作为一门语言的入门,都是从HelloWord学起的。当然OpenGL也不例外,绘制一个三角形应该就是OpenGLES的“HelloWorld”了,但是这个HelloWorld有的复杂,并不像java等语言输入一句话就行了。绘制一个三角形虽然是“HelloWorld”,但是麻雀虽小五脏俱全,工作量和操作步骤却没有变小。

本文选择的渲染载体为:GLSurfaceView

简单的介绍下GLSurfaceView,它是继承自SurfaceView,一个双缓冲机制并可在子线程更新的具有两个View体系结构的View,在SurfaceView的基础上增加了对EGL的管理。减少了我们对EGL的操作,简化了步骤。简单说EGL是OpenGLES和底层硬件的过渡层,他抽象了硬件的细节,完成了OpenGLES的跨平台性。

编写前,先介绍一个概念:图形渲染管线,它是从输入数据开始到绘制到界面的过程。大体步骤,可以参考如下官方给出的步骤。蓝色部分是用户可定制开发的。


管线操作
  • 顶点着色器:它把一个顶点作为输入。把3D坐标转为标准化设备坐标,同时顶点着色器允许我们对顶点属性进行一些基本处理,如矩阵变换。
  • 图元装配:它将所有的顶点作为输入,将其装配成一个具体的形状。
  • 几何着色器:几何着色器把图元形式的一系列顶点的集合作为输入,通过产生新顶点构造出新的图元来生成其他形状。
  • 光栅化: 图元映射为最终屏幕上相应的像素
  • 片段着色器: 计算一个像素的最终颜色
  • 测试和混合: 根据Z轴选择是否抛弃该点(被遮住),以及对于透明的颜色进行混合。

三、绘制三角形

绘制三角形,分为如下几步:

  • 存储顶点数据到ByteBuffer中
  • 编写顶点和片段着色器,并加载相应的着色器(shader)
  • 创建着色器程序,并对顶点和片段着色器进行链接
  • 视口变换操作(glViewport)
  • 传递顶点和颜色数据
  • 绘制物体

1)、环境准备

在AndroidManifest中配置OpenGLES版本

<uses-feature android:glEsVersion="0x00020000" android:required="true" />

并用GLSurfaceView搭建好一个EGL环境,所有的操作在GLSurfaceView.Render的回调中进行。回调开始的时候代表着EGL环境搭建完毕,否则会导致操作失败。

2)、存储顶点数据到ByteBuffer中

    // 三维的顶点坐标,有方向的
    private static final float triangleCoords[] = {
            -0.5f, 1f, 0.0f,  // bottom right
            -1f, -1f, 0.0f, // bottom left
            0.5f, 1f, 0.0f, // top
    };


    private static final short indices[] = {
            0,1,2
    };

    // 颜色
    private static final  float colors[] = {0.8f, 0.4f, 0.1f, 0f};

        // 1、存储顶点坐标
        vertexBuffer = initBuffer(triangleCoords,4);
        ByteBuffer mbb = ByteBuffer.allocateDirect(triangleCoords.length * 4);
        // 数组排列用nativeOrder
        mbb.order(ByteOrder.nativeOrder());
        FloatBuffer floatBuffer = mbb.asFloatBuffer();
        floatBuffer.put(vertexBuffer);
        floatBuffer.flip();
        
        mbb = ByteBuffer.allocateDirect(indices.length * 2);
        // 数组排列用nativeOrder
        mbb.order(ByteOrder.nativeOrder());
        indiceBuffer = mbb.asShortBuffer();
        indiceBuffer.put(indices);
        indiceBuffer.flip();

将顶点坐标存放在ByteBuffer中,供下文传递数据到Shader中。

3)、编写顶点和片段着色器代码,并加载相应的着色器

分别编写顶点和片段GLSL代码,Shader是通过输入和输出进行通信的。顶点着色器中用四个元素的向量的属性值传递顶点值,传递到内置的变量gl_Position中。片段着色器是输入颜色的,这里传入的类型是uniform。precision mediump float是代表精度。

    // 顶点着色器code
    private static final String vertexShaderCode =
            "attribute vec4 vPosition;" +
            "void main() {" +
            "  gl_Position = vPosition;" +
            "}";

    // 片元着色器code
    private static final String fragmentShaderCode =
            "precision mediump float;" +
            "uniform vec4 vColor;" +
            "void main() {" +
            "    gl_FragColor = vColor;" +
            "}";
加载shader:

分别创建、加载以及编译着色器代码:

    private int loadShader(int type, String shaderCode) {
        //根据type创建顶点着色器或者片元着色器
        int shader = GLES20.glCreateShader(type);
        //将资源加入到着色器中,并编译
        GLES20.glShaderSource(shader, shaderCode);
        GLES20.glCompileShader(shader);
        return shader;
    }

        int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER,vertexShaderCode);
        int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER,fragmentShaderCode);

4)、创建着色器程序,并对顶点和片段着色器进行链接

顶点和片段着色器只有在链接到着色器程序中,才会起作用。

        //创建一个空的OpenGLES程序
        mProgram = GLES20.glCreateProgram();
        //将顶点着色器加入到程序
        GLES20.glAttachShader(mProgram, vertexShader);
        //将片元着色器加入到程序中
        GLES20.glAttachShader(mProgram, fragmentShader);
        //连接到着色器程序
        GLES20.glLinkProgram(mProgram);

5)、视口变换操作(glViewport)

// 设置窗口的大小
GLES20.glViewport(0,0,width,height);

6)、传递顶点和颜色数据

现在需要将顶点、颜色、顶点索引数据传入到相应的地方,这样才会绘制出想要的形状。

首先需要获取属性为vPosition的句柄和uniform为vColor的句柄,并根据Attribute和Uniform两种方式传入对应的数据。

        GLES20.glUseProgram(mProgram);
        GLES20.glClearColor(0, 0, 0, 1);
        GLES20.glDisable(GLES20.GL_DEPTH_TEST); // 当我们需要绘制透明图片时,就需要关闭它
        // 填充数据
        //获取顶点着色器的vPosition成员句柄
        mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
        //启用三角形顶点的句柄
        GLES20.glEnableVertexAttribArray(mPositionHandle);
        //准备三角形的坐标数据
        GLES20.glVertexAttribPointer(mPositionHandle, 3,
                GLES20.GL_FLOAT, false,
                12, vertexBuffer);

        mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
        Log.e(TAG, "onDrawFrame: mPositionHandle="+mPositionHandle+"  mColorHandle="+mColorHandle);
        //设置绘制三角形的颜色
        GLES20.glUniform4fv(mColorHandle, 1, colors, 0);

7)、绘制物体

通过glDrawElements方法绘制图形,glDrawElements需要使用到索引数组。绘制的API还可以是glDrawArrays,这里不需要明确的设置绘制的顺序,而是通过绘制的模式来决定的。

        //绘制三角形
        GLES20.glDrawElements(GLES20.GL_TRIANGLE_STRIP, 3,GLES20.GL_UNSIGNED_SHORT,indiceBuffer);

四、绘制模式详解

对于绘制的模式,上面的代码使用的是GLES20.GL_TRIANGLE_STRIP,除此之外还有些其它的模式:、

  • GLES20.GL_POINTS:绘制点精灵模式
  • GLES20.GL_LINES:对于n各定点,会绘制(v0、v1)、(v2、v3)、(v4、v5)、(vn-2、vn-1)条线段
  • GLES20.GL_LINE_LOOP:对于n各定点,会绘制(v0、v1)、(v1、v2)、(v2、v3)、(vn-2、vn-1)条线
  • GLES20.GL_LINE_STRIP:对于n各定点,会绘制(v0、v1)、(v1、v2)、(v2、v3)、(vn-2、vn-1)、(vn-1、v0)条线,这绘制的会多了个首位相连的线段。
  • GLES20.GL_TRIANGLES:同GL_LINES,它是不重复的使用顶点
  • GLES20.GL_TRIANGLE_STRIP :绘制一系列相互连接的三角形,对于5个顶点,它绘制的是(v0,v1,v2)、(v2,v1,v3)、(v2,v3,v4)
  • GLES20.GL_TRIANGLE_FAN: 绘制一系列相互连接的三角形,对于5个顶点,它绘制的是(v0,v1,v2)、(v0,v2,v3)、(v0,v3,v4)

五、参考

LearnOpenGL-CN
Jhuster的专栏

上例代码下载

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

推荐阅读更多精彩内容