我没有实现透视投影 [英] My unworking implementation of perspective projection

查看:161
本文介绍了我没有实现透视投影的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



我遵循了两个教程。我真的了解我读过的几何定义和属性。但是,我的实现失败了......我会逐渐引用这两个课程,以使您的阅读更加容易:)。
$ b

概述:几何提示 h1>

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


  1. 绘制点的定义,根据世界坐标系表示;投影矩阵的定义是将根据世界坐标系表示的点转换为根据相机坐标系表示的点的转换矩阵(注意:该矩阵也可以理解为相机)这些点的乘积(在下面的适当部分中定义):这些点的乘积导致这些点转换为相机的点坐标系统。请注意,点和矩阵用4D表示(均匀坐标的概念)。

  2. 使用类似的三角形概念进行投影(仅在此步骤完成计算)在画布上显示相机内的点(使用它们的4D坐标):它们现在用3D表示(第三个坐标是计算出来的,但实际上并未在画布上使用)

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




首先,问题



嗯,我想绘制一个立方体,但它不会出现。投影点似乎在同一坐标上绘制。



不是我的立方体,只有一个黑色像素可见。





Scastie(snippet)



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



https://scastie.scala-lang.org/2LQ1wSMBTWqQQ7hql35sOg



条目



也许这个问题是绑定到条目上的?好的,我给你。

立方体的分数



参考文献。 :我自己

  val world_cube_points:Seq [Seq [Double]] = Seq(
Seq(0,40,0 ,
Seq(0,40,10,1),
Seq(0,0,0,1),
Seq(0,0,10,1),
Seq(20,40,0,1),
Seq(20,40,10,1),
Seq(20,0,0,1),
Seq(20,40,0,1)转换(投影)矩阵 0,10,1)
) >

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



<$矩阵=新矩阵(Seq(
Seq(1,0,0,0)),
Seq(0,1,0,0),p $ p> val matrix_world_to_camera:
Seq(0,0,1,0),
Seq(0,0,-1,1)
))



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



Ref 。 : https://github.com/ssloy/

  / ** $ b $小学生/维基/ Lesson-4:-Perspective-projection#homogeneous-coordinates  

b *矩阵形式(使用同类坐标):
* c00 c01 c02 c03
* c10 c11 c12 c13
* c20 c21 c22 c23
* 0 0 0 1
*
* @param内容矩阵的内容
* /
class Matrix(val内容:Seq [Seq [Double]]){

/ **
*计算点P(x; y; z)与矩阵之间的乘积。
*
* @param指向一个点P(x; y; z; 1)
* @返回一个新点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

}


$ / code>



然后,使用相似的三角形



Ref。 1/2:部分。 转换点对相机空间的重要性
of https://www.scratchapixel.com/lessons/3d-basic-rendering/computing-pixel-coordinates-of-3d-点/数学计算-2d-坐标-3点



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



注意:在这一步,这些条目是根据相机表示的点(即:它们是先前定义的产品与先前定义的矩阵的结果)。

  class Projector {

/ **
*计算画布上点P投影的坐标。
*画布假定为1个单位转发相机。
*计算使用类似三角形的定义。
*
* @param指出我们想要在画布上投影的点P.在使用此功能之前,其坐标必须在相机的坐标
*系统中表示。
* @返回点P',P的投影
* /
def drawPointsOnCanvas(points:Seq [Seq [Double]]):Seq [Seq [Double]] = {$ b $ point.map(point => {
point.map(coordinate => {
coordinate / point(3)
})。dropRight(1)
})

}

}



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



Ref。 :部分。 从屏幕空间到光栅空间 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

/ **
*假定为1个单元转发摄像机。
*包含绘制的点。
* /
class Canvas(val drawn_points:Seq [Seq [Double]])扩展JFrame {

val CANVAS_WIDTH = 60
val CANVAS_HEIGHT = 60
val IMAGE_WIDTH = 55
val IMAGE_HEIGHT = 55

def display = {
setTitle(Perlin)
setSize(CANVAS_WIDTH,CANVAS_HEIGHT)
setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE)
setVisible(true)
}

覆盖def paint(graphics:Graphics):Unit = {
super.paint(graphics)$ b (b)(point.head)(=点数(1))(b) )
println(警告:point(+ point.head +;+ point(1)+)无法在此画布中绘制。 (点(1)+(CANVAS_HEIGHT / 2))/ CANVAS_HEIGHT)
graphics.drawRect((点头) normalized_drawn_point.head.toInt * IMAGE_WIDTH,(1 - normalized_drawn_point(1).toInt)* IMAGE_HEIGHT,1,1)
}
})
}

}



...和启动器



  object Main {
def main(args:Array [String]):Unit = {
val projector = new Projector()

val world_cube_points:Seq [Seq [Double]] = Seq(
Seq(0,40,0,1),
Seq(0,40,10,1),
Seq(0,0,0,1 ),
Seq(0,0,10,1),
Seq(20,40,0,1),
Seq(20,40,10,1),
Seq(20,0,0,1),
Seq(20,0,10,1)


val matrix_world_to_camera:Matrix = new Matrix(Seq(
Seq(1,0,0,0),
Seq(0,1,0,0),
Seq(0,0,1,0),
Seq( 0,-1,1)
))

val points_to_draw_on_canvas = projector.drawPointsOnCanvas(world_cube_points.map(point => {
matrix_world_to_camera.product(point)
)))
new Canvas(points_to_draw_on_canvas).display

}
}



问题



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

解决方案

您称 toInt 在标准化的设备坐标上(意思是有效范围是[0,1]):

  normalized_drawn_point.head.toInt * IMAGE_WIDTH 
-----

这会将其舍入为0或1,所有点将位于屏幕的边框上。 乘以屏幕分辨率:

 (normalized_drawn_point.head * IMAGE_WIDTH).toInt 



<技术上它应该是 *(IMAGE_WIDTH - 1)如果屏幕坐标从零开始,这很常见,垂直方向也是如此。)


I wrote a program that takes in entry some points, expressed in 3D coordinates and that must be drawn in a 2D canvas. I use perspective projection, homogeneous coordinates and similar triangles to do that. However, my program does not work and I actually don't know why.

I followed two tutorials. I really understood the geometrical definitions and properties I have read. However, my implementation fails... I will write references to these both courses little by little, to make your reading more confortable :).

Overview : geometrical reminders

The perspective projection is done following this workflow (cf. these 2 courses - I wrote pertinent links (HTML anchors) further down, in this post) :

  1. Definition of the points to draw, expressed according to the world's coordinates system ; Definition of the matrix of projection, which is a matrix of transformation that "converts" a point expressed according to the world coordinates system into a point expressed according to the camera's coordinates system (NB : this matrix also can be understood as being the camera)

  2. Product of these points with this matrix (as defined in the adequat part, below) : the product these points results in the conversion of these points to the camera's coordinates system. Note that points and matrix are expressed in 4D (concept of homogenous coordinates).

  3. Use of similar triangles concept to project (only computing is done at this step) on the canvas the in-camera-expressed points (using their 4D coordinates) : they are now expressed in 3D (the third coordinate is computed but not actually used on the canvas)

  4. Last step : rasterization, to actually draw the pixels on the canvas (other computing AND displaying are done at this step).

First, the problem

Well, I want to draw a cube but it doesn't appear. The projected points seem to be drawn on the same coordinates.

Instead of my cube, only one black pixel is visible.

The Scastie (snippet)

NB : since X11 is not activated on Scastie, the window I want to create won't be shown.

https://scastie.scala-lang.org/2LQ1wSMBTWqQQ7hql35sOg

Entries

Perhaps the problem is bound to the entries ? Well, I give you them.

Cube's points

Ref. : myself

val world_cube_points : Seq[Seq[Double]] = Seq(
  Seq(0, 40, 0, 1),
  Seq(0, 40, 10, 1),
  Seq(0, 0, 0, 1),
  Seq(0, 0, 10, 1),
  Seq(20, 40, 0, 1),
  Seq(20, 40, 10, 1),
  Seq(20, 0, 0, 1),
  Seq(20, 0, 10, 1)
)

Transformation (Projection) matrix

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

val matrix_world_to_camera : Matrix = new Matrix(Seq(
  Seq(1, 0, 0, 0),
  Seq(0, 1, 0, 0),
  Seq(0, 0, 1, 0),
  Seq(0, 0, -1, 1)
))

Second, the first operation my program does : a simple product of a point with a matrix.

Ref. : 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
    )
  }

}

Then, use of similar triangles

Ref. 1/2 : Part. "Of the Importance of Converting Points to Camera Space " of https://www.scratchapixel.com/lessons/3d-basic-rendering/computing-pixel-coordinates-of-3d-point/mathematics-computing-2d-coordinates-of-3d-points

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

NB : at this step, the entries are points expressed according to the camera (i.e. : they are the result of the precedently defined product with the precedently defined matrix).

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)
    })

  }

}

Finally, the drawing of the projected points, onto the canvas.

Ref. : Part. "From Screen Space to Raster Space" of 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 = 60
  val CANVAS_HEIGHT = 60
  val IMAGE_WIDTH = 55
  val IMAGE_HEIGHT = 55

  def display = {
    setTitle("Perlin")
    setSize(CANVAS_WIDTH, CANVAS_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.drawRect(normalized_drawn_point.head.toInt * IMAGE_WIDTH, (1 - normalized_drawn_point(1).toInt) * IMAGE_HEIGHT, 1, 1)
      }
    })
  }

}

... and the launcher

object Main {
  def main(args : Array[String]) : Unit = {
    val projector = new Projector()

    val world_cube_points : Seq[Seq[Double]] = Seq(
      Seq(0, 40, 0, 1),
      Seq(0, 40, 10, 1),
      Seq(0, 0, 0, 1),
      Seq(0, 0, 10, 1),
      Seq(20, 40, 0, 1),
      Seq(20, 40, 10, 1),
      Seq(20, 0, 0, 1),
      Seq(20, 0, 10, 1)
    )

    val matrix_world_to_camera : Matrix = new Matrix(Seq(
      Seq(1, 0, 0, 0),
      Seq(0, 1, 0, 0),
      Seq(0, 0, 1, 0),
      Seq(0, 0, -1, 1)
    ))

    val points_to_draw_on_canvas = projector.drawPointsOnCanvas(world_cube_points.map(point => {
      matrix_world_to_camera.product(point)
    }))
    new Canvas(points_to_draw_on_canvas).display

  }
}

Question

What's wrong with my program ? I understood the geometrical concepts explained by these both tutorials that I read carefully. I'm pretty sure my product works. I think either the rasterization, or the entries (the matrix) could be wrong...

解决方案

You called toInt on a normalized device coordinate (meaning that the valid range is [0, 1]):

normalized_drawn_point.head.toInt * IMAGE_WIDTH
                            ----- 

This will round it to either 0 or 1 so all points will be on the border of the screen. Only round after you multiply by the screen resolution:

(normalized_drawn_point.head * IMAGE_WIDTH).toInt

(Technically it should be * (IMAGE_WIDTH - 1) if screen coordinates start from zero, which is very common. Similarly for the vertical.)

这篇关于我没有实现透视投影的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆