簡體   English   中英

將3D點投影到2D屏幕坐標

[英]Projecting a 3D point to a 2D screen coordinate

根據3D Programming for Windows(Charles Petzold)第7章中的信息,我試圖編寫一個輔助函數,將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。 最初我只是希望得到一個基本的計算工作,無論它與矩陣相比效率如何。

任何人都可以建議需要添加或更改的內容嗎?

我已經使用3DUtils Codeplex源庫創建並成功測試了一種工作方法。

真正的工作是在3DUtils的TryWorldToViewportTransform()方法中執行的。 沒有它,此方法將無法工作(請參閱上面的鏈接)。

Eric Sink的文章中也發現了非常有用的信息: 自動縮放

NB。 可能有更可靠/有效的方法,如果是這樣,請添加它們作為答案。 與此同時,這對我的需求來說已經足夠了。

    /// <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; 
        }
    }

這並沒有解決有問題的算法,但它可能對於遇到這個問題很有用(正如我所做的那樣)。

在.NET 3.5中,您可以使用Visual3D.TransformToAncestor(Visual ancestor) 我使用它在我的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

由於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]。 考慮到從笛卡爾y翻轉的Windows y,通過從1.0減去前一個屏幕,從[1.0,0.0](左上角到左下角)轉換為[0.0,1.0](從大到小)。 然后,您可以縮放到ActualHeight。

  1. 目前還不清楚你想用aspectRatio coeff實現什么。 如果該點位於視野的邊緣,那么它應該位於屏幕的邊緣,但是如果aspectRatio!= 1則不是。 嘗試設置aspectRatio = 1並使窗口平方。 坐標是否仍然不正確?

  2. ActualWidthActualHeight似乎真的是窗口大小的一半,所以screenX將是[-ActualWidth; ActualWidth],但不是[0; ActualWidth的。 那是你要的嗎?

screenX和screenY應該相對於屏幕中心進行計算...

我沒有看到使用Windows API繪圖時,原點位於屏幕左上角的事實。 我假設你的坐標系是

y
|
|
+------x

另外,根據斯科特的問題,您的坐標系是否位於中心,還是位於左下角?

但是,Windows屏幕API是

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

你需要坐標轉換從經典的笛卡兒到Windows。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM