光线追踪7 - 抗锯齿(Antialiasing)
�� 目前为止,如果你放大渲染出的图像,可能会注意到图像边缘的明显“阶梯状”效果。这种阶梯效果通常被称为“走样”或“锯齿”。当真实相机拍摄图片时,边缘通常没有锯齿,因为边缘像素是一些前景和一些背景的混合。请考虑,与我们渲染的图像不同,真实世界的图像是连续的。换句话说,世界(及其真实图像)具有无限的分辨率。我们可以通过对每个像素进行多次采样来获得相同的效果。
通过每个像素中心传递单一光线,我们正在进行常见的点采样。点采样的问题可以通过远处渲染一个小的棋盘格来说明。如果该棋盘由一个8×8的黑白格子组成,但只有四条光线射到它上面,那么这四条光线可能只会与白色格子相交,或只与黑色格子相交,或者是一些奇怪的组合。在真实世界中,当我们用眼睛远处观察一个棋盘时,我们会感知到它是灰色的,而不是黑白的尖点。那是因为我们的眼睛自然而然地完成了我们希望光线追踪完成的任务:将落在所渲染图像的特定(离散的)区域上的(连续函数的)光线进行积分。
显然,通过多次对像素中心进行相同光线的重新采样,并不能带来任何好处——我们每次都会得到相同的结果。相反,我们想要采样像素周围的光线,然后对这些采样进行积分以近似真正的连续结果。那么,我们如何积分像素周围的光线呢?
我们将采用最简单的模型:采样以像素为中心、向四个相邻像素各自延伸一半距离的正方形区域。这并不是最优的方法,但它是最直接的方法。(请参阅 A Pixel is Not a Little Square 以深入了解此主题。)Figure 8: Pixel samples
Some Random Number Utilities
我们需要一个能返回真正随机数的随机数生成器。这个函数应该返回一个经典的随机数,按照惯例应在0≤n max) return max; return x; } ... };
Listing 38: [interval.h] The interval::clamp() utility function以下是更新后的write_color()函数,它接受像素的所有光的总和和涉及的样本数量:
void write_color(std::ostream &out, color pixel_color, int samples_per_pixel) { auto r = pixel_color.x(); auto g = pixel_color.y(); auto b = pixel_color.z(); // Divide the color by the number of samples. auto scale = 1.0 / samples_per_pixel; r *= scale; g *= scale; b *= scale; // Write the translated [0,255] value of each color component. static const interval intensity(0.000, 0.999); out