OpenGL ES 绘制图元:glDrawArrays 和 glDrawElements 介绍

一、简介

在 OpenGL ES 2.0 中,使用 glDrawArraysglDrawElements 两个接口绘制图元。
在 OpenGL ES 3.0 中,又新增了 glDrawRangeElementsglDrawElementsInstancedglDrawArraysInstanced 三个接口用于绘制图元。

二、函数介绍

1. glDrawArrays

/**
 * @param mode 渲染的图元模式,有:GL_POINTS、GL_LINES、GL_LINE_LOOP、GL_LINE_STRIP、GL_TRIANGLES、GL_TRIANGLE_STRIP、GL_TRIANGLE_FAN
 * @param first 起始位置
 * @param count 顶点数量
 */
void glDrawArrays(GLenum mode, GLint first, GLsizei count);

2. glDrawElements

/**
 * @param mode 渲染的图元模式,有:GL_POINTS、GL_LINES、GL_LINE_LOOP、GL_LINE_STRIP、GL_TRIANGLES、GL_TRIANGLE_STRIP、GL_TRIANGLE_FAN
 * @param count 顶点数量
 * @param type 元素类型,有:GL_UNSIGNED_BYTE、GL_UNSIGNED_SHORT、GL_UNSIGNED_INT
 * @param indices 元素索引数组
 */
void glDrawElements(GLenum mode, GLsizei count, GLenum type, const void *indices);

3. 区别

首先,这两个函数的作用都是从一个数据数组中提取数据,然后渲染图元。

区别在于:glDrawArrays 是直接绘制真实的顶点数据,而 glDrawElements 是按照指定的索引顺序取出真实数据再绘制。

于是对于顶点存在共享的场景时,使用 glDrawElements 对于重复的顶点数据只需要传输一份数据,绘制时通过索引反复的获取其值,最终降低内存占用和内存带宽需求。

三、图元介绍

上面我们知道,渲染的时候需要指定一个渲染的图元模式,下面我们就详细介绍一下这些图元和图元模式。

1. 点精灵

对应的模式为 GL_POINTS,即在每个顶点位置绘制一个点。OpenGL ES 中绘制的点实则是一个方块,顶点位置是方块的中心点,边长在顶点着色器中由内建变量 gl_PointSize 指定。

点的尺寸大小范围可以通过如下方式获取:

GLfloat pointSizeRange[2];
glGetFloatv(GL_ALIASED_POINT_SIZE_RANGE, pointSizeRange);

如果我们想自定义点的外形,通常可以使用纹理。一个使用纹理的片段着色器示例如下:

#version 300 es
precision mediump float;

uniform sampler2D u_TextureUnit;
layout(location = 0) out vec4 outColor;

void main() {
    outColor = texture2D(u_TextureUnit, gl_PointCoord);
}

这样我们在外面给 u_TextureUnit 指定纹理的 id 即可,关于纹理的相关使用后续文章再介绍。

还有上例中我们使用到了一个内建变量 gl_PointCoord,它只在绘制点精灵时可以使用,描述了这个点内部的坐标空间,其左上角为 (0, 0),右下角为 (1, 1)。

2. 直线

对应的模式为 GL_LINESGL_LINE_LOOPGL_LINE_STRIP,用指定的顶点绘制相应的线段。
在这里插入图片描述
如图,假设指定的顶点坐标为 (v0, v1, v2, v3),那么

  • GL_LINES 模式下,将绘制 (v0, v1) 和 (v2, v3) 这两条线段
  • GL_LINE_STRIP 模式下,将绘制 (v0, v1)、(v1, v2) 和 (v2, v3) 三条线段
  • GL_LINE_LOOP 模式下,将绘制 (v0, v1)、(v1, v2)、(v2, v3) 和 (v3, v0) 四条线段

线段的宽度使用如下 API 指定:

/**
 * @param width 线宽,以像素数表示,默认的宽度为 1.0
 */
void glLineWidth(GLFloat width);

指定的线宽将被 OpenGL 记住,直到由应用程序更新。

支持的线宽范围可以通过如下方式获取:

GLfloat lineWidthRange[2];
glGetFloatv(GL_ALIASED_LINE_WIDTH_RANGE, lineWidthRange);

3. 三角形

对应的模式为 GL_TRIANGLESGL_TRIANGLE_STRIPGL_TRIANGLE_FAN。三角形图元可谓是最常使用的了。
在这里插入图片描述
如图,假设指定的顶点坐标为上图所示,那么

  • GL_TRIANGLES 模式下,将绘制 (v0, v1, v2) 和 (v3, v4, v5) 这两个三角形。
  • GL_TRIANGLE_STRIP 模式下,将绘制 (v0, v1, v2)、(v2, v1, v3) (注意顺序)和 (v2, v3, v4) 三个三角形。
  • GL_TRIANGLE_FAN 模式下,将绘制 (v0, v1, v2)、(v0, v2, v3) 和 (v0, v3, v4) 三个三角形。

四、3.0 新增的绘制接口介绍

最开始说到在 OpenGL ES 3.0 中新增了几个绘制图元的接口,接下来对其简单了解一下。

1. glDrawRangeElements

/**
 * @param mode 渲染的图元模式,有:GL_POINTS、GL_LINES、GL_LINE_LOOP、GL_LINE_STRIP、GL_TRIANGLES、GL_TRIANGLE_STRIP、GL_TRIANGLE_FAN
 * @param start 起始索引位置
 * @param end 结束索引位置
 * @param count 顶点数量
 * @param type 元素类型,有:GL_UNSIGNED_BYTE、GL_UNSIGNED_SHORT、GL_UNSIGNED_INT
 * @param indices 元素索引数组
 */
void glDrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices);

相比于 glDrawElements 接口,它新增了 startend 两个参数,用来指定使用的数据数组的起始和结束位置。其余区别不大。

2. glDrawElementsInstanced 和 glDrawArraysInstanced

当需要绘制大量相似对象时(例如颜色、大小存在不同),以往我们只能向 OpenGL ES 引擎发送许多 API 调用。但是使用几何形状实例化,可以用一次 API 调用多次渲染具有不同属性的同一对象,大大降低 API 调用的 CPU 处理开销。

简单的说,如果我们想做一次批量的渲染,就可以使用 glDrawElementsInstancedglDrawArraysInstanced

函数定义:

/**
 * @param mode 渲染的图元模式,有:GL_POINTS、GL_LINES、GL_LINE_LOOP、GL_LINE_STRIP、GL_TRIANGLES、GL_TRIANGLE_STRIP、GL_TRIANGLE_FAN
 * @param first 起始位置
 * @param count 顶点数量
 * @param instancecount 绘制的图元实例数量
 */
void glDrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instancecount);

/**
 * @param mode 渲染的图元模式,有:GL_POINTS、GL_LINES、GL_LINE_LOOP、GL_LINE_STRIP、GL_TRIANGLES、GL_TRIANGLE_STRIP、GL_TRIANGLE_FAN
 * @param count 顶点数量
 * @param type 元素类型,有:GL_UNSIGNED_BYTE、GL_UNSIGNED_SHORT、GL_UNSIGNED_INT
 * @param indices 元素索引的指针
 * @param instancecount 绘制的图元实例数量
 */
void glDrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount);

相比于 glDrawArraysglDrawElements,它多出了一个 instancecount 参数,表示我们需要绘制的图元数量。例如我们想要一次批量绘制 100 个相似的图元,这个值对应就传 100。

关于多实例绘制想要详细了解的,可以参考其它博客:

博客链接:

https://www.jianshu.com/p/71b7149d9dc1
https://www.cnblogs.com/xin-lover/p/9147905.html

  • 9
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,可以按照以下步骤实现: 1. 在类定义中加入QObject和QGraphicsPathItem的公有继承: ```cpp class LineItem : public QObject, public QGraphicsPathItem { Q_OBJECT public: LineItem(QObject* parent = nullptr); ~LineItem(); }; ``` 2. 在构造函数中进行初始化,设置可选中、可移动、可编辑等属性: ```cpp LineItem::LineItem(QObject* parent) : QObject(parent) { setFlag(QGraphicsItem::ItemIsSelectable, true); setFlag(QGraphicsItem::ItemIsMovable, true); setFlag(QGraphicsItem::ItemIsFocusable, true); setFlag(QGraphicsItem::ItemSendsGeometryChanges, true); setAcceptHoverEvents(true); setPen(QPen(Qt::black, 2)); } ``` 3. 重写鼠标事件函数,实现绘制直线: ```cpp void LineItem::mousePressEvent(QGraphicsSceneMouseEvent* event) { if (event->button() == Qt::LeftButton) { // 记录起始点 m_startPoint = event->scenePos(); // 清空路径 m_path = QPainterPath(); m_path.moveTo(m_startPoint); setPath(m_path); } } void LineItem::mouseMoveEvent(QGraphicsSceneMouseEvent* event) { if (event->buttons() & Qt::LeftButton) { // 更新路径 m_endPoint = event->scenePos(); m_path = QPainterPath(); m_path.moveTo(m_startPoint); m_path.lineTo(m_endPoint); setPath(m_path); } } void LineItem::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { if (event->button() == Qt::LeftButton) { // 更新路径 m_endPoint = event->scenePos(); m_path = QPainterPath(); m_path.moveTo(m_startPoint); m_path.lineTo(m_endPoint); setPath(m_path); } } ``` 4. 在场景中添加该图: ```cpp LineItem* line = new LineItem(); scene->addItem(line); ``` 这样就实现了一个可以用鼠标绘制直线的图
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值