简体   繁体   English

这个OpenGL Haskell代码如何工作?

[英]How does this OpenGL Haskell code work?

I'm learning Haskell by jumping straight into OpenGL and I can't seem to decipher this code: 我正在通过直接进入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

My understanding so far: 到目前为止我的理解:

  1. display is a function. display是一种功能。 QUESTION: What is DisplayCallback? 问题:什么是DisplayCallback?
  2. do indicates chaining of computations, something to do with IO monads do表示计算的链接,与IO monads有关
  3. color3f and vertex3f are local functions with three arguments defined within do using the let keyword color3fvertex3f是使用let关键字在do定义的三个参数的本地函数
  4. I assume that color and vertex is an openGL wrapper function for glColor* and glVertex* . 我假设colorvertexglColor*glVertex*的openGL包装函数。

Now this is where it gets confusing: 现在这是令人困惑的地方:

  • Color3 and Vertex3 appears to be some sort of data structure consisting of three arguments. Color3Vertex3似乎是由三个参数组成的某种数据结构。 QUESTION: Why is it necessary to use a data structure here? 问题:为什么有必要在这里使用数据结构? Why the designers of the API chose to use it here? 为什么API的设计者选择在这里使用它?

  • color3f function calls color function and passes in single argument - data structure Color3 with data rg b. color3f函数调用color函数并传入单个参数 - 数据结构Color3 ,数据为rg b。 For some reason data type here is specified for only the last argument (b :: GLfloat) QUESTION: Why is the data type specified only for the last argument, why is it specified here at all? 由于某种原因,此处的数据类型仅为最后一个参数指定(b :: GLfloat) 问题:为什么仅为最后一个参数指定数据类型,为什么在此处指定它?

  • QUESTION: Why is the dollar sign used when calling the color function color $ Color3 rg (b :: GLfloat) ? 问题:为什么在调用color函数color $ Color3 rg (b :: GLfloat)时使用美元符号?

Continuing on: 继续:

  1. clear is wrapper for opengl glClear and takes as an argument a list, in this case containing only a single element [ColorBuffer] . clear是opengl glClear包装器,并将列表作为参数,在这种情况下只包含一个元素[ColorBuffer]
  2. renderPrimitive appears to be wrapper function for opengl glBegin , Quads appears to be datatype. renderPrimitive似乎是opengl glBegin包装函数, Quads似乎是数据类型。 QUESTION: What happens next? 问题:接下来会发生什么? What does $ do indicate? $ do表示什么? (series of computations? something, something IO monads?). (计算系列? 某事, IO monads?)。
  3. flush is wrapper for glFlush . flushglFlush包装器。

1 . 1。 DisplayCallback is a type synonym for IO () . DisplayCallbackIO ()的类型同义词。 You can use hoogle to look things up, or type :i DisplayCallback in ghci. 您可以使用hoogle查找内容,或在ghci中键入:i DisplayCallback

The reason that IO () is given a special name is because GLUT is based on callbacks: functions are registered to handle a specific event, such as displaying, input events, resize events, etc. For the full list, see the docs . IO ()被赋予特殊名称的原因是因为GLUT基于回调:注册函数以处理特定事件,例如显示,输入事件,调整大小事件等。有关完整列表,请参阅文档 You don't need the type synonyms, obviously, but they are provided for clarity and better communication. 显然,您不需要类型同义词,但它们是为了清晰和更好的通信而提供的。

2 . 2。 "I assume that color and vertex is an openGL wrapper function for glColor* and glVertex*." “我认为颜色和顶点是glColor *和glVertex *的openGL包装函数。” Not quite - OpenGL is a wrapper around the more basic OpenGLRaw , which is a 1:1 mapping of the c opengl library to haskell. 不完全 - OpenGL是更基本的OpenGLRaw的包装器,它是c opengl库到haskell的1:1映射。 vertex and color are slightly more sophisticated that glColor and glVertex , but you can probably assume for most uses they are the same. vertexcolor稍微复杂一点glColorglVertex ,但你可以假设大多数用途它们是相同的。

More specifically, vertex is a member of the Vertex class which has 4 instances Vertex4 , Vertex3 , and Vertex2 . 更具体地说, vertexVertex类的成员,它有4个实例Vertex4Vertex3Vertex2

3 . 3。 Color3 is defined as data Color3 a = Color3 !a !a !a . Color3定义为data Color3 a = Color3 !a !a !a The exclamation indicates that the fields are strict. 感叹号表明这些字段很严格。 . Why is this necessary? 为什么这有必要? Well, they could have easily used (a,a,a) -> IO () or a -> a -> a -> IO () but then a function taking a color is indistinguishable from one taking a vector, which are "ideologically" different objects, even if they are represented by precisely the same data (Vertex is data Vertex3 a = Vertex3 !a !a !a ). 好吧,他们可以很容易地使用(a,a,a) -> IO ()a -> a -> a -> IO ()但是接受颜色的函数与采用矢量的函数无法区分,这是“在意识形态上“不同的对象,即使它们由完全相同的数据表示(顶点是data Vertex3 a = Vertex3 !a !a !a )。 So you don't want to be able to pass a vertex to a function requiring a color. 因此,您不希望能够将顶点传递给需要颜色的函数。 Also, these datatypes give better performance, ideally, due to the strictness. 此外,由于严格性,这些数据类型在理想情况下提供了更好的性能。

4 . 4。 Why is the type specified at all? 为什么指定类型? Short answer, is the type system requires it. 简短的回答,是类型系统需要它。 The type of a literal is Num a => a which is too polymorphic for the datatype, which requires a concrete type. 文字的类型是Num a => a ,它对于数据类型来说过于多态,这需要具体的类型。 So you pick said concrete type by using a type annotation. 因此,您使用类型注释选择所述具体类型。

Why is it only required in one place? 为什么只在一个地方需要? The compiler can infer the type of the other fields. 编译器可以推断其他字段的类型。 Look back to the data type decleration - all three fields must have the same type. 回顾数据类型decleration - 所有三个字段必须具有相同的类型。 If one field is annotated, the rest are trivially inferred. 如果一个字段被注释,则其余字段被简单推断。 You can also write Color3 r (g :: GLfloat) b or Color3 (r :: GLfloat) gb or give the function vertex3f a type signature. 您还可以编写Color3 r (g :: GLfloat) bColor3 (r :: GLfloat) gb或者为函数vertex3f提供类型签名。

5 . 5。 The $ is just function application with low precedence, it is defined as f $ a = fa; infixr $ 0 $只是具有低优先级的函数应用程序,它被定义为f $ a = fa; infixr $ 0 f $ a = fa; infixr $ 0 . f $ a = fa; infixr $ 0 You can also write color (Color3 rg (b :: GLfloat)) so it is purely a matter of style here. 你也可以写color (Color3 rg (b :: GLfloat))所以这里纯粹是一种风格问题。

6 . 6。 Perhaps the docs will again best explain what renderPrimitive is doing. 也许文档将再次最好地解释renderPrimitive正在做什么。 But the short version is this: instead of writing things inside of a glBegin - glEnd block, you write it inside of renderPrimitive . 但是短版本是这样的:不是在glBegin - glEnd块中写入内容,而是在renderPrimitive写入它。 You don't need to write glEnd because this is hidden inside of renderPrimitive . 您不需要编写glEnd因为它隐藏在renderPrimitive So your code is equivalent to: 所以你的代码相当于:

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

The OpenGL does some magic in order to bring exceptions from c into the haskell universe properly, but you don't really have to worry about that. OpenGL做了一些魔术,以便将c中的异常正确地引入haskell Universe,但你真的不必担心这一点。

Final comment: If you plan to use haskell for graphics, the writing actual openGL code is probably not the best idea. 最后评论:如果您计划将haskell用于图形,那么编写实际的openGL代码可能不是最好的主意。 After all, if you are going to use openGL, why not just use c? 毕竟,如果你打算使用openGL,为什么不用c? There are a whole myriad of graphics libraries out there. 那里有无数的图形库。 You should browse hackage for a package that is right for you. 您应该浏览hackage以获得适合您的包装。 I'm afraid I can't recommend anything as I'm not familiar with what packages are available. 我担心我不能推荐任何东西,因为我不熟悉哪些包可用。

I don't know the actual library in question, but I think I can answer a few of your points. 我不知道有问题的实际图书馆,但我想我可以回答你的一些观点。

I would expect that DisplayCallback is merely an alias to some more complicated type signature. 我希望DisplayCallback只是一些更复杂的类型签名的别名。 If you open up GHCi and import the necessary modules, you should be able to say 如果你打开GHCi并导入必要的模块,你应该可以说

:i DisplayCallback

and it'll tell you what it refers to. 它会告诉你它的含义。 Then at least you'll see the definition, even if that doesn't necessarily tell you what it's for . 那么至少你会看到这个定义,即使这并不一定能告诉你它的用途

The do notation isn't just for the IO monad, it's for any monad . do记号不只是为IO单子,这对任何单子 I wouldn't be surprised if OpenGL drawing operations happen in their own specialised monad. 如果OpenGL绘图操作发生在他们自己的专用monad中,我不会感到惊讶。

Why is GLFloat specified? 为什么要指定GLFloat I imagine that Color3 and Vector3 are defined to hold any type of ordinate. 我想Color3Vector3被定义为包含任何类型的纵坐标。 We want it to be GLFloat , hence the type signature. 我们希望它是GLFloat ,因此是类型签名。 Why is it only on one of the coordinates? 为什么它只在一个坐标上? I would imagine that the definition of Color3 requires all ordinates to have the same type, so specifying it for one automatically causes the other two to have the same type. 我认为Color3的定义要求所有纵坐标都具有相同的类型,因此将其指定为一个会自动导致其他两个具有相同的类型。

Why does Color3 even exist at all? 为什么Color3甚至都存在? Why can't we just call color with three inputs? 为什么我们不能只用三个输入调用color Well, that's an API design choice, but again I wouldn't be surprised if the definition of Color3 and Vector3 allows you to do arithmetic on entire vectors or colors. 嗯,这是一个API设计选择,但如果Color3Vector3的定义允许您对整个矢量或颜色进行算术运算,我也不会感到惊讶。 So by putting coordinates into a vector, you get to treat them as a single unit, do arithmetic on them, easily store them in lists, etc. If you're doing all that, you don't really want to have to unpack them all again in order to actually call OpenGL functions. 因此,通过将坐标放入向量中,您可以将它们视为一个单元,对它们进行算术运算,轻松地将它们存储在列表中等等。如果您正在完成所有这些操作,那么您实际上并不想要将它们解压缩所有这些都是为了实际调用OpenGL函数。

What is the dollar sign for? 美元符号是什么? Well, if I write 好吧,如果我写

color Color3 r g b

this means that I'm calling color , passing it four arguments: Color3 , r , g and b . 这意味着我正在调用color ,传递四个参数: Color3rgb That isn't what we want. 那不是我们想要的。 What we want is 我们想要的是什么

color (Color3 r g b)

That is, calling color with one argument. 也就是说,用一个参数调用color You can do that using brackets, or you can use $ instead. 您可以使用括号来执行此操作,也可以使用$代替。 It's a bit like the Unix pipe, in that it lets you turn 它有点像Unix管道,因为它可以让你转向

func3 (func2 (func1 x))

into

func3 $ func2 $ func1 $ x

If you have a lot of functions, it can be annoying to get the right number of closing brackets. 如果你有很多功能,那么获得正确数量的右括号会很烦人。 For just one function call, it's a matter of taste. 对于一个函数调用,这是一个品味问题。

The renderPrimitive function is taking two arguments. renderPrimitive函数renderPrimitive 两个参数。 One of them is Quads (whatever that is), and the other is the entire do-block . 其中一个是Quads (无论是什么),另一个是整个do-block So you're passing all of that code as an argument to the renderPrimitive function (which presumably executes it somehow). 因此,您将所有代码作为参数传递给renderPrimitive函数(可能以某种方式执行它)。

Hopefully this gives you some enlightenment. 希望这会给你一些启示。

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

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