蒙特卡洛光线追踪技术系列 见 蒙特卡洛光线追踪技术
几乎均匀地在方向上采样的问题是,光的采样率不超过其他不重要的方向的采样率。我们可以使用阴影光线并分离出直接照明。但是,作为替代方法,我只会发射更多的采样光线到光源上。我们以后可以用它来向我们想要的任何方向发射更多的光线。
很容易选择一个指向灯光的随机方向;只需在灯光上选择一个随机点并向该方向发送光线即可。但我们也需要知道pdf(方向)。那是什么?对于a区域的光,如果我们在该光上均匀采样,则该光表面上的pdf为1/a。但定义方向的单位球体区域上的pdf是多少?幸运的是,有一个简单的对应关系,如图中所示:
如果我们观察光上的一个小区域dA,采样的概率是 p_q(q)*dA 。在球面上,对球面上的小面积dw进行采样的概率为p(direction)*dw 。dw和dA之间存在几何关系:
dw = dA cos(alpha) / (distance(p,q)^2)
我们假设上面的球面是单位球面,则光源投影到球面上的面积就是其立体角。
因为抽样的概率dw和dA必须相同,所以我们有:
p(direction)*cos(alpha)*dA / (distance(p,q)^2 ) = p_q(q)*dA = dA / A
这是因为,首先我们要保证我们发出光线的pdf积分为1,同时这个pdf又是作为在灯光上的pdf而存在的。
所以:
p(direction) = distance(p,q)^2 / ( cos(alpha) *A )
如果我们修改颜色函数,以一种非常硬编码的方式采样光,只是为了检查数学和获得概念,我们可以添加它(见突出显示的区域):(就是把上面的公式直接搞进程序里去)
if (depth < 1 && rec.mat_ptr->scatter(r, rec, albedo, scattered,pdf)) {
vec3 on_light = vec3(213 + myRandom()*(343 - 213), 554.0f, 227 + myRandom()*(332 - 227));
vec3 to_light = on_light - rec.p;
float distance_squared = to_light.squaredLength();
to_light.makeUnitVector();
if (dot(to_light, rec.normal) < 0)//如果光的方向与它夹角超过90度
return emitted;
float light_area = (343 - 213)*(332 - 227);
float light_cosine = fabs(to_light.y());
if (light_cosine < 0.000001)
return emitted;
pdf = distance_squared / (light_cosine*light_area);
scattered = ray(rec.p, to_light, r.time());
float mpdf = rec.mat_ptr->scattering_pdf(r, rec, scattered);
return emitted + albedo*mpdf*color(scattered, world, depth + 1) / pdf;
}//
else {
return emitted;
}
这里的depth=50很明显是没什么用的,因为采样光线只会有两种情况,第一是碰到物体以后反射到灯光上,第二是碰到物体以后反射向灯光,但是中间被别的物体挡住了,在这种情况下,再次朝着灯光方向进行反射则会遇到第一个if的判断这种情况:
所以说反射一次的结果和设置反射50次的结果并没有什么本质区别:
反射一次。
设置反射五十次(其实也就只是反射了一次)。
这是关于我们期望的东西,只采样光源,所以这似乎正常工作了。天花板上的灯周围发出的噪音是因为灯是双面的,而且灯和天花板之间有一个很小的空间:
验证方法:保留灯的两面,并把后面的pdf屏蔽掉。
就发现没有毛边了:说明是由pdf引起的。
我们可能想只让光向下发射。我们可以让hitable的发出成员函数获取额外的信息:
virtual vec3 emitted(const ray&r_in, const hit_record&rec,float u, float v, const vec3&p)const {
if (dot(rec.normal, r_in.direction()) < 0.0)
return emit->value(u, v, p);
else
return vec3(0, 0, 0);
}
我们还需要翻转灯光,使其法线指向-y方向,我们得到: