《一周學習光線追蹤》(一)序言及動態模糊
來自專欄 從光柵到光線追蹤
這一系列文章是繼續翻譯Peter Shirley光線追蹤系列書籍的第二本,《Ray Tracing: The Next Week》,這個系列一共有三本,分別為
《用兩天學習光線追蹤》
《用一周學習光線追蹤》
《用餘生學習光線追蹤》
第一本已經由 @破曉 翻譯了:【翻譯】兩天學會光線追蹤(一)
原書的網站在這裡
《用一周學習光線追蹤》作為此系列的第二本,一共有九章:
- 動態模糊
- 層次包圍盒BVH
- 純色紋理
- 柏林雜訊
- 圖像紋理映射
- 矩形和光源
- 實例化
- 體渲染
- 一個場景測試所有新功能
我計劃用5篇文章來翻譯完,存疑的內容或者補充的內容我會標記出來。
《一周學習光線追蹤》(二)層次包圍盒BVH
《一周學習光線追蹤》(三)紋理和柏林噪音
《一周學習光線追蹤》(四)光源和矩形
《一周學習光線追蹤》(五)體渲染
所有代碼可以在GitHub找到(僅RT-1Week分支)
Asixa/ALight關於隨機數
在第一本書中 @破曉 使用的是Unity引擎的Random.Range() 函數,具體實現並不清楚。
我之前的代碼使用的系統的Random.Next()函數,但是有一個很大的弊端就是這個函數很慢,並且需要線程鎖。
書原作者使用的是C語言的 drand48(),所以我參照這個函數寫了一個新的隨機函數。
public static class Random { static long seed = 1; public static float Get() { seed = (0x5DEECE66DL * seed + 0xB16) & 0xFFFFFFFFFFFFL; return (seed >> 16) / (float) 0x100000000L; } }
我之前也試過其他的不同的隨機演算法,但是不同演算法對渲染結果影響十分大。
更多有關隨機演算法的可以看 低差異序列(一)- 常見序列的定義及性質
第一章:動態模糊
在真實世界的相機中,快門每次拍攝會保持打開一段時間,並且在此期間相機和物體可能會發生移動。要在渲染中實現這個效果,我們首先需要使射線能夠儲存其存在的時間段。
public class Ray { public readonly Vector3 original; public readonly Vector3 direction; public readonly Vector3 normalDirection; public float time; // [NEW] 儲存時間的變數 public Ray(Vector3 o, Vector3 d,float t=0)// [NEW] 構造函數賦值,默認值為0 { time = t; // [NEW] 賦值 original = o; direction = d; normalDirection = d.Normalized(); } public Vector3 GetPoint(float t) => original + direction * t; }
現在我們需要修改相機代碼使它在時間1和時間2之間的隨機時刻生成射線。
public class Camera { /*...省略其他代碼...*/ public float time0, time1;//[NEW] 儲存時間1和時間2的變數 /*...省略其他代碼...*/ public Camera( /*...省略其他構造函數參數...*/ float t0=0,float t1=0) { time0 = t0; time1 = t1; /*...省略其他構造函數初始化賦值...*/ } public Ray CreateRay(float x, float y) { //[NEW]在下面聲明射線對象的時候賦予參數,一個在時間0到時間1內的隨機時間。 if (radius == 0f)return new Ray(position, lowLeftCorner + x * horizontal + y * vertical - position, time0 + Random.Get() * (time1 - time0)); var rd = radius * GetRandomPointInUnitDisk(); var offset = rd.x * u + rd.y * v; //[NEW]在下面聲明射線對象的時候賦予參數,一個在時間0到時間1內的隨機時間。 return new Ray(position + offset, lowLeftCorner + x * horizontal + y * vertical - position - offset, time0 + Random.Get() * (time1 - time0)); } /*...省略其他代碼...*/ }
接下來我們需要一個移動中的物體。這個物體會在時間0到時間1從坐標0移動到坐標1。在這個時間段外這個物體依然移動,所以不需要配合攝像機快門的時間。
我們可以給每種物體都寫上移動的功能,給靜止的物體的初始位置和終點位置設置為相同的值。也可以為移動的物體單獨寫一個類。後者可以提高一些效率。這都取決於你的個人習慣。
如果想要讓所有普通的物體都可以實現動態模糊,只需要在Hit函數中,將center替換為Center(ray.time)函數。(center是中心點坐標,Center() 是物體在某一時刻的中心店坐標)
public class MovingSphere:Hitable { public Vector3 pos0, pos1;//起點坐標和終點坐標 public float time0, time1;//開始時間和結束時間 public float radius;//球形半徑 public Material material;//球體材質 //構造函數 public MovingSphere(Vector3 p0, Vector3 p1, float t0, float t1, float r, Material m) { pos0 = p0;pos1 = p1; time0 = t0;time1 = t1; radius = r;material = m; } //獲取在某一時刻的中心店坐標 public Vector3 Center(float time) => pos0 + ((time - time0) / (time1 - time0)) * (pos1 - pos0); public override bool Hit(Ray ray, float t_min, float t_max, ref HitRecord rec) { Vector3 oc = ray.original - Center(ray.time); var a = Vector3.Dot(ray.direction, ray.direction); var b = Vector3.Dot(oc, ray.direction); var c = Vector3.Dot(oc, oc) - radius * radius; var discriminant = b * b - a * c; if (discriminant > 0) { var temp = (-b - Mathf.Sqrt(discriminant)) / a; if (temp < t_max && temp > t_min) { rec.t = temp; rec.p = ray.GetPoint(rec.t); rec.normal = (rec.p - Center(ray.time)) / radius; rec.material = material; return true; } temp = (-b + Mathf.Sqrt(discriminant)) / a; if (temp < t_max && temp > t_min) { rec.t = temp; rec.p = ray.GetPoint(rec.t); rec.normal = (rec.p - Center(ray.time)) / radius; rec.material = material; return true; } } return false; } }
之後我們需要更改 所有 涉及散射的材質代碼,使之創建新的射線時賦值此射線的時間。
public override bool scatter(Ray rayIn, HitRecord record, ref Color32 attenuation, ref Ray scattered) { var target = record.p + record.normal + GetRandomPointInUnitSphere(); scattered = new Ray(record.p, target - record.p,rayIn.time);//[NEW]添加 「,rayIn.time」 attenuation = albedo; return true; }
之後我們在場景里添加物體
world.list.Add( new MovingSphere( new Vector3(0.3f, 0f, -1), //開始坐標 new Vector3(-0.3f, 0f, -1),//結束坐標 0,1, //開始時間 0.5f, //結束時間 new Lambertian(newColor32(1, 0, 0))));//白色漫反射材質
設置攝像機的構造函數的最後兩個參數為快門時間
camera = new Camera(new Vector3(0, 1, 1), new Vector3(0, 0, -1), new Vector3(0, 1, 0), 75, 1, lens_radius, forcus_dis, 0,1);
渲染結果:
推薦閱讀: