当前位置: 首页> 教育> 培训 > 广州优化排名推广_办公室oa管理系统_淄博信息港聊天室网址_网络推广赚钱平台有哪些

广州优化排名推广_办公室oa管理系统_淄博信息港聊天室网址_网络推广赚钱平台有哪些

时间:2025/7/11 18:22:53来源:https://blog.csdn.net/Ziwubiancheng/article/details/144302247 浏览次数:0次
广州优化排名推广_办公室oa管理系统_淄博信息港聊天室网址_网络推广赚钱平台有哪些

一、前言:

之前代码,我们都是直接在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;// ... 删除非关键代码@Overridepublic 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);// 解绑 VBOGLES30.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(); // 将字节缓冲区转换为浮点缓冲区mVertexBuffer.put(mTriangleCoords); // 将顶点三角形坐标放入缓冲区mVertexBuffer.position(0);// 2.加载并编译vertexShader和fragmentShaderString vertexShaderCode = ShaderController.loadShaderCodeFromFile("triangle_vertex.glsl", context);String fragmentShaderCode = ShaderController.loadShaderCodeFromFile("triangle_fragment.glsl", context);// 3.创建一个OpenGL程序,并链接程序mProgram = ShaderController.createGLProgram(vertexShaderCode, fragmentShaderCode);// 生成并绑定 VBO (一定要在createGLProgram之后)int[] vbos = new int[1];GLES30.glGenBuffers(1, vbos, 0);mVboId = vbos[0];GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mVboId);// 传递顶点数据到 VBOGLES30.glBufferData(GLES30.GL_ARRAY_BUFFER, byteBuffer.capacity(), byteBuffer, GLES30.GL_STATIC_DRAW);}// 定义的fragment的颜色数组,表示每个像素的颜色private final float[] mColor = new float[]{0.0f, 1.0f, 0.0f, 1.0f};// 顶点着色器的位置句柄private int mPositionHandle = 0;// 片元着色器的位置句柄private int mColorHandle = 0;private final int COORDS_PER_VERTEX = 3;public void draw() {// 使用programGLES30.glUseProgram(mProgram);// 确保绑定 VBO (保险措施)GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mVboId);// 设置顶点属性指针mPositionHandle = GLES30.glGetAttribLocation(mProgram, "vPosition");GLES30.glEnableVertexAttribArray(mPositionHandle);// 最后一个参数 0 是为绑定的 缓冲区对象(VBO) 指定偏移,而在没有 VBO 的情况下,0 是顶点缓冲区(mVertexBuffer)的直接内存地址GLES30.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX, GLES30.GL_FLOAT, false, 0, 0);// 获取片元着色器的颜色句柄mColorHandle = GLES30.glGetUniformLocation(mProgram, "vColor");// 设置绘制三角形的颜色GLES30.glUniform4fv(mColorHandle, 1, mColor, 0);// 绘制三角形GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, mTriangleCoords.length / COORDS_PER_VERTEX);// 禁用顶点属性数组GLES30.glDisableVertexAttribArray(mPositionHandle);// 解绑 VBOGLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0);}
}

三、EBO:

1、概念:

EBO 是 Element Buffer Object 的缩写,也称为索引缓冲对象。EBO可以帮助减少重复顶点数据的存储,提高性能并减少内存占用。

1)使用EBO的好处:

使用 EBO 的基本思想是,你可以定义一组顶点,并使用索引数组来指定这些顶点在绘制时的顺序。通过使用 EBO,你可以在绘制时只指定顶点一次,然后通过索引数组多次引用这些顶点,从而避免重复存储相同的顶点数据。

2)EBO使用步骤:

  • 生成并绑定 EBO

    • 使用 glGenBuffers 生成一个缓冲区对象的标识符。

    • 使用 glBindBuffer 绑定该缓冲区对象为 GL_ELEMENT_ARRAY_BUFFER 类型,表示这个缓冲区将用于存储索引数据。

    • int[] ebo = new int[1];
      GLES30.glGenBuffers(1, ebo, 0);
      GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, ebo[0]);
      
  • 传递索引数据到 EBO

    • 定义索引数据,通常是一个数组,指定顶点数组中顶点的索引顺序。

    • 创建一个 ByteBuffer 或者 IntBuffer 来存储索引数据,并将数据传递给 EBO。

    • short[] indices = new short[] { 0, 1, 2 };
      ByteBuffer indexBuffer = ByteBuffer.allocateDirect(indices.length * 2);
      indexBuffer.order(ByteOrder.nativeOrder());
      ShortBuffer shortIndexBuffer = indexBuffer.asShortBuffer();
      shortIndexBuffer.put(indices);
      GLES30.glBufferData(GLES30.GL_ELEMENT_ARRAY_BUFFER, indexBuffer.capacity(), indexBuffer, GLES30.GL_STATIC_DRAW);
      
  • 绘制时使用 EBO:

    • 在绘制时,使用 glDrawElements 替代 glDrawArrays,并指定绘制的图元类型、索引数据的数量以及数据类型。

    • GLES30.glDrawElements(GLES30.GL_TRIANGLES, indices.length, GLES30.GL_UNSIGNED_SHORT, 0);
      
  • 解绑和清理

    • 在不再需要 EBO 时,解绑缓冲区对象并删除它。

    • GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, 0); // 解绑 EBO
      GLES30.glDeleteBuffers(1, ebo, 0); // 删除 EBO
      

2、修改之前的代码:

将代码比较聚合的部分提取成了单独的方法,便于以后迭代开发,完整代码如下:

文件路径:com/example/glsurfaceviewdemo/Triangle.java

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 java.nio.IntBuffer;
import java.nio.ShortBuffer;import javax.microedition.khronos.opengles.GL;public class Triangle {private final int COORDS_PER_VERTEX = 3;private FloatBuffer mVertexBuffer;private int[] mIndices = new int[]{0, 1, 2}; // EBO索引数据private int mVboId;private int mEboId;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) {// 初始化顶点数据initVertexBuffer();// 加载并编译着色器initShaders(context);// 下面对VBO和EBO的操作,一定要在createGLProgram之后// 生成并绑定 VBOinitVbo();// 生成并绑定 EBOinitEbo();}// 初始化顶点数据private void initVertexBuffer() {// 为顶点坐标分配DMA内存空间ByteBuffer byteBuffer = ByteBuffer.allocateDirect(mTriangleCoords.length * 4);byteBuffer.order(ByteOrder.nativeOrder());mVertexBuffer = byteBuffer.asFloatBuffer();mVertexBuffer.put(mTriangleCoords);mVertexBuffer.position(0);}// 加载并编译着色器private void initShaders(Context context) {String vertexShaderCode = ShaderController.loadShaderCodeFromFile("triangle_vertex.glsl", context);String fragmentShaderCode = ShaderController.loadShaderCodeFromFile("triangle_fragment.glsl", context);mProgram = ShaderController.createGLProgram(vertexShaderCode, fragmentShaderCode);}private void initVbo() {int[] vbos = new int[1];GLES30.glGenBuffers(1, vbos, 0);mVboId = vbos[0];GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mVboId);mVertexBuffer.position(0);// 指定顶点属性指针,从 VBO 读取数据GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER,       // 缓冲区目标:顶点缓冲区mVertexBuffer.capacity() * 4, // 总字节大小(mVertexBuffer.capacity()个float,每个占4字节)mVertexBuffer,                // 数据源:顶点缓冲区GLES30.GL_STATIC_DRAW         // 缓冲区类型:静态数据);}// 生成并绑定 EBOprivate void initEbo() {// 生成并绑定 EBO (一定要在createGLProgram之后)int[] ebos = new int[1];GLES30.glGenBuffers(1, ebos, 0);mEboId = ebos[0];GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, mEboId);// 传递索引数据到 EBOByteBuffer indexBuffer = ByteBuffer.allocateDirect(mIndices.length * 4); // 每个索引是int,4字节indexBuffer.order(ByteOrder.nativeOrder());IntBuffer intIndexBuffer = indexBuffer.asIntBuffer();intIndexBuffer.put(mIndices);intIndexBuffer.position(0);GLES30.glBufferData(GLES30.GL_ELEMENT_ARRAY_BUFFER, indexBuffer.capacity() * 1, indexBuffer, GLES30.GL_STATIC_DRAW);}public void draw() {// 定义的fragment的颜色数组,表示每个像素的颜色float[] color = new float[]{0.0f, 1.0f, 0.0f, 1.0f};// 使用program对象GLES30.glUseProgram(mProgram);// 绑定 EBOGLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, mEboId);// 获取顶点属性的位置int positionHandle = GLES30.glGetAttribLocation(mProgram, "vPosition");GLES30.glEnableVertexAttribArray(positionHandle);// 绑定 VBO(存储顶点坐标数据)GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mVboId);// 设置顶点属性指针(告诉OpenGL如何解释顶点数据缓冲区中的数据)GLES30.glVertexAttribPointer(positionHandle,    // 顶点属性位置句柄,指示OpenGL应该将这些数据连接到哪个着色器属性COORDS_PER_VERTEX, // 每个顶点包含的坐标数GLES30.GL_FLOAT,   // 数据类型false,             // 是否数据应该被标准化,通常用于整数类型的数据0,                 // 步长,指定在连续的顶点属性之间的偏移量,如果所有属性是紧密排列在一起的,可以设置为00);                // 0 是为绑定的 缓冲区对象(VBO) 指定偏移,否则,顶点缓冲区(mVertexBuffer)的直接内存地址// 设置片元着色器属性指针int colorHandle = GLES30.glGetUniformLocation(mProgram, "vColor");GLES30.glUniform4fv(colorHandle, 1, color, 0);// 绘制三角形GLES30.glDrawElements(GLES30.GL_TRIANGLES, mIndices.length, GLES30.GL_UNSIGNED_INT, 0);// 禁用顶点属性数组GLES30.glDisableVertexAttribArray(positionHandle);// 解绑 EBO(索引缓冲区)GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, 0);// 解绑 VBO(顶点缓冲区)GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0);}// 释放资源public void release() {GLES30.glDeleteBuffers(1, new int[]{mVboId}, 0); // 释放VBOGLES30.glDeleteBuffers(1, new int[]{mEboId}, 0); // 释放EBOGLES30.glDeleteProgram(mProgram);}
}

同时,添加一个资源回收的方法release(),在不需要绘制的时候,回收GPU中分配的资源:

调用地方在GLRenderTest中的onDestroy()当中;

文件路径:com/example/glsurfaceviewdemo/GLRenderTest.java

public class GLRenderTest implements GLSurfaceView.Renderer {private Triangle mTriangle;// ... 删除非关键代码public void onDestroy() {mTriangle.release();}
}

GLRenderTest中的onDestroy()GLSurfaceViewTestonDestroy()被调用;

文件路径:com/example/glsurfaceviewdemo/GLSurfaceViewTest.java

public class GLSurfaceViewTest extends GLSurfaceView {private GLRenderTest mGlRenderTest;// ... 删除非关键代码public void onDestroy() {mGlRenderTest.onDestroy();}
}

GLSurfaceViewTestonDestroy()将会在MainActivityonDestroy()中被调用:

public class MainActivity extends AppCompatActivity {private GLSurfaceViewTest mGlSurfaceViewTest;// ... 删除非关键代码@Overrideprotected void onDestroy() {super.onDestroy();mGlSurfaceViewTest.onDestroy();}
}

3、运行结果:

在这里插入图片描述

四、VAO:

1、概念:

1)基本概念:

VAO(Vertex Array Object)是OpenGL中的一个顶点数组对象,用于存储顶点数据的配置信息。

在现代 OpenGL 中,绘制一个几何图形通常需要以下步骤:

  1. 顶点数据存储:将顶点数据存储在 GPU 的缓冲区对象(如 VBO, Vertex Buffer Object)中;
  2. 索引数据存储:如果使用索引绘制,则需要将索引数据存储在 EBO(Element Buffer Object)中;
  3. 顶点属性配置:通过调用 glVertexAttribPointer 配置顶点数据在缓冲区中的存储布局(例如位置、法线、纹理坐标等);
  4. 绑定状态:每次绘制时需要重新绑定这些缓冲区对象,并重新设置对应的顶点属性。

为了简化这些繁琐的绑定和配置过程,VAO(顶点数组对象)被引入。VAO 是一个对象,它可以记录所有与顶点属性相关的状态,比如:

  • 顶点缓冲区对象(VBO)的绑定;
  • 元素缓冲区对象(EBO)的绑定;
  • 顶点属性的启用与配置;
  • 调用各种绘制命令时需要的状态。

有了 VAO 后,只需将顶点属性的配置和绑定动作写在 VAO 中,后续只需绑定 VAO 即能快速恢复这些状态。

2)使用VAO的好处:

  • 绘制不同物体时,不用重置VBO;
  • 可以提高性能和效率;

3)VAO使用步骤:

  • 生成VAO并绑定到GPU;
  • 生成并绑定一个或者多个VBO;
  • VBO ID自动会保存到VAO当中;
  • 拷贝数据到VBO;
  • 连接VAO与Shader;

2、关键API:

void glGenVertexArrays(GLsizei n, GLuint *arrays)

glGenVertexArrays 用于生成一个或多个 VAO 对象的 ID。

  • 参数说明

    • n:要生成的 VAO 的数量;
    • arrays:传递一个 GLuint 数组(一般使用 int[]),用于存储生成的 VAO 的 ID。
  • 用法

    • 一般是使用一个存储 VAO ID 的变量,比如:

      int[] vao = new int[1];
      GLES30.glGenVertexArrays(1, vao, 0);
      // vao[0] 存储第一个生成的 VAO 的 ID。
      
    • 可以生成多个 VAO:

      int[] vaos = new int[2];
      GLES30.glGenVertexArrays(2, vaos, 0);
      // vaos[0] 和 vaos[1] 分别存储两个 VAO 的 ID
      

void glBindVertexArray(GLuint array)

glBindVertexArray 用于绑定一个 VAO,使其成为活动状态。

  • 参数说明

    • array:要绑定的 VAO 的 ID。当 array 为 0 时表示解绑当前 VAO。
  • 用法

    • 每次需要切换 VAO 或启用某个 VAO 时使用:

      GLES30.glBindVertexArray(vao[0]);
      
    • 如果不再需要任何 VAO,解除绑定:

      GLES30.glBindVertexArray(0);
      

3、应用实例:

以下是一个绘制三角形的完整示例,说明如何使用 VAO 简化状态管理。


Java 示例代码
// 顶点数据(一个简单的三角形)
private final float[] vertices = {0.0f,  0.5f, 0.0f,  // 顶点1:顶部-0.5f, -0.5f, 0.0f,  // 顶点2:左下角0.5f, -0.5f, 0.0f   // 顶点3:右下角
};// VAO 和 VBO 的 ID
private int[] vao = new int[1];
private int[] vbo = new int[1];public void initOpenGL() {// Step 1: 生成 VAOGLES30.glGenVertexArrays(1, vao, 0);// Step 2: 绑定 VAOGLES30.glBindVertexArray(vao[0]);// Step 3: 生成并绑定 VBOGLES30.glGenBuffers(1, vbo, 0);GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vbo[0]);// Step 4: 传递顶点数据到 VBOFloatBuffer vertexBuffer = ByteBuffer.allocateDirect(vertices.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();vertexBuffer.put(vertices);vertexBuffer.position(0);GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER, vertexBuffer.capacity() * 4, vertexBuffer, GLES30.GL_STATIC_DRAW);// Step 5: 配置顶点属性int positionHandle = GLES30.glGetAttribLocation(shaderProgram, "vPosition"); // 激活的顶点属性变量名GLES30.glEnableVertexAttribArray(positionHandle);GLES30.glVertexAttribPointer(positionHandle, 3, GLES30.GL_FLOAT, false, 0, 0);// Step 6: 解绑 VAO 和 VBO(可选,避免对当前缓冲区的后续操作)GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0);GLES30.glBindVertexArray(0);
}public void draw() {// Step 1: 使用 Shader ProgramGLES30.glUseProgram(shaderProgram);// Step 2: 绑定 VAO (恢复顶点属性状态)GLES30.glBindVertexArray(vao[0]);// Step 3: 绘制三角形GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, vertices.length / 3);// Step 4: 解绑 VAO(可选)GLES30.glBindVertexArray(0);
}

代码解析

  • 初始化阶段
    • 生成 VAO 和 VBO。
    • 绑定 VAO 后,可以在其上下文中完成所有顶点缓冲区数据设置和属性配置(顶点数据、顶点属性等)。
    • 配置完成后可以解绑 VAO。
  • 绘制阶段
    • 不需要再重新绑定顶点缓冲区或重新配置顶点属性,只需绑定 VAO。
    • 绑定 VAO 后会恢复之前配置的所有缓冲区和顶点属性的状态,按需要调用绘制操作。
  • 优势
    • 简化绘图流程:只需一条 glBindVertexArray() 就可以切换到指定的绘制状态;
    • 支持多个 VAO 方便管理:可以通过多个 VAO 分别记录不同的绘制状态。

效果示意图

在上面的例子中,屏幕会绘制一个绿色的三角形,是因为对应的 vertices 是定义一个三角形的数据,整个渲染过程简化为:

  1. 初始化 VAO 后,只需 glBindVertexArray(vao[0]) 即能自动配置所有顶点缓冲区和属性;
  2. 解除绑定或切换 VAO 后,可以为其他几何图形绘制新的 VAO 状态。

注意事项

  1. VAO 不直接存储数据
    • VAO 本质上是一个状态管理对象,记录与顶点属性相关的绑定状态。
    • 数据实际存储在绑定的 VBO 或 EBO 中。
  2. 解绑谨慎
    • 在配置完成后解绑 VAO 是一个好习惯,避免意外修改 VAO 状态。
    • 在绘制阶段不需要频繁解绑 VAO,除非需要切换到其他 VAO。
  3. 上下文限制
    • 如果在多种顶点数据形式之间切换(如多个物体需要不同的顶点数据),可以使用多个 VAO 分别记录不同的状态。

利用 glGenVertexArraysglBindVertexArray 的 VAO 机制,可以提升代码结构的清晰性,同时也减少了顶点缓冲区的重复绑定和配置操作。

4、修改之前代码:

之前我们已经使用了VBO和EBO,现在其基础上增加VAO:

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 java.nio.IntBuffer;public class Triangle {private final int COORDS_PER_VERTEX = 3;private FloatBuffer mVertexBuffer;private int[] mIndices = new int[]{0, 1, 2}; // EBO索引数据private int mVboId;private int mEboId;private int mVaoId; // 添加 VAO IDprivate int mProgram;// 定义三角形顶点坐标数组private final float[] mTriangleCoords = new float[]{0.0f, 0.2f, 0.0f,  // 顶点 1:顶部-0.5f, -0.5f, 0.0f,  // 顶点 2:左下角0.5f, -0.5f, 0.0f   // 顶点 3:右下角};public Triangle(Context context) {// 初始化顶点数据initVertexBuffer();// 加载并编译着色器initShaders(context);// 使用 VAO,对 VBO 和 EBO 的绑定进行封装(简化 draw)initVao();}// 初始化顶点数据private void initVertexBuffer() {// 为顶点坐标分配内存ByteBuffer byteBuffer = ByteBuffer.allocateDirect(mTriangleCoords.length * 4);byteBuffer.order(ByteOrder.nativeOrder());mVertexBuffer = byteBuffer.asFloatBuffer();mVertexBuffer.put(mTriangleCoords);mVertexBuffer.position(0);}// 加载并编译着色器private void initShaders(Context context) {String vertexShaderCode = ShaderController.loadShaderCodeFromFile("triangle_vertex.glsl", context);String fragmentShaderCode = ShaderController.loadShaderCodeFromFile("triangle_fragment.glsl", context);mProgram = ShaderController.createGLProgram(vertexShaderCode, fragmentShaderCode);}// 初始化 VAO,封装 VBO 和 EBO 的绑定private void initVao() {// 生成 VAOint[] vaos = new int[1];GLES30.glGenVertexArrays(1, vaos, 0);mVaoId = vaos[0];GLES30.glBindVertexArray(mVaoId); // 绑定 VAO// 初始化 VBOinitVbo();// 初始化 EBOinitEbo();// 配置顶点属性int positionHandle = GLES30.glGetAttribLocation(mProgram, "vPosition");GLES30.glEnableVertexAttribArray(positionHandle); // 启用顶点属性GLES30.glVertexAttribPointer(positionHandle,COORDS_PER_VERTEX, // 每个顶点的分量个数GLES30.GL_FLOAT,   // 数据类型false,0,0  // 偏移量(VBO 内部偏移位置));// 解绑 VAO(可选,防止后续操作误改 VAO 状态)GLES30.glBindVertexArray(0);}// 生成并绑定 VBOprivate void initVbo() {int[] vbos = new int[1];GLES30.glGenBuffers(1, vbos, 0);mVboId = vbos[0];GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mVboId); // 绑定 VBOGLES30.glBufferData(GLES30.GL_ARRAY_BUFFER,mVertexBuffer.capacity() * 4,  // 顶点缓冲大小(字节)mVertexBuffer,                 // 顶点数据GLES30.GL_STATIC_DRAW          // 静态缓冲区);}// 生成并绑定 EBOprivate void initEbo() {int[] ebos = new int[1];GLES30.glGenBuffers(1, ebos, 0);mEboId = ebos[0];GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, mEboId); // 绑定 EBO// 分配并传递索引数据ByteBuffer indexBuffer = ByteBuffer.allocateDirect(mIndices.length * 4); // 使用 IntBufferindexBuffer.order(ByteOrder.nativeOrder());IntBuffer intIndexBuffer = indexBuffer.asIntBuffer();intIndexBuffer.put(mIndices);intIndexBuffer.position(0);GLES30.glBufferData(GLES30.GL_ELEMENT_ARRAY_BUFFER,indexBuffer.capacity(),indexBuffer,GLES30.GL_STATIC_DRAW);}public void draw() {// 设置片元着色器的颜色float[] color = new float[]{0.0f, 1.0f, 0.0f, 1.0f};// 使用 Shader ProgramGLES30.glUseProgram(mProgram);// 绑定 VAO(自动恢复顶点属性、VBO 和 EBO 的绑定状态)GLES30.glBindVertexArray(mVaoId);// 设置片段着色器的颜色值int colorHandle = GLES30.glGetUniformLocation(mProgram, "vColor");GLES30.glUniform4fv(colorHandle, 1, color, 0);// 绘制三角形GLES30.glDrawElements(GLES30.GL_TRIANGLES, mIndices.length, GLES30.GL_UNSIGNED_INT, 0);// 解绑 VAO(可选,防止影响其他绘图操作)GLES30.glBindVertexArray(0);}// 释放资源public void release() {GLES30.glDeleteBuffers(1, new int[]{mVboId}, 0); // 删除 VBOGLES30.glDeleteBuffers(1, new int[]{mEboId}, 0); // 删除 EBOGLES30.glDeleteVertexArrays(1, new int[]{mVaoId}, 0); // 删除 VAOGLES30.glDeleteProgram(mProgram); // 删除 shader program}
}
  1. 添加了 VAO 支持

    • 增加了 mVaoId 用于存储 VAO 的 ID。
    • initVao 方法中,生成 VAO 并绑定后,将 VBO 和 EBO 的绑定、顶点属性配置都记录到 VAO。
  2. 修改了 VBO 和 EBO 的绑定方式

    • 原代码中,VBO 和 EBO 在 draw() 中每次都需要绑定。改为在 initVao() 中通过 VAO 封装一次,在绘制时只需绑定 VAO。
  3. 绘制时只需绑定 VAO 而不是分别绑定 VBO 和 EBO

    • 原代码需要分别绑定 VBO 和 EBO,然后设置顶点属性。改为直接绑定 VAO,简化绘制代码。
  4. 新增 VAO 的删除操作

    • release 方法中,新增 VAO 的清理代码:

      GLES30.glDeleteVertexArrays(1, new int[]{mVaoId}, 0);
      
  5. 代码简化

    • 绘制过程只需绑定 VAO 和调用绘制函数。无需每次重新绑定和配置缓冲区,便于代码维护。

VAO 与 VBO、EBO 的关系

  • VBO:存储顶点数据(顶点位置、颜色、纹理等)。
  • EBO:存储索引数据,用于优化重复顶点的绘制。
  • VAO:记录 VBO 的绑定状态、EBO 的绑定状态以及顶点属性的设置,使得这些状态可以在 VAO 中统一管理,简化渲染流程。

使用 VAO 之后,整个渲染流程更加高效,避免了重复绑定和配置缓冲区的操作。

5、问题与思考:

针对刚才的修改大家不妨思考两个问题:
问题一:

为什么draw里面只解绑了vao没有解绑vbo和ebo?
问题二:

为什么initVao里面最后要调用GLES30.glBindVertexArray(0)来解绑VAO?如果解绑了,构造函数中调用完这个函数之后,等draw函数被调用的时候不就没有绑定VAO吗?

----------------------------------------------Two minutes later-------------------------------------

下面是对刚才提出的两个问题的详细解答,同时解释 OpenGL 的状态管理机制以及为什么在特定场景下需要解绑 VAO 和其他对象。


问题一:为什么 draw 方法里只解绑了 VAO,没有解绑 VBO 和 EBO?

原因:

当 VAO 被绑定时,它会记录所有与顶点属性、VBO 和 EBO 绑定相关的状态。在 draw 方法中通过绑定 VAO (GLES30.glBindVertexArray(mVaoId)),OpenGL 自动恢复与 VAO 相关的所有状态,包括之前绑定的 VBO 和 EBO。因此:

  • draw 方法中,我们只需要绑定/解绑 VAO,无需再单独解绑 VBO 和 EBO,因为它们的状态已经包含在 VAO 的绑定关系中。
  • 像 VBO 和 EBO 的绑定状态在解绑 VAO 时一起解绑了,VBO 和 EBO 不需要单独解绑。

进一步解释:VAO、VBO 和 EBO 的解绑机制

  • VAO 的绑定状态:当绑定 VAO (glBindVertexArray) 时,VBO 和 EBO 绑定的状态会被记录到 VAO 中。
    • 绑定一个新的 VAO 后,VBO 和 EBO 的状态会被替换成新的 VAO 的绑定关系。
    • 当解绑 VAO(glBindVertexArray(0))时,与当前 VAO 相关的状态会被清除,但不会影响 GPU 中数据本身。
  • 因此,解绑 VBO(glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0))和 EBO(glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, 0))在使用 VAO 的场景中是多余的。

原代码设计的正确性(对应问题一)

draw 函数中:

// 只解绑 VAO
GLES30.glBindVertexArray(0);
  • 当解绑 VAO 后,OpenGL 会自行解除所有与这个 VAO 绑定的状态(包括 VBO 和 EBO)。
  • 没有解绑 VBO 和 EBO 的原因是:绘制和状态管理都被 VAO 完全封装了,只需要管理 VAO 的绑定/解绑。
  • 如果明确解绑 VBO 和 EBO,效果不会改变,但代码显得冗余,不符合 VAO 的设计意图。

问题二:为什么 initVao 方法里最后要调用 GLES30.glBindVertexArray(0) 来解绑 VAO?解绑后 draw 时不久没绑定 VAO 吗?

原因:

initVao() 中调用 GLES30.glBindVertexArray(0) 是为了防止后续的任何 OpenGL 函数调用意外修改当前 VAO 的状态


详细解释:VAO 状态与解绑的目的

在 OpenGL 中,VAO 是一个状态对象,包含了顶点属性的配置、VBO 和 EBO 的绑定关系。也就是说,任何与顶点属性相关的操作(比如配置 glVertexAttribPointer、绑定新的缓冲区)都会作用在当前绑定的 VAO 上。因此,绑定 VAO 后你必须注意避免对现有状态的污染

未解绑会导致的问题:

如果你在 initVao 中没有解绑 VAO,后续的其他代码(初始化其他对象或者顶点数据)可能会无意中修改当前绑定的 VAO。例如:

  • 如果在未解除绑定的情况下再调用 glBindBuffer(GL_ARRAY_BUFFER, other_vbo) 配置另一个 VBO,这会直接影响当前 VAO;
  • 如果无意中调用了 glVertexAttribPointer(),也会污染已经配置好的顶点属性。

解绑的实际效果:

当调用 GLES30.glBindVertexArray(0) 时,相当于告诉 OpenGL 当前不再操作任何 VAO。以后对 VBO、EBO 或顶点属性的任何修改都不会影响当前 VAO。

draw() 中,VAO 仍然可以再次绑定:

解绑 VAO 并不会清除记录在 VAO 内部的状态。VAO 是一个离线的 “状态包”,在以后需要时(比如 draw 方法中),你可以重新绑定:

GLES30.glBindVertexArray(mVaoId);

再次绑定 VAO 后,之前的顶点属性设置、VBO 和 EBO 的绑定状态都会完全恢复。


具体场景解释:initVaodraw 的逻辑分工

  • initVao 是初始化阶段,在这一步完成 VAO 的状态记录,确保 VAO 捕捉到 VBO、EBO 和顶点属性的完整绑定逻辑。解绑 VAO 可以防止后续代码污染它。
  • draw 是绘制阶段。在需要绘制时,可以重新绑定 VAO 恢复其状态,无需重复设置 VBO、EBO 或顶点属性。

可以选择不解绑 VAO 吗?

技术上可以,但不是好习惯。

  • 如果在 initVao() 中不解绑 VAO,绘制功能仍然正常,但后续对其他顶点数据或渲染状态的修改可能意外污染当前 VAO。具体而言,其他地方的错误会难以追踪,因为开发者可能忘记哪些状态被 VAO 无意修改了
  • 解绑 VAO 是为了明确状态,特别是在复杂的渲染场景下(有多个对象且每个对象有自己 VAO 时)。解绑后可以较好地遵循“一个对象只负责自己的状态”的准则。

问题二的结论

  • GLES30.glBindVertexArray(0) 的目的是防止意外修改 VAO 状态
  • draw 方法可以重新绑定 VAO,此时 VAO 恢复记录的状态,不会丢失任何内容。
  • 在初始化阶段解绑 VAO 是一个良好的开发习惯,以避免潜在的调试难题。

总结:

问题一的小结:为什么 draw 中只解绑 VAO?

  • 因为 VAO 已经封装了所有与顶点属性、VBO 和 EBO 的绑定状态。在解绑 VAO 时,这些绑定状态会自动解除,不需要单独解绑 VBO 或 EBO。
  • VAO 的目的是简化 OpenGL 中复杂的绑定管理,手动解绑 VBO 或 EBO 会破坏这种机制。

问题二的小结:为什么初始化时要解绑 VAO?

  • 在初始化阶段解绑 VAO 是为了防止后续的其他逻辑修改 VAO 的绑定状态。
  • 解绑 VAO 后不会影响 VAO 内部记录的配置状态,可以随时在 draw() 中重新绑定并恢复这些状态。
  • 这是 OpenGL 编程中常见的安全实践,避免意外修改或错用 VAO。

整体原则:

  1. 在使用 VAO 时,尽量只操作 VAO,而尽可能少直接操作 VBO 和 EBO。
  2. 初始化阶段解绑 VAO 是为了保护其状态,确保状态修改的独立性。
  3. 绘制阶段只需要绑定和解绑 VAO 即可,VBO 和 EBO 的状态由 VAO 管理,不需要再手动解绑。

五、总结:

本文主要介绍了VBO、EBO、VAO,大家掌握了基本概念和使用之后,其实也就掌握了一种产品性能优化的思路。

关键字:广州优化排名推广_办公室oa管理系统_淄博信息港聊天室网址_网络推广赚钱平台有哪些

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: