Android OpenGL ES 5.渐变色

前言

之前我们绘制的图形都是只有一个颜色,而本章节我们绘制一个正方形,并且给图形上渐变色,让简单的图形变得绚丽些。

原理

在基础概念的课程中,我们讲解了渲染管道的流程,现在我们再回顾一下。

OpenGL ES开发重点

之前的章节我们都了解使用过了uniform、attribute这两个限定符,现在我们再了解下varying这个限定符。

  • attritude:一般用于各个顶点各不相同的量。如顶点位置、纹理坐标、法向量、颜色等等。
  • uniform:一般用于对于物体中所有顶点或者所有的片段都相同的量。比如光源位置、统一变换矩阵、颜色等。
  • varying:表示易变量,一般用于顶点着色器传递到片段着色器的量。

从上图我们了解到,varying是从顶点着色器传递给片段着色器的变量数据,但这不够严谨准确,接下来仔细分析下这个流程。

我们举例的场景是这样的:有一条线段,有2个顶点,顶点A是红色,顶点B是蓝色,他们做渐变色处理。相关代码如下:

private static final String VERTEX_SHADER = "" +
        "uniform mat4 u_Matrix;\n" +
        "attribute vec4 a_Position;\n" +
        // a_Color:从外部传递进来的每个顶点的颜色值
        "attribute vec4 a_Color;\n" +
        // v_Color:将每个顶点的颜色值传递给片段着色器
        "varying vec4 v_Color;\n" +
        "void main()\n" +
        "{\n" +
        "    v_Color = a_Color;\n" +
        "    gl_Position = u_Matrix * a_Position;\n" +
        "}";
private static final String FRAGMENT_SHADER = "" +
        "precision mediump float;\n" +
        // v_Color:从顶点着色器传递过来的颜色值
        "varying vec4 v_Color;\n" +
        "void main()\n" +
        "{\n" +
        "    gl_FragColor = v_Color;\n" +
        "}";
  1. 顶点着色器 : 每个顶点都执行一次,比如我们绘制一个线段,包含了2个顶点,那么就是执行2次顶点着色器。而我们这里传递给顶点着色器的数据包含了每个顶点的位置、颜色。
  2. 组装图元:将顶点连接,根据需求绘制顶点、线段、三角形,本次案例是线段。
  3. 光栅化图元:关键! 在光栅化图元的时候,将两个顶点之间的线段分解成大量的小片段,varying数据在这个过程中计算生成,记录在每个片段中(而不是从顶点着色器直接传递给片段着色器)。
  4. 片段着色器:每个片段都计算一次,假如是线段中间的片段,那么传递过来的varying值是紫色的。

所以,梳理下上面的流程:顶点的位置(attribute)、颜色(attribute) → 顶点着色器 → 光栅化:计算出每个片段的具体颜色值 → 片段着色器

代码实现

了解了原理、GLSL代码后,其实Java层的部分和之前差不多,应该部分读者可以自己写出来了。
这次课程的案例代码是一个彩色的正方形,GLSL代码如上,Java代码如下。

private static final float[] POINT_DATA = {
        -0.5f, -0.5f,
        0.5f, -0.5f,
        -0.5f, 0.5f,
        0.5f, 0.5f,
};
private static final float[] COLOR_DATA = {
        // 一个顶点有3个向量数据:r、g、b
        1f, 0.5f, 0.5f,
        1f, 0f, 1f,
        0f, 1f, 1f,
        1f, 1f, 0f,
};


/**
 * 坐标占用的向量个数
 */
private static final int POSITION_COMPONENT_COUNT = 2;
/**
 * 颜色占用的向量个数
 */
private static final int COLOR_COMPONENT_COUNT = 3;

@Override
public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
    // 省略部分代码
    int aPositionLocation = getAttrib("a_Position");
    int aColorLocation = getAttrib("a_Color");
    mProjectionMatrixHelper = new ProjectionMatrixHelper(mProgram, "u_Matrix");

    mVertexData.position(0);
    GLES20.glVertexAttribPointer(aPositionLocation,
            POSITION_COMPONENT_COUNT, GLES20.GL_FLOAT, false, 0, mVertexData);
    GLES20.glEnableVertexAttribArray(aPositionLocation);

    mColorData.position(0);
    GLES20.glVertexAttribPointer(aColorLocation,
            COLOR_COMPONENT_COUNT, GLES20.GL_FLOAT, false, 0, mColorData);
    GLES20.glEnableVertexAttribArray(aColorLocation);
}

@Override
public void onDrawFrame(GL10 glUnused) {
    GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
    GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, POINT_DATA.length / POSITION_COMPONENT_COUNT);
}

效果如下:


渐变色正方形.png

数据传递格式优化

上面我们在描述顶点的信息时候,用了2个数组去分别描述位置、颜色两个信息。那么我们就得去确保两个数组中顶点的位置和颜色是否一一对应,一定错乱就会得不到想要的效果。除了2个数组存两种信息的方式外,我们还可以选择一个数组存两种信息的方式。可以采用“顶点1位置+顶点1颜色+顶点2位置+顶点2颜色......”这样的方式去存储。代码如下:

private static final float[] POINT_DATA = {
    // 一个顶点有5个向量数据:x、y、r、g、b
    -0.5f, -0.5f, 1f, 0.5f, 0.5f,
    0.5f, -0.5f, 1f, 0f, 1f,
    -0.5f, 0.5f, 0f, 1f, 1f,
    0.5f, 0.5f, 1f, 1f, 0f,
};

这样我们每个顶点的颜色和位置从代码层面上的清晰度就比较明确了。

而在将这些数据传递给GLSL中,则会稍微复杂点,我们需要引入跨距“Stride”这个概念。

/**
 * 坐标占用的向量个数
 */
private static final int POSITION_COMPONENT_COUNT = 2;
/**
 * 颜色占用的向量个数
 */
private static final int COLOR_COMPONENT_COUNT = 3;
/**
 * 数据数组中每个顶点起始数据的间距:数组中每个顶点相关属性占的Byte值
 */
private static final int STRIDE =
        (POSITION_COMPONENT_COUNT + COLOR_COMPONENT_COUNT) * BYTES_PER_FLOAT;
@Override
public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
    // 省略部分代码......
    mVertexData.position(0);
    GLES20.glVertexAttribPointer(aPositionLocation,
            POSITION_COMPONENT_COUNT, GLES20.GL_FLOAT, false, STRIDE, mVertexData);
    GLES20.glEnableVertexAttribArray(aPositionLocation);

    // 将数组的初始读取位置右移2位,所以数组读取的顺序是r1, g1, b1, x2, y2, r2, g2, b2...
    mVertexData.position(POSITION_COMPONENT_COUNT);
    // COLOR_COMPONENT_COUNT:从数组中每次读取3个向量
    // STRIDE:每次读取间隔是 (2个位置 + 3个颜色值) * Float占的Byte位
    GLES20.glVertexAttribPointer(aColorLocation,
            COLOR_COMPONENT_COUNT, GLES20.GL_FLOAT, false, STRIDE, mVertexData);
    GLES20.glEnableVertexAttribArray(aColorLocation);
}

现在我们结合上面方法内第二段代码中,实现颜色数据传递的内容,重新再讲解下glVertexAttribPointer这个方法的参数要求。

  1. 顶点信息索引,位置、纹理坐标、法向量、颜色等等 - aColorLocation,顶点颜色索引
  2. 每个顶点属性需要关联的分量个数(必须为1、2、3或者4。初始值为4。) - |COLOR_COMPONENT_COUNT,颜色RGB需要3个向量
  3. 数据类型 - GLES20.GL_FLOAT,浮点类型
  4. 指定当被访问时,固定点数据值是否应该被归一化(GL_TRUE)或者直接转换为固定点值(GL_FALSE)(只有使用整数数据时)
  5. 指定连续顶点属性之间的偏移量。如果为0,那么顶点属性会被理解为:它们是紧密排列在一起的。初始值为0。- STRIDE:每次读取间隔是 (2个位置 + 3个颜色值) * Float占的Byte位 = 5 * 4 = 20.
  6. 数据缓冲区 - mVertexData,顶点数据,包含了位置和颜色

场景应用

在顶点数据较多,且属性数据不变的情况下,使用单个数组来存储数据是个比较好的方案,而在部分数据有所变动,如顶点位置不变,颜色变化的情况下,用多个数组存储数据会是比较好的方案。

参考

Android OpenGL ES学习资料所列举的博客、资料。

GitHub代码工程

本系列课程所有相关代码请参考我的GitHub项目GLStudio

课程目录

本系列课程目录详见 简书 - Android OpenGL ES教程规划

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

推荐阅读更多精彩内容