我写了一个程序,它输入了一些点,以3D坐标表示,必须在2D画布中绘制。我使用透视投影,齐次坐标和类似的三角形来做到这一点。但是,我的程序不起作用,实际上我不知道为什么。
我遵循了两个教程。我真的了解我读过的几何定义和属性。但是,我的实现失败了......我会一点一点地写出对这两门课程的引用,以使你的阅读更加舒适:)。
透视投影是在此工作流程之后完成的(参见这两个课程 - 我在本文中进一步编写了相关链接(HTML锚点)):
绘制点的定义,根据世界坐标系表示;投影矩阵的定义,它是一个变换矩阵,将根据世界坐标系表示的点“转换”为根据摄像机坐标系表示的点(NB:此矩阵也可以理解为摄像机)
带有此矩阵的这些点的乘积(如下面的适当部分所定义):这些点的乘积导致这些点转换为摄像机的坐标系。注意,点和矩阵用4D表示(同质坐标的概念)。
使用类似的三角形概念在画布上投影(仅在此步骤进行计算)相机内表达点(使用其4D坐标):它们现在以3D表示(第三个坐标是已计算但未在画布上实际使用
最后一步:光栅化,实际绘制画布上的像素(其他计算和显示在此步骤完成)。
好吧,我想画一个立方体,但它没有出现。投影点似乎是在相同的坐标上绘制的。
而不是我的立方体,只有一个黑色像素可见。
注意:由于Scastie上没有激活X11,我想创建的窗口将不会显示。
https://scastie.scala-lang.org/2LQ1wSMBTWqQQ7hql35sOg
问题可能与条目有关吗?好吧,我给你他们。
参考。 :我自己
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)
)
参考。 :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)
))
参考。 :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)
})
}
}
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)
}
})
}
}
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
}
}
我的计划有什么问题?我理解了这两个教程所解释的几何概念,我仔细阅读过。我很确定我的产品有效。我认为光栅化或条目(矩阵)可能是错误的......
答案 0 :(得分:1)
您在规范化设备坐标上调用toInt
(意味着有效范围为[0,1]):
normalized_drawn_point.head.toInt * IMAGE_WIDTH
-----
这会将其四舍五入为0或1,因此所有点都将位于屏幕的边框上。只有在之后再乘以屏幕分辨率:
(normalized_drawn_point.head * IMAGE_WIDTH).toInt
(从技术上讲,如果屏幕坐标从零开始,它应该是* (IMAGE_WIDTH - 1)
,这很常见。垂直方向也是如此。)