Z缓冲区的深度插值,带有扫描线


10

我必须编写自己的软件3d栅格化器,到目前为止,我已经能够将由三角形构成的3d模型投影到2d空间中:

我旋转,平移和投影点,以获得每个三角形的二维空间表示。然后,我取了3个三角形点,并实施了扫描线算法(使用线性插值法)以找到沿三角形边缘(左右)的所有点[x] [y],以便可以水平扫描三角形,一行一行地填充像素

这可行。除了我还必须实现z缓冲。这意味着,知道三角形的3个顶点的旋转平移后的z坐标后,我必须对使用扫描线算法找到的所有其他点的z坐标进行插值。

这个概念似乎很清楚,我首先通过以下计算得出Za和Zb:

var Z_Slope = (bottom_point_z - top_point_z) / (bottom_point_y - top_point_y);
var Za = top_point_z + ((current_point_y - top_point_y) * Z_Slope);

然后,对于每个Zp,我都会在水平方向执行相同的插值:

var Z_Slope = (right_z - left_z) / (right_x - left_x);
var Zp = left_z + ((current_point_x - left_x) * Z_Slope);

在此处输入图片说明

并且如果当前z在该索引处比前一个z更接近查看器,则将颜色写入颜色缓冲区,并将新z写入z缓冲区。(我的坐标系是x:左->右; y:上->下; z:脸部->电脑屏幕;)

问题是,它陷入了困境。该项目在这里,如果您选择“ Z缓冲”单选按钮,您将看到结果...(请注意,我在“ Z缓冲”模式下使用了画家的算法(仅绘制线框)用于调试

PS:我在这里读到,z = 1/z在进行插值之前,必须将z转换为它们的倒数(即)。我尝试过,看来没有变化。我想念什么?(任何人都可以澄清,确切地说,您必须将z变成1 / z的位置,以及将z变成1 / z的位置。)

[编辑]以下是有关我获得的最大和最小z值的一些数据:

    max z: 1;                  min z: -1;                 //<-- obvious, original z of the vertices of the triangles
    max z: 7.197753398761272;  min z: 3.791703256899924;   //<-- z of the points that were drawn to screen (you know, after rotation, translation), by the scanline with zbuffer, gotten with interpolation but not 1/z.
    max z: 0.2649908532179404; min z: 0.13849507306889008;//<-- same as above except I interpolated 1/z instead of z.
//yes, I am aware that changing z to 1/z means flipping the comparison in the zBuffer check. otherwise nothing gets drawn.

在进行艰苦的调试之前,有人可以确认我到目前为止的概念是正确的吗?

[EDIT2]

我已经解决了z缓冲问题。事实证明,绘制顺序根本没有搞乱。z坐标正在正确计算。

问题是,为了提高帧速率,我每4个像素绘制4px / 4px的框,而不是屏幕上的实际像素。所以我每像素绘制16px,但是只检查了其中一个的z缓冲区。我真是个傻瓜。

TL / DR:问题仍然存在:如何/为什么/何时使用Z的倒数(如1 / z)代替Z?因为现在,一切都可以进行。(没有明显差异)。


回复:“当然,如果当前z比该索引处的先前值更靠近查看器,那么我会添加到zBuffer中。” 对我来说,您说的是什么意思并不清楚,但您想确保您的意思是“如果当前z在该索引处比以前的z更靠近查看器,则将颜色写入颜色缓冲区并写入向z缓冲区中添加新的z”“ z缓冲区的目的是阻止已经写在该像素上的颜色更靠近相机眼睛的颜色写入。
Alturis 2012年

那是正确的。抱歉,我说的问题已经很晚了。我会修改。
Spectraljump 2012年

Answers:


5

快速解答:Z不是(X',Y')的线性函数,而是1 / Z。由于您是线性插值的,因此您可以获得1 / Z的正确结果,但没有获得Z的正确结果。

您不会注意到,因为只要Z1和Z2之间的比较正确,即使两个值都不正确,zbuffer也会做正确的事。您肯定会在添加纹理映射时注意到(并回答以下问题:插入1 / Z,U / Z和V / Z,并从这些值重构U和V:U =(U / Z) /(1 / Z),V =(V / Z)/(1 / Z)。稍后您将感谢我)

一个例子。拿一张纸。俯视图,所以忘记Y坐标。X是水平轴,Z是垂直轴,相机位于(0,0),投影平面为z = 1。

考虑点A(-2,2)和B(2,4)。段AB的中点M为(0,3)。到目前为止,一切都很好。

您将A投影到A'中:X'= X / Z = -1,所以A'为(-1,1)。同样,B'为(0.5,1)。但是请注意,M的投影为(0,1),这不是A'B'的中点。为什么?由于线段的右半部分比左半部分距离相机更远,因此看起来更小。

那么,如果您尝试使用线性插值来计算M'的Z,会发生什么?dx =(0.5--1)= 1.5,dz =(4-2)= 2,因此对于M'(其中X'= 0),线性插值Z为zA +(dz / dx)(x-xA)= 2 +(2 / 1.5)(0--1)= 2 + 1.333 = 3.3333-不是3!

为什么?因为对于X'方向上的每一步,您都不会在Z方向上移动相同的量(或者换句话说,Z不是X'的线性函数)。为什么?由于您走得越远,该片段离相机越远,因此一个像素代表更长的空间距离。

最后,如果您插值1 / Z会怎样?首先,您在A和B分别计算1 / Z:0.5和0.25。然后进行插值:dx =(0.5--1)= 1.5,dz =(0.25-0.5)= -0.25,因此在X'= 0时,您计算1 / Z = 0.5 +(-0.25 / 1.5)*(0- -1)= 0.3333。但是那是1 / Z,所以Z的值是……正好是3。应该是。

是的,数学很棒。


1
哦,关于“何时”:在开始对三角形进行栅格化之前(例如,在垂直循环之前)计算1 / Z值,这样您就可以在扫描线的左侧和右侧进行插值1 / Z。线性插值(不要再进行1 / Z-插值已经是1 / Z!),并在检查zbuffer之前撤消转换。
ggambett 2012年

1
最后,为什么。一个平面(其中嵌入了三角形的平面)是Ax + By + Cz + D =0。z显然是(x,y)的线性函数。您进行投影,因此x'= x / z和y'= y / z。从那里开始,x = x'z和y = y'z。如果将它们替换为原始方程式,则得到Ax'z + By'x + Cz + D =0。现在z = -D /(Ax'+ By'+ C),显然z不是线性函数的(x',y')。但是1 / z因此是(Ax'+ By'+ C)/ -D,它是(x',y')的线性函数。
ggambett 2012年

1
您知道,我读了很多文章和课程,但是没有一个和您的答案一样清楚。为了后代,我还将注意到“字母“ U”和“ V”表示2D纹理的轴,因为“ X”,“ Y”和“ Z”已经用于表示模型中3D对象的轴UV纹理可以使组成3D对象的多边形涂上图像中的颜色。” -维基百科- UV贴图
Spectraljump

很高兴听你这样说。实际上,我确实曾在前世教过计算机图形学:)
ggambett 2012年

非常感谢-我一直对此感到很好奇-而且我不知道我是否会找到更好的答案!+1
Codesmith
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.