【问题标题】:3D data point to 2D data point3D 数据点到 2D 数据点
【发布时间】:2018-01-30 01:04:49
【问题描述】:

我正在使用 GDI+ 来实现一些简单的图形,我已经从这个示例 http://www.vcskicks.com/3d_gdiplus_drawing.php 中获取了代码,并且可以让它做我想做的事情,但我不明白它是如何从 3D 数据进行转换的指向二维数据点:

            //Convert 3D Points to 2D
            Math3D.Point3D vec;
            for (int i = 0; i < point3D.Length; i++)
            {
                vec = cubePoints[i];
                if (vec.Z - camera1.Position.Z >= 0)
                {
                    point3D[i].X = (int)((double)-(vec.X - camera1.Position.X) / (-0.1f) * zoom) + drawOrigin.X;
                    point3D[i].Y = (int)((double)(vec.Y - camera1.Position.Y) / (-0.1f) * zoom) + drawOrigin.Y;
                }
                else
                {
                    tmpOrigin.X = (int)((double)(cubeOrigin.X - camera1.Position.X) / (double)(cubeOrigin.Z - camera1.Position.Z) * zoom) + drawOrigin.X;
                    tmpOrigin.Y = (int)((double)-(cubeOrigin.Y - camera1.Position.Y) / (double)(cubeOrigin.Z - camera1.Position.Z) * zoom) + drawOrigin.Y;

                    point3D[i].X = (float)((vec.X - camera1.Position.X) / (vec.Z - camera1.Position.Z) * zoom + drawOrigin.X);
                    point3D[i].Y = (float)(-(vec.Y - camera1.Position.Y) / (vec.Z - camera1.Position.Z) * zoom + drawOrigin.Y);

                    point3D[i].X = (int)point3D[i].X;
                    point3D[i].Y = (int)point3D[i].Y;
                }
            }

我找到了一些讨论从 3d 数据点转换为 2d 数据点的资源:

但是,这些资源似乎都没有详细说明上述示例中使用的数学。

如果有人能指出数学的推导和/或解释上述代码的工作原理,我将不胜感激。

【问题讨论】:

  • / (vec.Z - camera1.Position.Z) therm 只是透视除以Z 到相机的距离(越远的点被划分得越多,所以物体越远越小)。然而,代码不会转换到相机坐标系,所以相机不能旋转只是移动......

标签: c# graphics 3d linear-algebra gdi


【解决方案1】:

确实,文章和代码有点混乱。在开始之前,让我们对其余代码进行一些修改。通过这些修改,您可能会更容易地看到正在发生的事情。让我们指定一个静态摄像机位置。而不是这个奇怪的公式:

double cameraZ = -(((anchorPoint.X - cubeOrigin.X) * zoom) / cubeOrigin.X) + anchorPoint.Z;

让我们这样做吧:

cameraZ = 200;
zoom = 100;

然后,我们保持

camera1.Position = new Math3D.Point3D(cubeOrigin.X, cubeOrigin.Y, cameraZ);

这会将相机定位在 200 深度,使其 x/y 坐标与立方体中心重合。我会回到zoom的含义。

相机模型使用透视投影和右手坐标系。这意味着相机朝负 z 方向看,远处的东西会显得更小。

让我们一步一步仔细看一下3D->2D转换代码:

if (vec.Z - camera1.Position.Z >= 0)

vec 是我们要投影的点。更直观的写法是:

if (vec.Z >= camera1.Position.Z)

因此,此分支适用于相机后面的所有点(请记住,相机朝向负 z 方向)。这个分支中发生的事情有点骇人听闻。它与真实的预测无关。您真正想要做的是切断这些点(因为它们不可见)。幸运的是,在这个例子中,没有一个点位于相机后面。所以,我们不需要关心这个。稍后我会谈到这一点。

让我们继续else 分支。

tmpOrigin = ...

这个变量没有在任何地方使用,所以我们可以忽略它。

point3D[i].X = (float)((vec.X - camera1.Position.X) / (vec.Z - camera1.Position.Z) * zoom + drawOrigin.X);

这是实际的投影(我只考虑X 部分。Y 部分也是如此)。让我们来看看各个部分:

vec.X - camera1.Position.X

这是从相机位置到绘制点的向量。相机左边的一切都有一个负坐标,相机右边的一切都有一个正坐标。

vec.Z - camera1.Position.Z

这是相机的负深度。不知道为什么这里使用负深度。这将为您提供镜像。你真正想要做的是(由于相机看着负 z 轴)

camera1.Position.Z - vec.Z

那么,

(vec.X - camera1.Position.X) / (vec.Z - camera1.Position.Z)

是透视分界线。差异向量按其反向深度进行缩放(即远处的物体变得更小)。

* zoom 

这会将图像从世界空间(非常小)缩放到图像空间(将世界单位转换为像素)。这个因素有点随意(这就是我们刚刚指定100 的原因)。更多涉及的相机模型使用视野。

  • drawOrigin.X 最后,我们将相机中心对准drawOrigin。请记住,相机左侧的点具有负坐标。这样,这些将获得一个正坐标(但仍位于drawOrigin 的左侧)。

    point3D[i].X = (int)point3D[i].X; 这只是对 int 的强制转换。

对于 y 坐标,还有一个额外的-。这会使 y 轴旋转(在图像的像素坐标系中,y 轴指向下方)。

让我们回到 hacky if 分支。您会看到公式完全相同。除了之前点的负深度的部分现在有(-0.1f)。所以这些点将被认为具有0.1 的恒定深度。相当可疑,与实际预测相去甚远。

基本上就是这样。还有一点需要注意:文章中有一节是关于云台锁的。问题是,这里描述的矩阵乘法的属性与 Gimbal 锁无关。所以,不要太依赖这篇文章。这是一个很好的实际应用程序,但它有很多缺陷。

【讨论】:

  • 谢谢 Nico,解释得很好!
猜你喜欢
  • 2019-10-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-08
  • 2011-01-22
  • 1970-01-01
  • 2013-02-11
相关资源
最近更新 更多