【问题标题】:Basic render 3D perspective projection onto 2D screen with camera (without opengl)使用相机将 3D 透视投影基本渲染到 2D 屏幕(无 opengl)
【发布时间】:2012-01-27 19:34:41
【问题描述】:

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

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

【问题讨论】:

    标签: c# java math 3d projection


    【解决方案1】:

    您可能有兴趣了解GLUT does it 的幕后工作。所有这些方法都有类似的文档来显示其中的数学原理。

    UCSD 的前三讲可能很有帮助,并且包含关于这个主题的几个插图,据我所知,这就是你真正想要的。

    【讨论】:

      【解决方案2】:

      只需使用matrix 即可将 3D 空间中的点转换为屏幕上的 2D 点。使用矩阵计算你的点在屏幕上的位置,这样可以节省你很多工作。

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

      【讨论】:

      • @BerlinBrown 很高兴我能提供帮助。如果您对矩阵有疑问,请在此处询问,我将发布一些示例(我有一个用于矩阵的工作库)。
      • 能否添加或显示它们在矩阵乘法后转换为二维坐标的位置。
      • @BerlinBrown 看看这里:answers.yahoo.com/question/index?qid=20090624084105AAmVf6q 它是一个简单的矩阵乘法与一个 4x1 矩阵(Vector4; x,y,z,w; w 是 1 )。我不在我的工作电脑上,否则我会复制代码。
      【解决方案3】:

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

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

      【讨论】:

      • 没有,但是原理是一样的。如果您查看这些函数的文档,您将看到它们如何计算它们的矩阵。也许你可以直接在你的程序中使用它们?
      【解决方案4】:

      按照维基百科,先计算“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”,一个二维点:

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

      【讨论】:

        【解决方案5】:

        通过光线追踪器运行它:

        Ray Tracer in C# - 他拥有的一些物品你会觉得很熟悉;-)

        只是为了踢LINQ version

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

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

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

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

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

        如果您不熟悉光线追踪,请阅读博客文章 - 我认为它解释了从每个 2D 像素到物体、然后是决定像素值的灯光的实际工作原理。

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

        然后,当您要投影一个点时,请转到该对象的列表,找到离您要投影的点最近的点,然后查找您关心的 2d 像素。数学比您文章中的方程式要少得多。 不幸的是,例如使用您的对象+点结构映射到 2d 像素的字典,我不确定如何在不遍历整个映射点列表的情况下找到对象上的最近点。虽然这不会是世界上最慢的事情,你可能会弄清楚,但我只是没有时间去想它。有人吗?

        祝你好运!

        另外,我在 wiki 条目中不明白观众的位置与相机位置的关系是什么”......我 99% 确定这是同一件事。

        【讨论】:

          【解决方案6】:

          “完成方式”是使用同质变换和坐标。你在空间中取一个点:

          • 使用模型矩阵将其相对于相机定位。
          • 使用投影矩阵进行正交投影或透视投影。
          • 应用视口转换将其放置在屏幕上。

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

          同质向量、点、变换

          在 3D 中,同质点是 [x, y, z, 1] 形式的列矩阵。最后一个组件是'w',一个比例因子,对于向量来说是0:这意味着你不能翻译向量,这在数学上是正确的。我们不会去那里,我们在谈论要点。

          齐次变换是 4x4 矩阵,使用它们是因为它们允许将平移表示为矩阵乘法,而不是加法,这对您的视频卡来说既好又快。也很方便,因为我们可以通过将它们相乘来表示连续的变换。我们通过执行transformation * point来对点应用变换。

          有 3 种主要的齐次变换:

          还有其他值得探索的,特别是“看”转换。但是,我只想提供一个简短的列表和一些链接。应用于点的移动、缩放和旋转的连续应用统称为模型变换矩阵,并将它们相对于相机放置在场景中。重要的是要意识到我们所做的是类似于在相机周围移动物体,而不是相反。

          正交和透视

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

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

          一个正交投影矩阵的构造如下:

          其中参数包括:

          • Top:可见空间上边缘的 Y 坐标。
          • 底部:可见空间底部边缘的 Y 坐标。
          • Left:可见空间左边缘的X坐标。
          • Right:可见空间右边缘的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 弧度是一个不错的值。
          • aspect:高宽比。
          • znear, zfar:用于裁剪,我将忽略这些。

          并且生成的矩阵是column 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]) .您需要按照上面和链接中的描述生成转换。一旦你了解了程序,这并不是那么困难。祝你好运:)。

          【讨论】:

            【解决方案7】:

            @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

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

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

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

            【讨论】:

            • 这是一个很好的答案和简单的拼写技术。但是,有一点:如果相机和目标不在同一个 XZ 平面(相同高度)中,那么您不能使用全局“向上”向量将这些点投影到上面。相反,通过将 V 与 U 交叉推导出 R,然后通过将 R 与 V 交叉推导出实际的向上向量以获得正交基。
            【解决方案8】:

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

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

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

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

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2018-05-03
              • 1970-01-01
              • 2023-03-03
              • 2019-06-08
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多