当前位置: 首页 > news >正文

【OPENGL ES 3.0 学习笔记】第十一天:glDrawArrays和glDrawElements

请添加图片描述

glDrawArrays与glDrawElements

在OpenGL ES渲染管线中,glDrawArraysglDrawElements是触发图元渲染的两大核心函数。

二者的核心差异在于顶点数据的使用方式glDrawArrays直接按顺序读取顶点缓冲区数据,无法复用顶点;glDrawElements通过索引缓冲区(IBO/EBO)指定顶点使用顺序,支持顶点复用。

理解这种差异是优化渲染性能、适配复杂模型的关键。

glDrawArrays

glDrawArrays是最简单的渲染调用方式,它从顶点缓冲区的指定位置开始,按连续顺序读取顶点数据,组装成图元。

无需额外索引,适合结构简单、顶点复用少的场景。

1.1 基本定义

函数原型(OpenGL ES 2.0/3.x通用):

void glDrawArrays(GLenum mode, GLint first, GLsizei count);

作用:根据mode指定的图元类型(如GL_TRIANGLES),从顶点缓冲区的第first个顶点开始,连续读取count个顶点,完成渲染。

1.2 关键参数解析

参数名类型核心作用示例
modeGLenum指定图元类型,如GL_POINTSGL_LINESGL_TRIANGLES渲染三角形用GL_TRIANGLES
firstGLint顶点缓冲区中起始顶点的索引(从0开始)从第0个顶点开始取数据用0
countGLsizei需读取的顶点总数渲染2个三角形(6个顶点)用6

1.3 工作流程

  1. 绑定顶点缓冲区:通过glBindBuffer(GL_ARRAY_BUFFER, vboId)绑定存储顶点数据的VBO;
  2. 配置顶点属性:通过glVertexAttribPointer指定顶点数据的格式(如每个顶点3个位置分量);
  3. 触发渲染:调用glDrawArrays,GPU按顺序从VBO的first位置读取count个顶点;
  4. 组装图元:根据mode的规则(如GL_TRIANGLES每3个顶点组成一个三角形),完成图元装配并光栅化。

1.4 代码示例:用glDrawArrays画2个相邻三角形

需求:绘制两个共用一条边的三角形(组成一个矩形),需6个顶点(其中2个顶点重复存储)。

// 1. 定义顶点数据(2个三角形,6个顶点,重复存储顶点1和2)
float[] vertices = {// 三角形1(0,1,2)-0.5f, 0.5f, 0.0f,  // 0: 左上-0.5f, -0.5f, 0.0f, // 1: 左下(重复)0.5f, -0.5f, 0.0f,  // 2: 右下(重复)// 三角形2(0,2,3)-0.5f, 0.5f, 0.0f,  // 0: 左上(重复)0.5f, -0.5f, 0.0f,  // 2: 右下(重复)0.5f, 0.5f, 0.0f    // 3: 右上
};// 2. 创建并绑定VBO
int[] vbo = new int[1];
GLES20.glGenBuffers(1, vbo, 0);
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vbo[0]);
// 将顶点数据写入VBO
GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, vertices.length * 4, createFloatBuffer(vertices), GLES20.GL_STATIC_DRAW);// 3. 配置顶点属性(位置分量)
int aPositionLoc = GLES20.glGetAttribLocation(programId, "aPosition");
GLES20.glEnableVertexAttribArray(aPositionLoc);
GLES20.glVertexAttribPointer(aPositionLoc, 3, GLES20.GL_FLOAT, false, 3 * 4, 0 // 每个顶点3个float,步长12字节,偏移0
);// 4. 调用glDrawArrays渲染(6个顶点,图元类型GL_TRIANGLES)
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 6);// 5. 清理状态
GLES20.glDisableVertexAttribArray(aPositionLoc);
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);

关键问题:顶点1(左下)和顶点2(右下)被两个三角形重复使用,但需在VBO中存储2次,造成内存冗余。

glDrawElements

glDrawElements是更高效的渲染方式,它通过索引缓冲区(IBO/EBO) 指定顶点的使用顺序,实现顶点复用——相同顶点只需在VBO中存储一次,通过索引重复引用。

这种方式在复杂3D模型中能大幅减少内存开销和数据传输量。

2.1 基本定义

函数原型(OpenGL ES 2.0/3.x通用):

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

作用:根据mode指定的图元类型,通过indices指向的索引缓冲区,读取对应的顶点数据,组装成图元。

2.2 关键参数解析

参数名类型核心作用示例
modeGLenum图元类型,与glDrawArrays一致GL_TRIANGLES
countGLsizei索引数组的元素总数(不是顶点数)6个索引对应2个三角形
typeGLenum索引数据的类型(必须是无符号整数)GL_UNSIGNED_SHORT(常用)、GL_UNSIGNED_BYTE
indicesconst void *索引缓冲区的偏移量(绑定IBO后,此参数为偏移值,非指针)从索引缓冲区起始位置读取用0

2.3 工作流程

  1. 绑定VBO和IBO:分别绑定存储顶点数据的VBO和存储索引数据的IBO;
  2. 配置顶点属性:与glDrawArrays一致,指定顶点数据格式;
  3. 触发渲染:调用glDrawElements,GPU先从IBO读取count个索引;
  4. 索引查顶点:根据每个索引的值,到VBO中查找对应的顶点数据;
  5. 组装图元:按mode规则组装图元,完成渲染。

2.4 代码示例:用glDrawElements画2个相邻三角形

需求:与上例相同,但通过索引复用顶点,仅需4个顶点+6个索引。

// 1. 定义顶点数据(4个独特顶点,无重复)
float[] vertices = {-0.5f, 0.5f, 0.0f,  // 0: 左上-0.5f, -0.5f, 0.0f, // 1: 左下0.5f, -0.5f, 0.0f,  // 2: 右下0.5f, 0.5f, 0.0f    // 3: 右上
};// 2. 定义索引数据(6个索引,对应2个三角形:0,1,2 和 0,2,3)
short[] indices = {0, 1, 2, 0, 2, 3};// 3. 创建并绑定VBO(存储顶点数据)
int[] vbo = new int[1];
GLES20.glGenBuffers(1, vbo, 0);
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vbo[0]);
GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, vertices.length * 4, createFloatBuffer(vertices), GLES20.GL_STATIC_DRAW);// 4. 创建并绑定IBO(存储索引数据)
int[] ibo = new int[1];
GLES20.glGenBuffers(1, ibo, 0);
GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, ibo[0]);
GLES20.glBufferData(GLES20.GL_ELEMENT_ARRAY_BUFFER, indices.length * 2, createShortBuffer(indices), GLES20.GL_STATIC_DRAW);// 5. 配置顶点属性(位置分量)
int aPositionLoc = GLES20.glGetAttribLocation(programId, "aPosition");
GLES20.glEnableVertexAttribArray(aPositionLoc);
GLES20.glVertexAttribPointer(aPositionLoc, 3, GLES20.GL_FLOAT, false, 3 * 4, 0
);// 6. 调用glDrawElements渲染(6个索引,类型GL_UNSIGNED_SHORT)
GLES20.glDrawElements(GLES20.GL_TRIANGLES, 6, GLES20.GL_UNSIGNED_SHORT, 0 // 索引偏移0
);// 7. 清理状态(注意:IBO需最后解绑)
GLES20.glDisableVertexAttribArray(aPositionLoc);
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0);

核心优势:顶点数据从6个减少到4个,内存占用降低约33%;若模型复杂(如立方体、人物),顶点复用率更高,内存节省效果更显著。

核心差异对比

对比维度glDrawArraysglDrawElements
顶点复用能力无。顶点需按顺序连续使用,重复顶点必须重复存储有。通过索引重复引用VBO中的顶点,无需重复存储
内存开销高。复杂模型中重复顶点多,VBO体积大低。VBO仅存储独特顶点,IBO体积小(索引多为2字节)
数据传输量大。每次渲染需传输更多顶点数据到GPU小。仅传输索引+独特顶点,带宽占用低
渲染效率简单场景高效(无索引查找),复杂场景低效复杂场景高效(硬件优化索引查找,抵消额外开销)
适用场景简单图形(点、线、单个三角形、矩形)、粒子系统复杂3D模型(立方体、人物、场景)、需复用顶点的图形
灵活性低。顶点使用顺序固定,无法自定义高。通过索引可任意调整顶点顺序,支持复杂拓扑(如凹多边形)
额外依赖仅需VBO(顶点缓冲区)需VBO+IBO(索引缓冲区),多一步IBO管理
索引类型支持不支持索引支持GL_UNSIGNED_BYTE(最大256顶点)、GL_UNSIGNED_SHORT(最大65536顶点)

实践选择策略

选择依据:场景优先

  1. 优先用glDrawArrays的场景

    • 渲染简单图形:如单个三角形、线段、点集(如粒子系统,每个粒子是独立点);
    • 顶点无复用:如连续的独立三角形(无共用边/顶点);
    • 追求代码简洁:无需管理IBO,适合快速验证功能(如测试着色器)。
  2. 优先用glDrawElements的场景

    • 复杂3D模型:如立方体(8个顶点→36个索引)、人物模型(数万顶点,大量复用);
    • 需节省内存/带宽:移动设备内存有限,减少VBO体积可降低内存压力;
    • 自定义顶点顺序:如绘制凹多边形(通过索引调整顶点顺序,避免图元异常)。

常见误区

  1. “glDrawElements代码复杂,没必要用”
    虽然glDrawElements需多管理一个IBO,但现代开发中,建模工具(如Blender、Maya)导出模型时会自动生成索引,无需手动编写;且复杂模型的内存节省收益远大于代码复杂度。

  2. “glDrawElements有索引查找,效率更低”
    现代GPU有专门的硬件单元优化索引查找(如索引缓存),额外开销极小;而减少顶点数据传输(内存带宽是移动设备的常见瓶颈)带来的效率提升,远超过索引查找的开销。

  3. “索引类型随便选,用GL_UNSIGNED_BYTE就行”
    GL_UNSIGNED_BYTE仅支持最大256个顶点,超过此数量会导致索引溢出;复杂模型需用GL_UNSIGNED_SHORT(支持最大65536个顶点),OpenGL ES 3.0+还支持GL_UNSIGNED_INT(支持更多顶点)。

总结

glDrawArraysglDrawElements的核心差异是是否通过索引复用顶点:前者简单直接,适合简单场景;后者高效灵活,是复杂3D渲染的标配。

二者没有绝对的“优劣”,而是针对不同场景的优化选择——理解顶点复用的价值,是掌握OpenGL ES渲染性能优化的关键一步。
请添加图片描述

http://www.dtcms.com/a/540028.html

相关文章:

  • Linux入门1(2/2)
  • ubuntu24安装mysql遇到的坑----解决Mysql报错缺少libaio.so.1
  • 【星光不负 码向未来 | 万字解析:基于ArkUI声明式UI与分布式数据服务构建生产级跨设备音乐播放器】
  • UniApp 在手机端(Android)打开选择文件和文件写入
  • HarmonyOS分布式媒体播放器——跨设备音视频无缝流转
  • 【金融行业案例】基于Vaadin全栈Java框架重构内部系统,全面提升开发效率与用户体验
  • 小型网站开发要多少钱苏州专业做网站的公司哪家好
  • RocketMQ 生产环境性能调优实战:从 0 到 1 打造高可用消息队列系统
  • 脉冲按摩贴方案开发, 脉冲按摩贴MCU控制方案设计
  • 特别酷炫网站做网站有费用吗
  • DrissionPage 基于 Python 的网页自动化工具
  • Next.js vs Vue.js:2025年全栈战场,谁主沉浮?
  • DAY01笔记
  • 10-js基础(ESMAScript)
  • 一次深入排查:Spring Cloud Gateway TCP 连接复用导致 K8s 负载均衡失效
  • 基于 Vue3 及TypeScript 项目后的总结
  • Android下解决滑动冲突的常见思路是什么?
  • 建筑外观设计网站如何做一个门户网站
  • SQL多表查询完全指南-从JOIN到复杂关联的数据整合利器
  • Redis主从复制与哨兵集群
  • 电科金仓“异构多活架构”:破解浙江省人民医院集团化信创难题的密钥
  • 从零搭建群晖私有影音库:NasTool自动化追剧全流程拆解与远程访问协议优化实践
  • Maven项目管理:高效构建与依赖管理!
  • 【win11】funasr 1:配置conda环境
  • 2025年--Lc219-590. N 叉树的后序遍历(递归版,带测试用例)-Java版
  • YOLO11追踪简单应用
  • Spring Web MVC 入门秘籍:从概念到实践的快速通道(上)
  • 网站是什么字体企业内网模板
  • 建一个小型购物网站要有服务器网易博客搬家wordpress
  • 申威服务器安装Nacos 2.0.3 RPM包详细步骤(Kylin V10 sw_64架构)​附安装包