将 3D 点投影到 2D 屏幕坐标

问题描述 投票:0回答:7

根据 Windows 3D 编程 (Charles Petzold) 中的信息,我尝试编写辅助函数,将 Point3D 投影到包含相应屏幕坐标 (x,y) 的标准 2D 点:

public Point Point3DToScreen2D(Point3D point3D,Viewport3D viewPort )
{
    double screenX = 0d, screenY = 0d;

    // Camera is defined in XAML as:
    //        <Viewport3D.Camera>
    //             <PerspectiveCamera Position="0,0,800" LookDirection="0,0,-1" />
    //        </Viewport3D.Camera>

    PerspectiveCamera cam = viewPort.Camera as PerspectiveCamera;

    // Translate input point using camera position
    double inputX = point3D.X - cam.Position.X;
    double inputY = point3D.Y - cam.Position.Y;
    double inputZ = point3D.Z - cam.Position.Z;

    double aspectRatio = viewPort.ActualWidth / viewPort.ActualHeight;

    // Apply projection to X and Y
    screenX = inputX / (-inputZ * Math.Tan(cam.FieldOfView / 2));

    screenY = (inputY * aspectRatio) / (-inputZ * Math.Tan(cam.FieldOfView / 2));

    // Convert to screen coordinates
    screenX = screenX * viewPort.ActualWidth;

    screenY = screenY * viewPort.ActualHeight;


    // Additional, currently unused, projection scaling factors
    /*
    double xScale = 1 / Math.Tan(Math.PI * cam.FieldOfView / 360);
    double yScale = aspectRatio * xScale;

    double zFar = cam.FarPlaneDistance;
    double zNear = cam.NearPlaneDistance;

    double zScale = zFar == Double.PositiveInfinity ? -1 : zFar / (zNear - zFar);
    double zOffset = zNear * zScale;

    */

    return new Point(screenX, screenY);
}

然而,在测试时,此函数返回不正确的屏幕坐标(通过将 2D 鼠标坐标与简单的 3D 形状进行比较来检查)。由于我缺乏 3D 编程经验,我很困惑为什么。

块注释部分包含可能必不可少的缩放计算,但我不确定如何进行,本书继续使用 XAML 介绍 MatrixCamera。最初,我只想进行基本计算,无论它与矩阵相比效率有多低。

任何人都可以建议需要添加或更改哪些内容吗?

c# wpf math 3d
7个回答
4
投票

我使用 3DUtils Codeplex 源库创建并成功测试了一种工作方法。

真正的工作是在 3DUtils 的 TryWorldToViewportTransform() 方法中执行的。如果没有它,此方法将不起作用(请参阅上面的链接)。

在 Eric Sink 的文章中还发现了非常有用的信息:自动缩放

注意。可能有更可靠/更有效的方法,如果有,请将它们添加为答案。与此同时,这足以满足我的需求。

    /// <summary>
    /// Takes a 3D point and returns the corresponding 2D point (X,Y) within the viewport.  
    /// Requires the 3DUtils project available at http://www.codeplex.com/Wiki/View.aspx?ProjectName=3DTools
    /// </summary>
    /// <param name="point3D">A point in 3D space</param>
    /// <param name="viewPort">An instance of Viewport3D</param>
    /// <returns>The corresponding 2D point or null if it could not be calculated</returns>
    public Point? Point3DToScreen2D(Point3D point3D, Viewport3D viewPort)
    {
        bool bOK = false;

        // We need a Viewport3DVisual but we only have a Viewport3D.
        Viewport3DVisual vpv =VisualTreeHelper.GetParent(viewPort.Children[0]) as Viewport3DVisual;

        // Get the world to viewport transform matrix
        Matrix3D m = MathUtils.TryWorldToViewportTransform(vpv, out bOK);

        if (bOK)
        {
            // Transform the 3D point to 2D
            Point3D transformedPoint = m.Transform(point3D);

            Point screen2DPoint = new Point(transformedPoint.X, transformedPoint.Y);

            return new Nullable<Point>(screen2DPoint);
        }
        else
        {
            return null; 
        }
    }

3
投票

由于 Windows 坐标是屏幕中的 z(x 交叉 y),所以我会使用类似

screenY = viewPort.ActualHeight * (1 - screenY);

而不是

screenY = screenY * viewPort.ActualHeight;

更正 screenY 以适应 Windows。

或者,您可以使用 OpenGL。当您设置视口 x/y/z 范围时,您可以将其保留为“本机”单位,并让 OpenGL 转换为屏幕坐标。

编辑: 因为你的原点是中心。我会尝试

screenX = viewPort.ActualWidth * (screenX + 1.0) / 2.0
screenY = viewPort.ActualHeight * (1.0 - ((screenY + 1.0) / 2.0))

屏幕+1.0从[-1.0, 1.0]转换为[0.0, 2.0]。此时,除以 2.0 即可得到 [0.0, 1.0] 进行乘法。为了考虑到 Windows y 从笛卡尔 y 翻转,您可以通过从 1.0 减去前一个屏幕来从 [1.0, 0.0](从左上到左下)转换为 [0.0, 1.0](从上到下)。然后,您可以缩放到实际高度。


3
投票

这并没有解决有问题的算法,但它可能对遇到这个问题的人有用(就像我所做的那样)。

在.NET 3.5中,您可以使用Visual3D.TransformToAncestor(视觉祖先)。我用它在 3D 视口的画布上绘制线框:

    void CompositionTarget_Rendering(object sender, EventArgs e)
    {
        UpdateWireframe();
    }

    void UpdateWireframe()
    {
        GeometryModel3D model = cube.Content as GeometryModel3D;

        canvas.Children.Clear();

        if (model != null)
        {
            GeneralTransform3DTo2D transform = cube.TransformToAncestor(viewport);
            MeshGeometry3D geometry = model.Geometry as MeshGeometry3D;

            for (int i = 0; i < geometry.TriangleIndices.Count;)
            {
                Polygon p = new Polygon();
                p.Stroke = Brushes.Blue;
                p.StrokeThickness = 0.25;
                p.Points.Add(transform.Transform(geometry.Positions[geometry.TriangleIndices[i++]]));
                p.Points.Add(transform.Transform(geometry.Positions[geometry.TriangleIndices[i++]]));
                p.Points.Add(transform.Transform(geometry.Positions[geometry.TriangleIndices[i++]]));
                canvas.Children.Add(p);
            }
        }
    }

这还考虑了模型等上的任何变换。

另请参阅:http://blogs.msdn.com/wpf3d/archive/2009/05/13/transforming-bounds.aspx


1
投票
  1. 目前尚不清楚您想通过aspectRatio coeff 实现什么目标。如果该点位于视野的边缘,那么它应该位于屏幕的边缘,但如果aspectRatio!=1则不是。尝试设置aspectRatio=1并使窗口成为正方形。坐标还是不正确吗?

  2. ActualWidth
    ActualHeight
    看起来确实是窗口大小的一半,所以 screenX 将是 [-ActualWidth;实际宽度],但不是 [0;实际宽度]。这就是你想要的吗?


0
投票

screenX 和 screenY 应该相对于屏幕中心进行计算...


0
投票

我没有看到针对以下事实的更正:使用 Windows API 绘图时,原点位于屏幕的左上角。我假设你的坐标系是

y
|
|
+------x

另外,按照斯科特的问题,你的坐标系假设原点位于中心,还是位于左下角?

但是,Windows 屏幕 API 是

+-------x
|
|
|
y

您需要坐标变换才能从经典笛卡尔坐标转换为 Windows。


0
投票

格罗基斯:

在您上面的回复中有一行

GeometryModel3D model = cube.Content as GeometryModel3D;

你能多写一点关于什么是“立方体”吗?

TY!

© www.soinside.com 2019 - 2024. All rights reserved.