3D数据点到2D数据点
3D data point to 2D data point
我正在使用 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 数据点到 2d 数据点转换的资源:
- https://amycoders.org/tutorials/3dbasics.html
- https://en.wikipedia.org/wiki/Isometric_projection
- https://en.wikipedia.org/wiki/3D_projection
但是 none 这些资源似乎详细说明了上例中使用的数学。
如果有人能指出我的数学推导,我将不胜感激and/or解释上面的代码是如何工作的。
这篇文章和代码确实有点混乱。在我们开始之前,让我们对其余代码做一些修改。通过这些修改,您可能会更容易地看到发生了什么。让我们指定一个静态相机位置。而不是这个奇怪的公式:
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 方向看)。这个分支中发生的事情有点老套。它与实际预测无关。你真正想要做的是切断这些点(因为它们不可见)。幸运的是,在示例中,none 个点位于相机后面。所以,我们不需要关心这个。我稍后再谈。
让我们继续 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
这会将图像从世界 space(非常小)缩放到图像 space(将世界单位转换为像素)。该因素是任意的(这就是我们刚刚指定 100
的原因)。更多涉及的相机模型使用视野。
drawOrigin.X
最后,我们将相机中心对准 drawOrigin
。请记住,相机左侧的点坐标为负。有了这个,这些将得到一个正坐标(但仍然留在drawOrigin
)。
point3D[i].X = (int)point3D[i].X;
这只是对 int.
的转换
对于y坐标,还有一个额外的-
。这样就把y轴转过来了(在图像的像素坐标系中,y轴指向下方)。
让我们回到 hacky if
分支。你看公式是完全一样的。除了之前点的负深度部分现在有(-0.1f)
。因此,这些点将被视为具有 0.1
的恒定深度。非常可疑,与实际预测相去甚远。
基本上就是这样。还有一点要注意:这篇文章有一节是关于 Gimbal lock 的。事实是,那里描述的矩阵乘法的属性与万向节锁无关。所以,不要过分依赖这篇文章。这是一个很好的实用应用程序,但它有很多缺陷。
我正在使用 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 数据点到 2d 数据点转换的资源:
- https://amycoders.org/tutorials/3dbasics.html
- https://en.wikipedia.org/wiki/Isometric_projection
- https://en.wikipedia.org/wiki/3D_projection
但是 none 这些资源似乎详细说明了上例中使用的数学。
如果有人能指出我的数学推导,我将不胜感激and/or解释上面的代码是如何工作的。
这篇文章和代码确实有点混乱。在我们开始之前,让我们对其余代码做一些修改。通过这些修改,您可能会更容易地看到发生了什么。让我们指定一个静态相机位置。而不是这个奇怪的公式:
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 方向看)。这个分支中发生的事情有点老套。它与实际预测无关。你真正想要做的是切断这些点(因为它们不可见)。幸运的是,在示例中,none 个点位于相机后面。所以,我们不需要关心这个。我稍后再谈。
让我们继续 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
这会将图像从世界 space(非常小)缩放到图像 space(将世界单位转换为像素)。该因素是任意的(这就是我们刚刚指定 100
的原因)。更多涉及的相机模型使用视野。
drawOrigin.X 最后,我们将相机中心对准
drawOrigin
。请记住,相机左侧的点坐标为负。有了这个,这些将得到一个正坐标(但仍然留在drawOrigin
)。point3D[i].X = (int)point3D[i].X; 这只是对 int.
的转换
对于y坐标,还有一个额外的-
。这样就把y轴转过来了(在图像的像素坐标系中,y轴指向下方)。
让我们回到 hacky if
分支。你看公式是完全一样的。除了之前点的负深度部分现在有(-0.1f)
。因此,这些点将被视为具有 0.1
的恒定深度。非常可疑,与实际预测相去甚远。
基本上就是这样。还有一点要注意:这篇文章有一节是关于 Gimbal lock 的。事实是,那里描述的矩阵乘法的属性与万向节锁无关。所以,不要过分依赖这篇文章。这是一个很好的实用应用程序,但它有很多缺陷。