这个OpenGL Haskell代码如何工作?

时间:2014-02-21 08:35:10

标签: opengl haskell monads

我正在通过直接进入OpenGL学习Haskell,我似乎无法破译这段代码:

display :: DisplayCallback
display = do
  let color3f r g b = color $ Color3 r g (b :: GLfloat)
      vertex3f x y z = vertex $ Vertex3 x y (z :: GLfloat)

  clear [ColorBuffer]
  renderPrimitive Quads $ do
    color3f 1 0 0
    vertex3f 0 0 0
    vertex3f 0 0.2 0
    vertex3f 0.2 0.2 0
    vertex3f 0.2 0 0

    color3f 0 1 0
    vertex3f 0 0 0
    vertex3f 0 (-0.2) 0
    vertex3f 0.2 (-0.2) 0
    vertex3f 0.2 0 0

    color3f 0 0 1
    vertex3f 0 0 0
    vertex3f 0 (-0.2) 0
    vertex3f (-0.2) (-0.2) 0
    vertex3f (-0.2) 0 0

    color3f 1 0 1
    vertex3f 0 0 0
    vertex3f 0 0.2 0
    vertex3f (-0.2) 0.2 0
    vertex3f (-0.2) 0 0
  flush

到目前为止我的理解:

  1. display是一个功能。 问题:什么是DisplayCallback?
  2. do表示计算的链接,与IO monads有关
  3. color3fvertex3f是本地函数,使用do关键字在let中定义了三个参数
  4. 我认为colorvertexglColor*glVertex*的openGL包装函数。
  5. 现在这让人感到困惑:

    • Color3Vertex3似乎是某种由三个参数组成的数据结构。 问题:为什么有必要在这里使用数据结构?为什么API的设计者选择在这里使用它?

    • color3f函数调用color函数并传入单个参数 - 数据结构Color3,数据为r g b。由于某种原因,此处的数据类型仅针对最后一个参数(b :: GLfloat)指定问题:为什么仅为最后一个参数指定数据类型,为什么在此处指定它?

    • 问题:为什么在调用color函数color $ Color3 r g (b :: GLfloat)时会使用美元符号?

    继续:

    1. clear是opengl glClear的包装器,并将列表作为参数,在这种情况下只包含一个元素[ColorBuffer]
    2. renderPrimitive似乎是opengl glBegin的包装函数,Quads似乎是数据类型。 问题:接下来会发生什么? $ do表示什么? (一系列计算?某事, IO monads?)。
    3. flushglFlush的包装。

2 个答案:

答案 0 :(得分:6)

1。 DisplayCallbackIO ()的类型同义词。您可以使用hoogle查找内容,或在ghci中输入:i DisplayCallback

IO ()被赋予特殊名称的原因是因为GLUT基于回调:函数被注册以处理特定事件,例如显示,输入事件,调整大小事件等。完整列表,请参阅docs。显然,您不需要类型同义词,但它们是为了清晰和更好的通信而提供的。

2。 “我认为颜色和顶点是glColor *和glVertex *的openGL包装函数。”不完全 - OpenGL是更基本的OpenGLRaw的包装器,它是c opengl库到haskell的1:1映射。 vertexcolor稍微复杂一点glColorglVertex,但您可以假设它们在大多数情况下都是相同的。

更具体地说,vertexVertex类的成员,其中有4个实例Vertex4Vertex3Vertex2

3。 Color3定义为data Color3 a = Color3 !a !a !aThe exclamation indicates that the fields are strict.。为什么这有必要?好吧,他们可以很容易地使用(a,a,a) -> IO ()a -> a -> a -> IO (),但是一个颜色的函数与采用矢量的函数无法区分,这是“意识形态上”不同的对象,即使它们由完全相同的表示数据(顶点为data Vertex3 a = Vertex3 !a !a !a)。因此,您不希望能够将顶点传递给需要颜色的函数。此外,由于严格性,这些数据类型在理想情况下提供了更好的性能。

4。为什么指定类型?简短的回答,是类型系统需要它。文字的类型是Num a => a,它对于数据类型来说过于多态,这需要具体的类型。因此,您使用类型注释选择所述具体类型。

为什么只在一个地方需要?编译器可以推断其他字段的类型。回顾数据类型decleration - 所有三个字段必须具有相同的类型。如果一个字段被注释,则其余字段被简单推断。您还可以编写Color3 r (g :: GLfloat) bColor3 (r :: GLfloat) g b或为函数vertex3f提供类型签名。

5。 $只是具有低优先级的函数应用程序,它被定义为f $ a = f a; infixr $ 0。你也可以写color (Color3 r g (b :: GLfloat))所以这里纯粹是一种风格问题。

6。 Perhaps the docs will again best explain what renderPrimitive is doing.但简短的版本就是这样:不是在glBegin - glEnd块内写东西,而是在renderPrimitive内写。您无需编写glEnd,因为它隐藏在renderPrimitive内。所以你的代码相当于:

glBegin (GL_QUADS); 
// All the stuff inside goes here ...
glEnd ();

为了将c中的异常正确地带入haskell宇宙,OpenGL有一些魔力,但你真的不必担心这一点。

最终评论:如果您打算将haskell用于图形,那么编写实际的openGL代码可能不是最好的主意。毕竟,如果你打算使用openGL,为什么不用c?那里有无数的图形库。您应该浏览hackage以获得适合您的包装。我担心我不能推荐任何东西,因为我不熟悉哪些包可用。

答案 1 :(得分:2)

我不知道有问题的实际图书馆,但我想我可以回答你的一些观点。

我希望DisplayCallback仅仅是一些更复杂的类型签名的别名。如果你打开GHCi并导入必要的模块,你应该可以说

:i DisplayCallback

它会告诉你它的含义。那么至少你会看到这个定义,即使这并不一定能告诉你

do符号不仅适用于IO monad,而适用于任何monad 。如果OpenGL绘图操作发生在他们自己的专用monad中,我不会感到惊讶。

为什么要指定GLFloat?我想Color3Vector3被定义为包含纵坐标的任何类型。我们希望它是GLFloat,因此是类型签名。为什么它仅在坐标的一个上?我认为Color3的定义要求所有纵坐标具有相同的类型,因此将其指定为一个会自动导致其他两个具有相同的类型。

为什么Color3甚至存在?为什么我们不能只用三个输入调用color?嗯,这是一个API设计选择,但如果Color3Vector3的定义允许您对整个矢量或颜色进行算术运算,我也不会感到惊讶。因此,通过将坐标放入向量中,您可以将它们视为一个单元,对它们进行算术运算,轻松地将它们存储在列表中等等。如果您正在完成所有这些操作,那么您实际上并不想要将它们解压缩所有这些都是为了实际调用OpenGL函数。

美元符号是什么?好吧,如果我写

color Color3 r g b

这意味着我正在调用color,传递四个参数:Color3rgb。那不是我们想要的。我们想要的是

color (Color3 r g b)

即,使用一个参数调用color。您可以使用括号执行此操作,也可以使用$代替。它有点像Unix管道,因为它可以让你转向

func3 (func2 (func1 x))

func3 $ func2 $ func1 $ x

如果你有 lot 的功能,那么获得正确数量的右括号会很烦人。对于一个函数调用,这是一个品味问题。

renderPrimitive函数正在使用两个参数。其中一个是Quads(无论是什么),另一个是整个do-block 。因此,您将所有代码作为参数传递给renderPrimitive函数(可能以某种方式执行它)。

希望这会给你一些启示。