为简化现实,我的OpenGL程序具有以下结构:
f : (Double,Double,Double) -> Double
。 triangulize :: ((Double,Double,Double) -> Double) -> [Triangle]
,以便triangulize f
计算表面f(x,y,z)=0
的三角形网格。 displayCallback
,这是一个display :: IORef Float -> DisplayCallBack
函数,用于显示图形(也就是说,它显示三角形网格)。这里的第一个参数IORef Float
用于旋转图形,由于稍后定义的keyboardCallback
,当用户按下键盘上的某个键时,其值(旋转角度)会更改。别忘了display
函数调用triangulize f
。 display
函数被触发。然后重新评估triangulize f
,而无需重新评估:旋转图形不会更改三角形网格(即triangulize f
的结果与之前相同)。 因此,有没有一种方法可以通过按一个键来旋转图形而不触发
triangulize f
?换句话说,要“冻结” triangulize f
,使其仅被评估一次,而不会被重新评估,这很耗时,但是却没有用,因为无论如何结果都是一样的。我相信这是在Haskell OpenGL中旋转图形的标准方法(我在某些tuto中以这种方式查看),因此我认为不必发布我的代码。但是我当然可以在需要时发布它。
由于存在其他
IORef
来控制表面的某些参数,因此实际情况更加复杂。但是,我首先想知道一些针对这种简化情况的解决方案。编辑:更多详细信息和一些代码
简化代码
所以,如果我按照上面的简化描述,我的程序看起来像
fBretzel5 :: (Double,Double,Double) -> Double
fBretzel5 (x,y,z) = ((x*x+y*y/4-1)*(x*x/4+y*y-1))^2 + z*z
triangles :: [Triangle] -- Triangle: triplet of 3 vertices
triangles =
triangulize fBretzel5 ((-2.5,2.5),(-2.5,2.5),(-0.5,0.5))
-- "triangulize f (xbounds, ybounds, zbounds)"
-- calculates a triangular mesh of the surface f(x,y,z)=0
display :: IORef Float -> DisplayCallback
display rot = do
clear [ColorBuffer, DepthBuffer]
rot' <- get rot
loadIdentity
rotate rot $ Vector3 1 0 0
renderPrimitive Triangles $ do
materialDiffuse FrontAndBack $= red
mapM_ drawTriangle triangles
swapBuffers
where
drawTriangle (v1,v2,v3) = do
triangleNormal (v1,v2,v3) -- the normal of the triangle
vertex v1
vertex v2
vertex v3
keyboard :: IORef Float -- rotation angle
-> KeyboardCallback
keyboard rot c _ = do
case c of
'e' -> rot $~! subtract 2
'r' -> rot $~! (+ 2)
'q' -> leaveMainLoop
_ -> return ()
postRedisplay Nothing
这会导致上述问题。每次按下
'e'
或'r'
键时,triangulize
函数将运行,而其输出保持不变。真实代码(几乎)
现在,这是我的程序最接近实际的版本。实际上,它会为表面
f(x,y,z)=l
计算一个三角形网格,在该网格中可以使用键盘更改“isolevel” l
。voxel :: IO Voxel
voxel = makeVoxel fBretzel5 ((-2.5,2.5),(-2.5,2.5),(-0.5,0.5))
-- the voxel is a 3D-array of points; each entry of the array is
-- the value of the function at this point
-- !! the voxel should never changes throughout the program !!
trianglesBretz :: Double -> IO [Triangle]
trianglesBretz level = do
vxl <- voxel
computeContour3d vxl level
-- "computeContour3d vxl level" calculates a triangular mesh
-- of the surface f(x,y,z)=level
display :: IORef Float -> IORef Float -> DisplayCallback
display rot level = do
clear [ColorBuffer, DepthBuffer]
rot' <- get rot
level' <- get level
triangles <- trianglesBretz level'
loadIdentity
rotate rot $ Vector3 1 0 0
renderPrimitive Triangles $ do
materialDiffuse FrontAndBack $= red
mapM_ drawTriangle triangles
swapBuffers
where
drawTriangle (v1,v2,v3) = do
triangleNormal (v1,v2,v3) -- the normal of the triangle
vertex v1
vertex v2
vertex v3
keyboard :: IORef Float -- rotation angle
-> IORef Double -- isolevel
-> KeyboardCallback
keyboard rot level c _ = do
case c of
'e' -> rot $~! subtract 2
'r' -> rot $~! (+ 2)
'h' -> level $~! (+ 0.1)
'n' -> level $~! subtract 0.1
'q' -> leaveMainLoop
_ -> return ()
postRedisplay Nothing
解决方案的一部分
实际上,我已经找到一种解决方案以“冻结”体素:
voxel :: Voxel
{-# NOINLINE voxel #-}
voxel = unsafePerformIO $ makeVoxel fBretzel5 ((-2.5,2.5),(-2.5,2.5),(-0.5,0.5))
trianglesBretz :: Double -> IO [Triangle]
trianglesBretz level =
computeContour3d voxel level
这样,我认为
voxel
永远不会重新评估。但是仍然存在问题。当
IORef
rot
更改时,要旋转图形,则没有理由重新评估trianglesBretz
:无论旋转什么,f(x,y,z)=level
的三角形网格始终是相同的。因此,我如何对
display
函数说:“嘿!当rot
更改时,不要重新评估trianglesBretz
,因为您会发现相同的结果”?我不知道如何像
NOINLINE
一样将trianglesBretz
用于voxel
。除非trianglesBretz level
更改,否则将“冻结” level
的内容。这是5孔椒盐脆饼:
编辑:基于@PetrPudlák的答案的解决方案。
在@PetrPudlák很好的回答之后,我来到了下面的代码。我在这里给出此解决方案,以便将答案更多地放在
OpenGL
的上下文中。data Context = Context
{
contextRotation :: IORef Float
, contextTriangles :: IORef [Triangle]
}
red :: Color4 GLfloat
red = Color4 1 0 0 1
fBretz :: XYZ -> Double
fBretz (x,y,z) = ((x2+y2/4-1)*(x2/4+y2-1))^2 + z*z
where
x2 = x*x
y2 = y*y
voxel :: Voxel
{-# NOINLINE voxel #-}
voxel = unsafePerformIO $ makeVoxel fBretz ((-2.5,2.5),(-2.5,2.5),(-1,1))
trianglesBretz :: Double -> IO [Triangle]
trianglesBretz level = computeContour3d voxel level
display :: Context -> DisplayCallback
display context = do
clear [ColorBuffer, DepthBuffer]
rot <- get (contextRotation context)
triangles <- get (contextTriangles context)
loadIdentity
rotate rot $ Vector3 1 0 0
renderPrimitive Triangles $ do
materialDiffuse FrontAndBack $= red
mapM_ drawTriangle triangles
swapBuffers
where
drawTriangle (v1,v2,v3) = do
triangleNormal (v1,v2,v3) -- the normal of the triangle
vertex v1
vertex v2
vertex v3
keyboard :: IORef Float -- rotation angle
-> IORef Double -- isolevel
-> IORef [Triangle] -- triangular mesh
-> KeyboardCallback
keyboard rot level trianglesRef c _ = do
case c of
'e' -> rot $~! subtract 2
'r' -> rot $~! (+ 2)
'h' -> do
l $~! (+ 0.1)
l' <- get l
triangles <- trianglesBretz l'
writeIORef trianglesRef triangles
'n' -> do
l $~! (- 0.1)
l' <- get l
triangles <- trianglesBretz l'
writeIORef trianglesRef triangles
'q' -> leaveMainLoop
_ -> return ()
postRedisplay Nothing
main :: IO ()
main = do
_ <- getArgsAndInitialize
_ <- createWindow "Bretzel"
windowSize $= Size 500 500
initialDisplayMode $= [RGBAMode, DoubleBuffered, WithDepthBuffer]
clearColor $= white
materialAmbient FrontAndBack $= black
lighting $= Enabled
lightModelTwoSide $= Enabled
light (Light 0) $= Enabled
position (Light 0) $= Vertex4 0 0 (-100) 1
ambient (Light 0) $= black
diffuse (Light 0) $= white
specular (Light 0) $= white
depthFunc $= Just Less
shadeModel $= Smooth
rot <- newIORef 0.0
level <- newIORef 0.1
triangles <- trianglesBretz 0.1
trianglesRef <- newIORef triangles
displayCallback $= display Context {contextRotation = rot,
contextTriangles = trianglesRef}
reshapeCallback $= Just yourReshapeCallback
keyboardCallback $= Just (keyboard rot level trianglesRef)
idleCallback $= Nothing
putStrLn "*** Bretzel ***\n\
\ To quit, press q.\n\
\ Scene rotation:\n\
\ e, r, t, y, u, i\n\
\ Increase/Decrease level: h, n\n\
\"
mainLoop
现在,无需执行无用的计算,即可旋转我的bretzel。
最佳答案
我对OpenGL不太熟悉,因此在详细了解代码方面有些困难-如果我误解了一些内容,请纠正我。
我会尽量避免使用不安全的函数或尽可能多地依赖INLINE
。这通常会使代码变脆,并掩盖更自然的解决方案。
在最简单的情况下,如果您不需要重新评估triangularize
,我们可以将其替换为输出。所以我们有
data Context = Context
{ contextRotation :: IORef Float,
, contextTriangles :: [Triangle]
}
接着
display :: Context -> DisplayCallback
根本不会重新评估三角形,创建
Context
时,它们只会被计算一次。现在,如果有两个参数,旋转和水平,则三角形取决于水平,而不取决于旋转:这里的技巧是正确管理依赖关系。现在,我们显式公开了参数的存储(
IORef Float
),因此,我们无法监视内部值何时更改。但是,调用者不需要知道如何存储参数的表示形式。它只需要以某种方式存储它们。因此,让我们有data Context = Context
{ contextRotation :: IORef Float,
, contextTriangles :: IORef [Triangle]
}
和
setLevel :: Context -> Float -> IO ()
也就是说,我们公开了一个存储参数的函数,但隐藏了内部结构。现在我们可以将其实现为:
setLevel (Context _ trianglesRef) level = do
let newTriangles = ... -- compute the new triangles
writeIORef trianglesRef newTriangles
由于三角形不取决于旋转参数,因此我们可以得到:
setRotation :: Context -> Float -> IO ()
setRoration (Context rotationRef _) = writeIORef rotationRef
现在,依赖关系对于调用者是隐藏的。他们可以设置水平或旋转,而无需知道取决于它们的水平。同时,仅在需要时(级别更改)才更新三角形。 Haskell的惰性评估提供了一个不错的好处:如果在需要三角形之前水平发生多次更改,则不会对其进行评估。仅当
[Triangle]
请求时,才会评估IORef
内部的display
thunk。
关于haskell - 如何在Haskell中旋转OpenGL图形而又无用地重新评估图形对象?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49827020/