2009-02-06 139 views
6

根据3D Programming For Windows (Charles Petzold)第7章中的信息,我试图编写一个辅助函数,将Point3D投影到包含相应屏幕坐标(x,y)的标准2D点上, :将3D点投影到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。最初,我只想得到一个基本的计算工作,不管它与矩阵相比可能有多低效。

任何人都可以建议需要添加或更改什么吗?

回答

2

由于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](从上到下)。然后,您可以缩放到ActualHeight。

1
  1. 目前还不清楚你试图用aspectRatio coeff实现什么。如果点位于视场的边缘,那么它应该位于屏幕的边缘,但是如果aspectRatio!= 1,则不是。尝试设置aspectRatio = 1并使窗口呈方形。坐标是否仍然不正确?

  2. ActualWidth and ActualHeight确实是窗口大小的一半,所以screenX会是[-ActualWidth; ActualWidth],但不是[0; ActualWidth的。那是你要的吗?

0

screenX和screenY应该得到计算相对于屏幕中心......

0

我没有看到一个修正的事实,使用Windows API绘制时,原点是左上角屏幕的一角。我假设你的坐标系是

y 
| 
| 
+------x 

而且,是你的坐标系中的中心假设起源,每斯科特的问题,或者是在左下角?

但是,Windows的屏幕API是

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

您需要的坐标变换,从经典笛卡尔去到Windows。

+0

X坐标,正面是右 y坐标,正向上 z坐标,正面向外(朝着你) 我相信这是右手坐标系。 原产地位于中心。目标是将X和Y坐标计算为-1.0和1.0之间的值,然后通过widht/height计算*。 – Ash 2009-02-06 06:00:09

+0

对不起格式化,评论不喜欢回车:X坐标:正确是正确的。 Y坐标:正向上。 Z坐标:正面不在屏幕上(朝着你) – Ash 2009-02-06 06:01:44

4

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

真正的工作是在3DUtils的TryWorldToViewportTransform()方法中执行的。没有它,这种方法将无法工作(参见上面的链接)。

在Eric Sink的文章中发现了非常有用的信息:Auto-Zoom

注意:可能会有更可靠/有效的方法,如果有的话,请将它们添加为答案。同时这对我的需求来说已经足够了。

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

这并没有解决有问题的算法,但它可能有助于人们遇到这个问题(如我一样)。

在.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