3D数据指向2D数据点

时间:2017-08-21 21:35:02

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

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

            //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数据点到第二个数据点的转换:

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

如果有人能指出数学的推导和/或解释上述代码是如何工作的,我将非常感激。

1 个答案:

答案 0 :(得分: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-&gt; 2D转换代码:

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

vec是我们想要投射的点。一种更直观的写作方式是:

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

因此,此分支适用于相机后面的所有点(请记住相机看向负z方向)。在这个分支中发生了什么有点hacky。它与真实预测无关。你真正想做的是切断那些点(因为它们不是可见的)。幸运的是,在这个例子中,相机后面没有任何一点。所以,我们不需要关心这一点。我稍后会回来。

让我们继续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深度。非常可疑,远非实际预测。

基本上就是这样。还有一点需要注意:文章中有一节关于万向节锁。事实上,那里描述的矩阵乘法的性质与万向节锁无关。所以,不要太依赖这篇文章。这是一个很好的实际应用,但它有一些缺陷。