基本渲染3D透视投影到2D屏幕上的相机(没有opengl)

时间:2011-12-26 05:01:19

标签: c# java math 3d projection

假设我有一个如下所示的数据结构:

Camera {
   double x, y, z

   /** ideally the camera angle is positioned to aim at the 0,0,0 point */
   double angleX, angleY, angleZ;
}

SomePointIn3DSpace {
   double x, y, z
}

ScreenData {
   /** Convert from some point 3d space to 2d space, end up with x, y */
   int x_screenPositionOfPt, y_screenPositionOfPt

   double zFar = 100;

   int width=640, height=480
}

...

如果没有屏幕剪辑或其他任何内容,我将如何在空间中给出一些3d点的情况下计算某个点的屏幕x,y位置。我想将这个3d点投射到2d屏幕上。

Camera.x = 0
Camera.y = 10;
Camera.z = -10;


/** ideally, I want the camera to point at the ground at 3d space 0,0,0 */
Camera.angleX = ???;
Camera.angleY = ????
Camera.angleZ = ????;

SomePointIn3DSpace.x = 5;
SomePointIn3DSpace.y = 5;
SomePointIn3DSpace.z = 5;

ScreenData.x和y是空间中3d点的屏幕x位置。我该如何计算这些值?

我可能会使用此处找到的公式,但我不明白屏幕宽度/高度是如何发挥作用的。另外,我不了解wiki条目中观众的位置与摄影机位置的关系。

http://en.wikipedia.org/wiki/3D_projection

7 个答案:

答案 0 :(得分:48)

“完成的方式”是使用同质变换和坐标。你在太空中占据一席之地,并且:

  • 使用模型矩阵将其相对于相机定位。
  • 使用投影矩阵以正交或透视方式投影。
  • 应用视口trnasformation将其放置在屏幕上。

这变得非常模糊,但我会尝试覆盖重要的部分,并留下一些给你。我假设您了解矩阵数学的基础知识:)。

同质向量,点,转换

在3D中,同质点将是[x,y,z,1]形式的列矩阵。最后一个组件是'w',一个缩放因子,对于向量是0:这会产生无法转换向量的影响,这在数学上是正确的。我们不会去那里,我们正在谈论要点。

均匀变换是4x4矩阵,因为它们允许将平移表示为矩阵乘法,而不是加法,这对于你的视频来说是很好的和快速的。也很方便,因为我们可以通过将它们相乘来表示连续变换。我们通过执行变换*点将变换应用于点。

有3个主要的同质转换:

还有其他人,特别是“看待”转型,值得探讨。但是,我只是想简要列出一些链接。应用于点的移动,缩放和旋转的连续应用共同是模型变换矩阵,并且将它们相对于相机放置在场景中。重要的是要意识到我们正在做的事情类似于在相机周围移动物体,而不是相反。

正交和透视

要从世界坐标转换为屏幕坐标,首先要使用投影矩阵,它通常有两种形式:

  • 正交,通常用于2D和CAD。
  • 透视,适用于游戏和3D环境。

正交投影矩阵构造如下:

An orthographic projection matrix, courtesy of Wikipedia.

参数包括:

  • 顶部:可见空间上边缘的Y坐标。
  • 底部:可见空间底边的Y坐标。
  • :可见空间左边缘的X坐标。
  • :可见空间右边缘的X坐标。

我认为这很简单。你建立的是一个空间区域,它将出现在屏幕上,你可以剪辑它。这里很简单,因为可见空间区域是一个矩形。剪切透视更复杂,因为屏幕上显示的区域或观看体积为frustrum

如果您在维基百科上进行透视投影时遇到困难,以下是构建合适矩阵的代码courtesy of geeks3D

void BuildPerspProjMat(float *m, float fov, float aspect,
float znear, float zfar)
{
  float xymax = znear * tan(fov * PI_OVER_360);
  float ymin = -xymax;
  float xmin = -xymax;

  float width = xymax - xmin;
  float height = xymax - ymin;

  float depth = zfar - znear;
  float q = -(zfar + znear) / depth;
  float qn = -2 * (zfar * znear) / depth;

  float w = 2 * znear / width;
  w = w / aspect;
  float h = 2 * znear / height;

  m[0]  = w;
  m[1]  = 0;
  m[2]  = 0;
  m[3]  = 0;

  m[4]  = 0;
  m[5]  = h;
  m[6]  = 0;
  m[7]  = 0;

  m[8]  = 0;
  m[9]  = 0;
  m[10] = q;
  m[11] = -1;

  m[12] = 0;
  m[13] = 0;
  m[14] = qn;
  m[15] = 0;
}

变量是:

  • fov :视野,pi / 4弧度是一个很好的价值。
  • 方面:高度与宽度的比率。
  • znear,zfar :用于裁剪,我会忽略这些。

并且生成的矩阵是列major,在上面的代码中索引如下:

0   4   8  12
1   5   9  13
2   6  10  14
3   7  11  15

视口转换,屏幕坐标

这两种转换都需要另一个矩阵矩阵将事物放在屏幕坐标中,称为视口转换。 That's described here, I won't cover it (it's dead simple)

因此,对于点p,我们会:

  • 执行模型转换矩阵* p,得到pm。
  • 执行投影矩阵* pm,得到pp。
  • 针对观看音量剪裁pp。
  • 执行视口转换矩阵* pp,结果是ps:屏幕上的点。

<强>摘要

我希望涵盖大部分内容。上面有漏洞,地方含糊不清,发布下面的任何问题。这个主题通常值得在教科书的整个章节中,我尽力提炼这个过程,希望对你有利!

我链接到上面的内容,但我强烈建议您阅读本文,并下载二进制文件。这是一个很好的工具,可以帮助您进一步理解这些变换以及它如何在屏幕上获得点数:

http://www.songho.ca/opengl/gl_transform.html

就实际工作而言,你需要为同构变换实现一个4x4矩阵类,以及一个可以与它相乘以应用变换的同类点类(记住,[x,y,z,1]) 。您需要生成如上所述和链接中的转换。一旦理解了这个程序,就不那么困难了。祝你好运:)。

答案 1 :(得分:10)

@BerlinBrown就像一般性评论一样,你不应该将你的相机旋转存储为X,Y,Z角度,因为这会导致模糊。

例如,x = 60度与-300度相同。当使用x,y和z时,模糊可能性的数量非常高。

相反,尝试在3D空间中使用两个点,相机位置使用x1,y1,z1,相机“目标”使用x2,y2,z2。角度可以向/从位置/目标向后计算,但在我看来,不建议这样做。使用摄像机位置/目标可以构建一个“LookAt”向量,它是摄像机方向的单位向量(v')。由此您还可以构建一个LookAt矩阵,它是一个4x4矩阵,用于将3D空间中的对象投影到2D空间中的像素。

请参阅this related question,其中我将讨论如何计算与摄像机正交的平面中的矢量R.

  

给定相机的矢量目标,v = xi,yj,zk
  归一化向量,v'= xi,yj,zk / sqrt(xi ^ 2 + yj ^ 2 + zk ^ 2)
  设U =全局世界向上矢量u = 0,0,1   然后我们可以计算R =与摄像机的视图方向平行的水平矢量R = v'^ U,
  其中^是交叉乘积,由
给出    a ^ b =(a2b3 - a3b2)i +(a3b1 - a1b3)j +(a1b2 - a2b1)k

     

这会给你一个看起来像这样的矢量。

     

Computing a vector orthogonal to the camera

这可能对您的问题有用,因为一旦您拥有LookAt Vector v',正交矢量R就可以开始从3D空间中的点投射到相机的平面上。

基本上所有这些3D操作问题归结为将世界空间中的一个点转换为局部空间,其中局部x,y,z轴与相机对齐。那有意义吗?因此,如果你有一个点,Q = x,y,z,你知道R和v'(摄像机轴),那么你可以使用简单的矢量操作将它投影到“屏幕”。可以使用Vectors上的点积运算符找出所涉及的角度。

Projecting Q onto the screen

答案 2 :(得分:5)

在维基百科之后,首先计算“d”:

http://upload.wikimedia.org/wikipedia/en/math/6/0/b/60b64ec331ba2493a2b93e8829e864b6.png

为此,请在代码中构建这些矩阵。从示例到变量的映射:

θ= Camera.angle*

a = SomePointIn3DSpace

c = Camera.x | y | z

或者,在不使用矩阵的情况下单独执行方程式,您的选择:

http://upload.wikimedia.org/wikipedia/en/math/1/c/8/1c89722619b756d05adb4ea38ee6f62b.png

现在我们计算“b”,一个2D点:

http://upload.wikimedia.org/wikipedia/en/math/2/5/6/256a0e12b8e6cc7cd71fa9495c0c3668.png

在这种情况下ex和ey是观众的位置,我相信在大多数图形系统中,屏幕尺寸的一半(0.5)用于默认设置(0,0)屏幕的中心,但你可以使用任何值(玩转)。 ez是视野发挥作用的地方。这是你缺少的一件事。选择一个fov角度并将ez计算为:

ez = 1 / tan(fov / 2)

最后,要获得bx和实际像素,您必须按与屏幕尺寸相关的因子进行缩放。例如,如果b从(0,0)映射到(1,1),则可以将x缩放1920,并将y缩放1080以用于1920 x 1080显示。这样任何屏幕尺寸都会显示相同的内容。当然,实际的3D图形系统还涉及许多其他因素,但这是基本版本。

答案 3 :(得分:4)

使用matrix简单地将3D空间中的点转换为屏幕上的2D点。使用矩阵计算点的屏幕位置,这可以为您节省大量的工作。

使用相机时,您应该考虑使用look-at-matrix并将矩阵的外观与投影矩阵相乘。

答案 4 :(得分:3)

假设相机位于(0,0,0)并指向正前方,则方程式为:

ScreenData.x = SomePointIn3DSpace.x / SomePointIn3DSpace.z * constant;
ScreenData.y = SomePointIn3DSpace.y / SomePointIn3DSpace.z * constant;

其中“常数”是一些正值。将其设置为以像素为单位的屏幕宽度通常会产生良好的效果如果将其设置得更高,那么场景将看起来更“放大”,反之亦然。

如果您希望相机处于不同的位置或角度,则需要移动并旋转场景,使相机处于(0,0,0)并指向正前方,然后您可以使用上面的等式。

您基本上计算穿过相机的线和3D点之间的交点,以及在相机前面稍微漂浮的垂直平面。

答案 5 :(得分:2)

您可能有兴趣了解幕后的GLUT does it。所有这些方法都有类似的文档,显示了进入它们的数学。

来自UCSD的三个第一次讲座可能非常有用,并且包含了关于这个主题的几个插图,据我所知,这是你真正想要的。

答案 6 :(得分:0)

您希望使用类似于OpenGL gluLookAt的矩阵转换场景,然后使用类似于OpenGL gluPerspective的投影矩阵计算投影。

您可以尝试仅计算矩阵并在软件中进行乘法运算。