在特定的Z深度处的XY位置

5
在处理(Java方言)中,有screenX、screenY方法(但现在我们跳过screenZ)。
假设我有一个对象在xyz = 50, 100, 500。那么通过使用screenX和screenY,你可以知道它们将出现在画布上的位置。
float x = screenX(50, 100, 500);
float y = screenY(50, 100, 500);
这是参考资料: http://processing.org/reference/screenX_.html 我感兴趣的是一种反向方法。
例如,我想让一个球出现在x = 175和y = 100的画布上。该球应该具有z = 700。那么,在z = 700时,使其出现在画布上的实际x和y位置是多少?
因此,该方法将是float unscreenX(float x,float y,float z),并返回x值。
我的数学/编程技能不是很高级(可以称之为糟糕)(我更多的是设计师),所以我正在寻求一些帮助。我已经在processing论坛上提出了问题,但通常这里有更多人对矩阵等有更深入的知识。 processing的正常screenX方法可以在这里找到: https://github.com/processing/processing/blob/master/core/src/processing/opengl/PGraphicsOpenGL.java
public float screenX(float x, float y, float z) {
    return screenXImpl(x, y, z);
  }

protected float screenXImpl(float x, float y, float z) {
    float ax =
      modelview.m00*x + modelview.m01*y + modelview.m02*z + modelview.m03;
    float ay =
      modelview.m10*x + modelview.m11*y + modelview.m12*z + modelview.m13;
    float az =
      modelview.m20*x + modelview.m21*y + modelview.m22*z + modelview.m23;
    float aw =
      modelview.m30*x + modelview.m31*y + modelview.m32*z + modelview.m33;
    return screenXImpl(ax, ay, az, aw);
  }


  protected float screenXImpl(float x, float y, float z, float w) {
    float ox =
      projection.m00*x + projection.m01*y + projection.m02*z + projection.m03*w;
    float ow =
      projection.m30*x + projection.m31*y + projection.m32*z + projection.m33*w;

    if (nonZero(ow)) {
      ox /= ow;
    }
    float sx = width * (1 + ox) / 2.0f;
    return sx;
  }

当然,还有y和z(我不理解z,但让我们忽略它)。我认为这可能会提供一些反转它的见解。modelview和投影是3D矩阵,代码在这里:https://github.com/processing/processing/blob/master/core/src/processing/core/PMatrix3D.java,但我想这是相当基础和常见的。我还在processing论坛上发了帖子,因为你永远不知道。它以不同的方式解释了我想要什么。http://forum.processing.org/topic/unscreenx-and-unscreeny对于描述这篇文章的标签,我没有去特定的原因,因为我可以想象一个从未使用过Java但在矩阵方面有经验的C ++程序员仍然能够提供一个很好的答案。希望有人能帮忙。

1
如果我正确理解问题,我认为这是不可能的。当计算将空间坐标映射到屏幕时,信息会丢失(考虑多对一映射)。 多对一映射的反向操作是不可能的。 - Suedocode
4个回答

3
我强烈建议您学习一些线性代数或矩阵数学,以便进行3D图形。这很有趣且容易,但比SO答案要长一些。尽管我会尝试解释 :) 声明:我不知道您正在使用的API!
看起来您正在返回一个位置的3个坐标(通常称为顶点)。但是您还提到了投影矩阵,并且该函数具有4个坐标。通常,着色器或API将采用4个坐标来表示顶点。x,y,z,w。为了在屏幕上显示它们,它会执行以下操作:
xscreen = x/w
yscreen = y/w
zbuffer = z/w

这很有用,因为你可以选择w。如果你只是做2d绘图,你可以把w=1。但如果你正在做3d并想要一些透视效果,你需要除以到相机的距离。这就是投影矩阵的作用。它主要将你的点的z(表示到相机的距离)放入w中。它还可能略微调整一下比例,如视野。
回顾你发布的代码,这正是最后一个ScreenXImpl函数所做的事情。它应用了一个投影矩阵,主要是将z移动到w中,然后再除以w。最后,它会进行额外的缩放和偏移,从(-1,1)到(0,像素宽度),但我们可以忽略这一点。
现在,我为什么要唠叨这些东西呢?你只想获取给定xscreen、yscreen、zbuffer的x、y、z坐标,对吧?嗯,诀窍就在于反向操作。为了做到这一点,你需要牢固掌握前进的方法 :)
倒退有两个问题:1)您是否真的知道或关心zbuffer值?2)您知道投影矩阵做了什么吗?
对于1),假设我们不关心。有许多可能的值,所以我们可能只选择一个。
对于2),您将不得不查看它的作用。一些投影矩阵可能只需要(x,y,z,w)并输出(x,y,z,1)。那将是2D。或者(x,y + z,z,1),这将是等角的。但在透视中,通常会执行(x,y,1,z)。加上一些缩放等等。
我刚刚注意到您的第二个screenXImpl已经将x,y,z,w传递给下一个阶段。有时很有用,但对于所有实际情况,w将为1。
此时我意识到我很糟糕地解释事物。 :) 您真的应该拿起线性代数书,我从这本书学到了http://www.amazon.com/Elementary-Linear-Algebra-Howard-Anton,但它附带了一个好的讲座,所以我不知道它本身有多有用。

无论如何!让我们更加实际一些。回到你的代码:screenXImpl的最后一个函数。我们现在知道输入w=1,且ow=~z和ox=~x;这里的波浪线表示乘以某个比例加上某个偏移量。而我们必须从~ox/ow开始得到屏幕x。(+1/2,*width..这就是波浪线的作用)。现在我们回到了1)……如果你想要特殊的oz-现在就选择一个。否则,我们可以随便选一个。对于渲染来说,选择任何在相机前面且易于使用的东西都是有意义的,比如1。

protected float screenXImpl(float x, float y, float z, float w==1) {
float ox = 1*x + 0*y + 0*z + 0*w; // == x
float ow = 0*x + 0*y + 1*z + 0*w; // == z == 1

ox /= ow; // == ox

float sx = width * (1 + ox) / 2.0f;
return sx;
}

什么鬼?sx = width * (1+ox)/2?为什么我不直接这么说呢?嗯,我放在那里的所有零可能都不是零。但最终结果会变得很简单。有些一可能并不是一。我试图展示你需要做出的重要假设才能够回退。现在,从sx回到ox应该就像倒退一样容易了。

那是困难的部分!但你仍然需要从最后一个函数回到第二个函数。我猜从第二个函数回到第一个函数很容易。:) 那个函数正在执行线性矩阵变换。这对我们很有好处。它接受四个值(x,y,z)和(w = 1)隐式作为输入,并输出另外四个值(ax,ay,az,aw)。我们可以手动找出如何回去那里!我在学校里不得不这样做...四个未知数,四个方程式。你知道ax,ay,az,aw...解出x,y,z,你就免费得到了w=1!非常可能而且是一个好练习,但也很繁琐。好消息是,这些方程式的编写方式被称为矩阵。(x,y,z,1)* MODELMATRIX =(ax,ay,az,aw)。非常方便,因为我们可以找到MODELMATRIX^-1。它被称为反转!就像1/2是实数乘法的2的倒数或-1是加法的1的倒数一样。你真的应该读一下这个,它很有趣,而且也不难,顺便说一句:) 。无论如何,使用任何标准库来获得模型矩阵的逆。可能是像modelView.Inverse()这样的东西。然后用它做同样的函数,你就可以回去了。很容易!

现在,为什么我们之前没有用同样的方法来处理 PROJECTION 矩阵呢?很高兴你问了!那个矩阵需要 4 个输入(x、y、z、w),但只输出三个结果(screenx、screeny、zbufferz)。所以如果不作出一些假设,我们就无法解决它!直观地看,如果你有一个 3D 点,将其投影到 2D 屏幕上,会有很多可能的方案。所以我们必须选择一些东西。而且我们不能使用方便的矩阵逆函数。

请告诉我这是否有所帮助。我感觉可能没有,但写这篇文章还是很有趣的!此外,在 Processing 中搜索“unproject”会得到以下结果:http://forum.processing.org/topic/read-3d-positions-gluunproject


嗨,我真的很欣赏你的帖子,但对我来说太难了。我上一次在学校学数学是十一年前,那时只是学百分比之类的东西 :) 我对你提到的书很感兴趣,但它太贵了:) 到目前为止还是谢谢。 - clankill3r
不用管那本书了,省点钱吧。我希望自己擅长解释事情,但是在短文中很难做到。 - starmole
@clankill3r,你也可以通过Khan Academy免费获得线性代数的很好介绍。这是相关章节 - Justin R.

0

在让这个工作之前,你需要知道项目矩阵,而Processing并没有提供给你。然而,我们可以通过检查三个向量(1,0,0)、(0,1,0)和(0,0,1)的screenX/Y/Z值来自己解决这个问题。从这些值中,我们可以计算出屏幕的平面公式(实际上只是一个裁剪的平面,穿过3D空间)。然后,给定“屏幕”表面上的(x,y)坐标和预定的z值,我们可以找到通过我们的屏幕平面的法线线与z=...平面的交点。

然而,这不是你想做的事情,因为你可以简单地重置任何你想要做的东西的坐标系。使用pushMatrix来“保存”你当前的3D变换,使用resetMatrix将一切都恢复到“直线”,然后根据你的世界轴和视图轴对齐的事实绘制你的球体。完成后,调用popMatrix来恢复你先前的世界变换即可。省去了实现数学计算的麻烦 =)


嗨,promax,我不太理解第一部分。但我确实发现你可以访问投影矩阵:PGraphicsOpenGL pgl = (PGraphicsOpenGL) g; println(pgl.projection.m00); 这可能会使它变得更容易。我很了解pushMatrix和popMatrix,但我不太理解resetMatrix。我尝试过一些东西,但它没有意义。 - clankill3r
只要你意识到这仅适用于在JVM中访问较低级别的Java堆栈,因此在不能运行依赖于JVM(如Processing.js)的环境中无法正常运行处理代码。从技术上讲,任何普通的Java调用都是“不再使用Processing,而是介于Java和Processing之间的东西”=) - Mike 'Pomax' Kamermans
那就是这个想法。将其重置为默认值,然后绘制你的球体,然后使用 popMatrix 将坐标系转换回在绘制“投影”的球之前所使用的状态。 - Mike 'Pomax' Kamermans
嗨Promax,为什么这个不起作用?void setup() { size(800, 600, OPENGL); smooth(); } void draw() { pushMatrix(); translate(width/2, height/2); sphere(50); popMatrix(); pushMatrix(); translate(width/4, height/2, -500); resetMatrix(); sphere(50); popMatrix(); } - clankill3r
你可以尝试使用以下代码来进行矩阵变换:pushMatrix(); translate(width/2, height/2); sphere(50); resetMatrix(); translate(width/4, height/2, -500); sphere(50); popMatrix(); - Mike 'Pomax' Kamermans

0

您可以通过简单的三角函数来解决这个问题。您需要知道h,即眼睛到画布中心的距离,以及表示画布中心的cxcy。为了简单起见,假设cxcy都是0。请注意,这不是您实际眼睛的距离,而是用于构建3D场景透视的虚拟眼睛的距离。

接下来,给定sxsy,计算到中心的距离:b = sqrt(sx * sx + sy * sy)

现在,您有一个底边为b,高度为h的直角三角形。该三角形由“眼睛”、画布中心和屏幕上物体的期望位置:(sx, sy)组成。

该三角形形成了另一个直角三角形的顶部,该直角三角形由“眼睛”、向后推移给定深度z的画布中心和物体本身:(x, y)组成。

三角形的底边和高的比例完全相同,因此根据其高度 hh = zhh = h + z(取决于 z 值是来自眼睛还是画布),计算大三角形的底边 bb 应该很容易。要使用的方程式是 b / h = bb / hh,其中你已知 bhhh

从那里,你可以轻松计算出 (x, y),因为两个底边与水平线的夹角相同。即 sy / sx = y / x

唯一混乱的部分将是从 3D 设置中提取眼睛到画布的距离以及画布中心的位置。


0

将3D点转换为2D屏幕的概述

当您拥有对象的三维表示(x,y,z)时,您希望将其“投影”到二维监视器上。为此,有一个转换函数,它接受您的三维坐标并输出二维坐标。在底层(至少在openGL中),发生的转换函数是一个特殊的矩阵。要进行转换,您需要取您的点(由向量表示)并进行简单的矩阵乘法。

如果您好奇想看一些漂亮的图表和推导(这不是必需的),请查看:http://www.songho.ca/opengl/gl_projectionmatrix.html

对我来说,screenXImpl 看起来像是在执行矩阵乘法。

反向转换

反向转换只是原始转换的逆矩阵。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接