【问题标题】:Modern OpenGL: Object picking (C#, OpenTK)现代 OpenGL:对象拾取(C#、OpenTK)
【发布时间】:2020-05-30 20:12:04
【问题描述】:

我正在尝试使用 C# 和 OpenTK 在 OpenGL 中实现对象拾取。我基于两个来源为此目的编写了一个类:

OpenGL ray casting (picking): account for object's transform

https://www.bfilipek.com/2012/06/select-mouse-opengl.html

目前我的代码仅用于计算鼠标指针与 (0,0,0) 的任意测试坐标的距离,但一旦工作,迭代场景中的对象以找到匹配项并不需要太多。

该方法是在鼠标指针下方在近剪裁平面和远剪裁平面之间定义一条射线。然后找到该射线上最接近被测点的点,并返回两者之间的距离。当鼠标指针直接位于 (0,0,0) 上方时,该值应为零,并随着它向任何方向移开而增加。

谁能帮忙解决这个问题?它执行没有错误,但返回的距离显然不正确。我了解原理,但不了解计算的细节。

虽然我在网上找到了各种几乎的例子,但它们通常使用不同的语言或框架和/或使用过时的方法和/或不完整或不起作用。

public class ObjectPicker{

    public static float DistanceFromPoint(Point mouseLocation, Vector3 testPoint, Matrix4 modelView, Matrix4 projection)
    {
        Vector3 near = UnProject(new Vector3(mouseLocation.X, mouseLocation.Y, 0), modelView, projection); // start of ray
        Vector3 far = UnProject(new Vector3(mouseLocation.X, mouseLocation.Y, 1), modelView, projection); // end of ray
        Vector3 pt = ClosestPoint(near, far, testPoint); // find point on ray which is closest to test point
        return Vector3.Distance(pt, testPoint); // return the distance
    }
    private static Vector3 ClosestPoint(Vector3 A, Vector3 B, Vector3 P) 
    {
        Vector3 AB = B - A;
        float ab_square = Vector3.Dot(AB, AB);
        Vector3 AP = P - A;
        float ap_dot_ab = Vector3.Dot(AP, AB);
        // t is a projection param when we project vector AP onto AB 
        float t = ap_dot_ab / ab_square;
        // calculate the closest point 
        Vector3 Q = A + Vector3.Multiply(AB, t); 
        return Q; 
    }   
    private static Vector3 UnProject(Vector3 screen, Matrix4 modelView, Matrix4 projection)
    {
        int[] viewport = new int[4];
        OpenTK.Graphics.OpenGL.GL.GetInteger(OpenTK.Graphics.OpenGL.GetPName.Viewport, viewport);

        Vector4 pos = new Vector4();

        // Map x and y from window coordinates, map to range -1 to 1 
        pos.X = (screen.X - (float)viewport[0]) / (float)viewport[2] * 2.0f - 1.0f;
        pos.Y = 1 - (screen.Y - (float)viewport[1]) / (float)viewport[3] * 2.0f;
        pos.Z = screen.Z * 2.0f - 1.0f;
        pos.W = 1.0f;

        Vector4 pos2 = Vector4.Transform( pos, Matrix4.Invert(modelView) * projection );
        Vector3 pos_out = new Vector3(pos2.X, pos2.Y, pos2.Z);

        return pos_out / pos2.W;
    }
}

它是这样称呼的:

    private void GlControl1_MouseMove(object sender, MouseEventArgs e)
    {

        float dist = ObjectPicker.DistanceFromPoint(new Point(e.X,e.Y), new Vector3(0,0,0), model, projection);
        this.Text = dist.ToString(); // display in window caption for debugging

    }

我知道矩阵是如何被传入的(根据上面的代码)。我很确定这些矩阵的内容一定是正确的,因为渲染效果很好,而且我可以成功地旋转/缩放。这是顶点着色器 FWIW:

        string vertexShaderSource =
            "# version 330 core\n" +
            "layout(location = 0) in vec3 aPos;" +
            "layout(location = 1) in vec3 aNormal;" +
            "uniform mat4 model;    " +
            "uniform mat4 view;" +
            "uniform mat4 projection;" +
            "out vec3 FragPos;" +
            "out vec3 Normal;" +
            "void main()" +
            "{" +
            "gl_Position = projection * view * model * vec4(aPos, 1.0);" +
            "FragPos = vec3(model * vec4(aPos, 1.0));" +
            "Normal = vec3(model * vec4(aNormal, 1.0))";
            "}";

我使用 Arcball 的实现进行旋转。缩放是使用翻译完成的,如下所示:

private void glControl1_MouseWheel(object sender, System.Windows.Forms.MouseEventArgs e)
    {
        zoom += (float)e.Delta / 240;
        view = Matrix4.CreateTranslation(0.0f, 0.0f, zoom);
        SetMatrix4(Handle, "view", view);
        glControl1.Invalidate();
    }

【问题讨论】:

    标签: c# opengl glsl opentk perspectivecamera


    【解决方案1】:

    每个顶点坐标由模型视图矩阵转换。这会将坐标从模型空间转换到视图空间。然后每个顶点坐标由投影矩阵变换。这从视图空间转换为剪辑空间。透视分割将剪辑空间坐标转换为规范化的设备空间。
    如果要从规范化设备空间转换为模型空间,则必须执行相反的操作。这意味着您必须通过逆投影矩阵和逆模型视图矩阵进行变换:

    Vector4 pos2 = Vector4.Transform(pos, Matrix4.Invert(projection) * Matrix4.Invert(modelView));
    

    分别

    Vector4 pos2 = Vector4.Transform(pos, Matrix4.Invert(modelView * projection));
    

    注意,OpenTK 矩阵必须从左到右相乘。查看OpenGL 4.2 LookAt matrix only works with -z value for eye position的答案。

    【讨论】:

    • 谢谢。相应地编辑代码以合并您的上述行。
    • 好的 - 我会改回来的。我认为对代码实施更正已经完成,但我可能错了。 (我的想法是它向其他用户展示什么是正确的,这似乎比不正确的更有用。)
    • 正确,还是不行。我现在总是得到非常小的数字(例如 5E-09 的顺序,但不是恒定的)
    • 是的,没错。计算出来的距离很小。
    • viewport 包含 [0, 0, 598, 583]
    【解决方案2】:

    在这里回答我自己的问题,以便我可以发布工作代码以造福其他用户,但至少有一半的答案是由 Rabbid76 提供的,我非常感谢他的帮助。

    我的原始代码中有两个错误:

    Vector4 pos2 = Vector4.Transform( pos, Matrix4.Invert(modelView) * projection );

    其中两个矩阵相乘顺序错误,投影矩阵没有倒置。

    float dist = ObjectPicker.DistanceFromPoint(new Point(e.X,e.Y), new Vector3(0,0,0), model, projection);

    我传入的是模型矩阵而不是模型视图矩阵(它是模型矩阵和视图矩阵的乘积)。

    这行得通:

    private void GlControl1_MouseMove(object sender, MouseEventArgs e)
    {
    
        float dist = ObjectPicker.DistanceFromPoint(new Point(e.X,e.Y), new Vector3(0,0,0), model * view, projection);
        // do something with the result
    
    }
    
    public class ObjectPicker{
    
        public static float DistanceFromPoint(Point mouseLocation, Vector3 testPoint, Matrix4 modelView, Matrix4 projection)
        {
    
            int[] viewport = new int[4];
            OpenTK.Graphics.OpenGL.GL.GetInteger(OpenTK.Graphics.OpenGL.GetPName.Viewport, viewport);
            Vector3 near = UnProject(new Vector3(mouseLocation.X, mouseLocation.Y, 0), modelView, projection); // start of ray (near plane)
            Vector3 far = UnProject(new Vector3(mouseLocation.X, mouseLocation.Y, 1), modelView, projection); // end of ray (far plane)
            Vector3 pt = ClosestPoint(near, far, testPoint); // find point on ray which is closest to test point
    
            return Vector3.Distance(pt, testPoint); // return the distance
        }
        private static Vector3 ClosestPoint(Vector3 A, Vector3 B, Vector3 P) 
        {
            Vector3 AB = B - A;
            float ab_square = Vector3.Dot(AB, AB);
            Vector3 AP = P - A;
            float ap_dot_ab = Vector3.Dot(AP, AB);
            // t is a projection param when we project vector AP onto AB 
            float t = ap_dot_ab / ab_square;
            // calculate the closest point 
            Vector3 Q = A + Vector3.Multiply(AB, t); 
            return Q; 
        }   
        private static Vector3 UnProject(Vector3 screen, Matrix4 modelView, Matrix4 projection)
        {
            int[] viewport = new int[4];
            OpenTK.Graphics.OpenGL.GL.GetInteger(OpenTK.Graphics.OpenGL.GetPName.Viewport, viewport);
    
            Vector4 pos = new Vector4();
    
            // Map x and y from window coordinates, map to range -1 to 1 
            pos.X = (screen.X - (float)viewport[0]) / (float)viewport[2] * 2.0f - 1.0f;
            pos.Y = 1 - (screen.Y - (float)viewport[1]) / (float)viewport[3] * 2.0f;
            pos.Z = screen.Z * 2.0f - 1.0f;
            pos.W = 1.0f;
    
            Vector4 pos2 = Vector4.Transform( pos, Matrix4.Invert(projection) * Matrix4.Invert(modelView) );
            Vector3 pos_out = new Vector3(pos2.X, pos2.Y, pos2.Z);
    
            return pos_out / pos2.W;
        }
    
    }
    

    自从发布这个问题以来,我了解到该方法通常称为光线投射,并找到了一些很好的解释:

    Mouse Picking with Ray Casting 作者:Anton Gerdelan

    OpenGL 3D Game Tutorial 29: Mouse Picking 来自 ThinMatrix

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-09-01
      • 1970-01-01
      • 2017-09-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-15
      相关资源
      最近更新 更多