Android显示系统(06)- OpenGL ES - VBO和EBO和VAO

Android显示系统(01)- 架构分析
Android显示系统(02)- OpenGL ES - 概述
Android显示系统(03)- OpenGL ES - GLSurfaceView的使用
Android显示系统(04)- OpenGL ES - Shader绘制三角形
Android显示系统(05)- OpenGL ES - Shader绘制三角形(使用glsl文件)
Android显示系统(06)- OpenGL ES - VBO和EBO和VAO
Android显示系统(07)- OpenGL ES - 纹理Texture
Android显示系统(08)- OpenGL ES - 图片拉伸
Android显示系统(09)- SurfaceFlinger的使用
Android显示系统(10)- SurfaceFlinger内部结构
Android显示系统(11)- 向SurfaceFlinger申请Surface
Android显示系统(12)- 向SurfaceFlinger申请Buffer
Android显示系统(13)- 向SurfaceFlinger提交Buffer

一、前言:

之前代码,我们都是直接在java代码中定义顶点数组,然后,将顶点数据存储在FloatBuffer对象,最后,传递给GPU。如下所示:

public class Triangle {
   
   
    private FloatBuffer mVertexBuffer;
    // 定义的三角形顶点坐标数组
    private final float[] mTriangleCoords = new float[]{
   
   
            0.0f, 0.2f, 0.0f,   // 顶部
            -0.5f, -0.5f, 0.0f, // 左下角
            0.5f, -0.5f, 0.0f   // 右下角
    };

    public Triangle(Context context) {
   
   
        // 为顶点坐标分配DMA内存空间
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(mTriangleCoords.length * 4);
        // 设置字节顺序为本地字节顺序(会根据硬件架构自适应大小端)
        byteBuffer.order(ByteOrder.nativeOrder());
        // 将字节缓冲区转换为浮点缓冲区
        mVertexBuffer = byteBuffer.asFloatBuffer();
        // 将顶点三角形坐标放入缓冲区
        mVertexBuffer.put(mTriangleCoords);
        // 设置缓冲区的位置指针到起始位置
        mVertexBuffer.position(0);
// ... 删除不相关代码
    }
}

但是,这个FloatBuffer顶点数组的存储对象是在我们CPU管理的主内存当中,一种叫做DMA的内存(Direct Memory Access),它可以直接供底层 GPU 使用,不受 Java 垃圾回收的影响,不需要CPU参与。

而我们绘制三角形时候怎么做的呢?看代码:

public class GLRenderTest implements GLSurfaceView.Renderer {
   
   
    private Triangle mTriangle;
    // ... 删除非关键代码
    @Override
    public void onDrawFrame(GL10 gl){
   
   
        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
        mTriangle.draw();
    }
}

看得出我们会在onDrawFrame当中调用draw方法,并且,onDrawFrame是每一帧都要被调用,如果视频帧率是60fps,那么一秒钟就要拷贝60次数据,显然非常低效。

那么,有没有更好的方法呢?当然有,要么我写文章干啥呢?( ̄▽ ̄)"~~,当然有,因此聪明的工程师们已经帮我们搞出来了本文主角VBO\EBO\VAO。

二、VBO:

  • VAO(vertex-array object)顶点数组对象,用来管理VBO。
  • VBO(vertex buffer object)顶点缓冲对象,用来缓存用户传入的顶点数据。
  • EBO(element buffer object)索引缓冲对象,用来存放顶点索引数据。

来,分开看看:

1、概念:

先在GPU中分配一个存储空间VBO,然后,一次性从主内存拷贝数据到GPU的VBO当中;

1)使用VBO的好处:

  • 减少了主内存到GPU的数据传输次数,原来是每一帧都拷贝,现在只拷贝一次;
  • 数据保存在GPU当中,可以重复使用;
  • 还可以在GPU中对数据进行转换,减少CPU负载;

2)VBO使用步骤:

  • 生成并绑定VBO:

    • 使用 glGenBuffers() 生成一个 VBO 对象。

    • 使用 glBindBuffer() 将 VBO 绑定到指定的缓冲区类型(如 GL_ARRAY_BUFFER)。

  • 分配内存并传递数据:

    • 使用 glBufferData() 分配内存空间并传递顶点数据到 VBO。
    • 可以使用 glBufferSubData() 更新部分数据或者使用 glMapBuffer() 来映射缓冲区进行数据修改。
  • 设置顶点属性指针:

    • 在绑定 VBO 后,使用 glVertexAttribPointer() 来告诉OpenGL如何解释顶点数据。
    • 使用 glEnableVertexAttribArray() 启用顶点属性数组。
  • 解绑VBO:

    • 在完成数据传递后,使用 glBindBuffer(GL_ARRAY_BUFFER, 0) 来解绑 VBO。

3)关键API:

glGenBuffers(GLsizei n, GLuint *buffers)

该函数用于生成 VBO 对象的名称。

  • 参数 n 指定要生成的 VBO 对象的数量。
  • 参数 buffers 是一个指向 GLuint 类型的数组,用于存储生成的 VBO 对象的名称。

glDeleteBuffers(GLsizei n, const GLuint *buffers)

用于删除通过 glGenBuffers 生成的 VBO 对象。

  • 参数 n 指定要删除的 VBO 对象的数量。
  • 参数 buffers 是一个指向 GLuint 类型的数组,包含要删除的 VBO 对象的名称。

glBindBuffer(GLenum target, GLuint buffer)

用于绑定一个 VBO 对象到指定的缓冲区类型。

  • 参数 target 指定要绑定的缓冲区类型,如 GL_ARRAY_BUFFER 表示顶点属性数据缓冲区。
  • 参数 buffer 是要绑定的 VBO 对象的名称。

glBufferData(GLenum target, GLsizeiptr size, const GLvoid *data, GLenum usage)

用于分配内存空间并传递数据到 VBO。

  • target 指定要分配数据的缓冲区类型。
  • size 指定要分配的数据大小。
  • data 是指向要传递数据的指针。
  • usage 表示数据在未来的使用方式,如 GL_STATIC_DRAW 表示数据将被修改一次,但使用多次。

glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer)

用于告诉 OpenGL 如何解释顶点数据。

  • index 指定顶点属性的索引。
  • size 指定每个顶点属性的组件数量。
  • type 指定顶点属性数据的类型。
  • normalized 表示是否对非浮点型数据进行归一化。
  • stride 表示相邻两个顶点属性之间的字节偏移量。
  • pointer 指定顶点数据在缓冲区中的偏移量。

glEnableVertexAttribArray(GLuint index)

启用指定索引的顶点属性数组。

  • index 是要启用的顶点属性数组的索引。

2、修改之前的代码:

代码都是在com/example/glsurfaceviewdemo/Triangle.java修改的。

1)生成并绑定 VBO:

Triangle 类中,你需要生成并绑定一个 VBO 来存储顶点数据。这应该在构造函数中完成。

 private int mVboId; // 类成员变量
// 生成并绑定 VBO
int[] vbos = new int[1];
GLES30.glGenBuffers(1, vbos, 0);
mVboId = vbos[0];
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mVboId);

2)传递顶点数据到 VBO:

替代在构造函数中直接将顶点数据传递给 FloatBuffer,你应该将顶点数据传递到GPU的 VBO 中。这可以通过 glBufferData 实现。

 // 传递顶点数据到 VBO
GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER, byteBuffer.capacity(), byteBuffer, GLES30.GL_STATIC_DRAW);
  • GLES30.GL_ARRAY_BUFFER: 这是一个缓冲区对象类型标识符,表示我们正在操作的是一个顶点数组缓冲区对象。在这里,我们将顶点数据存储在一个顶点数组缓冲区中。
  • byteBuffer.capacity(): 这里计算了顶点数据数组的总字节数。mTriangleCoords 是包含顶点坐标的浮点数组,每个浮点数占4个字节(float类型为32位,即4字节),所以乘以4得到总字节数。
  • byteBuffer: 这是存储顶点数据的缓冲区对象。在这里,我们使用 mVertexBuffer 存储了顶点数据,它是一个 FloatBuffer 类型的对象。
  • GLES30.GL_STATIC_DRAW: 这个标志告诉OpenGL ES如何处理缓冲区的数据。GL_STATIC_DRAW 表示数据将被设置一次,但将被多次使用。这个标志有助于OpenGL ES优化内存使用和性能。

注意:我们对所有的VBO操作都应该放在glLinkProgram后面,因为,所有的Shader都link之后,再操作VBO;

3)设置顶点属性指针:

draw() 方法中,你需要设置顶点属性指针以从 VBO 中读取顶点数据(就是告诉OpenGL如何解释顶点数据)。这可以通过 glVertexAttribPointerglEnableVertexAttribArray 实现。

public void draw() {
   
   
    GLES30.glUseProgram(mProgram);

    // 确保绑定 VBO (保险措施)
    GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mVboId[0]);

    // 设置顶点属性指针
    int positionHandle = GLES30.glGetAttribLocation(mProgram, "vPosition");
    GLES30.glEnableVertexAttribArray(mPositionHandle);
    GLES30.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX, GLES30.GL_FLOAT, false, 0, 0);

    // 设置颜色句柄和绘制三角形
    int colorHandle = GLES30.glGetUniformLocation(mProgram, "vColor");
    GLES30.glUniform4fv(colorHandle, 1, mColor, 0);
    GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, mTriangleCoords.length / COORDS_PER_VERTEX);

    // 禁用顶点属性数组
    GLES30.glDisableVertexAttribArray(mPositionHandle);

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

通过在每次绘制结束后禁用顶点属性数组和解绑 VBO,您可以确保在下一次绘制之前不会使用或修改之前设置的数据,从而避免潜在的渲染问题。

4)修改后完整代码:

package com.example.glsurfaceviewdemo;

import android.content.Context;
import android.opengl.GLES30;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.opengles.GL;

public class Triangle {
   
   
    // 顶点数据是float类型,因此,使用这个存储
    private FloatBuffer mVertexBuffer;
    // VBO存储顶点数据
    private int mVboId;

    private int mProgram;
    // 定义的三角形顶点坐标数组
    private final float[] mTriangleCoords = new float[]{
   
   
            0.0f, 0.2f, 0.0f,   // 顶部
            -0.5f, -0.5f, 0.0f, // 左下角
            0.5f, -0.5f, 0.0f   // 右下角
    };

    public Triangle(Context context) {
   
   
        // 为顶点坐标分配DMA内存空间
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(mTriangleCoords.length * 4);
        byteBuffer.order(ByteOrder.nativeOrder()); // 设置字节顺序为本地字节顺序(会根据硬件架构自适应大小端)
        mVertexBuffer = byteBuffer.asFloatBuffer(); // 将字节缓冲区转换为浮点缓冲区
        mVertexBuff
### Android 平台上 OpenGLVBOEBO VAO 的用法 #### 一、VBO (Vertex Buffer Object) VBO 是用于存储大量顶点数据的对象。通过将顶点数据上传至 GPU 显存中,可以显著提高渲染效率。 ```java // 创建并绑定一个VBO int vbo; vbo = GLES20.glGenBuffers(); GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vbo); FloatBuffer vertexBuffer = ... // 初始化顶点缓冲区 GLES20.glBufferData( GLES20.GL_ARRAY_BUFFER, vertexBuffer.capacity() * Float.BYTES, vertexBuffer, GLES20.GL_STATIC_DRAW ); ``` 此代码片段展示了如何创建初始化一个 VBO 来保存顶点坐标[^1]。 #### 二、EBO (Element Buffer Object 或 Index Buffer) EBO 存储的是索引列表,用来定义哪些顶点应该被连接起来形成几何图形。这有助于减少重复顶点的数量,从而节省内存空间并加快绘制速度。 ```java // 创建并填充EBO int ebo; ebo = GLES20.glGenBuffers(); GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, ebo); ShortBuffer indexBuffer = ... // 初始化索引缓冲区 GLES20.glBufferData( GLES20.GL_ELEMENT_ARRAY_BUFFER, indexBuffer.capacity() * Short.BYTES, indexBuffer, GLES20.GL_STATIC_DRAW ); ``` 上述代码说明了怎样构建 EBO 及其关联的数据结构。 #### 三、VAO (Vertex Array Object) VAO 负责管理多个 VBOs 它们之间的配置关系。它记录了每个属性指针的位置以及相应的格式信息,使得切换不同类型的顶点布局变得简单快捷。 ```java // 设置VAO状态 int vao; vao = GLES20.glGenVertexArrays(); GLES20.glBindVertexArray(vao); // 配置顶点属性指针 GLES20.glEnableVertexAttribArray(0); // 启用第一个属性列 GLES20.glVertexAttribPointer( 0, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, offset ); // 解绑当前VAO GLES20.glBindVertexArray(0); ``` 这段程序描述了如何建立一个新的 VAO 实例,并对其进行必要的参数设定[^3]。 当准备实际绘图时: ```java // 绑定先前设置好的VAO GLES20.glBindVertexArray(vao); // 执行绘制命令 if(ebo != null){ GLES20.glDrawElements(primitiveType, count, type, indicesOffset); }else{ GLES20.glDrawArrays(primitiveType, firstIndex, count); } // 清理工作 GLES20.glBindVertexArray(0); ``` 以上就是针对 Android 设备上使用 OpenGL ES 进行三维图形编程时涉及到的三个重要概念及其具体实现方法[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值