本次光线追踪系列从基础重新开始,主要参照 Ray Tracing in One Weekend ,具体实现代码框架见 https://github.com/RayTracing/raytracing.github.io/。本文只是主要精炼光追相关理论,具体实现可参照原文。
当真实的相机拍摄照片时,通常沿边缘没有锯齿,因为边缘像素是某些前景和某些背景的混合。通过平均每个像素中的一堆样本,我们可以获得相同的效果。我们不会为分层而烦恼。这是有争议的,但是在我的程序中很常见。对于某些光线跟踪器来说,它很关键,但是我们正在编写的那种通用的射线跟踪器并不能从中受益很多,这会使代码更丑陋。我们对相机类进行了一些抽象,以便以后可以制作更酷的相机。
一、相机类
跟原来入口函数里面的逻辑完全一样,新增了个get_ray(float u, float v)函数,用于获得从相机位置发射到屏幕对应位置的射线。
class camera
{
public:
vec3 origin;
vec3 lower_left_corner;
vec3 horizontal;
vec3 vertical;
camera()
{
lower_left_corner = vec3(-2.0, -1.0, -1.0);
horizontal = vec3(4.0, 0.0, 0.0);
vertical = vec3(0.0, 2.0, 0.0);
origin = vec3(0.0, 0.0, 0.0);
}
ray get_ray(float u, float v)
{
return ray(origin, lower_left_corner + u * horizontal + v * vertical - origin);
}
};
二、随机采样
中的随机数函数rand()会返回0到RANDMAX之间的一个随机数,要生成[0,1)的随机数,我们可以将代码写成这样:
#include <cstdlib>
inline double random_double() {
return rand() / (RAND_MAX + 1.0);
}
三、随机采样并求平均
在一个像素里面随机使用多条光线进行采样,最后求平均,可以实现抗锯齿。
添加像素随机采样代码:
void RayTracing()
{
...
int ns = 100; //new
camera cam; //new
for (int j = ny - 1; j >= 0; j --)
{
for (int i = 0; i < nx; i++)
{
vec3 col(0, 0, 0); //new
for(int s = 0; s < ns; s++) //随机采样100次
{
float u = float(i + random_double()) / float(nx);
float v = float(j + random_double()) / float(ny);
ray r = cam.get_ray(u, v);
col += color(r, world);
}
col /= float(ns); //new
int ir = int(255.99 * col[0]);
int ig = int(255.99 * col[1]);
int ib = int(255.99 * col[2]);
DrawPixel(i, j, ir, ig, ib);
}
}
}
对每个像素随机发射100条光线后,求平均,得到如下的抗锯齿效果:
此时你可以对比下随机采样1次的效果:
再夸张点,下边贴上一张每个像素采样1000次的图片: