为简化现实,我的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
,在该网格上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很好的回答之后,我来到了下面的代码。我在这里给出此解决方案,以便将答案更多地放在的上下文中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不太熟悉,因此在理解详细代码方面有些困难-如果我误解了一些内容,请纠正我。
I'd try to abstain from using unsafe functions or relying on INLINE
as much as possible. This usually makes code brittle and obscures more natural solutions.
In the simplest case, if you don't need to re-evaluate triangularize
, we could just replace it with its output. So we'd have
data Context = Context
{ contextRotation :: IORef Float,
, contextTriangles :: [Triangle]
}
and then
display :: Context -> DisplayCallback
which won't reevaluate triangles at all, they'll be computed only once when Context
is created.
Now if there are two parameters, rotation and level, and triangles depend on the level, but not on rotation: The trick here would be to manage dependencies properly. Now we expose the storage for parameters explicitly (IORef Float
), and as a consequence, we can't monitor when the value inside changes. But the caller doesn't need to know the representation of how the parameters are stored. It just needs to store them somehow. So instead, let's have
data Context = Context
{ contextRotation :: IORef Float,
, contextTriangles :: IORef [Triangle]
}
and
setLevel :: Context -> Float -> IO ()
That is, we expose a function to store the parameter, but we hide the internals. Now we can implement it as:
setLevel (Context _ trianglesRef) level = do
let newTriangles = ... -- compute the new triangles
writeIORef trianglesRef newTriangles
And as triangles don't depend on the rotation parameter, we can have just:
setRotation :: Context -> Float -> IO ()
setRoration (Context rotationRef _) = writeIORef rotationRef
现在,依赖关系对于调用者是隐藏的。他们可以设置水平或旋转,而无需知道什么取决于它们。同时,仅在需要时(级别更改)才更新三角形。Haskell的惰性评估提供了一个不错的好处:如果在需要三角形之前级别发生多次更改,则不会对其进行评估。该[Triangle]
内部的thunkIORef
会时要求只评估display
。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句