我的 2D/3D 转换工作完美,如何进行透视

发布于 2024-09-02 13:39:56 字数 1632 浏览 4 评论 0原文

虽然这个问题的背景是关于制作 2d/3d 游戏,但我遇到的问题可以归结为一些数学问题。 虽然这是一个 2.5D 世界,但对于这个问题,我们假设它只是 2D 世界。

// xa: x-accent, the x coordinate of the projection
// mapP: a coordinate on a map which need to be projected
// _Dist_ values are constants for the projection, choosing them correctly will result in i.e. an isometric projection 
xa = mapP.x * xDistX + mapP.y * xDistY; 
ya = mapP.x * yDistX + mapP.y * yDistY;

xDistX 和 yDistX 确定 x 轴的角度,xDistY 和 yDistY 确定投影上 y 轴的角度(以及网格的大小,但为了简单起见,我们假设这是 1 像素)。

x-axis-angle = atan(yDistX/xDistX)
y-axis-angle = atan(yDistY/yDistY)

像这样的“正常”坐标系

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

has values like this:
xDistX = 1;
yDistX = 0;
xDistY = 0;
YDistY = 1;

因此,x 方向上的每一步都会导致投影到右端向下 0 像素 1 像素。投影 y 方向上的每一步都会导致向右 0 步和向下 1 像素。 当选择正确的 xDistX、yDistX、xDistY、yDistY 时,您可以投影任何三轴或二轴系统(这就是我选择此系统的原因)。

到目前为止一切都很好,当绘制出来时一切都很好。如果“我的系统”和思维方式很清晰,那么让我们继续讨论观点。 我想向这个网格添加一些视角,所以我添加了一些额外的内容,如下所示:

camera = new MapPoint(60, 60);
dx = mapP.x - camera.x; // delta x
dy = mapP.y - camera.y; // delta y
dist = Math.sqrt(dx * dx + dy * dy); // dist is the distance to the camera, Pythagoras etc.. all objects must be in front of the camera

fac = 1 - dist / 100; // this formula determines the amount of perspective

xa = fac * (mapP.x * xDistX  + mapP.y * xDistY) ;
ya = fac * (mapP.x * yDistX + mapP.y * yDistY );

现在真正困难的部分...如果你在投影上有一个 (xa,ya) 点并想要计算原始点 (x,y )。 对于第一种情况(没有透视),我确实找到了反函数,但是对于具有透视的公式如何做到这一点。可能数学技能还不能完全应对解决这个问题的挑战。

(我依稀记得很久以前mathematica可以为某些特殊情况创建反函数......它可以解决这个问题吗?有人可以尝试吗?)

Although the context of this question is about making a 2d/3d game, the problem i have boils down to some math.
Although its a 2.5D world, lets pretend its just 2d for this question.

// xa: x-accent, the x coordinate of the projection
// mapP: a coordinate on a map which need to be projected
// _Dist_ values are constants for the projection, choosing them correctly will result in i.e. an isometric projection 
xa = mapP.x * xDistX + mapP.y * xDistY; 
ya = mapP.x * yDistX + mapP.y * yDistY;

xDistX and yDistX determine the angle of the x-axis, and xDistY and yDistY determine the angle of the y-axis on the projection (and also the size of the grid, but lets assume this is 1-pixel for simplicity).

x-axis-angle = atan(yDistX/xDistX)
y-axis-angle = atan(yDistY/yDistY)

a "normal" coordinate system like this

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

has values like this:
xDistX = 1;
yDistX = 0;
xDistY = 0;
YDistY = 1;

So every step in x direction will result on the projection to 1 pixel to the right end 0 pixels down. Every step in the y direction of the projection will result in 0 steps to the right and 1 pixel down.
When choosing the correct xDistX, yDistX, xDistY, yDistY, you can project any trimetric or dimetric system (which is why i chose this).

So far so good, when this is drawn everything turns out okay. If "my system" and mindset are clear, lets move on to perspective.
I wanted to add some perspective to this grid so i added some extra's like this:

camera = new MapPoint(60, 60);
dx = mapP.x - camera.x; // delta x
dy = mapP.y - camera.y; // delta y
dist = Math.sqrt(dx * dx + dy * dy); // dist is the distance to the camera, Pythagoras etc.. all objects must be in front of the camera

fac = 1 - dist / 100; // this formula determines the amount of perspective

xa = fac * (mapP.x * xDistX  + mapP.y * xDistY) ;
ya = fac * (mapP.x * yDistX + mapP.y * yDistY );

Now the real hard part... what if you got a (xa,ya) point on the projection and want to calculate the original point (x,y).
For the first case (without perspective) i did find the inverse function, but how can this be done for the formula with the perspective. May math skills are not quite up to the challenge to solve this.

( I vaguely remember from a long time ago mathematica could create inverse function for some special cases... could it solve this problem? Could someone maybe try?)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

潇烟暮雨 2024-09-09 13:39:56

您定义的函数没有反函数。举个例子,正如 user207422 已经指出的那样,距离相机 100 个单位的任何东西都将被映射到 (xa,ya)=(0,0),因此逆函数不是唯一定义的。

更重要的是,这不是计算透视的方式。通常,透视缩放因子定义为 viewdist/zdist,其中 zdist 是从相机到物体的垂直距离,viewdist 是常数这是从摄像机到所有内容都被投影到的假设屏幕的距离。 (参见此处的图表,但请随意忽略该页面上的其他所有内容。)您在示例中使用的缩放因子没有相同的行为。

这是尝试将代码转换为正确的透视计算的尝试(请注意,我没有简化为 2D;透视是将三个维度投影为两个维度,尝试将问题简化为 2D 是毫无意义的)

camera = new MapPoint(60, 60, 10);
camera_z = camera.x*zDistX + camera.y*zDistY + camera.z*zDistz;

// viewdist is the distance from the viewer's eye to the screen in
// "world units". You'll have to fiddle with this, probably.
viewdist = 10.0;

xa = mapP.x*xDistX + mapP.y*xDistY + mapP.z*xDistZ;
ya = mapP.x*yDistX + mapP.y*yDistY + mapP.z*yDistZ;
za = mapP.x*zDistX + mapP.y*zDistY + mapP.z*zDistZ;

zdist = camera_z - za;
scaling_factor = viewdist / zdist;
xa *= scaling_factor;
ya *= scaling_factor;

:从此函数返回xayaza 仅用于透视计算。我假设“za 方向”指向屏幕之外,因此如果预投影 x 轴指向观看者,则 zDistX 应该为正,反之亦然,对于zDistY。对于三轴投影,您可能会有 xDistZ==0yDistZ<0zDistZ==0。这将使预投影 z 轴在投影后指向正上方。

现在有坏消息:这个函数也没有逆函数。任何点 (xa,ya) 都是无数个点 (x,y,z) 的图像。但!如果假设 z=0,那么您可以求解 x 和 y,这可能已经足够好了。

为此,您必须做一些线性代数。计算 camera_xcamera_y 类似于 camera_z。这是相机变换后的坐标。屏幕上的点具有变换后坐标(xa,ya,camera_z-viewdist)。通过这两个点画一条线,并计算向量 (xDistX, yDistX, zDistX)(xDistY, yDistY, zDistY) 所跨越的平面的相交位置。换句话说,您需要求解方程:

x*xDistX + y*xDistY == s*camera_x + (1-s)*xa
x*yDistX + y*yDistY == s*camera_y + (1-s)*ya
x*zDistX + y*zDistY == s*camera_z + (1-s)*(camera_z - viewdist)

这并不漂亮,但它会起作用。

The function you've defined doesn't have an inverse. Just as an example, as user207422 already pointed out anything that's 100 units away from the camera will get mapped to (xa,ya)=(0,0), so the inverse isn't uniquely defined.

More importantly, that's not how you calculate perspective. Generally the perspective scaling factor is defined to be viewdist/zdist where zdist is the perpendicular distance from the camera to the object and viewdist is a constant which is the distance from the camera to the hypothetical screen onto which everything is being projected. (See the diagram here, but feel free to ignore everything else on that page.) The scaling factor you're using in your example doesn't have the same behaviour.

Here's a stab at trying to convert your code into a correct perspective calculation (note I'm not simplifying to 2D; perspective is about projecting three dimensions to two, trying to simplify the problem to 2D is kind of pointless):

camera = new MapPoint(60, 60, 10);
camera_z = camera.x*zDistX + camera.y*zDistY + camera.z*zDistz;

// viewdist is the distance from the viewer's eye to the screen in
// "world units". You'll have to fiddle with this, probably.
viewdist = 10.0;

xa = mapP.x*xDistX + mapP.y*xDistY + mapP.z*xDistZ;
ya = mapP.x*yDistX + mapP.y*yDistY + mapP.z*yDistZ;
za = mapP.x*zDistX + mapP.y*zDistY + mapP.z*zDistZ;

zdist = camera_z - za;
scaling_factor = viewdist / zdist;
xa *= scaling_factor;
ya *= scaling_factor;

You're only going to return xa and ya from this function; za is just for the perspective calculation. I'm assuming the the "za-direction" points out of the screen, so if the pre-projection x-axis points towards the viewer then zDistX should be positive and vice-versa, and similarly for zDistY. For a trimetric projection you would probably have xDistZ==0, yDistZ<0, and zDistZ==0. This would make the pre-projection z-axis point straight up post-projection.

Now the bad news: this function doesn't have an inverse either. Any point (xa,ya) is the image of an infinite number of points (x,y,z). But! If you assume that z=0, then you can solve for x and y, which is possibly good enough.

To do that you'll have to do some linear algebra. Compute camera_x and camera_y similar to camera_z. That's the post-transformation coordinates of the camera. The point on the screen has post-tranformation coordinates (xa,ya,camera_z-viewdist). Draw a line through those two points, and calculate where in intersects the plane spanned by the vectors (xDistX, yDistX, zDistX) and (xDistY, yDistY, zDistY). In other words, you need to solve the equations:

x*xDistX + y*xDistY == s*camera_x + (1-s)*xa
x*yDistX + y*yDistY == s*camera_y + (1-s)*ya
x*zDistX + y*zDistY == s*camera_z + (1-s)*(camera_z - viewdist)

It's not pretty, but it will work.

别忘他 2024-09-09 13:39:56

我认为通过您的帖子我可以解决问题。不过,澄清一些问题:

在 2d 中解决问题确实没有用,但这只是为了让问题更容易理解(对于我和这里的读者)。我的程序实际上给出了完美的 3D 投影(我用搅拌机渲染的 3D 图像检查了它)。不过,我确实遗漏了一些关于反函数的内容。反函数仅适用于 0..camera.x * 0.5 和 0..camera.y*0.5 之间的坐标。所以在我的例子中是 0 到 30 之间。但即便如此,我仍然对我的功能有疑问。

在我的投影中,z 轴始终是笔直向上的,因此为了计算物体的高度,我只使用了视角。但由于你实际上不能飞或跳到天空中,所以所有东西都只有一个 2d 点。这也意味着当你尝试求解 x 和 y 时,z 实际上是 0。

我知道并非每个函数都有逆函数,有些函数有,但仅适用于特定域。我对此的基本想法是...如果我可以使用函数绘制一个网格...该网格上的每个点都映射到一个地图点。我可以读取 x 和 y 坐标,因此如果我有正确的函数,我就能够计算倒数。
但是没有比一些好的扎实的数学更好的替代品了,我很高兴你花时间给出非常有用的回应:)。

I think that with your post i can solve the problem. Still, to clarify some questions:

Solving the problem in 2d is useless indeed, but this was only done to make the problem easier to grasp (for me and for the readers here). My program actually give's a perfect 3d projection (i checked it with 3d images rendered with blender). I did left something out about the inverse function though. The inverse function is only for coordinates between 0..camera.x * 0.5 and 0.. camera.y*0.5. So in my example between 0 and 30. But even then i have doubt's about my function.

In my projection the z-axis is always straight up, so to calculate the height of an object i only used the vieuwingangle. But since you cant actually fly or jumpt into the sky everything has only a 2d point. This also means that when you try to solve the x and y, the z really is 0.

I know not every funcion has an inverse, and some functions do, but only for a particular domain. My basic thought in this all was... if i can draw a grid using a function... every point on that grid maps to exactly one map-point. I can read the x and y coordinate so if i just had the correct function i would be able to calculate the inverse.
But there is no better replacement then some good solid math, and im very glad you took the time to give a very helpfull responce :).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文