android平台下OpenGL ES 3.0实例详解顶点缓冲区对象(VBO)和顶点数组对象(VAO)

OpenGL ES 3.0学习实践

目录

顶点缓冲区对象(VBO)

顶点缓冲区对象(Vertex Buffer Object),简称VBO。如果不使用顶点缓冲区对象(VBO)则是将顶点、颜色、纹理坐标等数据存放在内存(客户内存)当中,在每次进行glDrawArxays或者gIDrawElements等绘图调用时,必须从客户内存复制到图形内存。而顶点缓冲区对象使OpenGL ES 3.0应用程序可以在高性能的图形内存中分配和缓存顶点数据,并从这个内存进行渲染,从而避免在每次绘制图元的时候重新发送数据。不仅是顶点数据,描述图元顶点索引、作为glDrawElements参数传递的元素索引也可以缓存。

OpenGL ES 3.0支持两类缓冲区对象:数组缓冲区对象元素数组缓冲区对象

GL_ARRAY_BUFFER标志指定的数组缓冲区对象用于创建保存顶点数据的缓冲区对象。
GL_ELEMENT_ARRAY_BUFFER标志指定的元素数组缓冲区对象用于创建保存图元索引的缓冲区对象。
OpenGL ES 3.0中的其他缓冲区对象类型:统一变量缓冲区变换反馈缓冲区像素解包缓冲区像素包装缓冲区复制缓冲区

OpenGL ES 3.0建议应用程序对顶点属性数据和元素索引使用顶点缓冲区对象

基于之前的工程项目,新建VertexBufferRenderer.java文件:

/**
 * @anchor: andy
 * @date: 2018-11-02
 * @description: 顶点缓冲区
 */
public class VertexBufferRenderer implements GLSurfaceView.Renderer {

    private static final String TAG = "VertexBufferRenderer";

    private static final int VERTEX_POS_INDEX = 0;

    private final FloatBuffer vertexBuffer;

    private static final int VERTEX_POS_SIZE = 3;

    private static final int VERTEX_STRIDE = VERTEX_POS_SIZE * 4;

    private int mProgram;

    /**
     * 点的坐标
     */
    private float[] vertexPoints = new float[]{
            0.0f, 0.5f, 0.0f,
            -0.5f, -0.5f, 0.0f,
            0.5f, -0.5f, 0.0f
    };

    /**
     * 缓冲数组
     */
    private int[] vboIds = new int[1];

    public VertexBufferRenderer() {
        //分配内存空间,每个浮点型占4字节空间
        vertexBuffer = ByteBuffer.allocateDirect(vertexPoints.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        //传入指定的坐标数据
        vertexBuffer.put(vertexPoints);
        vertexBuffer.position(0);
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        //设置背景颜色
        GLES30.glClearColor(0.5f, 0.5f, 0.5f, 0.5f);
        //编译
        final int vertexShaderId = ShaderUtils.compileVertexShader(ResReadUtils.readResource(R.raw.vertex_buffer_shader));
        final int fragmentShaderId = ShaderUtils.compileFragmentShader(ResReadUtils.readResource(R.raw.fragment_buffer_shader));
        //鏈接程序片段
        mProgram = ShaderUtils.linkProgram(vertexShaderId, fragmentShaderId);

        //1. 生成1个缓冲ID
        GLES30.glGenBuffers(1, vboIds, 0);

        //2. 绑定到顶点坐标数据缓冲
        GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vboIds[0]);
        //3. 向顶点坐标数据缓冲送入数据
        GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER, vertexPoints.length * 4, vertexBuffer, GLES30.GL_STATIC_DRAW);

        //4. 将顶点位置数据送入渲染管线
        GLES30.glVertexAttribPointer(VERTEX_POS_INDEX, VERTEX_POS_SIZE, GLES30.GL_FLOAT, false, VERTEX_STRIDE, 0);
        //5. 启用顶点位置属性
        GLES30.glEnableVertexAttribArray(VERTEX_POS_INDEX);

    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        GLES30.glViewport(0, 0, width, height);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
        //6. 使用程序片段
        GLES30.glUseProgram(mProgram);

        //7. 开始绘制三角形
        GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, 3);

        //8. 解绑VBO
        GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,0);
    }
}

顶点着色器

#version 300 es
layout (location = 0) in vec4 vPosition;
out vec4 vColor;
void main() {
     gl_Position  = vPosition;
     gl_PointSize = 10.0;
     vColor = vec4(0.2f,0.4f,0.6f,1.0f);
}

片段着色器

#version 300 es
precision mediump float;
in vec4 vColor;
out vec4 fragColor;
void main() {
     fragColor = vColor;
}

运行输出:

image
GLES30.glDeleteBuffers

顶点数组对象(VAO)

加载顶点属性的两种不同方式:使用客户顶点数组和使用顶点缓冲区对象

顶点缓冲区对象优于客户顶点数组,因为它们能够减少CPU和GPU之间复制的数据量,从而获得更好的性能。在OpenGL ES 3.0中引人了一个新特性,使顶点数组的使用更加高效:顶点数组对象(VAO)

使用顶点缓冲区对象设置绘图操作可能需要多次调用glBindBufferglVertexAttribPointerglEnableVertexAttribArray。为了更快地在顶点数组配置之间切换,OpenGL ES 3.0推出了顶点数组对象VAO提供包含在 顶点数组/顶点缓冲区对象配置之间切换所需要的所有状态的单一对象。

OpenGLES 3.0中总是有一个活动的顶点数组对象。之前的一些实例默认都在顶点数组对象上操作(默认VAO的ID为0)。要创建新的顶点数组对象,可以使用glGenVertexArrays方法。

还是基于之前的工程项目,新建VertexArrayRenderer.java文件:

/**
 * @anchor: andy
 * @date: 2018-11-02
 * @description:
 */
public class VertexArrayRenderer implements GLSurfaceView.Renderer {

    private static final String TAG = "VertexBufferRenderer";

    private static final int VERTEX_POS_INDEX = 0;

    private final FloatBuffer vertexBuffer;

    private static final int VERTEX_POS_SIZE = 3;

    private static final int VERTEX_STRIDE = VERTEX_POS_SIZE * 4;

    private int mProgram;

    /**
     * 点的坐标
     */
    private float[] vertexPoints = new float[]{
            0.0f, 0.5f, 0.0f,
            -0.5f, -0.5f, 0.0f,
            0.5f, -0.5f, 0.0f
    };

    /**
     * 缓冲数组
     */
    private int[] vaoIds = new int[1];

    private int[] vboIds = new int[1];

    public VertexArrayRenderer() {
        //分配内存空间,每个浮点型占4字节空间
        vertexBuffer = ByteBuffer.allocateDirect(vertexPoints.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        //传入指定的坐标数据
        vertexBuffer.put(vertexPoints);
        vertexBuffer.position(0);
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        //设置背景颜色
        GLES30.glClearColor(0.5f, 0.5f, 0.5f, 0.5f);
        //编译
        final int vertexShaderId = ShaderUtils.compileVertexShader(ResReadUtils.readResource(R.raw.vertex_array_shader));
        final int fragmentShaderId = ShaderUtils.compileFragmentShader(ResReadUtils.readResource(R.raw.fragment_array_shader));
        //鏈接程序片段
        mProgram = ShaderUtils.linkProgram(vertexShaderId, fragmentShaderId);
        //使用程序片段
        GLES30.glUseProgram(mProgram);

        //生成1个缓冲ID
        GLES30.glGenVertexArrays(1, vaoIds, 0);

        //绑定VAO
        GLES30.glBindVertexArray(vaoIds[0]);

        //1. 生成1个缓冲ID
        GLES30.glGenBuffers(1, vboIds, 0);
        //2. 向顶点坐标数据缓冲送入数据把顶点数组复制到缓冲中
        GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vboIds[0]);
        GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER, vertexPoints.length * 4, vertexBuffer, GLES30.GL_STATIC_DRAW);

        //3. 将顶点位置数据送入渲染管线
        GLES30.glVertexAttribPointer(VERTEX_POS_INDEX, VERTEX_POS_SIZE, GLES30.GL_FLOAT, false, VERTEX_STRIDE, 0);
        //启用顶点位置属性
        GLES30.glEnableVertexAttribArray(VERTEX_POS_INDEX);

        //4. 解绑VAO
        GLES30.glBindVertexArray(0);
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        GLES30.glViewport(0, 0, width, height);
    }

    @Override
    public void onDrawFrame(GL10 gl) {

        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
        //使用程序片段
        GLES30.glUseProgram(mProgram);

        //5. 绑定VAO
        GLES30.glBindVertexArray(vaoIds[0]);

        //6. 开始绘制三角形
        GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, 3);

        //7. 解绑VAO
        GLES30.glBindVertexArray(0);

    }
}

当应用程序结束一个或者多个顶点数组对象的使用时,可以删除它们。

void glDeleteVeitexAnays();

映射缓冲区对象

上面的两个例子基本已经了解了glBufferData的使用,应用程序也可以将缓冲区对象数据存储映射到应用程序的地址空间(也可以解除映射)。应用程序映射缓冲区而不使用glBufferData或者glBufferSubData加载数据的原因在于:

  • 映射缓冲区可以减少应用程序的内存占用,因为可能只需要存储数据的一个副本。
  • 在使用共享内存的架构上,映射缓冲区返回GPU存储缓冲区的地址空间的直接指针。

通过映射缓冲区,应用程序可以避免复制步骤,从而实现更好的更新性能。 glMapBufferRange方法返回指向所有或者一部分(范围)缓冲区对象数据存储的Buffer。 这个Buffer可以供应用程序使用,以读取或者更新缓冲区对象的内容。

Buffer GLES30.glMapBufferRange(int target,int offset,int length,int access);
访问标志 描述
GL_MAP_READ_BIT 应用程序将从返回的指针读取
GL_MAP_WRITE_BIT 应用程序将写人返回的指针

此外,应用程序可以包含如下可选访问标志:

访问标志 描述
GL_MAP_INVALIDATE_RANGE_BIT 表示指定范围内的缓冲区内容可以在返回指针之前由驱动程序放弃。这个标志不能与GL_MAP_READ_BIT组合使用
GL_MAP_INVALIDATE_BUFFER_BIT 表示整个缓冲区的内容可以在返回指针之前由驱动程序放弃。这个标志不能与GL_MAP_READ_BIT组合使用
GL_MAP_FLUSH_EXPLICIT_BIT 表示应用程序将明确地用glFlushMappedBufferRange刷新对映射范围子范围的操作。这个标志不能与GL_MAP_WRITE_BIT组合使用
GL一MAP一UNSYNCHRONIZED一BIT 表示驱动程序在返回缓冲区范围的指针之前不需要等待缓冲对象上的未决操作。如果有未决的操作,则未决操作的结果和缓冲区对象上的任何未来操作都变为未定义

基于SDK中的glMapBufferRange返回的实际上是java.nio.Buffer对象,这个对象可以直接操作本地内存。

还是基于之前的工程项目,新建MapBufferRenderer.java文件:

/**
 * @anchor: andy
 * @date: 2018-11-02
 * @description: 映射缓冲区对象
 */
public class MapBufferRenderer implements GLSurfaceView.Renderer {

    private static final String TAG = "VertexBufferRenderer";

    private static final int VERTEX_POS_INDEX = 0;

    private static final int VERTEX_POS_SIZE = 3;

    private static final int VERTEX_STRIDE = VERTEX_POS_SIZE * 4;

    private int mProgram;

    /**
     * 点的坐标
     */
    private float[] vertexPoints = new float[]{
            0.0f, 0.5f, 0.0f,
            -0.5f, -0.5f, 0.0f,
            0.5f, -0.5f, 0.0f
    };

    private int[] vboIds = new int[1];

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        //设置背景颜色
        GLES30.glClearColor(0.5f, 0.5f, 0.5f, 0.5f);
        //编译
        final int vertexShaderId = ShaderUtils.compileVertexShader(ResReadUtils.readResource(R.raw.vertex_map_buffer_shader));
        final int fragmentShaderId = ShaderUtils.compileFragmentShader(ResReadUtils.readResource(R.raw.fragment_map_buffer_shader));
        //鏈接程序片段
        mProgram = ShaderUtils.linkProgram(vertexShaderId, fragmentShaderId);

        //1. 生成1个缓冲ID
        GLES30.glGenBuffers(1, vboIds, 0);
        //2. 向顶点坐标数据缓冲送入数据把顶点数组复制到缓冲中
        GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vboIds[0]);
        GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER, vertexPoints.length * 4, null, GLES30.GL_STATIC_DRAW);

        //3. 映射缓冲区对象
        ByteBuffer buffer = (ByteBuffer) GLES30.glMapBufferRange(GLES30.GL_ARRAY_BUFFER, 0, vertexPoints.length * 4, GLES30.GL_MAP_WRITE_BIT | GLES30.GL_MAP_INVALIDATE_BUFFER_BIT);
        //4. 填充数据
        buffer.order(ByteOrder.nativeOrder()).asFloatBuffer().put(vertexPoints).position(0);

        //5. 将顶点位置数据送入渲染管线
        GLES30.glVertexAttribPointer(VERTEX_POS_INDEX, VERTEX_POS_SIZE, GLES30.GL_FLOAT, false, VERTEX_STRIDE, 0);
        //启用顶点位置属性
        GLES30.glEnableVertexAttribArray(VERTEX_POS_INDEX);

        //解除映射
        GLES30.glUnmapBuffer(GLES30.GL_ARRAY_BUFFER);
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        GLES30.glViewport(0, 0, width, height);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
        //使用程序片段
        GLES30.glUseProgram(mProgram);

        //6. 开始绘制三角形
        GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, 3);

        GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0);
    }
}

顶点着色器

#version 300 es
layout (location = 0) in vec4 vPosition;
out vec4 vColor;
void main() {
     gl_Position  = vPosition;
     gl_PointSize = 10.0;
     vColor = vec4(0.1f,0.2f,0.3f,1.0f);
}

片段着色器

#version 300 es
precision mediump float;
in vec4 vColor;
out vec4 fragColor;
void main() {
     fragColor = vColor;
}
image

取消映射glUnmapBuffer方法如下:

//target    必须设置为 GL_ARRAY_BUFFER
boolean glUnmapBuffer(int target);

如果取消映射操作成功,则glUnmapBufferglUnmapBuffer返回trueglMapBufferRange返回的Buffer在成功执行取消映射之后不再可以使用,如果顶点缓冲区对象数据存储中的数据在缓冲区映射之后已经破坏,glUnmapBuffer将返回false,这可能是因为屏幕分辨率的变化,OpenGL ES上下文使用多个屏幕或者导致映射内存被抛弃的内存不足事件所导致。

项目地址:
https://github.com/byhook/opengles4android

参考:
《OpenGL ES 3.0 编程指南第2版》
《OpenGL ES应用开发实践指南Android卷》

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

推荐阅读更多精彩内容