[OpenGL ES 06]使用VBO:顶点缓存

2023-11-19

[OpenGL ES 06]使用VBO:顶点缓存

罗朝辉 (http://www.cnblogs.com/kesalin/)

本文遵循“署名-非商业用途-保持一致”创作公用协议

 

这是《OpenGL ES 教程》的第六篇,前五篇请参考如下链接:

[OpenGL ES 01]iOS上OpenGL ES之初体验
[OpenGL ES 02]OpenGL ES渲染管线与着色器
[OpenGL ES 03]3D变换:模型,视图,投影与Viewport
[OpenGL ES 04]3D变换实践篇:平移,旋转,缩放
[OpenGL ES 05]相对空间变换及颜色

 

一,VBO简介

在前面几篇的示例中,都是通过类似如下代码直接从 CPU 主存中传递顶点数据到 GPU 中去进行运算与渲染的。

    glVertexAttrib4f(_colorSlot, color[0], color[1], color[2], color[3]);
    glVertexAttribPointer(_positionSlot, 3, GL_FLOAT, GL_FALSE, 0, vertices );
    glEnableVertexAttribArray(_positionSlot);
    
    glDrawElements(GL_LINES, sizeof(indices)/sizeof(GLubyte), GL_UNSIGNED_BYTE, indices);

在上面的代码中 vertices 和 indices 都是在主存中分配的内存空间,当需要进行渲染时,这些数据便通过 glDrawElements 或 glDrawArrays 从 CPU 主存中拷贝到 GPU 中去进行运算与渲染。这种做法需要频繁地在 CPU 与 GPU 之间传递数据,效率低下,因此出现了 VBO (Vertex Buffer object),即顶点缓存,它直接在 GPU 中开辟一个缓存区域来存储顶点数据,因为它是用来缓存储顶点数据,因此被称之为顶点缓存。我们只会在初始化缓冲区,以及在顶点数据有变化时才需要对该缓冲区进行写操作。使用顶点缓存能够大大较少了CPU-GPU 之间的数据拷贝开销,因此显著地提升了程序运行的效率。

今天我们就能学习 VBO 在 OpenGL ES 中的运用,示例程序演示了六种编程实现的物体,本文源码:点此查看,其运行效果如下:

 

二,API介绍

1,总览

OpenGL ES 中通过如下函数来实现 VBO:

顶点缓存对象 API
glGenBuffers 创建顶点缓存对象
glBindBuffer 将顶点缓存对象设置为当前数组缓存对象(array buffer object)或当前元素缓存对象(element buffer object)
glBufferData 为顶点缓存对象申请内存空间,并进行初始化(视传入的参数而定)
glBufferSubData 初始化或更新顶点缓存对象
glDeleteBuffers 删除顶点缓存对象



 



 

2,创建顶点缓存对象

void glGenBuffers (GLsizei n, GLuint* buffers);

参数 n : 表示需要创建顶点缓存对象的个数;
参数 buffers :用于存储创建好的顶点缓存对象句柄;

同第一篇文章《[OpenGL ES 01]OpenGL ES之初体验》中的讲的 render buffer 对象句柄一样,在这里,顶点缓存对象句柄始终是大于 0 的正整数,0 是 OpenGL ES 保留。该函数能够一次产生多个顶点缓存对象。

3,将顶点缓存对象设置为(或曰绑定到)当前数组缓存对象或元素缓存对象

void glBindBuffer (GLenum target, GLuint buffer);

参数 target :指定绑定的目标,取值为 GL_ARRAY_BUFFER(用于顶点数据) 或 GL_ELEMENT_ARRAY_BUFFER(用于索引数据);
参数 buffer :顶点缓存对象句柄;

4,为顶点缓存对象分配空间

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

参数 target:与 glBindBuffer 中的参数 target 相同;
参数 size :指定顶点缓存区的大小,以字节为单位计数;
data :用于初始化顶点缓存区的数据,可以为 NULL,表示只分配空间,之后再由 glBufferSubData 进行初始化;
usage :表示该缓存区域将会被如何使用,它的主要目的是用于提示OpenGL该对该缓存区域做何种程度的优化。其参数为以下三个之一:
GL_STATIC_DRAW:表示该缓存区不会被修改;
GL_DyNAMIC_DRAW:表示该缓存区会被周期性更改;
GL_STREAM_DRAW:表示该缓存区会被频繁更改;

如果顶点数据一经初始化就不会被修改,那么就应该尽量使用 GL_STATIC_DRAW,这样能获得更好的性能。

5,更新顶点缓冲区数据

void glBufferSubData (GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid* data);

参数 :offset 表示需要更新的数据的起始偏移量;
参数 :size 表示需要更新的数据的个数,也是以字节为计数单位;
data :用于更新的数据;

6,释放顶点缓存

void glDeleteBuffers (GLsizei n, const GLuint* buffers);

参数与 glGenBuffers 类似,就不再累述,该函数用于删除顶点缓存对象,释放顶点缓存。

 

三,多面手:glVertexAttribPointer 和 glDrawElements

在介绍如何使用 VBO 进行渲染之前,我们先来回顾一下之前使用顶点数组进行渲染用到的函数:

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

参数 index :为顶点数据(如顶点,颜色,法线,纹理或点精灵大小)在着色器程序中的槽位;
参数 size :指定每一种数据的组成大小,比如顶点由 x, y, z 3个组成部分,纹理由 u, v 2个组成部分;
参数 type :表示每一个组成部分的数据格式;
参数 normalized : 表示当数据为法线数据时,是否需要将法线规范化为单位长度,对于其他顶点数据设置为 GL_FALSE 即可。如果法线向量已经为单位长度设置为 GL_FALSE 即可,这样可免去不必要的计算,提升效率;
stride : 表示上一个数据到下一个数据之间的间隔(同样是以字节为单位),OpenGL ES根据该间隔来从由多个顶点数据混合而成的数据块中跳跃地读取相应的顶点数据;
ptr :值得注意,这个参数是个多面手。如果没有使用 VBO,它指向 CPU 内存中的顶点数据数组;如果使用 VBO 绑定到 GL_ARRAY_BUFFER,那么它表示该种类型顶点数据在顶点缓存中的起始偏移量。

那 GL_ELEMENT_ARRAY_BUFFER 表示的索引数据呢?那是由以下函数使用的:

void glDrawElements (GLenum mode, GLsizei count, GLenum type, const GLvoid* indices);

参数 mode :表示描绘的图元类型,如:GL_TRIANGLES,GL_LINES,GL_POINTS;
参数 count : 表示索引数据的个数;
参数 type : 表示索引数据的格式,必须是无符号整形值;
indices :这个参数也是个多面手,如果没有使用 VBO,它指向 CPU 内存中的索引数据数组;如果使用 VBO 绑定到 GL_ELEMENT_ARRAY_BUFFER,那么它表示索引数据在 VBO 中的偏移量。

 

四,使用示例

在今天的示例中,我借用《iPhone 3D Programming》中创建可编程3维物体的部分代码来创建3维物体的顶点以及索引,在这里就略去这部分的介绍,有兴趣研究的同学可以查看源码。在这里就只讲与顶点缓存相关的部分代码。

首先是创建顶点缓存对象,分配空间并初始化:

    // Create the VBO for the vertice.
    //
    GLuint vertexBuffer;
    glGenBuffers(1, &vertexBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, vBufSize * sizeof(GLfloat), vbuf, GL_STATIC_DRAW);
    
    // Create the VBO for the line indice
    //
    GLuint lineIndexBuffer;
    glGenBuffers(1, &lineIndexBuffer);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, lineIndexBuffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, lineIndexCount * sizeof(GLushort), lineBuf, GL_STATIC_DRAW);
    
    // Create the VBO for the triangle indice
    //
    GLuint triangleIndexBuffer;
    glGenBuffers(1, &triangleIndexBuffer);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, triangleIndexBuffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, triangleIndexCount * sizeof(GLushort), triangleBuf, GL_STATIC_DRAW);

然后,使用 VBO 进行渲染:

- (void)drawSurface
{
    if (_currentVBO == nil)
        return;
    
    glBindBuffer(GL_ARRAY_BUFFER, [_currentVBO vertexBuffer]);
    glVertexAttribPointer(_positionSlot, 3, GL_FLOAT, GL_FALSE, [_currentVBO vertexSize] * sizeof(GLfloat), 0);
    glEnableVertexAttribArray(_positionSlot);
    
    // Draw the red triangles.
    //
    glVertexAttrib4f(_colorSlot, 1.0, 0.0, 0.0, 1.0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, [_currentVBO triangleIndexBuffer]);
    glDrawElements(GL_TRIANGLES, [_currentVBO triangleIndexCount], GL_UNSIGNED_SHORT, 0);
    
    // Draw the black lines.
    //
    glVertexAttrib4f(_colorSlot, 0.0, 0.0, 0.0, 1.0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, [_currentVBO lineIndexBuffer]);
    glDrawElements(GL_LINES, [_currentVBO lineIndexCount], GL_UNSIGNED_SHORT, 0);
    
    glDisableVertexAttribArray(_positionSlot);
}

由于本示例可描绘 6 个3维几何物体,因此 _currentVBO 表示当前描绘的几何物体,这是一个 DrawableVBO 对象。DrawableVBO 类声明如下:

@interface DrawableVBO : NSObject

@property (nonatomic, assign) GLuint vertexBuffer;
@property (nonatomic, assign) GLuint lineIndexBuffer;
@property (nonatomic, assign) GLuint triangleIndexBuffer;
@property (nonatomic, assign) int vertexSize;
@property (nonatomic, assign) int lineIndexCount;
@property (nonatomic, assign) int triangleIndexCount;

- (void) cleanup;

@end

它包含一个用于顶点数据的顶点缓存对象 vertexBuffer 和两个用于索引数据的顶点缓存对象 lineIndexBuffer 和 triangleIndexBuffer,这些对象都是通过前面的创建顶点缓存对象部分代码生成的。vertexSize 表示顶点数据的大小,而 lineIndexCount 和 triangleIndexCount 表示索引数据的个数。方法 cleanup 是用于清理顶点缓存对象,其实现如下:

- (void) cleanup
{
    if (vertexBuffer != 0) {
        glDeleteBuffers(1, &vertexBuffer);
        vertexBuffer = 0;
    }
    
    if (lineIndexBuffer != 0) {
        glDeleteBuffers(1, &lineIndexBuffer);
        lineIndexBuffer = 0;
    }
    
    if (triangleIndexBuffer) {
        glDeleteBuffers(1, &triangleIndexBuffer);
        triangleIndexBuffer = 0;
    }
}

 

五,运行效果

本示例演示了 6 中不同形状的可编程几何物体,并使用 Quaternion 来响应手指滑动形成的旋转操作。示例运行效果如图所示:

 

六,练习作业

在示例中,是通过编程方式来生成顶点数据与索引数据。那如果我想用已有顶点数据和索引数据来使用 VBO,那么该如何做呢?下面提供一个立方体 cube 的顶点数据和索引数据,看聪明的你能不能修改它,加入本示例中成为第七个几何图形,这个作业就留个你了。

// Cube 顶点数据以及索引数据

    const GLfloat vertices[] = {
        -1.5f, -1.5f, 1.5f, -0.577350, -0.577350, 0.577350,
        -1.5f, 1.5f, 1.5f, -0.577350, 0.577350, 0.577350,
        1.5f, 1.5f, 1.5f, 0.577350, 0.577350, 0.577350,
        1.5f, -1.5f, 1.5f, 0.577350, -0.577350, 0.577350,
        
        1.5f, -1.5f, -1.5f, 0.577350, -0.577350, -0.577350,
        1.5f, 1.5f, -1.5f, 0.577350, 0.577350, -0.577350,
        -1.5f, 1.5f, -1.5f, -0.577350, 0.577350, -0.577350,
        -1.5f, -1.5f, -1.5f, -0.577350, -0.577350, -0.577350
    };
    
    const GLushort indices[] = {
        // Front face
        3, 2, 1, 3, 1, 0,
        
        // Back face
        7, 5, 4, 7, 6, 5,
        
        // Left face
        0, 1, 7, 7, 1, 6,
        
        // Right face
        3, 4, 5, 3, 5, 2,
        
        // Up face
        1, 2, 5, 1, 5, 6,
        
        // Down face
        0, 7, 3, 3, 7, 4
    };

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

[OpenGL ES 06]使用VBO:顶点缓存 的相关文章

  • 应用编排与管理:核心原理

    本节课程要点 K8s 资源的重要元信息 使用阿里云服务演示一下如何去修改或查看 K8s 重要元数据 详细分析控制器模式 总结控制器模式特点 资源元信息 1 Kubernetes 资源对象 首先 我们来回顾一下 Kubernetes 的资源对
  • g2o的基本使用

    参考 https www jianshu com p e16ffb5b265d 参考 https zhuanlan zhihu com p 36889150 图是一种数据结构 在图优化中 用顶点 vertex 表示优化变量 用边 edge
  • Smali--Dalvik虚拟机指令语言-->【android_smali语法学习一】

    最近一周在研究rom移植 所以就对Smali语言学习了一下 Smali语言其实就是Davlik的寄存器语言 Smali语言就是android的应用程序 apk通过apktool反编译出来的都有一个smali文件夹 里面都是以 smali结尾
  • 基于当前系统制作docker镜像

    1 通过tar 备份目录 tar cvpf home buildrpm tar directory exclude proc exclude sys exclude dev exclude run root localhost home l
  • 不能安装64位office提示已安装32位的

    问题描述 安装64位office办公软件的时候提示已经安装32位的office办公软件所以无法继续安装 但实际上之前安装的32位的office办公软件已经卸载了 问题现象截图如下 解决办法 从问题描述中 我们其实已经能够看出问题原因了 类似
  • React的超详细讲解

    React React的重点 webpack webpack 是一个现代 JavaScript 应用程序的静态模块打包器 module bundler 当 webpack 处理应用程序时 它会递归地构建一个依赖关系图 dependency
  • 虚幻引擎程序化资源生成框架PCG 之 UPCGBlueprintElement源码笔记(一)

    UPCGBlueprintElement是PCGGraph中自定义节点的基类 但官方目前还没有给出详细的文档 所以从源代码里找点答案 文章目录 可覆盖函数 Override Functions Excute 和 Excute with Co
  • web.xml加载顺序

    web xml加载顺序 应用服务器启动时web xml加载过程 至于这些节点在xml文件中的前后顺序没有关系 不过有些应用服务器 我曾碰到过的 websphere就严格要求web xml的节点顺序 否则部署不成功 所以还是赞成按照web x
  • CSS 样式穿透

    1 穿透语法 如果给样式中声明scop 那么页面的类会增加data 562e3wue8等属性 此时就需要用到 gt gt gt 或者 deep 或者 v deep 以后用后两个比较多 如果在样式类前面 v deep 则会自动找到 data
  • 快速搭建一个自己的服务器详解(java环境)

    一 服务器的购买 1 我选择的是阿里云的服务器 学生价9 5元一个月 百度直接搜索阿里云 然后点击右上角登录 推荐大家用支付宝扫码登录 方便快捷 阿里云官网的东西比较多 登录后我找了很久也没有找到学生服务器在哪里卖 最后在咨询里找到了这个网
  • 喜爱夜蒲3_百度百科

    喜爱夜蒲3 百度百科 喜爱夜蒲3
  • LeetCode707.设计链表

    题目描述 707 设计链表 力扣 LeetCode 这道题用单向链表和双向链表都能写 因为对双向链表不是很熟 所以就用双向链表来写了 由于初始化的链表并没有放入任何元素 所以这个结点就当成哑结点来处理 而且这样对之后的增删操作也更便利 C语
  • 通配符的使用

    通配符是一种特殊语句 主要有星号 和问号 用来模糊搜索文件 当查找文件夹时 可以使用它来代替一个或多个真正字符 当不知道真正字符或者懒得输入完整名字时 常常使用通配符代替一个或多个真正的字符 SQL 通配符 在搜索数据库中的数据时 SQL
  • java最大线程数

    Xms 初始堆大小 如 Xms256m Xmx 最大堆大小 如 Xmx512m Xmn 新生代大小 通常为 Xmx 的 1 3 或 1 4 新生代 Eden 2 个 Survivor 空间 实际可用空间为 Eden 1 个 Survivor
  • 华为OD机试 - 经典屏保(Java)

    题目描述 DVD机在视频输出时 为了保护电视显像管 在待机状态会显示 屏保动画 如下图所示 DVD Logo在屏幕内来回运动 碰到边缘会反弹 请根据如下要求 实现屏保Logo坐标的计算算法 屏幕是一个800 600像素的矩形 规定屏幕的左上
  • PermissionError: [WinError 5] 拒绝访问。解决办法

    PermissionError WinError 5 拒绝访问 问题描述 解决办法 一 找到programdata下anaconda3的python 二 进入Users的权限 将除了特殊权限外 全部挂上 三 在返回到anaconda3的目录
  • Nginx-简介

    用Python语言开发的站点使用的Web服务器主要有Nginx Apache以及淘宝的Tengine Nginx是由Igor Sysoev在2004年发布的一个开源 高性能的HTTP服务器和反向代理 它还可以用来作为IMAP POP3的代理
  • K8s基础6——应用配置管理方案、调度策略、污点和污点容忍

    文章目录 一 应用配置管理方案 1 1 ConfigMap 1 1 1 注入变量 1 1 2 挂载数据卷 1 2 Secret 1 2 1 数据卷挂载 1 2 2 变量注入 二 调度策略 2 1 nodeSelector定向调度 2 1 1
  • Github上 10 个开源免费且优秀的后台控制面板

    Web 开发中几乎的平台都需要一个后台管理 但是从零开发一套后台控制面板并不容易 幸运的是有很多开源免费的后台控制面板可以给开发者使用 那么有哪些优秀的开源免费的控制面板呢 我在 Github 上收集了一些优秀的后台控制面板 并总结得出 T
  • HTTP协议(下)

    前面我介绍了什么是HTTP协议 以及HTTP的协议格式 接下来我介绍一下如何使用代码构造出HTTP请求 以及在介绍一下HTTPS 目录 一 通过代码构造HTTP请求 1 基于HTML JS 1 1 基于form表单 1 2 基于ajax 2

随机推荐