OpenGL - 为什么我的光线拾取不起作用?

时间:2016-03-01 00:02:36

标签: opengl 3d opentk

我最近设置了一个使用OpenGL(通过C#Wrapper Library OpenTK)的项目,该项目应该执行以下操作:

  1. 创建透视投影相机 - 此相机将用于让用户旋转,移动等以查看我的3D模型。
  2. 绘制一些3d对象。
  3. 通过unproject使用3D光线拾取,让用户在3d视图中选择点/模型。
  4. 最后一步(光线拾取)在我的3D预览(GLControl)上看起来没问题,但返回无效结果,如Vector3d(1,86460186949617; -45,4086124979203; -45,0387025610247)。我不知道为什么会这样!

    我使用以下代码设置我的视口:

            this.RenderingControl.MakeCurrent();
    
            int w = RenderingControl.Width;
            int h = RenderingControl.Height;
    
            // Use all of the glControl painting area
            GL.Viewport(0, 0, w, h);
    
            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadIdentity();
            Matrix4 p = Matrix4.CreatePerspectiveFieldOfView(MathHelper.PiOver4, w / (float)h, 0.1f, 64.0f);
            GL.LoadMatrix(ref p);
    

    我使用此方法进行反投影:

        /// <summary>
        /// This methods maps screen coordinates to viewport coordinates.
        /// </summary>
        /// <param name="screen"></param>
        /// <param name="view"></param>
        /// <param name="projection"></param>
        /// <param name="view_port"></param>
        /// <returns></returns>
        private Vector3d UnProject(Vector3d screen, Matrix4d view, Matrix4d projection, int[] view_port)
        {
            Vector4d pos = new Vector4d();
    
            // Map x and y from window coordinates, map to range -1 to 1 
            pos.X = (screen.X - (float)view_port[0]) / (float)view_port[2] * 2.0f - 1.0f;
            pos.Y = (screen.Y - (float)view_port[1]) / (float)view_port[3] * 2.0f - 1.0f;
            pos.Z = screen.Z * 2.0f - 1.0f;
            pos.W = 1.0f;
    
            Vector4d pos2 = Vector4d.Transform(pos, Matrix4d.Invert(Matrix4d.Mult(view, projection)));
            Vector3d pos_out = new Vector3d(pos2.X, pos2.Y, pos2.Z);
    
            return pos_out / pos2.W;
        }
    

    我使用此代码定位我的相机(包括旋转)并进行光线拾取:

            // Clear buffers
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
            // Apply camera
            GL.MatrixMode(MatrixMode.Modelview);
    
            Matrix4d mv = Matrix4d.LookAt(EyePosition, Vector3d.Zero, Vector3d.UnitY);
            GL.LoadMatrix(ref mv);
    
            GL.Translate(0, 0, ZoomFactor);
    
            // Rotation animation
            if (RotationAnimationActive)
            {
                CameraRotX += 0.05f;
            }
    
            if (CameraRotX >= 360)
            {
                CameraRotX = 0;
            }
    
            GL.Rotate(CameraRotX, Vector3.UnitY);
            GL.Rotate(CameraRotY, Vector3.UnitX);
    
            // Apply useful rotation
            GL.Rotate(50, 90, 30, 0f);
    
            // Draw Axes
            drawAxes();
    
            // Draw vertices of my 3d objects ...
    
            // Picking Test
            int x = MouseX;
            int y = MouseY;
    
            int[] viewport = new int[4];
            Matrix4d modelviewMatrix, projectionMatrix;
            GL.GetDouble(GetPName.ModelviewMatrix, out modelviewMatrix);
            GL.GetDouble(GetPName.ProjectionMatrix, out projectionMatrix);
            GL.GetInteger(GetPName.Viewport, viewport);
    
            // get depth of clicked pixel
            float[] t = new float[1];
            GL.ReadPixels(x, RenderingControl.Height - y, 1, 1, OpenTK.Graphics.OpenGL.PixelFormat.DepthComponent, PixelType.Float, t);
    
            var res = UnProject(new Vector3d(x, viewport[3] - y, t[0]), modelviewMatrix, projectionMatrix, viewport);
    
            GL.Begin(BeginMode.Lines);
            GL.Color3(Color.Yellow);
    
            GL.Vertex3(0, 0, 0);
    
            GL.Vertex3(res);
    
            Debug.WriteLine(res.ToString());
            GL.End();
    

    我从我的光线选择器中得到以下结果:

    • Clicked Position =(1,86460186949617; -45,4086124979203; -45,0387025610247)

    此向量在附加的屏幕截图中显示为黄线。

    为什么Y和Z位置不在-1 / + 1的范围内?这些值如-45来自何处以及为什么在屏幕上正确渲染光线?

    如果你只有一个关于什么可能被打破的提示我也很感谢你的回复!

    截图:

    Screenshot

1 个答案:

答案 0 :(得分:2)

如果您将从屏幕到世界的变换分解为单个矩阵,则打印出M,V和P矩阵的反转,并从屏幕打印出每个(矩阵逆)*(点)计算的中间结果对于世界/模型,那么我认为你会看到问题。或者至少你会发现使用M-V-P矩阵的逆矩阵然后直观地掌握解决方案存在问题。或者只是阅读下面的步骤列表,看看是否有帮助。

这是我用过的方法:

  1. 将屏幕/控件/窗口小部件坐标中鼠标位置的2D矢量转换为4D矢量(mouse.x,mouse.y,0,1)。
  2. 将4D矢量从屏幕坐标转换为规范化设备坐标(NDC)空间。也就是说,将NDC到屏幕矩阵[或等效方程]的倒数乘以(mouse.x,mouse.y,0,1),得到NDC坐标空间中的4D向量:(nx,ny,0, 1)。
  3. 在NDC坐标中,定义两个4D向量:光线的源(近点)为(nx,ny,-1,1),远点为(nx,ny,+ 1,1)。
  4. 将每个4D向量乘以(透视)投影矩阵的倒数。
  5. 将每个4D矢量转换为3D矢量(即通过第四个分量,通常称为“w”)。 *
  6. 3D 向量乘以视图矩阵的倒数。
  7. 将3D矢量乘以模型矩阵的逆矩阵(可能是单位矩阵)。
  8. 减去3D矢量以产生光线。
  9. 规范化射线。
  10. 绮山楂。如果需要,可以返回并用数学证明每一步,或者保存该评论以供日后[如果有的话]并疯狂地努力追求创建实际的3D图形和交互等等。
  11. 如果需要,请返回并重构。
  12. (*我使用的框架允许将3D矢量乘以4x4矩阵,因为它将3D矢量视为4D矢量。如果需要,我可以在以后更清楚地说明这一点,但我希望这一点相当清楚。 )

    这对我有用。这组步骤也适用于Ortho投影,但是对于Ortho,你可以作弊并编写更简单的代码,因为投影矩阵不是傻瓜。

    我写这篇文章已经很晚了,我可能误解了你的问题。我可能也误解了你的代码,因为我使用了不同的UI框架。但是我知道OpenGL的光线投射是如何加剧的,所以我希望至少我写的一些内容是有用的,并且我可以因此减轻一些人类的痛苦。

    Postscript。说到痛苦:我找到了很多论坛帖子和博客页面,为OpenGL解决了光线投射问题,但大多数帖子都是从以下的一些变体开始的:“首先,你必须知道X “[其中X无需知道];或者“查看unproject函数[在存储库Y中的库X中,您需要客户端应用程序Z ... ..]”;或者是我最喜欢的:“回顾一下关于线性代数的教科书。”

    当您需要调试光线投射时,不得不翻阅OpenGL渲染管道或OpenGL转换conga线的另一个描述 - 一个常见的问题 - 就像当您发现自己的时候必须听一听液压系统讲座刹车踏板不起作用。