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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/byhook/article/details/83859432

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;
}

运行输出:

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;
}

取消映射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卷》

猜你喜欢

转载自blog.csdn.net/byhook/article/details/83859432