为什么我的透视图实现无法显示我的多维数据集的面孔?

时间:2018-03-03 17:29:22

标签: scala 3d projection perspectivecamera projection-matrix

我写了一个程序,它输入了一些点,以3D坐标表示,必须在2D画布中绘制。我使用透视投影,齐次坐标和类似的三角形来做到这一点。但是,我的程序不起作用,我实际上不知道为什么。

我遵循了两个教程。我真的了解我读过的几何定义和属性。但是,我的实现失败了......我会一点一点地写出对这两门课程的引用,以使你的阅读更加舒适:)。

概述:几何提醒

透视投影是在这个工作流程之后完成的(参见这两个课程 - 我在这篇文章中进一步向下写了关于后者(HTML锚点)的相关链接):

  1. 绘制点的定义,根据世界坐标系表示;投影矩阵的定义,它是一个转换矩阵,可以转换"根据世界坐标系表示的点到根据摄像机坐标系表示的点(注意:我相信这个矩阵可以理解为3D对象"摄像机")

  2. 带有此矩阵的这些点的乘积(在本文档下面的适当部分中定义):这些世界表达点的乘积导致这些点转换为摄像机坐标系。注意,点和矩阵用4D表示(同质坐标的概念)。

  3. 使用类似的三角形概念在画布上投影(仅在此步骤中进行计算)相机内表达点(使用其4D坐标)。在此操作之后,点现在以3D表示(计算第三个坐标但在画布上实际上没有使用)。删除第4个坐标,因为没用。请注意,除了处理z-fighting(但我不想这样做)之外,第3个坐标不会有用。

  4. 最后一步:光栅化,实际绘制画布上的像素(其他计算和显示在此步骤完成)。

  5. 首先,问题

    嗯,我想画一个立方体,但视角不起作用。投影点似乎是在没有透视的情况下绘制的。

    我应该期待

    的结果

    我期待的结果是显示在" Image"中的立方体。以下是PNG的一部分:

    The result I'm expecting is the cube displayed in "Image" part of this PNG

    我输出的内容

    我的立方体的面很奇怪,好像透视没有被很好地使用。

    The faces of my cube are odd, as if perspective wasn't well used.

    我想我知道为什么我会遇到这个问题......

    我认为我的投影矩阵(即:相机)没有良好的系数。 我使用非常简单的投影矩阵,没有fov,近和远剪裁平面的概念(如下所示)。

    确实,为了获得预期的结果(如前所述),如果我没有弄错的话,应该在 中心 (在根据世界坐标系和 画布的 中心 (在轴x和y上)表示立方体的轴x和y)这是(我做出这个假设)在相机前放置1个z。

    Scastie(片段)

    注意:由于Scastie上没有激活X11,我想要创建的窗口不会显示。

    https://scastie.scala-lang.org/N95TE2nHTgSlqCxRHwYnxA

    条目

    问题可能与条目有关吗?好吧,我给你他们。

    立方体积分

    参考。 :我自己

    val world_cube_points : Seq[Seq[Double]] = Seq(
      Seq(100, 300, -4, 1), // top left
      Seq(100, 300, -1, 1), // top left z+1
      Seq(100, 0, -4, 1), // bottom left
      Seq(100, 0, -1, 1), // bottom left z+1
      Seq(400, 300, -4, 1), // top right
      Seq(400, 300, -1, 1), // top right z+1
      Seq(400, 0, -4, 1), // bottom right
      Seq(400, 0, -1, 1) // bottom right z+1
    )
    

    转换(投影)矩阵

    参考。 :https://www.scratchapixel.com/lessons/3d-basic-rendering/perspective-and-orthographic-projection-matrix/building-basic-perspective-projection-matrix,部分结束。 "一个简单的透视矩阵"

    请注意,我使用最简单的透视投影矩阵:我不使用fov,近剪裁平面和远剪裁平面的概念。

    new Matrix(Seq(
      Seq(1, 0, 0, 0),
      Seq(0, 1, 0, 0),
      Seq(0, 0, -1, 0),
      Seq(0, 0, -1, 0)
    ))
    

    此矩阵的后果:使用此矩阵生成的每个点P(x;y;z;w)将为:P'(x;y;-z;-z)

    第二,我的程序执行的第一个操作:带矩阵的点的简单乘积。

    参考。 :https://github.com/ssloy/tinyrenderer/wiki/Lesson-4:-Perspective-projection#homogeneous-coordinates

    /**
      * Matrix in the shape of (use of homogeneous coordinates) :
      * c00 c01 c02 c03
      * c10 c11 c12 c13
      * c20 c21 c22 c23
      *   0   0   0   1
      *
      * @param content the content of the matrix
      */
    class Matrix(val content : Seq[Seq[Double]]) {
    
      /**
        * Computes the product between a point P(x ; y ; z) and the matrix.
        *
        * @param point a point P(x ; y ; z ; 1)
        * @return a new point P'(
        *         x * c00 + y * c10 + z * c20
        *         ;
        *         x * c01 + y * c11 + z * c21
        *         ;
        *         x * c02 + y * c12 + z * c22
        *         ;
        *         1
        *         )
        */
      def product(point : Seq[Double]) : Seq[Double] = {
        (0 to 3).map(
          i => content(i).zip(point).map(couple2 => couple2._1 * couple2._2).sum
        )
      }
    
    }
    

    然后,使用类似的三角形

    参考。 1/2:部分。 "将点转换为相机空间的重要性 " https://www.scratchapixel.com/lessons/3d-basic-rendering/computing-pixel-coordinates-of-3d-point/mathematics-computing-2d-coordinates-of-3d-points

    参考。 2/2:https://github.com/ssloy/tinyrenderer/wiki/Lesson-4:-Perspective-projection#time-to-work-in-full-3d

    注意:在此步骤中,条目是根据摄像机表示的点(即:它们是先前定义的产品的结果,具有先前定义的矩阵)。

    class Projector {
    
      /**
        * Computes the coordinates of the projection of the point P on the canvas.
        * The canvas is assumed to be 1 unit forward the camera.
        * The computation uses the definition of the similar triangles.
        *
        * @param points the point P we want to project on the canvas. Its coordinates must be expressed in the coordinates
        *          system of the camera before using this function.
        * @return the point P', projection of P.
        */
      def drawPointsOnCanvas(points : Seq[Seq[Double]]) : Seq[Seq[Double]] = {
        points.map(point => {
          point.map(coordinate => {
            coordinate / point(3)
          }).dropRight(1)
        })
    
      }
    
    }
    

    最后,将投影点绘制到画布上。

    参考。 :部分。 "从屏幕空间到光栅空间" https://www.scratchapixel.com/lessons/3d-basic-rendering/computing-pixel-coordinates-of-3d-point/mathematics-computing-2d-coordinates-of-3d-points

    import java.awt.Graphics
    import javax.swing.JFrame
    
    /**
      * Assumed to be 1 unit forward the camera.
      * Contains the drawn points.
      */
    class Canvas(val drawn_points : Seq[Seq[Double]]) extends JFrame {
    
      val CANVAS_WIDTH = 820
      val CANVAS_HEIGHT = 820
      val IMAGE_WIDTH = 900
      val IMAGE_HEIGHT = 900
    
      def display = {
        setTitle("Perlin")
        setSize(IMAGE_WIDTH, IMAGE_HEIGHT)
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)
        setVisible(true)
      }
    
      override def paint(graphics : Graphics): Unit = {
        super.paint(graphics)
        drawn_points.foreach(point => {
    
          if(!(Math.abs(point.head) <= CANVAS_WIDTH / 2 || Math.abs(point(1)) <= CANVAS_HEIGHT / 2)) {
            println("WARNING : the point (" + point.head + " ; " + point(1) + ") can't be drawn in this canvas.")
          } else {
            val normalized_drawn_point = Seq((point.head + (CANVAS_WIDTH / 2)) / CANVAS_WIDTH, (point(1) + (CANVAS_HEIGHT / 2)) / CANVAS_HEIGHT)
            graphics.fillRect((normalized_drawn_point.head * IMAGE_WIDTH).toInt, ((1 - normalized_drawn_point(1)) * IMAGE_HEIGHT).toInt, 5, 5)
    
            graphics.drawString(
              "P(" + (normalized_drawn_point.head * IMAGE_WIDTH).toInt + " ; "
              + ((1 - normalized_drawn_point(1)) * IMAGE_HEIGHT).toInt + ")",
              (normalized_drawn_point.head * IMAGE_WIDTH).toInt - 50, ((1 - normalized_drawn_point(1)) * IMAGE_HEIGHT).toInt - 10
            )
          }
        })
      }
    
    }
    

    问题

    我的计划有什么问题?我理解了这两个教程所解释的几何概念,我仔细阅读过。我很确定我的产品有效。我认为光栅化或条目(矩阵)可能是错误的......

2 个答案:

答案 0 :(得分:1)

  

请注意,我使用的是最简单的透视投影矩阵:我不使用fov,近剪裁平面和远剪裁平面的概念。

我认为你的投影矩阵太简单了。通过删除近处和远处的剪裁平面,您将完全丢弃透视投影。

您不必执行z裁剪步骤,但您需要定义视锥体以使透视工作。我相信你的投影矩阵定义了一个立方的“视图截头”,因此没有视角。

有关投影矩阵如何工作的讨论,请参阅http://www.songho.ca/opengl/gl_projectionmatrix.html

答案 1 :(得分:1)

引用Scratchapixel页面:

  

......如果我们在上面的等式中替换这些数字,我们得到:

     

enter image description here

     

y'P'的y坐标。因此:

     

enter image description here

     

这可能是计算机图形学中最简单,最基本的关系,称为z或透视鸿沟。完全相同的原理适用于x坐标。 ...

在您的代码中:

def drawPointsOnCanvas(points : Seq[Seq[Double]]) : Seq[Seq[Double]] = {
    points.map(point => {
      point.map(coordinate => {
        coordinate / point(3)
                     ^^^^^^^^
    ...

(3)索引是point第4个分量,即其W坐标,而不是其Z坐标。也许你的意思是coordinate / point(2)