三维透视投影的剪辑矩阵

6
我正在尝试创建一个简单的3D图形引擎,并找到并使用了此处的方程:http://en.wikipedia.org/wiki/3D_projection#cite_note-0(我有Dx、Dy、Dz和Bx、By的计算)。它可以工作,但是当我旋转相机足够多次时,线条开始四处飞动,最终你会看到超出屏幕的多边形开始从屏幕的对面回来(你可以访问http://mobile.sheridanc.on.ca/~claassen/3d.html 并使用W、A、S和D键旋转相机,以查看我所说的内容)。
我阅读了这个讨论:How to convert a 3D point into 2D perspective projection?,其中他谈到使用剪辑矩阵,但我仍然有些困惑,不确定如何确切地使用。此外,我不确定是否像讨论中描述的那样使用了“齐次坐标”。
2个回答

29

将透视投影矩阵(也称剪裁矩阵)乘以后,您将得到一个齐次的4向量[x,y,z,w]。 这被称为npc(标准化投影坐标),也称为剪辑坐标。 为了获得屏幕上的2D坐标,通常使用类似以下的方法:

xscreen = (x/w) * screen_width
yscreen = (y/w) * screen_width

对于相机前的点,这会给你想要的结果。但是相机后面的点将具有w<0,即使该点在相机后面,您仍将获得映射到有效屏幕坐标的值。为避免这种情况,您需要进行剪切。任何具有w<0的顶点都需要被剪切。

一个快速的解决方法是,如果任一顶点具有w<0,则不绘制任何线条。这应该可以修复场景中出现的奇怪多边形。但是这也会删除应该可见的一些线条。

要完全解决问题,您需要剪切所有具有相机前方一个顶点和相机后方一个顶点的线段。剪切意味着将线段分成两半,并丢弃在相机后面的那一半。通过一个穿过相机并平行于显示屏幕的平面来“剪切”该线段。问题是找到与该平面相交的线段上的点(即线段与平面相交的点)。这将发生在w==0的线段上的点处。您可以找到此点,但是当您尝试找到屏幕坐标时

xscreen = (x/w) * screen_width
yscreen = (y/w) * screen_width

如果你除以0(w==0),就会出现问题。这就是“近裁剪平面”的原因。“近裁剪平面”也与显示屏平行,但在相机前面(在相机和场景之间)。相机和近裁剪平面之间的距离是投影矩阵的“近”参数:

[    near/width   ][        0        ][         0              ][        0       ]
[        0        ][    near/height  ][         0              ][        0       ]
[        0        ][        0        ][(far+near)/(far-near)   ][        1       ]
[        0        ][        0        ][-(2*near*far)/(far-near)][        0       ]

要将物体剪切至近剪裁平面,您需要找到与该平面相交的线上的点。这个点就是当w == near时的点。因此,如果您有一个由顶点v1,v2组成的线段,

v1 = [x1, y1, z1, w1]
v2 = [x2, y2, z2, w2]

你需要检查每个顶点是在近裁剪平面的前面还是后面。如果w1>= near,则V1在前面;如果w1<near,则V1在后面。如果V1和V2都在前面,则绘制该线。如果V1和V2都在后面,则不绘制该线。如果V1在前面而V2在后面,则需要找到线与近裁剪平面相交的vc:

n = (w1 - near) / (w1 - w2)
xc = (n * x1) + ((1-n) * x2)
yc = (n * y1) + ((1-n) * y2)
zc = (n * z1) + ((1-n) * z2)
wc = near
vc = [xc, yc, zc, wc]

现在画一条线连接v1和vc。


我无法感谢你写得如此好的答案。这真的帮助了我很多...点赞也不足以表达我的感激之情。 - shbi
这是一个很好的答案,但有几个问题我不太清楚。首先,您是否遗漏了wc = (n * w1) + ((1 - n) * w2)这一行?此外,您的投影矩阵P是右乘还是左乘,即v_e * P?更重要的是,我使用过的任何渲染器都不需要在绘制时指定近平面-当不知道近值时,如何执行此剪裁? - Qualia
我可能暂时找到了一个答案。显然,通过剪裁z,您会约束w,因为它们是线性相关的。我的问题是,如果您正在插值w,则正在将剪辑值与测试z相互插值。实际上,这些方程并不像我想象的那么难。对于给定从v_1和v_2得出的交点与剪辑平面的线相交的顶点v_3,w_3 = w_1 + r*(w_2-w_1)a_3 = a_1 + r*(a_2-a_3),其中a_n = dot(plane,v_n)。但是a_3等于剪辑值w_3,因此a_1 + r*(a_2-a_1) = w_1 + r*(w_2-w_1)。解出r,你就完成了。(希望如此)。 - Qualia
在实现以上内容后,我现在意识到这当然不能解决-w问题,因为你会被错误的裁剪平面所截断。但是,你不再需要将w带到近平面,只需带到>= 0即可。 - Qualia
正如Qualia所指出的,我没有定义wc。它等于near(因为这是与w=near相交的平面上的点)。 (我已将其添加到答案中)。 是的,投影(又名剪辑)矩阵在其右侧乘以视图坐标中的列主点:Projection * Pview = Pnpc。据我所知,如果没有近平面,实际上不可能进行投影,因此,如果您的渲染器不要求您提供近值,则可能会为您选择近值。 - Acorn

4
这可能是对术语的误解。剪辑矩阵更恰当地称为投影矩阵。在OpenGL中,投影矩阵将视图坐标空间(VCS)中的4D齐次坐标转换为剪辑坐标空间(CCS)。从CCS到归一化设备坐标空间(NDCS)的投影需要透视除法,即将每个分量除以W分量。剪辑正确在此步骤之前完成。因此,“剪辑矩阵”并不能消除在投影之前剪辑几何形状的需求。希望我理解得没错,并且没听起来有点傲慢。
话虽如此,我认为你的投影矩阵肯定是正确的-它有效果。我怀疑通过“眼睛”后传递的顶点具有负的W,这意味着它们应该被剪辑;但我也怀疑它们具有负的Z,所以除法产生正的Z值。如果你真的想要剪辑几何形状而不是丢弃整个三角形,请搜索“齐次剪辑”。如果你没有真正在4D齐次空间中工作,可以从“Sutherland-Hodgman” 3D剪辑开始。

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