使用相机将基本渲染3D透视投影到2D屏幕上(无opengl)

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

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

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

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

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

同质向量,点,变换

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

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

有3个主要的同质转换:

  • 翻译,
  • 旋转,和
  • 缩放。

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

正视和透视

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

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

正交投影矩阵构造如下:

一个正交投影矩阵,由维基百科提供。

参数包括:

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

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

如果你对维基百科的透视投影很难,这里是构建合适矩阵的代码, 由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 

视口转换,屏幕坐标

这两种转换都需要另一个矩阵矩阵将事物放在屏幕坐标中,称为视口转换。 这里描述的,我不会介绍它(这很简单) 。

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

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

概要

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

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

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

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

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

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

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

请参阅此相关问题 ,我将讨论如何计算矢量R,该矢量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

这将为您提供一个看起来像这样的矢量。

计算与相机正交的矢量

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

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

将Q投影到屏幕上

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

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

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

θ= Camera.angle*

a = SomePointIn3DSpace

c = Camera.x | y | z 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图形系统还涉及许多其他因素,但这是基本版本。

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

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

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

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

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

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

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

您可能只想看看GLUT在幕后如何做到这一点 。 所有这些方法都有类似的文档,显示了进入它们的数学。

来自加州大学圣地亚哥分校的三个第一次讲座可能非常有帮助,并且包含了关于这个主题的几个插图,据我所知,这是你真正追求的。

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

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

通过光线跟踪器运行它:

C#中的Ray Tracer – 他所拥有的一些对象对你来说会很熟悉;-)

只是为了踢LINQ版本

我不确定你的应用程序的更大目的是什么(你应该告诉我们,它可能会引发更好的想法),但是很明显投影和光线追踪是不同的问题集,它们有很多重叠。

如果你的应用只是试图绘制整个场景,那就太好了。

解决问题#1不会投射遮挡点。
解决方案 :虽然我没有在博客页面上看到任何关于不透明度或透明度的内容,但您可以添加这些属性和代码来处理一个反弹(正常)和继续(对于“透明度”)的光线。

解决问题#2投影单个像素将需要对所有像素进行昂贵的全图像跟踪
显然,如果您只是想绘制对象,请使用光线跟踪器来获取它的用途! 但是如果你想在图像中查找数千个像素,从随机对象的随机部分(为什么?),为每个请求做一个完整的光线跟踪将是一个巨大的性能狗。

幸运的是,通过更多地调整代码,您可以预先进行一次光线跟踪(具有透明度),并缓存结果直到对象发生变化。

如果您对光线跟踪不熟悉,请阅读博客条目 – 我认为它解释了事物从每个2D像素到对象,然后是确定像素值的灯光的实际工作方式。

您可以添加代码,以便与对象建立交叉点,构建由对象的交叉点索引的列表,其中项目是要跟踪的当前2d像素。

然后,当你想要投射一个点时,转到该对象的列表,找到你想要投影的点的最近点,并查找你关心的第二个像素。 数学将远比文章中的方程式更小。 不幸的是,使用例如对象的字典+点结构映射到2d像素,我不知道如何在不遍历整个映射点列表的情况下找到对象上的最近点。 虽然这不是世界上最慢的东西,你可能想出来,但我没有时间考虑它。 任何人?

祝你好运!

另外,我不明白在wiki条目中观众的位置与相机位置有什么关系 ”……我99%肯定这是同样的事情。