透视投影bug(不懂理论)
Perspective projection bug (not understanding theory)
我正在使用 CPU 实现我自己的基于光栅化和深度缓冲的渲染器。正如您在下一张图片中看到的,它 有效 !
但是,有一点非常不对劲!即使盒子看起来像一个立方体,它的尺寸也是 1000x1000x10。 "foreshortening" 太高了。如果我将尺寸更改为 1000x1000x1000,则框变为无限大:
发生这种情况是因为我在透视投影中遗漏了一些东西。当我进行透视投影(3D 世界到 2D 屏幕)时,我应用视图变换(将坐标系放置在相机位置并具有正确的方向)。为了简化事情,我的相机与世界的方向相同,唯一改变的是位置,在 (0, 0, -1):
const Point3D point_camera = {
point_world.x * m_left.x + point_world.y * m_up.x + point_world.z * m_forward.x - m_position.x,
point_world.x * m_left.y + point_world.y * m_up.y + point_world.z * m_forward.y - m_position.y,
point_world.x * m_left.z + point_world.y * m_up.z + point_world.z * m_forward.z - m_position.z
};
然后我应用透视除法,将 3D 世界点的每个分量除以其 Z:
const Point2D point_projected = {
(point_camera.x * m_near * m_zoom) / point_camera.z,
(point_camera.y * m_near * m_zoom) / point_camera.z
};
我觉得我应该将 Z 乘以某种因子...但我想不出来。也许 w
与此有关?如果有人可以帮助我或转发给我对透视投影背后的理论的一个很好的解释,我将非常感激。
所有代码都在我的 github 上。相关的 类 是 PerspectiveCamera
和 ForwardRasterizer
因此,如果您想使用 4x4 矩阵转换 3 维向量或点,您可能已经知道,您需要用另一个组件(通常称为 w 来扩充它).为了适当的变换 w 通常选择为 1 这样 (x, y, z) 变成 (x , y, z, 1).
假设您的点已经转换为视图 space,因此它是相机的 'in front'。您要执行的下一个也是最后一个转换是实际投影。如果将向量与 this 矩阵(与 Datenwolfs 有点不同)相乘,您将得到类似于此
的结果列向量
我应该提一下,在这个向量中,我省略了 x 和 z 的比例缩放,只是为了使事情成为一个更清楚一点。
您可以看到向量中的第三个元素有效地归一化了 z,正如您提到的那样。假设 w 仍然是 1,我们的远平面 f 是 100,而 n 我们的近平面plane为10,有了这个假设,第三个元素就变成了。
如果您在 [10, 100] 范围内为 z 插入几个不同的数字,那么您会发现这确实只是对 z.
投影的魔力发生在一个看不见的步骤中,除以 x'、y'、 z' by w' 将实际透视效果应用于 x' 和 y' 在屏幕 space 中,但也会重新规范化您的 z',使其值在点远离观察者时看起来像这样。
附带说明一下,图表的形状是有意设计的。这是为了利用深度缓冲区的精度限制,但那是另一个讨论。
我最后要注意的是列向量中 w' 的负值。您可能会想,"Why on earth would they do that? Wouldn't that flip everything vertically and horizontally?" 您是对的,这正是它的作用。原因是我已经提到过几次了。标准视图体积,对于 OpenGL 而言,屏幕 space 映射看起来像这样。
与其他屏幕坐标系不同,原点在屏幕死点。无论屏幕尺寸如何。除了那个怪癖之外,最积极的坐标是在屏幕的右上角,而不是右下角。因此,在许多情况下,您的投影坐标必须翻转,因此 -z 作为 w'.
的值
希望对您有所帮助!
我正在使用 CPU 实现我自己的基于光栅化和深度缓冲的渲染器。正如您在下一张图片中看到的,它 有效 !
但是,有一点非常不对劲!即使盒子看起来像一个立方体,它的尺寸也是 1000x1000x10。 "foreshortening" 太高了。如果我将尺寸更改为 1000x1000x1000,则框变为无限大:
发生这种情况是因为我在透视投影中遗漏了一些东西。当我进行透视投影(3D 世界到 2D 屏幕)时,我应用视图变换(将坐标系放置在相机位置并具有正确的方向)。为了简化事情,我的相机与世界的方向相同,唯一改变的是位置,在 (0, 0, -1):
const Point3D point_camera = {
point_world.x * m_left.x + point_world.y * m_up.x + point_world.z * m_forward.x - m_position.x,
point_world.x * m_left.y + point_world.y * m_up.y + point_world.z * m_forward.y - m_position.y,
point_world.x * m_left.z + point_world.y * m_up.z + point_world.z * m_forward.z - m_position.z
};
然后我应用透视除法,将 3D 世界点的每个分量除以其 Z:
const Point2D point_projected = {
(point_camera.x * m_near * m_zoom) / point_camera.z,
(point_camera.y * m_near * m_zoom) / point_camera.z
};
我觉得我应该将 Z 乘以某种因子...但我想不出来。也许 w
与此有关?如果有人可以帮助我或转发给我对透视投影背后的理论的一个很好的解释,我将非常感激。
所有代码都在我的 github 上。相关的 类 是 PerspectiveCamera
和 ForwardRasterizer
因此,如果您想使用 4x4 矩阵转换 3 维向量或点,您可能已经知道,您需要用另一个组件(通常称为 w 来扩充它).为了适当的变换 w 通常选择为 1 这样 (x, y, z) 变成 (x , y, z, 1).
假设您的点已经转换为视图 space,因此它是相机的 'in front'。您要执行的下一个也是最后一个转换是实际投影。如果将向量与 this 矩阵(与 Datenwolfs 有点不同)相乘,您将得到类似于此
的结果列向量我应该提一下,在这个向量中,我省略了 x 和 z 的比例缩放,只是为了使事情成为一个更清楚一点。
您可以看到向量中的第三个元素有效地归一化了 z,正如您提到的那样。假设 w 仍然是 1,我们的远平面 f 是 100,而 n 我们的近平面plane为10,有了这个假设,第三个元素就变成了。
如果您在 [10, 100] 范围内为 z 插入几个不同的数字,那么您会发现这确实只是对 z.
投影的魔力发生在一个看不见的步骤中,除以 x'、y'、 z' by w' 将实际透视效果应用于 x' 和 y' 在屏幕 space 中,但也会重新规范化您的 z',使其值在点远离观察者时看起来像这样。
附带说明一下,图表的形状是有意设计的。这是为了利用深度缓冲区的精度限制,但那是另一个讨论。
我最后要注意的是列向量中 w' 的负值。您可能会想,"Why on earth would they do that? Wouldn't that flip everything vertically and horizontally?" 您是对的,这正是它的作用。原因是我已经提到过几次了。标准视图体积,对于 OpenGL 而言,屏幕 space 映射看起来像这样。
与其他屏幕坐标系不同,原点在屏幕死点。无论屏幕尺寸如何。除了那个怪癖之外,最积极的坐标是在屏幕的右上角,而不是右下角。因此,在许多情况下,您的投影坐标必须翻转,因此 -z 作为 w'.
的值希望对您有所帮助!