[英]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
到目前为止我的理解:
display
是一种功能。 问题:什么是DisplayCallback? do
表示计算的链接,与IO monads有关 color3f
和vertex3f
是使用let
关键字在do
定义的三个参数的本地函数 color
和vertex
是glColor*
和glVertex*
的openGL包装函数。 现在这是令人困惑的地方:
Color3
和Vertex3
似乎是由三个参数组成的某种数据结构。 问题:为什么有必要在这里使用数据结构? 为什么API的设计者选择在这里使用它?
color3f
函数调用color
函数并传入单个参数 - 数据结构Color3
,数据为rg b。 由于某种原因,此处的数据类型仅为最后一个参数指定(b :: GLfloat)
问题:为什么仅为最后一个参数指定数据类型,为什么在此处指定它?
问题:为什么在调用color
函数color $ Color3 rg (b :: GLfloat)
时使用美元符号?
继续:
clear
是opengl glClear
包装器,并将列表作为参数,在这种情况下只包含一个元素[ColorBuffer]
。 renderPrimitive
似乎是opengl glBegin
包装函数, Quads
似乎是数据类型。 问题:接下来会发生什么? $ do
表示什么? (计算系列? 某事, IO monads?)。 flush
是glFlush
包装器。 1。 DisplayCallback
是IO ()
的类型同义词。 您可以使用hoogle查找内容,或在ghci中键入:i DisplayCallback
。
IO ()
被赋予特殊名称的原因是因为GLUT
基于回调:注册函数以处理特定事件,例如显示,输入事件,调整大小事件等。有关完整列表,请参阅文档 。 显然,您不需要类型同义词,但它们是为了清晰和更好的通信而提供的。
2。 “我认为颜色和顶点是glColor *和glVertex *的openGL包装函数。” 不完全 - OpenGL
是更基本的OpenGLRaw
的包装器,它是c opengl库到haskell的1:1映射。 vertex
和color
稍微复杂一点glColor
和glVertex
,但你可以假设大多数用途它们是相同的。
更具体地说, vertex
是Vertex
类的成员,它有4个实例Vertex4
, Vertex3
和Vertex2
。
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) b
或Color3 (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
? 我想Color3
和Vector3
被定义为包含任何类型的纵坐标。 我们希望它是GLFloat
,因此是类型签名。 为什么它只在一个坐标上? 我认为Color3
的定义要求所有纵坐标都具有相同的类型,因此将其指定为一个会自动导致其他两个具有相同的类型。
为什么Color3
甚至都存在? 为什么我们不能只用三个输入调用color
? 嗯,这是一个API设计选择,但如果Color3
和Vector3
的定义允许您对整个矢量或颜色进行算术运算,我也不会感到惊讶。 因此,通过将坐标放入向量中,您可以将它们视为一个单元,对它们进行算术运算,轻松地将它们存储在列表中等等。如果您正在完成所有这些操作,那么您实际上并不想要将它们解压缩所有这些都是为了实际调用OpenGL函数。
美元符号是什么? 好吧,如果我写
color Color3 r g b
这意味着我正在调用color
,传递四个参数: Color3
, r
, g
和b
。 那不是我们想要的。 我们想要的是什么
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.