《用兩天學習光線追蹤》10.散焦模糊

本項目參考自教程《Ray Tracing in One Weekend》,在跑通了所有例子之後,加上了自己的理解寫成筆記,項目使用CPU多線程提速,並增加了GUI進度顯示。
項目鏈接:https://github.com/maijiaquan/ray-tracing-with-imgui

目錄:
《用兩天學習光線追蹤》1.項目介紹和ppm圖片輸出
《用兩天學習光線追蹤》2.射線、簡單相機和背景輸出
《用兩天學習光線追蹤》3.球體和表面法向量
《用兩天學習光線追蹤》4.封裝成類
《用兩天學習光線追蹤》5.抗鋸齒
《用兩天學習光線追蹤》6.漫反射材質
《用兩天學習光線追蹤》7.反射向量和金屬材質
《用兩天學習光線追蹤》8.折射向量和電介質
《用兩天學習光線追蹤》9.可放置相機
《用兩天學習光線追蹤》10.散焦模糊

《用一週學習光線追蹤》1.動態模糊
《用一週學習光線追蹤》2.BVH樹、AABB相交檢測
《用一週學習光線追蹤》3.純色紋理和棋盤紋理
《用一週學習光線追蹤》4.柏林噪聲
《用一週學習光線追蹤》5.球面紋理貼圖
《用一週學習光線追蹤》6.光照和軸對齊矩形
《用一週學習光線追蹤》7.長方體和平移旋轉


本節內容

1.新增相機的參數,實現一個帶光圈可散焦模糊的相機。
2.場景中隨機生成更多的不同材質球體。

本節代碼:main10.cppmain11.cpp


散焦模糊

我們之前定義的相機,本質上是一個針孔相機。如下圖所示,真正的針孔相機成像是倒立的,但根據三角形相似,在代碼中可以將屏幕挪到相機的位置的前方,從而避免倒立的情況,並更直觀地去定義射線的起點和方向,回憶上一節camera類的代碼:camear類裏面有一個origin,表示相機的位置(對應下圖“我們相機的位置”),然後從相機的位置出發,對着屏幕(對應下圖“我們的屏幕”)上的每一個像素髮射射線進行採樣。

下面介紹一下帶鏡頭的相機和針孔相機的區別:

針孔相機中(假設孔徑足夠小),則從樹的頂點P到屏幕,只能通過一束光來成像這個點。帶鏡頭的相機中,光線不是透過一個點(或者說“孔”)傳入到成像屏幕的,而是透過具有一定半徑的透鏡傳入的,半徑的長度對應光圈的大小。這就導致成像的光線不僅只有一束,而是多束。

下圖中,相機位置依然跟上圖一樣。紅色光線反映了針孔相機中,將樹的頂點P和最低點Q,傳入相機屏幕的情況。藍色光線就是鏡頭相機的成像情況,對於頂點P,其傳入到成像屏幕的範圍,從之前的一條光線,擴大到L1到L2兩條光線之間的部分,儘管採樣的光線變多了,但並不影響這一棵樹的清晰成像,因爲目前這棵樹到相機的距離,剛好是新的屏幕到相機的距離,即焦距。

接下來,請大家發揮想象力去理解兩個場景:

1.將這顆樹往相機的方向移動,原本能採樣到樹頂的像素顏色,變成了多條光線採樣值的混合色,也即是樹頂部下面一片區域的顏色,從而導致這個像素變模糊,越往前移動,越模糊,因爲L1和L2的區間會擴大更多。

2.將這棵樹高度稍微拉高一點,並將其往後面移動,延長光線L1和L2至樹的縱切平面,則會採樣天空和樹頭頂的顏色的混合色,同樣實現模糊。越往後,L1和L2的區間將會擴大,從而越模糊。

因此,只要物體到相機的距離不等於焦距,就會出現模糊,光圈越大,採樣射線的跨度越大,模糊效果越明顯,從而實現景深這一裝逼效果。程序中,爲了簡化操作,可以將原來相機的位置,從一個點,變換到鏡頭所在圓盤內的某個點。因爲會多重採樣抗鋸齒(參考:第五節:抗鋸齒),所以會從圓盤內的多個點出發,發射射線並採樣求平均,以模擬上述鏡頭相機的原理。圓盤內點的位置,實際上爲相機原位置,加上光圈半徑範圍內的偏移量。

生成單位圓內的點的函數:

inline vec3 random_in_unit_disk() {
    vec3 p;
    do {
        p = 2.0*vec3(random_double(),random_double(),0) - vec3(1,1,0);
    } while (dot(p,p) >= 1.0);
    return p;
}

更新相機類:

class camera
{
public:
    vec3 origin;
    vec3 lower_left_corner;
    vec3 horizontal;
    vec3 vertical;
    vec3 u, v, w;
    float lens_radius;

    //lookfrom爲相機位置,lookat爲觀察位置,vup傳(0,1,0),vfov爲視野角度,aspect爲屏幕寬高比
    //aperture爲光圈大小,focus_dist爲相機到觀察點的距離
    camera(vec3 lookfrom, vec3 lookat, vec3 vup, float vfov, float aspect, float aperture, float focus_dist)
    {
        lens_radius = aperture / 2;
        float theta = vfov * M_PI / 180;
        float half_height = tan(theta / 2);
        float half_width = aspect * half_height;
        origin = lookfrom;
        w = unit_vector(lookfrom - lookat);
        u = unit_vector(cross(vup, w));
        v = cross(w, u);
        lower_left_corner = origin - half_width * focus_dist * u - half_height * focus_dist * v - focus_dist * w;
        horizontal = 2 * half_width * focus_dist * u;
        vertical = 2 * half_height * focus_dist * v;
    }

    ray get_ray(float s, float t)
    {
        vec3 rd = lens_radius * random_in_unit_disk();
        vec3 offset = u * rd.x() + v * rd.y();
        return ray(origin + offset, lower_left_corner + s * horizontal + t * vertical - origin - offset);
    }
};

下面是光圈從0.0遞增到3.0的效果:


增加更多球體

增加隨機場景函數,並修改相機參數:

vec3 lookfrom(13, 2, 3);
vec3 lookat(0, 0, 0);
float dist_to_focus = 10.0;
float aperture = 0.1;
hittable *random_scene() {
    int n = 500;
    hittable **list = new hittable*[n+1];
    list[0] =  new sphere(vec3(0,-1000,0), 1000, new lambertian(vec3(0.5, 0.5, 0.5)));
    int i = 1;
    for (int a = -11; a < 11; a++) {
        for (int b = -11; b < 11; b++) {
            float choose_mat = random_double();
            vec3 center(a+0.9*random_double(),0.2,b+0.9*random_double());
            if ((center-vec3(4,0.2,0)).length() > 0.9) {
                if (choose_mat < 0.8) {  // diffuse
                    list[i++] = new sphere(center, 0.2,
                        new lambertian(vec3(random_double()*random_double(),
                                            random_double()*random_double(),
                                            random_double()*random_double())
                        )
                    );
                }
                else if (choose_mat < 0.95) { // metal
                    list[i++] = new sphere(center, 0.2,
                            new metal(vec3(0.5*(1 + random_double()),
                                           0.5*(1 + random_double()),
                                           0.5*(1 + random_double())),
                                      0.5*random_double()));
                }
                else {  // glass
                    list[i++] = new sphere(center, 0.2, new dielectric(1.5));
                }
            }
        }
    }

    list[i++] = new sphere(vec3(0, 1, 0), 1.0, new dielectric(1.5));
    list[i++] = new sphere(vec3(-4, 1, 0), 1.0, new lambertian(vec3(0.4, 0.2, 0.1)));
    list[i++] = new sphere(vec3(4, 1, 0), 1.0, new metal(vec3(0.7, 0.6, 0.5), 0.0));

    return new hittable_list(list,i);
}

效果如下:


終於大結局了,如有錯誤歡迎指正,有看不懂的地方歡迎留言。

參考資料:《Ray Tracing in One Weekend》

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