首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >现代OpenGL:对象选择(C#,OpenTK)

现代OpenGL:对象选择(C#,OpenTK)
EN

Stack Overflow用户
提问于 2020-05-30 20:12:04
回答 2查看 1.1K关注 0票数 1

我试图在OpenGL中使用C#和OpenTK实现对象选择。为此,我编写了一个基于以下两个来源的类:

OpenGL射线投射(拾取):解释物体的变换

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

目前,我的代码仅用于计算鼠标指针与(0,0,0)的任意测试坐标之间的距离,但一旦工作正常,就无需在场景中的对象中迭代以找到匹配的对象。

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

有人能帮我解决这个问题吗?它执行时没有错误,但返回的距离显然是不正确的。我理解计算的原理,但不理解计算的精妙之处。

虽然我在网上发现了很多例子,但它们通常都是在另一种语言或框架中,并且/或使用了不推荐的方法和/或不完整或不起作用。

代码语言:javascript
运行
复制
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;
    }
}

它的名称如下:

代码语言:javascript
运行
复制
    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:

代码语言:javascript
运行
复制
        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的实现进行旋转。缩放是使用翻译完成的,如下所示:

代码语言:javascript
运行
复制
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();
    }
EN

回答 2

Stack Overflow用户

发布于 2020-05-30 20:20:28

通过模型视图矩阵对每个顶点坐标进行变换。这将坐标从模型空间转换为视图空间。然后利用投影矩阵对各顶点坐标进行变换。这将从视图空间转换为剪辑空间。透视分割将剪辑空间坐标转换为规范化的设备空间。

如果要从规范化设备空间转换为模型空间,则必须执行反向操作。这意味着您必须通过逆投影矩阵和逆模型视图矩阵进行转换:

代码语言:javascript
运行
复制
Vector4 pos2 = Vector4.Transform(pos, Matrix4.Invert(projection) * Matrix4.Invert(modelView));

分别

代码语言:javascript
运行
复制
Vector4 pos2 = Vector4.Transform(pos, Matrix4.Invert(modelView * projection));

注意,OpenTK矩阵必须从左到右相乘。见OpenGL 4.2 LookAt矩阵只适用于眼睛位置的-z值的答案。

票数 1
EN

Stack Overflow用户

发布于 2020-05-31 11:50:22

在这里回答我自己的问题,这样我就可以为其他用户发布工作代码,但至少一半的答案是由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);

其中我传递的是模型矩阵,而不是模型视图矩阵(它是模型矩阵和视图矩阵的乘积)。

这样做是可行的:

代码语言:javascript
运行
复制
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;
    }

}

在发布这个问题后,我了解到这种方法通常称为光线投射,并找到了一些很好的解释:

https://antongerdelan.net/opengl/raycasting.html作者: Anton Gerdelan

https://www.youtube.com/watch?v=DLKN0jExRIM&list=PLRIWtICgwaX0u7Rf9zkZhLoLuZVfUksDP&index=29 by ThinMatrix

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/62108117

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档