像素的中心是(0.5,0.5)

2020-06-11 23:01:12

现在更多的光线跟踪是通过眼睛完成的,这是一个需要重新学习的课程:如果像素的中心是分数(0.5,0.5),代码会更好,生活也更容易。如果你确定你这样做是对的,那就太好了;往前走,这里没什么可看的。取而代之的是享受这个。

将像素中心映射到(0.5,0.5)是保罗·赫克伯特(Paul Heckbert)的一篇可爱的小文章“像素的坐标是什么?”(What Are of a Pixel?)最先解释的(至少对我来说是这样),“图形宝石”(The Graphics Gems),第246-248页,1990年。

那篇文章现在很难找到了,所以这里有个要点。假设您的屏幕宽度和高度为1000。我们来谈谈X轴吧。可能很容易说0.0是一行最左边像素的中心,1.0是它旁边的中心,依此类推。您甚至可以使用四舍五入,其中浮点坐标73.6和74.4都会转到中心74.0。

然而,请三思而后行。使用此贴图,左边缘为-0.5%,右边缘为999.5。这不是一件令人愉快的工作。更糟糕的是,如果在像素坐标值上使用abs()或mod()等各种运算符,此映射可能会导致沿边缘的细微错误。

范围为0.0%到1000.0更容易,这意味着每个像素的中心在分数0.5时。例如,整数像素43然后对于其内部的子像素值具有43.0%到43.99999的敏感范围。以下是保罗的想象:

OpenGL一直将分数(0.5,0.5)视为像素中心。DirectX起初没有,但最终随着DirectX 10的推出而与该程序打交道。

从整型到浮点型像素坐标的正确转换操作是加0.5;浮点型到整型的操作是使用FLOOR()。

这已经是老生常谈了。每个人都是这样做的,对吗?我之所以提出它,是因为我开始在一些光线跟踪示例(伪)代码中看到,这些代码用于生成透视摄影机的方向:

float3 ray_Origin=Camera->;Eyes;float2 d=2.0*(float2(idx.x,idx.y)/float2(宽度,高度))-1.0;float3 ray_direction=d.x*Camera->;U+D.y*Camera->;V+Camera->;W;

矢量idx是像素的整数位置,宽度和高度是屏幕分辨率。通过将矢量D乘以U和V这两个矢量,计算矢量D并将其用于生成世界空间矢量。将摄像机在世界空间中的方向W矢量相加。U和V表示视图平面在距眼睛W的距离处的正X轴和Y轴。在上面的代码中,所有这些看起来都很漂亮和对称,而且大部分都是对称的。

矢量d_1应该表示屏幕上各点的归一化设备坐标(NDC)中从-1.0到1.0的一对值。但是,代码失败。继续我们的示例,整数像素位置(0,0)为(-1.0,-1.0)。听起来不错,对吧?但是我们的最高整数像素位置是(999,999),它转换为(0.998,0.998)。0.002的总差异是因为这个糟糕的贴图将整个视图移动了半个像素。这些像素中心应与每边的边缘相距0.001。

Float2 d=2.0*((Float2(idx.x,idx.y)+Float2(0.5,0.5))/Float2(宽,高))-1.0;

这就给出了像素中心的正确ndc范围,从-0.999到0.999。如果我们通过这个转换转换浮点角点值(0.0,0.0)和(1000.0,1000.0)(我们不添加0.5,因为我们已经处于浮点状态),我们就会得到从-1.0到1.0的整个NDC范围,从边到边,证明代码是正确的。

如果0.5让您感到恼火,并且您错过了对称性,则在像素内部生成随机值时(即,通过通过每个像素随机投射更多光线进行抗锯齿),此公式非常合适:

Float2 d=2.0*((Float2(idx.x,idx.y)+Float2(兰德(种子),兰德(种子))/Float2(宽,高))-1.0;

您只需将范围[0.0,1.0]中的一个随机数添加到每个整数像素位置值即可。此随机值的平均值为0.5,位于像素的中心。

长话短说:当心。把那半个像素弄对。根据我的经验,这些半像素的误差偶尔会出现在不同的地方(相机、纹理采样等)。多年来,我在Autodesk从事与光栅化相关的代码工作。他们的所作所为只会带来痛苦。如果我们不小心的话它们会再次出现在光线追踪器上。