《學一輩子光線追蹤》 四 重要性採樣材料

蒙特卡洛光線追蹤技術系列 見 蒙特卡洛光線追蹤技術

在接下來的兩章中,我們的目標是利用我們的程序向光源發送一束額外的光線,這樣我們的圖片噪聲就不會太多了。假設我們可以使用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是程序的第一個版本還是第二個版本,甚至兩者都是!

讓我們建立一些基礎設施來解決這個問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章