我正在使用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数据点到第二个数据点的转换:
但是,这些资源似乎都没有详细说明上述示例中使用的数学。
如果有人能指出数学的推导和/或解释上述代码是如何工作的,我将非常感激。
答案 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
深度。非常可疑,远非实际预测。
基本上就是这样。还有一点需要注意:文章中有一节关于万向节锁。事实上,那里描述的矩阵乘法的性质与万向节锁无关。所以,不要太依赖这篇文章。这是一个很好的实际应用,但它有一些缺陷。