这个学期,我在我的大学修了一门计算机图形学类(class)。目前,我们开始研究一些更高级的东西,比如高度图、平均法线、镶嵌等。
我来自面向对象的背景,所以我试图把我们所做的一切都放在可重用的类中。我已经成功创建了一个相机类,因为它主要依赖于对 gluLookAt() 的一次调用,它几乎独立于 OpenGL 状态机的其余部分。
但是,我在其他方面遇到了一些麻烦。使用对象来表示基元对我来说并没有真正成功。这是因为实际的渲染调用依赖于许多外部事物,例如当前绑定(bind)的纹理等。如果您突然想将特定类的表面法线更改为顶点法线,这会导致严重的头痛。
我开始怀疑 OO 原则是否适用于 OpenGL 编码。至少,我认为我应该让我的类(class)不那么细化。
堆栈溢出社区对此有何看法? OpenGL 编码的最佳实践是什么?
最佳答案
最实用的方法似乎是忽略大多数不直接适用的 OpenGL 功能(或者速度慢,或者没有硬件加速,或者不再与硬件匹配)。
OOP 与否,要渲染一些场景,这些场景是您通常拥有的各种类型和实体:
几何 (网格)。大多数情况下,这是一个顶点数组和索引数组(即每个三角形三个索引,又名“三角形列表”)。顶点可以是某种任意格式(例如,只有一个 float3 位置;一个 float3 位置 + float3 法线;一个 float3 位置 + float3 法线 + float2 texcoord;等等)。因此,要定义一个几何图形,您需要:
如果你在 OOP 领域,你可以称这个类为网格。
Material - 定义某些几何体如何渲染的东西。例如,在最简单的情况下,这可能是对象的颜色。或者是否应该应用照明。或者对象是否应该进行 alpha 混合。或者要使用的纹理(或纹理列表)。或者要使用的顶点/片段着色器。等等,可能性是无穷无尽的。首先将您需要的东西放入 Material 中。在 OOP 领域,该类可以被称为(惊喜!)一个 Material 。
场景 - 您有几何体、 Material 集合、定义场景中的内容的时间。在一个简单的情况下,场景中的每个对象都可以通过以下方式定义:
- 它使用什么几何体(指向网格的指针),
- 它应该如何呈现(指向 Material),
- 它位于何处。这可以是 4x4 变换矩阵,或 4x3 变换矩阵,或 vector (位置)、四元数(方向)和另一个 vector (比例)。让我们将其称为 OOP 领域中的节点。
相机 .好吧,相机只不过是“放置的位置”(同样是 4x4 或 4x3 矩阵,或位置和方向),加上一些投影参数(视野、纵横比……)。
所以基本上就是这样!你有一个场景,它是一堆引用网格和 Material 的节点,你有一个定义查看器所在位置的相机。
现在,将实际 OpenGL 调用放在哪里只是一个设计问题。我想说,不要将 OpenGL 调用放入 Node、Mesh 或 Material 类中。相反,制作类似 OpenGLRenderer 的东西,它可以遍历场景并发出所有调用。或者,更好的是,制作一些独立于 OpenGL 的场景,并将较低级别的调用放入 OpenGL 依赖类中。
所以是的,以上所有内容几乎都是独立于平台的。这样下去,你会发现 glRotate、glTranslate、gluLookAt 和 friend 是很没用的。您已经拥有所有矩阵,只需将它们传递给 OpenGL。无论如何,这就是真实游戏/应用程序中大多数真实代码的工作方式。
当然,上述内容可能因更复杂的要求而变得复杂。特别是, Material 可能非常复杂。网格通常需要支持许多不同的顶点格式(例如,为了效率而打包法线)。场景节点可能需要按层次结构组织(这个很容易 - 只需向节点添加父/子指针)。蒙皮网格和动画通常会增加复杂性。等等。
但主要思想很简单:场景中有几何体、 Material 、物体。然后一些小的代码能够呈现它们。
在 OpenGL 的情况下,设置网格很可能会创建/激活/修改 VBO 对象。在渲染任何节点之前,需要设置矩阵。设置 Material 会影响大部分剩余的 OpenGL 状态(混合、纹理、照明、组合器、着色器等)。
关于c++ - OpenGL 编码(特别是 w.r.t. 面向对象)有哪些最佳实践?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/166356/