如何处理被剪裁到视图平面上的3D点的投影?

4
我正在创作一个基于软件的3D渲染器,以便了解概念和数学。这很有趣,我有一个漂亮的立方体在网格上旋转,充当一种地板的角色。网格/地板使用线段进行渲染。我使用简单的观察变换来定位和定向虚拟相机。视平面被任意设置为距离“眼睛”处的距离n,或者在z = -n处。
除了一件事情之外,一切都正常(从对象到世界到相机空间的变换、裁剪、投影、剔除、渲染)。当渲染网格时,线段端点可能跨越虚拟相机的视平面。我想呈现可见部分,所以要对视平面进行裁剪。裁剪后的端点被投影到视平面上。投影公式如下:
p'(x) = -n * p(x) / p(z)
p'(y) = -n * p(y) / p(z)

所有可能可见的点都会满足 p(z) ≤ -n。被剪裁到视平面上的点满足 p(z) = -n。因此,我实际上有:

p'(x) = p(x)
p'(y) = p(y)

这类点需要进行正投影。

这里的值很容易超出视图平面窗口,导致视口变换将这些点远离操作系统窗口的范围。效果是我看到偶尔会有杂线飞来飞去,非常糟糕。

除了像OpenGL那样做所有事情(我会用OpenGL!),我还漏掉了什么?

感谢您阅读到这里!

这是一个屏幕截图,显示了异常情况。网格的近右侧角刚好不在视野内。向近左侧角的线段在相机后方,因此被剪裁。端点经历了(错误的)正交投影,并最终远离实际位置。

我还没有执行任何视锥体裁剪(但我应该吗?)

enter image description here


我理解你的逻辑,但在我看来,这些点不应该有一个直接的正交投影! - Matt Lacey
这个问题混淆了几个问题。1)你混淆了窗口外和视点后面的主题。2)你提到正交投影,但这似乎是透视投影的问题。 - ideasman42
2个回答

0

在进行截锥体裁剪之前,您应该能够摆脱那些超出屏幕空间的点而无需查看截锥体积。因此,在执行截锥体裁剪之前,您的屏幕空间剪辑算法是什么样子的?可以看一下这些算法:http://en.wikipedia.org/wiki/Line_clipping 看看是否有所收益。

无论如何,我认为您应该考虑让您的渲染器能够处理位于操作系统窗口外部的点。使用上述剪辑算法,您可以裁剪完全落在窗口外部的线段,并夹紧仅一个点在窗口外或两个点都在窗口外但线段穿过屏幕空间的线段。


1
我正在剪辑那些在近平面后面的线段端点以接近近平面。由于投影平面位于z=-距离处,因此这将导致通过z除法来抵消距离项。这样,实际上就变成了正交投影。我正在使用标准SH剪切来剪切到窗口。 - z8000
这个答案非常泛泛而谈,没有回答问题(就我所看到的),维基百科的链接也没有真正提供任何关于在三维环境中进行线裁剪的见解。 - ideasman42
@z8000,关于“因此,投影实际上变成了正交投影。”这句话,我不确定你的意思,但听起来不太对。透视投影仅仅因为在视图后面就不会变成正交投影。 - ideasman42

0

刚刚研究了这个问题,发现执行此操作并不需要什么高深的技巧。

首先,可以定义近平面和远平面,然后通过这些平面剪裁线段(请参见示例)。

虽然这样做很好用,但我想避免在投影之上进行额外的计算。对于熟悉投影矩阵的人来说,这可能是显而易见的,但我还是要再次确认这样做是否有效。

事实证明,您可以使用简单的逻辑执行近/远线剪裁。

  1. 将位置与透视矩阵相乘,以获得4D向量。
  2. 将第四个分量与近/远剪裁距离进行比较。
  3. 如果需要,则剪裁线段。

可以通过在计算完整个投影之前计算向量的第四个分量来优化此过程。

这也意味着您无需在剪裁后重新计算XYZ分量。

例如:这将一个4D向量与4x4矩阵相乘。

pub fn mul_m4v4(m: &[[f64; 4]; 4], v: &[f64; 4]) -> [f64; 4] {
    [
        v[0] * m[0][0] + v[1] * m[1][0] + v[2] * m[2][0] + m[3][0] * v[3],
        v[0] * m[0][1] + v[1] * m[1][1] + v[2] * m[2][1] + m[3][1] * v[3],
        v[0] * m[0][2] + v[1] * m[1][2] + v[2] * m[2][2] + m[3][2] * v[3],
        v[0] * m[0][3] + v[1] * m[1][3] + v[2] * m[2][3] + m[3][3] * v[3],
    ]
}

由于这是一个三维位置,我们可以假设第四个分量为1.0。

pub fn mul_m4v3_as_v4(m: &[[f64; 4]; 4], v: &[f64; 3]) -> [f64; 4] {
    [
        v[0] * m[0][0] + v[1] * m[1][0] + v[2] * m[2][0] + m[3][0],
        v[0] * m[0][1] + v[1] * m[1][1] + v[2] * m[2][1] + m[3][1],
        v[0] * m[0][2] + v[1] * m[1][2] + v[2] * m[2][2] + m[3][2],
        v[0] * m[0][3] + v[1] * m[1][3] + v[2] * m[2][3] + m[3][3],
    ]
}

为了避免进行完整的计算,将一个单独的函数拆分出来以获取第四个组件。
pub fn mul_project_m4_v3_zfac(m: &[[f64; 4]; 4], v: &[f64; 3]) -> [f64; 4] {
    v[0] * m[0][3] + v[1] * m[1][3] + v[2] * m[2][3] + m[3][3]
}

这里有一个提交,它实现了上述所描述的剪辑。

注意:矩阵是列主序(像OpenGL一样)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接