Modern OpenGL - 渲染矩体/矩形体/立方体/正方体/长方体

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Modern OpenGL - 渲染矩体/矩形体/立方体/正方体/长方体相关的知识,希望对你有一定的参考价值。

参考技术A

Minecraft 中的模型由一个个cube组成,这里的cube不是正方体而是矩体,也就是6个面中的每个面都是矩形。
在 Minecraft 1.16 中,一个cube包含6个quad。假定我们渲染一个普通的完整不透明方块,即一个正方体,那么这个模型的6个面中的每个面有一个quad。一个quad即一个矩形,包含了4个顶点的属性(attribute),因此一个cube便有 4 * 6 = 24 个顶点。
Minecraft 会创建 VBO,将这些 attributes 批量传入OpenGL内置shader,即使用固定管线,传入投影矩阵(projection)并使用 glDrawArrays GL_QUADS 模式下渲染。
那么我们如何在 Modern OpenGL(core-profile)中渲染这个矩体呢?

VBO(Vertex Buffer Object)即顶点缓冲对象,规范于OpenGL 1.5 (2003)。VBO是在显存中分配出的一块内存空间,用于存放大量顶点的属性(attribute),如坐标 position、颜色 color、法向量 normal、纹理映射 uv、光照映射 lightmap等。渲染时,由于VBO的数据是存储在显存中而不是内存中,不需要从CPU向GPU传入数据,GPU可以直接从VBO中按顺序读取数据,且这些数据是对齐的,性能更高。

所有的缓冲对象都由下面的方法所生成一个ID表示:

OpenGL中有各种类型的缓冲对象,VBO所对应的类型为 GL_ARRAY_BUFFER 。我们可以使用 glBindBuffer 同时绑定多个不同类型的缓冲对象。下面的方法可以将当前的 GL_ARRAY_BUFFER 绑定到 VBO_id,当第二个参数为 0 时,即解除绑定(unbind):

绑定之后,我们对所有 target 为 GL_ARRAY_BUFFER 的操作都会应用到当前所绑定的缓冲对象上,即我们刚刚生成的 VBO_id 。接着我们使用 glBufferData 来复制内存中的数据到缓冲对象中去,即显存里:

这里, size 即数据的大小,单位为字节。 usage 即用途,由频率(frequency)和性质(nature)两部分组成,每个部分有三种选择,可以组合出9种用途。

频率包括:

性质包括:

不同的 usage 所分配的内存空间是不同的,这里我们的VBO用于实时渲染,并且数据内容可能会发生变化(比如坐标),因此我们选择 GL_DYNAMIC_DRAW

在顶点着色器(vertex shader)中我们可以定义输入的每个顶点属性的变量名与类型。

我们必须要指定我们VBO的数据是如何布局的,这样着色器才能正确地得到每个顶点的每个属性,这就需要使用 glVertexAttribPointer

假设我们的顶点属性中只包含一个坐标,类型为 vec3 ,它的布局如下:

注意: glVertexAttribPointer 最终都会将数据转成浮点型。如果使用 glVertexAttribIPointer ,则会转成整数类型,并且 type 只能 GL_BYTE , GL_UNSIGNED_BYTE , GL_SHORT , GL_UNSIGNED_SHORT , GL_INT GL_UNSIGNED_INT 。这个方法不含 normalized 参数。

调用 glVertexAttribPointer 后,所有的这些配置会被保存成一个状态,这个状态也包括当前所绑定的VBO,这样以后就会从这个VBO中读取数据。如果使用VAO,则该状态会保存进VAO,这样就不再需要绑定VBO,即省事也能提高性能。
所有顶点属性默认都是关闭的,需要使用 glEnableVertexAttribArray 来给出它的 location ,即通用顶点属性的索引。

EBO(Element Buffer Object)即元素缓冲对象,或称为索引缓冲对象,规范于OpenGL 1.5 (2003)。假设我们要渲染的矩体中的顶点属性只包含坐标,那么事实上我们只需要8个坐标而不是24个,这样8个顶点就可以满足需求,同时可以节省显存占用,提高利用率和性能。EBO存储了一个组索引,OpenGL能按照这种顺序使用顶点,这也称为索引绘制(indexed drawing)。

与VBO类似,我们需要绑定EBO并使用 glBufferData 上传数据,但这里的 target 是 GL_ELEMENT_ARRAY_BUFFER 。EBO的数据一般不需要修改,所以这里的 usage 使用 GL_STATIC_DRAW

VAO(Vertex Array Object)即顶点数组对象,规范于OpenGL 3.0 (2008)。 在Modern OpenGL(core-profile)中必须绑定VAO才能进行渲染。 VAO可以保存我们的顶点属性配置,这样我们只需要配置好一个VAO,就不需要在每次渲染时调用 glVertexAttribPointer 等操作,并且可以轻松在不同的VBO或者是相同的VBO中使用不同的顶点属性配置之间进行切换。当绑定VAO时,会切换 glEnableVertexAttribArray glDisableVertexAttribArray 状态,进行调用 glVertexAttribPointer 的顶点属性配置,并使用某个EBO。

注意,VAO只会与某个EBO相关联而不会与某个VBO相关联,它会与在绑定VAO时调用 glVertexAttribPointer 的顶点属性配置时所绑定的VBO相关联,也就是那时的 GL_ARRAY_BUFFER ,所以不同的VAO可以使用相同的VBO,同时也能使用不同的顶点属性配置。此外,顶点属性默认就是关闭的,所以在某个的VAO内我们只需使用 glEnableVertexAttribArray 而几乎无需使用 glDisableVertexAttribArray ,除非需要重新配置顶点属性。

生成一个VAO:

接着使用 glBindVertexArray 绑定并配置VAO,配置好后解绑表示不再配置该VAO,当需要渲染时,我们只需重新绑定这个VAO即可。注意,VAO只需要在渲染前创建并配置好,通常不在渲染循环中创建。

假定渲染这样一个正方体,0为坐标原点, 01 的方向向量为 x 03 的方向向量为 y 40 的方向向量为 z

OpenGL中渲染任何图形最终只会转换为3种:点,线,三角形。所以我们要渲染正方体的6个面,每个面都是一个矩形,则由两个三角形组成,一共需要渲染12个三角形,即 mode 为 GL_TRIANGLES 。一个三角形需要3个顶点,在OpenGL中默认以逆时针方向为正面,OpenGL 2.0起可以通过 glFrontFace 来设置顺时针还是逆时针方向为正面,这里我们遵循默认行为,则EBO数据:

接着我们再使用 glDrawElements 来进行绘制。因为我们在VAO中已经绑定了EBO,所以 indices 需要传入所使用的EBO数据的指针即可,即起始偏移量(如果没绑定EBO,则为EBO数组的指针)。 type 则指定了EBO的数据类型。VBO只包含一个正方体的数据,所以 count 为 3 * 12 = 36。则渲染循环:

编写shader、投影矩阵、坐标系等较为复杂,这里暂不介绍。回到 Minecraft 中,如果每个面使用不同的纹理,我们还是需要24个顶点,即使有3个顶点的坐标值是相同的,但是其他属性不同,因此不同情况下要采取不同的策略,比如把所有顶点坐标存在一个VBO,其他数据存在另一个VBO等。

本文部分内容翻译自 https://learnopengl.com/ 。

从 Qt creator 访问 Modern OpenGL 函数

【中文标题】从 Qt creator 访问 Modern OpenGL 函数【英文标题】:Accessing Modern OpenGL functions from Qt creator 【发布时间】:2014-04-30 19:42:47 【问题描述】:

我正在使用 Open GL 3.0

我正在尝试将此示例更新为现代 OpenGL: http://qt-project.org/doc/qt-4.8/opengl-hellogl-es.html

我也在看这个例子: http://qt-project.org/doc/qt-5/qtopengl-cube-example.html

我正在查看 OpenGL ES 示例,因为它们可以在我的机器上轻松编译和渲染,而 OpenGL ES 2 示例使用了一些可编程流水线。

我想使用本教程中引用的管道函数: http://www.opengl-tutorial.org/beginners-tutorials/tutorial-2-the-first-triangle/

例如 glGenBuffers()

然而,这似乎并没有出现在开放的 Qt 中。如果我尝试编译使用 glGenBuffers 或 glBindBuffer 的程序。

为什么我可以使用某些 Open GL 函数而不能使用其他函数?

看第二个例子(OpenGL ES2)有一个类型“QGLShaderProgram”,它似乎包含了一些着色函数,但我不知道如何遵循一个简单的开放 GL 教程,完全访问渲染功能。

例如,openGL 教程引用了至少六个我似乎无法使用的函数。我对此很好,但我似乎无法找到 Qt 人员在哪里解释了哪些功能被包装、或掩盖或不存在。

我可能缺少包含或其他内容吗?

我包括#QGLWidget 和#QtOpenGL

【问题讨论】:

您不必使用包装类。 Qt 提供了一个可以继承的类(例如QGLFunctionsQOpenGLGLFunctions),如果您的平台支持,它将为您提供在运行时加载的可调用函数。它有点像 GLEW,只是不那么广泛。 我怀疑一个问题是 glGenBuffers 来自 OpenGLES,并非所有版本的 Qt 都支持。 @Jay 不是来自 ES;它来自OpenGL 1.5+。如果 Qt 不支持那么旧的东西,我会感到非常惊讶。 @AndonM.Coleman 我发现了!在正确回答/关闭之前,我正在检查这些功能是否正常工作。 @ColonelThirtyTwo 它在 khronos 上的 opengles 树中列出:khronos.org/opengles/sdk/docs/man/xhtml/glGenBuffers.xml 也许它也被采用到 1.5 版中 【参考方案1】:

请参阅下面我对您问题的回答。但是,您似乎正在寻找一个simpler OpenGL example with Qt,就像您链接到的三角形一样。 I also made an introductory post here 在开始示例之前,您可以在这里学习 Qt 和 OpenGL 如何协同工作的基础知识。

首先现在使用 Qt 5.5。它默认配置了 -opengl 动态选项,这可能会解决您的问题。如果您在 Qt 5.5 之前的版本中没有设置 -opengl 桌面配置选项,那么除了 Qt 支持的所有平台支持的 OpenGL ES 2 子集之外,您将无法访问现代 OpenGL 功能。

其次,不要使用 QGL* 函数或类,因为它们现在已过时/已弃用。它们被 QOpenGL* 函数和类所取代。

至于包含,您将需要一个 QOpenGLFunctions 或 QOpenGLFunctions_3_0 来确定您将获得哪组函数。您还需要任何其他类,例如 QOpenGLBuffer QOpenGLVertexArrayObject... 或者只包含 QtGui,因为这是所有 OpenGL 函数和类现在所在的位置。

【讨论】:

以上是关于Modern OpenGL - 渲染矩体/矩形体/立方体/正方体/长方体的主要内容,如果未能解决你的问题,请参考以下文章

Modern OpenGL - 顶点数组、属性与绑定点(OpenGL 4.5+)

现代OpenGL渲染管线介绍

Modern OpenGL 3.x 及更高版本如何绘制图元?

OpenGL实时渲染传输

OpenGL 不渲染三角形

OpenGL:渲染到 FBO 时是不是支持纹理组合器功能?