Haskell 从鼠标坐标到OpenGL世界坐标

Haskell 从鼠标坐标到OpenGL世界坐标,haskell,opengl,Haskell,Opengl,我使用下面的Haskell代码从鼠标坐标获得OpenGL世界坐标(代码有点长,但这是绘制立方体的最小代码)。我的期望是,最后给出的函数worldFromScreen返回鼠标坐标的世界坐标(以左上角的像素为单位)。多亏了我定义的键盘-鼠标回调,当用户右键单击鼠标时,这些假定的世界坐标会打印在终端上 代码在前视图中生成一个长度为1的立方体: 立方体的长度为1,且以原点为中心,因此该面的顶点的(x,y)坐标应为(+/-0.5,+/-0.5)。但是,当我右键单击该面的顶点时,我得到的坐标大约为(+/-

我使用下面的Haskell代码从鼠标坐标获得OpenGL世界坐标(代码有点长,但这是绘制立方体的最小代码)。我的期望是,最后给出的函数
worldFromScreen
返回鼠标坐标的世界坐标(以左上角的像素为单位)。多亏了我定义的键盘-鼠标回调,当用户右键单击鼠标时,这些假定的世界坐标会打印在终端上

代码在前视图中生成一个长度为1的立方体:

立方体的长度为1,且以原点为中心,因此该面的顶点的(x,y)坐标应为(+/-0.5,+/-0.5)。但是,当我右键单击该面的顶点时,我得到的坐标大约为(+/-0.2,+/-0.2)。这是什么0.2?是否可以通过更改代码中的某些内容来获得(+/-0.5,++/-0.5)

module Basic
  where
import           Graphics.Rendering.OpenGL
import           Graphics.UI.GLUT

white,black,red :: Color4 GLfloat
white      = Color4    1    1    1    1
black      = Color4    0    0    0    1
red        = Color4    1    0    0    1

display :: DisplayCallback
display = do
  clear [ColorBuffer, DepthBuffer]
  loadIdentity
  preservingMatrix $ do
    materialDiffuse Front $= red
    renderObject Solid $ Cube 1
  swapBuffers

resize :: Size -> IO ()
resize s@(Size w h) = do
  viewport $= (Position 0 0, s)
  matrixMode $= Projection
  loadIdentity
  perspective 45.0 (w'/h') 1.0 100.0
  lookAt (Vertex3 0 0 (-3)) (Vertex3 0 0 0) (Vector3 0 1 0)
  matrixMode $= Modelview 0
  where
    w' = realToFrac w
    h' = realToFrac h

keyboardMouse :: KeyboardMouseCallback
keyboardMouse key state _ position@(Position x y) =
  case (key,state) of
    (MouseButton LeftButton, Down) -> print (x,y)
    (MouseButton RightButton, Down) -> do
      (sx, sy, sz) <- worldFromScreen position
      print (sx, sy, sz)
    _                              -> return ()

main :: IO ()
main = do
  _ <- getArgsAndInitialize
  _ <- createWindow ""
  windowSize $= Size 500 500
  initialDisplayMode $= [RGBAMode, DoubleBuffered, WithDepthBuffer]
  clearColor $= black
  materialAmbient FrontAndBack $= black
  lighting $= Enabled
  light (Light 0) $= Enabled
  position (Light 0) $= Vertex4 200 200 (-500) 1
  ambient (Light 0) $= white
  diffuse (Light 0) $= white
  specular (Light 0) $= white
  depthFunc $= Just Less
  shadeModel $= Smooth
  displayCallback $= display
  reshapeCallback $= Just resize
  keyboardMouseCallback $= Just keyboardMouse
  idleCallback $= Nothing
  mainLoop

worldFromScreen :: Position -> IO (GLdouble, GLdouble, GLdouble)
worldFromScreen (Position sx sy) = do
  viewport@(_, Size _ viewSizeY) <- get viewport
  projectionMatrix <- get (matrix $ Just Projection) :: IO (GLmatrix GLdouble)
  modelviewMatrix <- get (matrix $ Just $ Modelview 0) :: IO (GLmatrix GLdouble)
  let screenPos = Vertex3 (fromIntegral sx) (fromIntegral ((viewSizeY - 1) - sy)) 0
  (Vertex3 wx wy wz) <- unProject screenPos projectionMatrix modelviewMatrix viewport
  return (wx, wy, wz)
基本模块
哪里
导入Graphics.Rendering.OpenGL
导入Graphics.UI.GLUT
白色、黑色、红色::彩色4 GLfloat
白色=彩色4 1
黑色=彩色4 0 0 1
红色=颜色41 0 0 1
display::DisplayCallback
显示=do
清除[颜色缓冲区,深度缓冲区]
负载标识
保存矩阵$do
材料扩散前$=红色
renderObject实体$Cube 1
天鹅垫
调整大小::大小->IO()
调整大小s@(大小w h)=do
视口$=(位置0,s)
矩阵模式$=投影
负载标识
透视图45.0(w'/h')1.0 100.0
注视(顶点3 0(-3))(顶点3 0 0 0)(向量3 0 1 0)
matrixMode$=Modelview 0
哪里
w'=realToFrac w
h'=realtofrach
键盘鼠标::键盘鼠标返回
键盘鼠标键状态@位置@(x y位置)=
案例(关键、状态)
(鼠标左键,向下)->打印(x,y)
(鼠标按钮右键,向下)->do
(sx、sy、sz)返回()
main::IO()
main=do

_我不知道Haskell,所以我无法提供有效的代码,但我想我理解你的问题

您有一个二维屏幕位置,可以将其取消投影到三维并获得一个点。该点是世界空间中摄影机近平面上的一个位置。这就是你的“0.2”。现在您还没有指定z值,但我可以计算它大约为
-1.8

当你们点击角点时,你们会得到(+/-0.2,++/-0.2)一个立方体,它的世界长度是1,所以角点应该是(+/-0.5,++/-0.5)

请注意,你们得到的是世界上相机近平面上的一个点 空间,它不是立方体上的点

该点可与摄影机位置一起使用以生成光线,然后将该光线与平面相交以获得该平面上的真实位置

根据你的代码

lookAt (Vertex3 0 0 (-3)) (Vertex3 0 0 0) (Vector3 0 1 0)
摄像机处于
(0,0,-3)

如果我们考虑立方体的右上角,假设你得到的3D点是<代码>(0.2,0.2,1.2)< /代码>。使用这两个点,可以将相机作为原点,将3D点

(0.2,0.2,-1.8)
作为第二个点来构造光线

Ray( (0.0, 0.0, -3.0), (0.2, 0.2, -1.8) )
然后,该光线可以与立方体所在的平面相交,以获得要查找的点。由于立方体位于原点的中心,并且您正直视它,因此平面可以由法线
(0,0,1)
和原点
(0,0,0)
定义

现在,如果光线与平面相交,我们得到:

(0.5,0.5,0.0)

大概就是你要找的那一点

PS:我使用此链接“”快速测试光线/平面交点


希望这有帮助。

屏幕坐标是二维坐标。要获得3D世界坐标,你需要第三个坐标。是的,Ripi,但我只需要(x,y)坐标。看起来我得到的x或y世界坐标是
0.5/(h-0.5)
,其中
h
是相机位置的z坐标。我观察到,对于不同的
h
,如果投影是正交的,那么任何z值都可能是后缀。对于透视投影,这是不正确的。画一个熔丝,用相机连接点,看看它们在近平面上的相交点。谢谢@Ripi2,我开始理解了。我将尝试使用正交投影(我还不知道它是什么,但我想它是正交投影?我稍后再看,我现在很忙)。谢谢。我刚刚醒来,我不知道什么都明白,但我明白了其中的道理。您是如何获得
1.2
的?@StéphaneLaurent,很抱歉出现了错误,我输入了矢量而不是点值(现在已更正为
-1.8
)。我将剩余的已知数字插入到直线/平面交点、摄影机(
0,0,-3
)和未投影点(
0.2,0.2,z
)的链接表单中,对于z=-1.8,交点是(
0.5,0.5,0.0
),这是我认为在世界空间原点的X-Y平面上的预期点。
Plane(Normal (0.0, 0.0, 1.0), Point (0.0, 0.0, 0.0)).