什么是“OpenGL状态机”?

4
我的大脑倾向于使用分层、面向对象、组件化的方式来构建概念。不幸的是,这使我难以理解OpenGL——我怀疑我的困惑源于我对“OpenGL状态机”的误解。你有了图形管线,但这只针对绘制用户图形组件的单个程序对象,对吗?
以下是需要翻译的内容:
  • 这个主状态机是什么?
    • 它是OpenGL API、我正在尝试绘制的帧,还是用户定义的东西(例如管线)?
  • 它可以存在哪些不同的状态?
    • 它是否也是事件驱动的?
  • 哪些输入会影响状态机可能处于的不同状态?
  • 分层、面向对象的概念是否适用于此状态机?
4个回答

18

这是什么状态机?

想象一下一个有几十个开关和旋钮的接线板,该接线板连接到加工厂中的机器上,根据这些开关和旋钮的切换方式,输入到工厂一侧的物料将沿着特定路径通过工厂。改变某些开关的状态,物料将走另一条路。

还有一种状态机,其中当物料通过某一处理步骤时,开关和旋钮会发生变化;OpenGL不属于这类。

OpenGL上下文就是一个具有处理厂的接线板,您可以通过API进行控制,而处理厂则是生成图像的地方。

它可以存在哪些不同的状态?

太多了,每个开关的状态数量乘以该开关可以处于的位置数量。假设您有20个切换开关、一个具有5个位置的旋钮和一个具有7个位置的旋钮,则您有36700160个可能的不同状态。2^20 * 5 * 7 = 36700160。一些OpenGL版本有超过300个状态变量,其中许多不仅仅是布尔值。因此,试图列出每一个可能的状态是徒劳的。

它也是事件驱动的吗?

不是!

哪些输入会影响状态机可以处于的不同状态?

OpenGL API中显式调用状态更改函数和默认初始状态。

分层,面向对象的概念是否适用于此状态机?

不是。或者说,您可以尝试,但实话实说,唯一真实的表示将是一个持有所有状态的单个OpenGL上下文类。

在OpenGL上下文中有一些行为就像具有自己状态的单独对象。例如纹理,但这些对象与OpenGL上下文本身紧密相关,不能被单独查看。


关于你的评论的个人看法

我的大脑倾向于以分层、面向对象、组件式的方式构建概念。

停止这样做!计算机不是这样工作的。面向对象编程是一种组织项目的方法,但它并不是理解某些现有系统的很好的工具,特别是那些本质上非分层的系统。

我强烈建议你通过学习其他的项目结构方式来扩展你的视野。学习一下函数式编程怎么样?Haskell是目前流行的函数式编程语言之一,对于学习体验来说非常好,因为它专注于成为一种纯语言。这里有一本非常好的在线书籍/教程:http://learnyouahaskell.com/chapters——准备好了,你的思维可能会被扭曲,有些东西看起来像面向对象编程,但实际上不是;当你最终理解了这些概念时,所得到的好处(即有顿悟)是真正值得的。

请注意,OpenGL既不是函数式编程,也不是真正的面向对象编程。由于它不是函数式的,因此它不能很好地映射到纯函数式编程中。有Haskell的OpenGL绑定,但它们是通过一种叫做“单子”的东西实现的;单子是函数式程序用来与状态甚至驱动环境联系起来的。

我推荐的另一个资源是https://mitpress.mit.edu/sicp/full-text/book/book.html——每个程序员都应该读过它。


1
关于面向对象设计和函数式编程的优秀笔记! - lisyarus

5
这是一个非常广泛的问题,所以我将为您提供一个概述。
什么是主状态机?
状态机是完整的OpenGL API和当前选择的渲染上下文的组合。渲染上下文包含构成状态机的所有状态,而OpenGL API提供改变这些状态的输入。
它可以存在哪些不同的状态?
有太多了,例如由glEnable和glDisable切换的功能(例如GL_TEXTURE_2D、GL_FOG、GL_BLEND)是状态的一部分。使用glBindTexture设置的当前纹理名称;从glUseProgram设置的当前着色器程序的名称;模型视图和投影矩阵的内容以及指示任何glMultMatrix(等)调用将影响哪个矩阵的glMatrixMode的状态;等等,这些都是状态的一部分。
哪些输入会影响状态机的不同状态?
机器的初始状态由OpenGL规范给出,除GPU驱动程序外。可以将机器置于不同状态的唯一输入是对OpenGL API的调用。
可以将分层的OOP概念应用于此状态机吗?
这个问题很模糊。什么是“分层的OOP概念”?
可以采用面向对象的方法来封装OpenGL API。可以创建一个“纹理”对象类,在初始化时使用glGenTextures分配纹理名称,并提供上传纹理图像或更改纹理参数的方法,这些方法始终在调用相关方法之前调用glBindTexture。这种方法通常很棘手,因为你仍然必须通过对象和子对象方法调用来跟踪当前状态。
OpenGL提供了几个“堆栈”(例如glPushMatrix,glPushAttrib),旨在帮助管理对象的层次结构。通过将当前状态的某些部分推入堆栈中,调用子对象的方法,然后从堆栈中弹出该状态,您可以将状态恢复为父对象所期望的状态。
使用OpenGL API的一般面向对象方法是通过场景图 - 一种有向无环图形对象,在其中每个对象表示对状态机状态的某些更改。对象将其状态推送到一个或多个OpenGL堆栈中,使用OpenGL API调用应用其状态,调用其子对象的方法,然后从这些OpenGL堆栈中弹出以恢复先前的状态。

1
glMatrixModeGL_FOGglPushMatrixglMultMatrixglPushAttrib在现代OpenGL中已经不再使用,而且这个问题被标记为“opengl-4”。 - RamblingMad
那么面向对象的方法就更加棘手了:您将不得不通过调用glIsEnabled或通过复制整个状态并可能通过包装glEnabled等来实现自己的状态堆栈,以使对象相信GL处于实际状态。 - codewarrior
什么?我已经做了很多OpenGL编程,从来没有必要做任何这样的事情。它应该在你的代码中是隐含的,永远不要进入一个没有意义的函数;这很容易。无论如何,OpenGL已经慢慢地走向了无状态的方式。 - RamblingMad

4
依据我的观点,这里使用“状态机”这个表达方式是具有误导性的。当我听到“状态机”时,我自然会想到一个有着一小组定义好的状态的有限自动机。它总是处于其中之一的状态,并根据输入来确定下一个状态和输出结果。
但是,这并不是OpenGL所做的事情。(从数学上来看,你可以将其视为这样的自动机,但如果你这样做,你将人为地创建出大量可能的状态,这是没有帮助的)。
是的,OpenGL API是一个“状态机型”API。你告诉它很多东西,它会在其“状态”内记住这些东西,这基本上是一组变量。然后,你要求它去渲染一些东西,OpenGL将使用你先前设置的状态来确定如何精确处理要渲染的数据。
顺便说一句:我发现Joey de Vries的教程对理解OpenGL非常有帮助。详情请参见https://learnopengl.com/Getting-started/OpenGL

0

说实话,我发现理解这个问题的最佳方式是面向对象编程。

从我所了解的关于OpenGL的一切,以下是我对OpenGL API的最佳解释:

首先,如果你将其与其他API(如SDL)进行比较,它可能会更加复杂和不同,使用API来尝试描述它真的无法做到公正。

因此,在面向对象编程方面,

想象一下,如果你有一个类,这个类是私有的,其所有工作方法都不打算被用户查看,但它也有一个默认状态,以便如果你“开箱即用”,它将在该默认状态下执行其功能(基本上什么也不做)。

当你创建一个上下文时,就像你创建了这个类的一个实例(你可以创建任意多个这个类的实例,也就是说你可以创建多个上下文),这是运行OpenGL所必需的。在OOP中,你不能使用一个类,直到你从它创建了一个对象/实例,唯一的区别是构造函数需要你的硬件/操作系统/其他API/硬件信息来完成实例/对象,以便它可用,或者将其插入到某个“屏幕”中。

然而,这个类中包含的方法(函数)和字段(数据存储)是赋予您使用此实例并执行所需操作的能力的关键。当然,您如何执行某些操作取决于方法,您不能更改此类/实例/对象的任何内容而不使用其方法/字段(即允许使用的公共字段),您不能自己说“我想这样做”或选择在哪里存储字段(外部等),所有这些都必须与实例、类和方法相关,并且在此实例/对象/类中有数百种方法和字段(OpenGL 4.5具有超过500个命令(方法/字段)影响此实例/类),无论您对它进行何种更改,都会更改此特定实例/对象,就像您拥有一些黏土并将其塑造成您想要的任何形状一样,当然,您必须将某些东西插入此实例中,例如具有其特定功能的着色器。

因此,它确实有点像电视屏幕的遥控器,它有其方法/按钮,根据您在遥控器/实例/方法/函数上按下的内容来影响显示的上下文(屏幕/电视/监视器),但在这个比喻中的区别是需要更多的按钮操作以及数据和您可以呈现它的各种方式(这就是您绘制到屏幕上的内容)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接