我写了一个程序,它输入了一些点,以3D坐标表示,必须在2D画布中绘制。我使用透视投影,齐次坐标和类似的三角形来做到这一点。但是,我的程序不起作用,我实际上不知道为什么。
我遵循了两个教程。我真的了解我读过的几何定义和属性。但是,我的实现失败了......我会一点一点地写出对这两门课程的引用,以使你的阅读更加舒适:)。
透视投影是在这个工作流程之后完成的(参见这两个课程 - 我在这篇文章中进一步向下写了关于后者(HTML锚点)的相关链接):
绘制点的定义,根据世界坐标系表示;投影矩阵的定义,它是一个转换矩阵,可以转换"根据世界坐标系表示的点到根据摄像机坐标系表示的点(注意:我相信这个矩阵可以理解为3D对象"摄像机")
带有此矩阵的这些点的乘积(在本文档下面的适当部分中定义):这些世界表达点的乘积导致这些点转换为摄像机坐标系。注意,点和矩阵用4D表示(同质坐标的概念)。
使用类似的三角形概念在画布上投影(仅在此步骤中进行计算)相机内表达点(使用其4D坐标)。在此操作之后,点现在以3D表示(计算第三个坐标但在画布上实际上没有使用)。删除第4个坐标,因为没用。请注意,除了处理z-fighting(但我不想这样做)之外,第3个坐标不会有用。
最后一步:光栅化,实际绘制画布上的像素(其他计算和显示在此步骤完成)。
嗯,我想画一个立方体,但视角不起作用。投影点似乎是在没有透视的情况下绘制的。
我期待的结果是显示在" Image"中的立方体。以下是PNG的一部分:
我的立方体的面很奇怪,好像透视没有被很好地使用。
我认为我的投影矩阵(即:相机)没有良好的系数。 我使用非常简单的投影矩阵,没有fov,近和远剪裁平面的概念(如下所示)。
确实,为了获得预期的结果(如前所述),如果我没有弄错的话,应该在 中心 (在根据世界坐标系和 画布的 中心 (在轴x和y上)表示立方体的轴x和y)这是(我做出这个假设)在相机前放置1个z。
注意:由于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
)
}
})
}
}
我的计划有什么问题?我理解了这两个教程所解释的几何概念,我仔细阅读过。我很确定我的产品有效。我认为光栅化或条目(矩阵)可能是错误的......
答案 0 :(得分:1)
请注意,我使用的是最简单的透视投影矩阵:我不使用fov,近剪裁平面和远剪裁平面的概念。
我认为你的投影矩阵太简单了。通过删除近处和远处的剪裁平面,您将完全丢弃透视投影。
您不必执行z裁剪步骤,但您需要定义视锥体以使透视工作。我相信你的投影矩阵定义了一个立方的“视图截头”,因此没有视角。
有关投影矩阵如何工作的讨论,请参阅http://www.songho.ca/opengl/gl_projectionmatrix.html。
答案 1 :(得分:1)
引用Scratchapixel页面:
......如果我们在上面的等式中替换这些数字,我们得到:
y'
是P'
的y坐标。因此:这可能是计算机图形学中最简单,最基本的关系,称为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)
?