繁体   English   中英

这个OpenGL Haskell代码如何工作?

[英]How does this OpenGL Haskell code work?

我正在通过直接进入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是使用let关键字在do定义的三个参数的本地函数
  4. 我假设colorvertexglColor*glVertex*的openGL包装函数。

现在这是令人困惑的地方:

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

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

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

继续:

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

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

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

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

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

3。 Color3定义为data Color3 a = Color3 !a !a !a 感叹号表明这些字段很严格。 为什么这有必要? 好吧,他们可以很容易地使用(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) gb或者为函数vertex3f提供类型签名。

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

6。 也许文档将再次最好地解释renderPrimitive正在做什么。 但是短版本是这样的:不是在glBegin - glEnd块中写入内容,而是在renderPrimitive写入它。 您不需要编写glEnd因为它隐藏在renderPrimitive 所以你的代码相当于:

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

OpenGL做了一些魔术,以便将c中的异常正确地引入haskell Universe,但你真的不必担心这一点。

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

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

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

:i DisplayCallback

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

do记号不只是为IO单子,这对任何单子 如果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

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

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

希望这会给你一些启示。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM