【RAY TRACING THE REST OF YOUR LIFE 超詳解】 光線追蹤 3-5 random direction & ONB

 

 Prefacehtml

日後看了幾章,對這本書有了新的理解算法

上一篇,咱們第一次嘗試把MC積分運用到了Lambertian材質中,固然,第一次嘗試是失敗的,做者發現它的渲染效果和現實有些出入,因此結尾處聲明要經過實踐,改進當前的效果dom

因而乎,就有了後面的章節,幾乎整本書都在講,如何一步一步地改進上一篇的畫質,使其更加符合現實,上一篇實際上是拋磚引玉函數

這本書的小標題名爲the rest of your life工具

經過前面幾章,咱們能夠更好地理解這句話:咱們經過MC積分優化效果,採用的是pdf函數,以前說過,這就是一場遊戲:尋找一個pdf函數,使得使用它進行重要性採樣獲得的渲染圖形更加貼合實際,其實它是沒有止境的,好比pdf是一次曲線、二次曲線、高次曲線、正態分佈、高斯分佈等等,對應的研究方法也是沒有止境的,好比:你能夠經過對光源進行pdf採樣實現最終目的(好比在雙向追蹤中,光源也要發射光線),你也能夠經過對不一樣材質表面的反射狀態進行pdf採樣,進而使得表面顏色變化更光滑更柔和更貼合實際。優化

上述爲我的理解,可能有些出入,吾姑妄言之,汝姑妄聽之,便罷。ui

 

 Readyspa

上述說到拋磚引玉,可是好像咱們用的不是一張圖,思量再三,仍是先把磚整一個,畢竟以後都是圍繞那塊磚評說效果的,另闢蹊徑可能不是明智之舉rest

因此,咱們先把磚搞到手code

造磚的代碼:

void Cornell(intersections** scene, camera** cam, rtvar aspect)

{

      intersect ** list = new intersect*[8];

      size_t cnt = 0;

      material * red = new lambertian(new constant_texture(rtvec(0.65, 0.05, 0.05)));

      material * white = new lambertian(new constant_texture(rtvec(0.73, 0.73, 0.73)));

      material * green = new lambertian(new constant_texture(rtvec(0.12, 0.45, 0.15)));

      material * light = new areaLight(new constant_texture(rtvec(15, 15, 15)));

 

      list[cnt++] = new flip_normal(new yz_rect(0, 555, 0, 555, 555, green));

      list[cnt++] = new yz_rect(0, 555, 0, 555, 0, red);

      list[cnt++] = new xz_rect(213, 343, 227, 332, 554, light);

      list[cnt++] = new flip_normal(new xz_rect(0, 555, 0, 555, 555, white));

      list[cnt++] = new xz_rect(0, 555, 0, 555, 0, white);

      list[cnt++] = new flip_normal(new xy_rect(0, 555, 0, 555, 555, white));

      list[cnt++] = new translate(new rotate_y(new box(rtvec(), rtvec(165, 165, 165), white), -18), rtvec(130, 0, 65));

      list[cnt++] = new translate(new rotate_y(new box(rtvec(), rtvec(165, 330, 165), white), 15), rtvec(265, 0, 295));
*scene = new intersections(list, cnt);
rtvec lookfrom(
278, 278, -800); rtvec lookat(278, 278, 0); rtvar dist_to_focus = 10.0; rtvar aperture = 0.; rtvar vfov = 40.0; *cam = new camera(lookfrom, lookat, rtvec(0, 1, 0), vfov, 1, aperture, dist_to_focus, 0., 1.); }

 

爲了清晰點,sample改成了250,圖片爲200*200,爲了方便重複作實驗,因此參數就這樣吧,能看清便可,"高清大圖"實在是熬不起,都是夜啊,各位見諒~

上一篇結束以後的代碼均不變,只是改一下Cornell 函數

咱們獲得

 

這就是上一篇最後獲得的效果,沒錯

咱們評說一下,這張圖不只沒有減小噪點,並且高的長方體表面的顏色趨於均勻一致,與實際有誤差

評說好壞固然要有個參考,咱們放上兩張第二本書中獲得的圖形(未加入MC積分)

   

 

上面三張圖,不管你看哪張圖都能發現,正對咱們的那個長方體表面是從上到下又黑到白漸變的,並且在正方體上表面高度處對應的長方體表面有一抹正方體上表面反射的白色光,而MC圖形基本上呈均勻色調,甚至可能上面部分還稍白一些

 

因此咱們進入今天的這一篇

 

 Content

今天咱們講兩章,第一章是講三維隨機方向向量的生成,這有什麼用呢,它給咱們的三維空間內進行重要性採樣用,MC須要它!!

Chapter5 Generating Random Direction

在這一章和後續的兩章,咱們須要增強咱們的理解和咱們手中的工具,搞明白什麼纔是正確的Cornell Box

讓咱們首先從如何生成隨機方向開始提及。

爲了方便,咱們假定z軸爲表面法線,以後再轉換座標系,與此同時,咱們規定θ爲從法線張開的角度

咱們將僅僅處理關於z軸旋轉對稱的分佈,因此其餘相關的量,均設爲均勻分佈

 

給定一個和方向相關的pdf,p(direction) = f(θ),一維pdf中θ 和 φ以下:

g(φ) = 1/(2π)   (均勻分佈)

h(θ) = 2πf(θ)sinθ

 

對於兩個隨機生成的均勻變量r1和r2,咱們在第三篇內容中推導出

r1 = 0->φ 1/(2π) dθ = φ/(2π)

得   φ = 2πr1

r2 =  0->θ 2πf(t)sin(t) dt

t只是一個虛擬的量,用以代替變化的θ,而後輔助實現從0~θ對咱們以前的被積函數f(θ)積分(變限積份量不能相同,因此引入t)

 

咱們從下面開始嘗試不一樣的f()

首先,咱們考慮球體上採起均勻密度採樣,由於整個球面爲4πsr,故單位球體表面均勻密度採樣的pdf函數爲:p(direction) = 1/(4π),因此獲得:

r20->θ sin(t)/2 dt

  = (-cos(t)/2)|0->θ

  = (1-cosθ)/2

cosθ = 1 - 2r2

通常cosθ更經常使用一些,就不進一步求θ了,用的時候再acos()

爲了在笛卡爾座標系下生成一個指向(θ,φ)的單位向量,這就涉及到咱們的球座標代換方程了:

x = cosφ sinθ

y = sinφ sinθ

z = cosθ

它對應的球座標以下

此處r爲1

 

咱們開始推導

把關於θ和φ的式子帶入x,y,z中

x = cos(2πr1)*sqrt(1-cos2θ)

y = sin(2πr1)*sqrt(1-cos2θ)

z = 1-2r2

解得:

x = cos(2πr1)*sqrt(r2*(1-r2))

y = sin(2πr1)*sqrt(r2*(1-r2))

z = 1-2r2

 

而後咱們來作個圖,驗證一下它是否如咱們所願,生成的點聚攏爲單位球面

做者給出的畫圖方法是plot.ly,能夠在線畫,可是它要求註冊帳號,流程還挺麻煩的,GitHub受權帳號總是沒響應(可能我網很差),若是有興趣的能夠直接到這裏

直接能夠載入數據使用的網址

其實matlab最好使了,對不對啊

因而乎,咱們先寫入txt,千萬不要寫入xls,仍是比較麻煩的

 

    stds ofstream outfile("random_direction.txt");

    for (int i = 0; i < 200; ++i)
    {
        double r1 = lvgm::rand01();
        double r2 = lvgm::rand01();
        double x = cos(2 * π * r1) * 2 * sqrt(r2 * (1 - r2));
        double y = sin(2 * π * r1) * 2 * sqrt(r2 * (1 - r2));
        double z = 1 - 2 * r2;
        outfile << x << "\t" << y << "\t" << z << stds endl;
    }
    
    outfile.close();

 

建立一個random_direction.xls的文件,點菜單欄中的數據->自文本,選擇random_direction.txt,一路回車就加載完畢了,可見xls加載文本容易多了

假設你的路徑是

E:\OpenGL\光線追蹤\code\ray tracing 1-3\ray tracing 1-3\random_direction.xls

數據所在表的表名爲sheet1

則matlab代碼和效果以下圖

 

 效果仍是很好的

咱們能夠看到,它是均勻隨機的,達到了咱們的預期

 

吶,咱們接下來試一下咱們第二經常使用的pdf

p(direction) = cosθ/π

r= 0->θ 2π(cos(t)/π)sint dt

   = (-cos2t)|0->θ

   = 1 - cos2θ

cosθ = sqrt(1-r2)

因而乎咱們獲得下述的x,y,z

x = cos(2πr1)*sqrt(r2)

y = sin(2πr1)*sqrt(r2)

z = sqrt(1-r2)

 

下面咱們就生成隨機向量

inline rtvec random_cosine_direction()
{
    double r1 = lvgm::rand01();
    double r2 = lvgm::rand01();
    double z = sqrt(1 - r2);
    double φ = 2 * π*r1;
    double x = cos(φ) * 2 * sqrt(r2);
    double y = sin(φ) * 2 * sqrt(r2);
    return rtvec(x, y, z);
}

 

咱們按照原來的方法把第二個pdf函數產生的隨機狀況模擬一下:

 

 

那麼咱們用pdf作一個數值模擬

 

void estimate2()
{
    int n = 1000000;
    double sum = 0.;
    for (int i = 0; i < n; ++i)
    {
        rtvec v = ::random_cosine_direction();
        sum += pow(v.z(), 3) / (v.z() / π);
    }
    stds cout << "π/2 = " << π / 2 << stds endl;
    stds cout << "Estimate = " << sum / n << stds endl;
}

 

還有一個模擬半球獲得的近似,pdf = 1/(2π)

有興趣的能夠本身推一下,這裏直接給代碼,爲了和上面作對比

void estimate()
{
    int n = 1000000;
    double sum = 0.;
    for (int i = 0; i < n; ++i)
    {
        double r1 = lvgm::rand01();
        double r2 = lvgm::rand01();
        double x = cos(2 * π * r1) * 2 * sqrt(r2 * (1 - r2));
        double y = sin(2 * π * r1) * 2 * sqrt(r2 * (1 - r2));
        double z = 1 - r2;
        sum += z*z*z / (1. / (2.*π));
    }
    stds cout << "π/2 = " << π / 2 << stds endl;
    stds cout << "Estimate = " << sum / n << stds endl;
}

 

主函數

int main()
{
    //build_1_1();

    estimate();
    estimate2();
}

 

結果

能夠看到模擬半球的pdf函數效果沒有p() = cos/π 這個好

這一章中全部的都是基於z軸的,而下一章咱們真正基於物體表面法線進行

 

Chapter6 Ortho-normal Bases

ONB是三個相互正交的單位向量集合,笛卡爾座標系中的xyz軸也是一種ONB

咱們這一章的目的就是把上一章基於z軸的隨機方向轉換到基於表面法線的

 

假設,咱們有一個原點o和笛卡爾座標系向量 x/y/z,當咱們描述一個位置

好比:(3,-2,7)時,咱們這樣計算:

location = o + 3x - 2y +7z

假若有另一個座標系,它的原點爲o',基向量爲 u/v/w

咱們將要用以下方式表示(u,v,w)這個位置:

location = o' + uu + vv + ww

 

若是學過計算機圖形學,那麼你就能夠用矩陣變換去完成座標系,可是咱們這裏不須要這種操做。

咱們須要的是生成具備相對於表面法線的集合分佈的隨機方向。 咱們不須要原點,由於向量無起點。 

咱們引用光線追蹤1-8中所述的相機座標來計算ONB的三個基向量

 

咱們如今擁有的是法向量n,咱們能夠把它看作是lookfrom->lookat向量

咱們須要一個vup,假設法向量n自己幾乎平行於特定軸,那麼vup就取和n垂直的基向量,反之,咱們就使用特定軸做爲vup

代碼是描述思惟最好的方式之一:(假設特定軸爲x軸)

if(fabs(n.x())>0.9) //單位法向量n的x份量大於0.9,認爲n//x

  vup = (0,1,0)

else

  vup = (1,0,0)

 

咱們設ONB基向量由 s,t,n 組成

那麼 t = cross(vup,n).單位化

   s = cross(t,n)

理解可參考上圖,t爲圖中的u, s爲圖中的v

 

因此咱們的座標系中的任一點(x,y,z)表示以下
隨機向量 = xs + yt + zn

 

至此,咱們上代碼:

/// onb.hpp
// 
// -----------------------------------------------------
// [author]        lv
// [begin ]        2019.3
// [brief ]        ONB
// -----------------------------------------------------

#pragma once

namespace rt
{

class onb
    {
public:

    onb() {  }

    inline const rtvec& operator[](int index)const { return axis[index]; }
        
    inline const rtvec& u()const { return axis[0]; }
        
    inline const rtvec& v()const { return axis[1]; }

    inline const rtvec& w()const { return axis[2]; }

public:

    inline rtvec local(double a, double b, double c)const;

    inline rtvec local(const rtvec& v)const;

    void build_from_w(const rtvec&);

private:
        
    rtvec axis[3];

    };



inline rtvec onb::local(double a, double b, double c)const
    {
    return a*u() + b*v() + c*w();
    }

inline rtvec onb::local(const rtvec& v)const
    {
    return local(v.x(), v.y(), v.z());
    }

inline void onb::build_from_w(const rtvec& V)
    {
    axis[2] = V.ret_unitization();
    rtvec a;
    if (fabs(w().x()) > 0.9)
        a = rtvec(0, 1, 0);
    else
        a = rtvec(1, 0, 0);
    axis[1] = lvgm::cross(w(), a).ret_unitization();
    axis[0] = lvgm::cross(w(), v());
    }

} // rt namespace

 

咱們的Lambertian材質的scatter函數須要作相應的改動

原書沒有do-while循環,可是可能會出現pdf爲零的除零錯誤,因此咱們生成的隨機方向必須可以算出一個非零的pdf,這並不妨礙咱們的算法,由於這一切都是隨機的,隨機錯誤就再隨機一次

bool lambertian::scatter(const ray& rIn, const hitInfo& info, rtvec& alb, ray& scattered, rtvar& pdf)const
    {
    onb uvw;
    uvw.build_from_w(info._n);
    do {
        rtvec direction = uvw.local(random_cosine_direction());
        scattered = ray(info._p, direction.ret_unitization(), rIn.time());
        pdf = dot(uvw.w(), scattered.direction()) / π;
    } while (pdf == rtvar(0));
    alb = _albedo->value(info._u, info._v, info._p);
    return true;
    }

 

而後咱們跑一遍場景

感受還不是很真實,好像和之前的差很少(正常,這不能算鴿~,由於第八章之後差很少才能夠。。)

探索過程也是很值得借鑑的嘛,畢竟這是在一步一步引入新的技術,算是循循善誘~

下面是原書結尾,下一篇咱們講牛逼的技術——直接光源採樣

 

感謝您的閱讀,生活愉快~

相關文章
相關標籤/搜索