《學一輩子光線追蹤》 六 正交基

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

在最後一章中,我們開發了生成與日晷相對的隨機方向的方法。我們要做的是基於任意曲面的法向量。正交正規基(ONB)是三個相互正交的單位向量的集合。笛卡爾xyz軸就是這樣的一個ONB,我有時忘記了它必須坐在某個真實的地方,有真實的方向,才能在真實世界中有意義,而在虛擬世界中有一些虛擬的地方和方向。圖片是相機和場景相對位置/方向的結果,只要相機和場景在同一座標系中描述,一切都是可以的。

假設我們有一個原點o和笛卡爾單位向量x/y/z。當我們說一個位置是(3,-2,7)時,我們實際上是說:

位置爲 o + 3*x - 2*y + 7*z

如果我們想測量另一個座標系中原點爲o'且基向量爲u/v/w的座標,我們只需找到數字(u,v,w)即可:

位置是o'+uu+vv+w。

如果你學習圖形入門課程,你會花很多時間在座標系和4x4座標變換矩陣上。注意,這在圖形中是很重要的!但我們不需要它。我們需要的是用相對於n的集合分佈生成隨機方向。我們不需要原點,因爲方向與原點無關。我們需要兩個相互垂直於n的餘切向量。

有些模型至少會有一個餘切向量。製作ONB的最困難的情況是我們只有一個向量。假設我們有任何不爲零長或不平行於n的向量a,我們可以利用 cross(c,d) 同時垂直於c和d的叉積的性質得到垂直於n的向量s和t:

t = unit_vector( cross(a, n) )
s = cross(t,n)

關鍵是,我們沒有a,如果我們在某個點上選擇一個特定的a,我們將得到一個與a平行的n。一個常見的方法是使用if語句來確定n是否是一個特定的軸,如果不是,則使用該軸。

If (fabs(n.x()) > 0.9)
    a = (0,1,0)
else
    s = (1,0,0)

一旦我們有一個ONB,並且我們有一個相對於z軸的隨機向量 (x,y,z) ,(該項量在原始座標系中的值是(x,y,z))我們就可以得到相對於n的向量:

Random vector = x*s + y*t + z*n

你可能會注意到我們用了類似的數學方法從相機獲取光線。這可以看作是對相機自然座標系的改變。我們應該爲ONB創建一個類還是實用函數足夠了?我不確定,但讓我們創建一個類,因爲它不會比實用函數更復雜:

#ifndef __ONB_H__
#define __ONB_H__
#include "vec3.h"
class onb {
public:
	onb(){}
	inline vec3 operator[](int i)const { return axis[i]; }
	vec3 u()const { return axis[0]; }
	vec3 v()const { return axis[1]; }
	vec3 w()const { return axis[2]; }
	vec3 local(float a, float b, float c)const { return a*u() + b*v() + c*w(); }
	vec3 local(const vec3&a)const { return a.x()*u() + a.y()*v() + a.z()*w(); }
	void build_from_w(const vec3&);
	vec3 axis[3];
};
void onb::build_from_w(const vec3&n) {
	axis[2] = unitVector(n);
	vec3 a;
	if (fabs(w().x()) > 0.9)
		a = vec3(0, 1, 0);
	else
		a = vec3(1, 0, 0);
	axis[1] = unitVector(cross(w(),a));
	axis[0] = cross(w(), v());
}
#endif

我們可以用這個重寫Lambertian材料,得到:

inline vec3 random_cosine_direction() {
	float r1 = myRandom();
	float r2 = myRandom();
	float z = sqrt(1 - r2);
	float phi = 2 * M_PI*r1;
	float x = cos(phi) * 2 * sqrt(r2);
	float y = sin(phi) * 2 * sqrt(r2);
	return vec3(x, y, z);
}
virtual bool scatter(const ray& r_in, const hit_record& rec, vec3& alb, ray& scattered, float &pdf)const {
	onb uvw;
	uvw.build_from_w(rec.normal);
	vec3 direction = uvw.local(random_cosine_direction());
	scattered = ray(rec.p, unitVector(direction), r_in.time());
	alb = albedo->value(rec.u, rec.v, rec.p);
	pdf = dot(uvw.w(), unitVector(scattered.direction())) / M_PI;//
	return true;
}

得到結果:

這個結果對嗎?我們還不能確定。在缺乏可靠的參考解決方案的情況下,追蹤錯誤是很困難的。讓我們暫時把它擱置一下,然後繼續消除一些噪音。

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