我们之前的采样方式都是在hitPoint的位置进行半球采样,这样做会有一个问题,如果我们这个点本来是被灯光照到的位置,但我们采样的过程中却并没有直射灯光的射线,会导致本来直射很亮的点却错误的着色。所以我们想到从灯光来进行采样
获取光源的PDF
在光源的面上进行采样,
求的对应的立体角,又图像可知在光源的面积采样的概率密度和在对应的立体角上采样的概率是必须一样的。因为是一一对应关系,一块面积的概率就是该区域的概率密度之和,也就是积分,当面积足够小的时候,可以认为:
对于概率密度,假设在光源上均匀的采样,那么,最后推出半球上的采样概率密度为:
这里解释为什么不一样,首先概率是一定一样,因为我采样某个区域的概率是对应的。但是概率密度不一样,比如区域A是均匀的,但是其对应的立体角的区域略大,此时在半球上的采样密度就会比较松,比如光源足够远,那么只能映射到半球上的一小块区域,但是概率密度是相较于半球而言.
Light Sampling
- 这里和101的思想不太一样,101是判断该点到光源的采样点之间是否有遮挡,然后作为直接光照进行补充。
- 文章的做法还是按照半球采样的主体思想
-
- 首先是如果是光源就直接返回
- 如果是其它物体,首先文章直接针对光源的方向进行采样,可以理解为重要性采样,文章认为这个方向的信息更加的重要,但是我们的pdf是针对半球的概率密度,此时就需要进行映射,因为我们已知光源面上的采样是均匀的,映射到我们的半球上。
-
-
- 其实文章还是对半球采样,并不是101如果有直接光源信息,进行补充。这样做可以理解为,其它方向也不一定有光线,但是光源方向一定有。
-
-
- 然后射线到下一个物体也是按照相同的方式,也就是改变了采样的方式。scatter_pdf可以理解为希望靠近法线的射线的影响更大。
目前的cornell_box的光源只有在天花板的那一个,其法线是向下的(0,-1,0),其x轴和z轴的跨度和坐标范围我们已知,所以我们可以在直接在光源上进行采样。
auto on_light = Point3(random_double(213, 343), 554, random_double(227, 332));
- 如果光源在半球的下面,我们认为无论怎么散射都不可能打到光源,所以直接返回emission的能量
auto on_light = Point3(random_double(213, 343), 554, random_double(227, 332));
auto to_light = on_light - rec.p;
auto distance_squared = to_light.length_squared();
if(dot(to_light, rec.normal) < 0)
{return color_from_emission;
}
- 接下来按照公式进行求解,需要注意这里的
是-to_light与光源的法向量的乘积,这里已经判断过是否光源在下面了,所以我们只需要判断to_light的y轴分量是否为0。
color ray_color(const Ray& r,int depth,const hittable& world) const{...auto on_light = Point3(random_double(213, 343), 554, random_double(227, 332));auto to_light = on_light - rec.p;auto distance_squared = to_light.length_squared();to_light = unit_vector(to_light);if(dot(to_light, rec.normal) < 0){return color_from_emission;}if(abs(to_light.y()) < 0.0001) {return color_from_emission;}double light_area = (343 - 213) * (332 - 227);pdf_value = distance_squared / (light_area *abs(to_light.y()));scattered = Ray(rec.p, to_light, r.time());double scatter_pdf = rec.mat->scattering_pdf(r, rec, scattered);color color_from_scatter = (scatter_pdf * attenuation * ray_color(scattered,depth-1,world)) / pdf_value;return color_from_emission + color_from_scatter;
}
结果
我们设置每个像素点的采样次数为10,也就是10spp,结果很夸张了
cam.samples_per_pixel = 10;
之前的做法1000spp
可以看到场景1000spp的肯定更亮,因为它采样的次数够多。但是上面的重要性采样的做法其噪声点相较于下面的非常少,更不用说如果是普通的半球采样使用10spp了,将会得到下面的结果,因为大部分的采样都无法有效的击中光源,形成无效路径。
Switching to Unidirectional Light
光源实际上就是一个可以自发光的平面,而他本来就是两面的,也就是说顶上的平面可能会采样到光源的上面,导致了这种现象。
这里我们设置单向光,让光源向下直射
color color_from_emission = rec.mat->emitted(r, rec, rec.u, rec.v, rec.p);
class material{
...virtual color emitted(const Ray& r, const hit_record& rec, double u,double v,const Point3& p) const{return color(0,0,0);...
};
class diffuse_light : public material{
...color emitted(const Ray& r, const hit_record& rec, double u,double v,const Point3& p) const override{if(!rec.front_face){return color(0, 0, 0);}return tex->value(u,v,p);
...
};