Search code examples
vb.netopenglmathmatrixopentk

Convert screen coordinate to world coordinate WITHOUT GLUUNPROJECT


I have been searching all over with Google and this site for an answer but I still get confused as to how one goes about converting the coordinates on the screen to the coordinate in model space. I do know the distance from the origin (0,0,0) to my camera, which is also the viewport's center:

enter image description here

If I have a different point on my view window such as (1,3) for example:

enter image description here

What is the best way to either mathematically calculate the new point in model view or to use matrix functions to determine it?

I apologize if this is considered "easy" for some people but I was hoping someone would post a good example using my sample coordinate since I am still rather confused.

Here is the matrix call that is done on every frame:

Public Sub SetupViewport()
    Dim w As Integer = GlControl1.Width
    Dim h As Integer = GlControl1.Height
    Dim perspective1 As Matrix4 = cam.GetViewMatrix() * Matrix4.CreatePerspectiveFieldOfView(1.3F, ClientSize.Width / CSng(ClientSize.Height), 0.1F, 200.0F)


    GL.MatrixMode(MatrixMode.Projection)
    GL.LoadIdentity()
    GL.LoadMatrix(perspective1)
    GL.Viewport(0, 0, w, h)

End Sub

This is done to allow me to rotate the camera using the cam matrix. I know generally in Open GL that you rotate the scene and not the camera but since I am using this as a CAD program, I am rotating my camera around the origin.

For testing purposes I have been constructing a line from what I calculate as the calculated start point, to the origin. As the title states, I do not wish to use gluunproject since I have read that this is based off of Tao framework so shouldn't be used.

My Draw Sub

Private Sub GlControl1_Paint(ByVal sender As System.Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles GlControl1.Paint

    GL.Clear(ClearBufferMask.ColorBufferBit)
    GL.Clear(ClearBufferMask.DepthBufferBit)
    GL.DepthMask(True)
    GL.Enable(EnableCap.DepthTest)
    GL.ClearDepth(1.0F)



    GL.MatrixMode(MatrixMode.Modelview)
    GL.LoadIdentity()


    Dim lightColor0 As Single() = {intensity, intensity, intensity, 1.0F}
    Dim lightPos0 As Single() = {camx, camy, camz, 1.0F}


    GL.Light(LightName.Light0, LightParameter.Diffuse, lightColor0)
    GL.Light(LightName.Light0, LightParameter.Position, lightPos0)
    GL.Enable(EnableCap.Light0)



    Dim mat_specular As Single() = {1.0F, 1.0F, 1.0F, 1.0F}
    Dim mat_shininess As Single() = {50.0F}


    GL.Material(MaterialFace.Front, MaterialParameter.Specular, mat_specular)
    GL.Material(MaterialFace.Front, MaterialParameter.Shininess, mat_shininess)


    GL.Disable(EnableCap.Lighting)

    GL.Begin(PrimitiveType.Lines)
    GL.Color3(Color.Red)
    GL.Vertex3(0, 0, 0)
    GL.Vertex3(100, 0, 0)

    GL.Color3(Color.Green)
    GL.Vertex3(0, 0, 0)
    GL.Vertex3(0, 100, 0)

    GL.Color3(Color.Blue)
    GL.Vertex3(0, 0, 0)
    GL.Vertex3(0, 0, 100)
    GL.End()


    GL.Begin(PrimitiveType.Lines)
    GL.Color3(Color.DarkRed)
    GL.Vertex3(0, 0, 0)
    GL.Vertex3(-100, 0, 0)

    GL.Color3(Color.DarkGreen)
    GL.Vertex3(0, 0, 0)
    GL.Vertex3(0, -100, 0)

    GL.Color3(Color.DarkBlue)
    GL.Vertex3(0, 0, 0)
    GL.Vertex3(0, 0, -100)
    GL.End()

    Dim projmatrix As Matrix4
    GL.GetFloat(GetPName.ProjectionMatrix, projmatrix)
    Dim mouse_ As New Vector2(_mouseStartX, _mouseStartY)
    Dim returnvec As Vector4

    returnvec = UnProject(projmatrix, cam.GetViewMatrix(), GlControl1.Size, mouse_)

    GL.Begin(PrimitiveType.Lines)
    GL.Color3(Color.Orange)
    GL.Vertex3(0, 0, 0)
    GL.Vertex3(returnvec.X, returnvec.Y, returnvec.Z)
    GL.End()


    GL.Enable(EnableCap.Lighting)

    draw_extras()


    GL.Flush()
    GlControl1.SwapBuffers()
End Sub

Solution

  • Thank you datenwolf for getting me down the right path. What I have done to solve my problem is I wrote an unproject function, with help of a few sites:

    Public Shared Function UnProject(ByRef projection As Matrix4, view As Matrix4, viewport As Size, mouse As Vector2) As Vector4
        Dim vec As Vector4
    
        vec.X = 2.0F * mouse.X / CSng(viewport.Width) - 1
        vec.Y = -(2.0F * mouse.Y / CSng(viewport.Height) - 1)
        vec.Z = 0
        vec.W = 1.0F
    
        Dim viewInv As Matrix4 = Matrix4.Invert(view)
        Dim projInv As Matrix4 = Matrix4.Invert(projection)
    
        Vector4.Transform(vec, projInv, vec)
        Vector4.Transform(vec, viewInv, vec)
    
        If vec.W > Single.Epsilon OrElse vec.W < Single.Epsilon Then
            vec.X /= vec.W
            vec.Y /= vec.W
            vec.Z /= vec.W
        End If
    
        Return vec
    End Function
    

    I have a mouse move event handler that simply captures the mouse location on the screen and saves the coordinates to two global variables.. Within my draw function I then call my new function and draw a line that extends from the origin to my mouse position:

    Dim projmatrix As Matrix4
        GL.GetFloat(GetPName.ProjectionMatrix, projmatrix)
        Dim modelmatrix As Matrix4
        GL.GetFloat(GetPName.ModelviewMatrix, modelmatrix)
    
        Dim mouse_ As New Vector2(_mouseStartX, _mouseStartY)
        Dim returnvec As Vector4
    
        returnvec = UnProject(projmatrix, modelmatrix, GlControl1.Size, mouse_)
    
        GL.Begin(PrimitiveType.Lines)
        GL.Color3(Color.Orange)
        GL.Vertex3(0, 0, 0)
        GL.Vertex3(returnvec.X, returnvec.Y, returnvec.Z)
        GL.End()
    

    I also had to change my setupviewport function:

    Public Sub SetupViewport()
        Dim w As Integer = GlControl1.Width
        Dim h As Integer = GlControl1.Height
    
        Dim perspective1 As Matrix4 = cam.GetViewMatrix() * Matrix4.CreatePerspectiveFieldOfView(1.3F, ClientSize.Width / CSng(ClientSize.Height), 0.1F, 200.0F)
    
    
        GL.MatrixMode(MatrixMode.Projection)
        GL.LoadIdentity()
        GL.LoadMatrix(perspective1)
        GL.MatrixMode(MatrixMode.Modelview)
        GL.LoadIdentity()
        GL.Viewport(0, 0, w, h)
        GL.Enable(EnableCap.DepthTest)
        GL.DepthFunc(DepthFunction.Less)
    
    End Sub
    

    The result is a line that has one side always on the origin and the other side always where the mouse is which is a fantastic example to show this working.

    enter image description here