蒙特卡洛光線追蹤技術系列 見 蒙特卡洛光線追蹤技術
在接下來的兩章中,我們的目標是利用我們的程序向光源發送一束額外的光線,這樣我們的圖片噪聲就不會太多了。假設我們可以使用pdf pLight(direction) 向光源發送一束光線。假設我們有一個與s相關的pdf,我們稱之爲pSurface(direction)。pdf 的一個優點是,可以使用它們的線性混合來形成混合密度,這也是pdf。
例如,最簡單的方法是:
p(direction) = 0.5*pLight(direction) + 0.5*pSurface(direction)
只要權重是正的並且加起來是1,任何這種pdf的混合都是pdf。記住,我們可以使用任何pdf-它不會改變我們的答案!所以,目標是要找出當乘積 s(direction)*color(direction) 更大的時候,如何使pdf更大。對於漫反射曲面,這主要是猜測顏色(方向)高的地方。對於鏡子來說,s()只在一個方向附近是巨大的,所以它更重要。事實上,大多數渲染器都會將鏡面作爲一種特殊情況,而只是將s/p隱式化——我們的代碼目前就是這樣做的。
讓我們做一個簡單的重構,暫時刪除所有不是Lambertian的材質(不想刪除也得改改裏面繼承的函數)。我們可以再次使用康奈爾盒子場景,還是使用之前的視點:
vec3 lookfrom(278, 278, -800);//
vec3 lookat(278, 278, 0);
框架也是我在上本書的最後實現的每次渲染都輸出結果的框架。
減少噪音是我們的目標。我們將通過構建一個pdf來實現這一點,它可以向光發送更多的光線。
首先,讓我們測試代碼,以便它顯式地對一些 pdf 進行採樣,然後對其進行規範化。記住MC基礎:積分 f(x) 大約等於 f(r)/p(r)。對於朗伯材料,讓我們像現在這樣取樣:p(direction)=cos(theta)/Pi。
我們修改材料以啓用此重要性採樣:
class material {
public:
virtual bool scatter(const ray &r_in, const hit_record& rec, vec3& albedo, ray& scattered,float &pdf)const {
return false;
}
virtual float scattering_pdf(const ray &r_in, const hit_record& rec, const ray& scattered)const {
return false;
}
virtual vec3 emitted(float u, float v, const vec3&p)const {
return vec3(0, 0, 0);
}
};
class lambertian :public material {
public:
lambertian(texture*a):albedo(a){}
virtual float scattering_pdf(const ray& r_in, const hit_record& rec, ray& scattered)const {
float cosine = dot(rec.normal, unitVector(scattered.direction()));
if (cosine < 0)cosine = 0;
return cosine / M_PI;
}
virtual bool scatter(const ray& r_in, const hit_record& rec, vec3& alb, ray& scattered, float &pdf)const {
vec3 target = rec.p + rec.normal + random_in_unit_sphere();
scattered = ray(rec.p, unitVector(target - rec.p));//r_in.time()
alb = albedo->value(rec.u, rec.v, rec.p);
pdf = dot(rec.normal, unitVector(scattered.direction())) / M_PI;
return true;
}
texture* albedo;
};
注意修改一些其他代碼,因爲凡是繼承後的類,都需要修改繼承的虛函數。
vec3 color(const ray&r, hitable *world, int depth) {
hit_record rec;
if (world->hit(r, 0.001, MAXFLOAT, rec)) {
ray scattered;
vec3 emitted = rec.mat_ptr->emitted(rec.u, rec.v, rec.p);
float pdf;
vec3 albedo;
if (depth < 50 && rec.mat_ptr->scatter(r, rec, albedo, scattered,pdf)) {
return emitted + albedo* rec.mat_ptr->scattering_pdf(r,rec,scattered) *color(scattered, world, depth + 1);
}
else {
return emitted;
}
}
else {
return vec3(0, 0, 0);
}
}
結果竟然只顯示一個燈,物體和牆都沒有了。我們開始調試,首先修改一下color函數:
vec3 color(const ray&r, hitable *world, int depth) {
hit_record rec;
if (world->hit(r, 0.001, MAXFLOAT, rec)) {
ray scattered;
vec3 emitted = rec.mat_ptr->emitted(rec.u, rec.v, rec.p);
float pdf;
vec3 albedo;
if (depth < 50 && rec.mat_ptr->scatter(r, rec, albedo, scattered,pdf)) {
return emitted + color(scattered, world, depth + 1);
}//albedo* rec.mat_ptr->scattering_pdf(r,rec,scattered) *
else {
return emitted;
}
}
else {
return vec3(0, 0, 0);
}
}
得到結果:
隱約能看到物體,說明是 albedo* rec.mat_ptr->scattering_pdf(r,rec,scattered) 出了問題。
深入檢查,發現
virtual float scattering_pdf(const ray& r_in, const hit_record& rec, ray& scattered)const
的最後一個參數 ray 竟然少了一個const修飾符。改過來以後就可以了。效果和前面的一樣。
現在,爲了體驗,嘗試不同的抽樣策略。讓我們從高於表面的半球中隨機選擇。這就是p(direction) = 1/(2*Pi)
virtual bool scatter(const ray& r_in, const hit_record& rec, vec3& alb, ray& scattered, float &pdf)const {
vec3 direction;
do {
direction = random_in_unit_sphere();
} while (dot(direction, rec.normal) < 0);
scattered = ray(rec.p, unitVector(direction), r_in.time());
alb = albedo->value(rec.u, rec.v, rec.p);
pdf = 0.5 / M_PI;
return true;
}
得到結果:
相同背景物體和光照條件下的對比:
再說一遍,我應該得到相同的圖片,除了不同的變化幅度,但並沒有。第二張照片的長方體非常均勻,這是爲什麼呢?
它很接近我們的老照片,但有一些差異不是噪聲。這個高盒子的正面顏色要均勻得多。所以我有最難在蒙特卡羅程序中找到的一種錯誤——一種能產生合理外觀圖像的錯誤。我不知道這個bug是程序的第一個版本還是第二個版本,甚至兩者都是!
讓我們建立一些基礎設施來解決這個問題。