Peter Shirley Ray Tracing in One Weekend(下篇)

Peter Shirley-Ray Tracing in One Weekend (2016)

原著:Peter Shirley
下篇主要對本書的後5章節進行學習,包括材質球的Metal,和Dielectrics。世界空間中相機的位置,相機經過光圈和焦距實現景深效果,最後結合全書知識點,渲染出一個如本篇封面的場景。c++

https://github.com/EStormLynn/Peter-Shirley-Ray-Tracing-in-one-weenkendgit

目錄

  • Chapter8:Metal
  • Chapter9:Dielectrics
  • Chapter10:Positionable camera
  • Chapter11:Defocus
  • Chapter12:Where next?

Chapter8:Metal

對於不一樣的物體,可能有不一樣的材質,因此就須要設計一個材質抽象類,包含一些參數。對於程序而言,材質須要作的事情包括github

  • 1.產生一個散射體(或者表示吸取了多少光線)
  • 2.若是發生散射,表達出光線應該衰減多少

抽象類以下:app

class material  {
public:
    // 散射虛函數
    // 參數:r_in 入射的光線, rec hit的記錄, attenuation v3的衰減,scattered 散射後的光線
    virtual bool scatter(const ray& r_in, const hit_record& rec, vec3& attenuation, ray& scattered) const = 0;
};

hitables 和material需要知道對方的數據,因此在c++代碼中,hit_record中加了一個指針 * mat_ptr 指向material這個類。dom

struct hit_record
{
    float t;
    vec3 p;
    vec3 normal;
    material *mat_ptr;
};

lambertian 材質,主要是漫反射,經過attenuation衰減,來控制散射以後的光線強度,散射的方向用random_in_unit_sphere()控制,albedo表示反射率函數

class lambertian : public material {
public:
    lambertian(const vec3& a) : albedo(a) {}
    virtual bool scatter(const ray& r_in, const hit_record& rec, vec3& attenuation, ray& scattered) const  {
        vec3 target = rec.p + rec.normal + random_in_unit_sphere();
        scattered = ray(rec.p, target-rec.p);
        attenuation = albedo;
        return true;
    }

    vec3 albedo;    // 反射率
};

對於光滑表面的物體,ray不會隨機的散射,物理規律是反射角等於入射角,會發生鏡面反射,向量的說明以下:學習

紅色的是反射光線,向量表示是(v+2B),N是單位法向量,v是入射光線的方向向量,B的模是v和N的點乘 dot(v,N)。公式爲:debug

vec3 reflect(const vec3& v, const vec3& n) {
    return v - 2*dot(v,n)*n;
}

metal材質只反射光線,代碼以下:設計

class metal : public material {
public:
    metal(const vec3& a, float f) : albedo(a) { if (f < 1) fuzz = f; else fuzz = 1; }
    virtual bool scatter(const ray& r_in, const hit_record& rec, vec3& attenuation, ray& scattered) const  {
        vec3 reflected = reflect(unit_vector(r_in.direction()), rec.normal);
        scattered = ray(rec.p, reflected + fuzz*random_in_unit_sphere());
        attenuation = albedo;
        return (dot(scattered.direction(), rec.normal) > 0);
    }
    vec3 albedo;
    float fuzz;
};

修改color方法,對散射進行遞歸,求color3d

vec3 color(const ray& r,hitable *world, int depth)
{
    hit_record rec;
    if(world->hit(r,0.0,MAXFLOAT,rec))
    {
        // 散射後的光線
        ray scattered;
        // 衰減
        vec3 attenuation;

        if(depth<50 && rec.mat_ptr->scatter(r,rec,attenuation,scattered))
        {
            // 遞歸 衰減
            return attenuation * color(scattered, world, depth+1);
        } else
        {
            return vec3(0,0,0);
        }
    }
    else
    {
        vec3 unit_direction = unit_vector(r.direction());
        float t = 0.5 *(unit_direction.y() + 1.0);
        return (1.0-t)*vec3(1.0,1.0,1.0) + t*vec3(0.5,0.7,1.0);
    }
}

再在場景中添加2個metal材質的球,main函數以下
注意由於加了hitrecord添加了material ,sphere的hit函數需要將hit_record的引用傳出來,需要在函數內形參的指針指向material的matptr。

int main()
{
    int nx =200;
    int ny =100;
    // 採樣數量ns
    int ns = 100;
    cout<<"P3\n"<<nx<<" "<<ny<<"\n255\n";

    camera cam;

    hitable *list[2];
    // 球1,2,3,4
    list[0] = new sphere(vec3(0,0,-1),0.5,new lambertian(vec3(0.8,0.3,0.3)));
    list[1] = new sphere(vec3(0,-100.5,-1),100,new lambertian(vec3(0.8,0.8,0.0)));
    list[2] = new sphere(vec3(1,0,-1),0.5,new metal(vec3(0.8,0.6,0.2),1));
    list[3] = new sphere(vec3(-1,0,-1),0.5,new metal(vec3(0.8,0.8,0.8),1));

    hitable *world = new hitable_list(list,4);
    random_device rd;

    for(int j=ny-1;j>=0;j--)
    {
        for(int i=0;i<nx;i++)
        {
            vec3 col(0,0,0);

            for(int s = 0; s<ns; s++)
            {
                float u = (float(i)+float(random(0,100))/100.0f)/float(nx);
                float v = (float(j)+float(random(0,100))/100.0f)/float(ny);

                ray r = cam.get_ray(u,v);
                vec3 p = r.point_at_parameter(2.0);
                col += color(r,world,0);
            }
            // color 取均值
            col /= float(ns);
            col = vec3(sqrt(col[0]),sqrt(col[1]),sqrt(col[2]));

            int ir=int(255.99* col[0]);
            int ig=int(255.99* col[1]);
            int ib=int(255.99* col[2]);;
            cout<<ir<<" "<<ig<<" "<<ib<<"\n";
        }
    }

}

關於metal的反射,也能夠用一個隨機性的反射方向,來作微量的偏移,至關於一個小球上選endpoint。fuzzinss就至關於這個小球的半徑,能夠決定反射偏移的多少。fuzz取值在[0,1]之間

最後效果以下,注意兩邊的metal sphere中反射的邊界模糊。

Chapter9:Dielectrics

透明的物體,好比水,玻璃,鑽石是電介質,當光射入的時候,不只發生反射,還會發生折射。折射光線是ray tracer中比較難debug的部分。本章節在場景中放入了2個玻璃球,渲染出來的畫面是這樣的:

光從一種介質進入另外一種介質時,實際上,有一部分光會折射進入另外一種介質,有另外一部分光則會反射回來。反射係數=反射光振幅(能量)/入射光振幅(能量)。

反射係數的求解是是一個很是複雜的過程,Christophe Schlick這我的提供一個逼近公式,這個公式被稱爲「ChristopheSchlick’s Approximation」。Wiki連接:

https://en.wikipedia.org/wiki/Schlick%27s_approximation

當反射係數爲0,只有折射,沒有反射。

折射知足 斯涅爾定律(Snell law)

n * sin(theta) = n' * sin(theat')

折射係數(air= 1,glass = 1.3-1.7, diamond = 2.4)

折射部分的代碼以下:

bool refract(const vec3& v, const vec3& n, float ni_over_nt, vec3& refracted) {
    vec3 uv = unit_vector(v);
    float dt = dot(uv, n);
    float discriminant = 1.0 - ni_over_nt*ni_over_nt*(1-dt*dt);
    if (discriminant > 0) {
        refracted = ni_over_nt*(uv - n*dt) - n*sqrt(discriminant);
        return true;
    }
    else
        return false;
}

電解質材質老是會發生折射,因此材質類中派生dielectric類。

class dielectric : public material {
public:
    dielectric(float ri) : ref_idx(ri) {}
    virtual bool scatter(const ray& r_in, const hit_record& rec, vec3& attenuation, ray& scattered) const  {
        vec3 outward_normal;
        vec3 reflected = reflect(r_in.direction(), rec.normal);
        float ni_over_nt;
        attenuation = vec3(1.0, 1.0, 1.0);
        vec3 refracted;
        float reflect_prob;
        float cosine;
        if (dot(r_in.direction(), rec.normal) > 0) {
            outward_normal = -rec.normal;
            ni_over_nt = ref_idx;
            //         cosine = ref_idx * dot(r_in.direction(), rec.normal) / r_in.direction().length();
            cosine = dot(r_in.direction(), rec.normal) / r_in.direction().length();
            cosine = sqrt(1 - ref_idx*ref_idx*(1-cosine*cosine));
        }
        else {
            outward_normal = rec.normal;
            ni_over_nt = 1.0 / ref_idx;
            cosine = -dot(r_in.direction(), rec.normal) / r_in.direction().length();
        }
        if (refract(r_in.direction(), outward_normal, ni_over_nt, refracted))
            reflect_prob = schlick(cosine, ref_idx);
        else
            reflect_prob = 1.0;

        // 隨機數小與反射係數,設爲反射光線,反之爲折射光線
        if (drand48() < reflect_prob)
            scattered = ray(rec.p, reflected);
        else
            scattered = ray(rec.p, refracted);
        return true;
    }

    float ref_idx;
};

衰減始終是1,玻璃表面不吸取任何光線。

當場景中添加4個球,渲染出來的畫面是這樣的

list[0] = new sphere(vec3(0,0,-1),0.5,new lambertian(vec3(0.8,0.3,0.3)));
    list[1] = new sphere(vec3(0,-100.5,-1),100,new lambertian(vec3(0.8,0.8,0.0)));
    list[2] = new sphere(vec3(1,0,-1),0.5,new metal(vec3(0.8,0.6,0.2),0.3));
    list[3] = new sphere(vec3(-1,0,-1),0.5,new dielectric(1.5));

若是對於電介質的球內部再加一個半徑爲 負的球,獲得的效果以下(感受是不一樣介質之間負負得正了):

list[0] = new sphere(vec3(0,0,-1),0.5,new lambertian(vec3(0.8,0.3,0.3)));
    list[1] = new sphere(vec3(0,-100.5,-1),100,new lambertian(vec3(0.8,0.8,0.0)));
    list[2] = new sphere(vec3(1,0,-1),0.5,new metal(vec3(0.8,0.6,0.2),0.3));
    list[3] = new sphere(vec3(-1,0,-1),0.5,new dielectric(1.5));
    list[4] = new sphere(vec3(-1,0,-1),-0.45,new dielectric(1.5));

Chapter10:Positionable camera

自由位置的camera,首先有了解FOV(Field of view)視場的概念,至關於視力看到的必定角度的內容。

從射線源點的位置,射向z=-1的平面,能夠看到的高度h,知足:

h = tan(theta/2)

修改camera部分的代碼,增長fov 和aspect來控制能夠看到的寬和高。

設置好camera的viewpoint以後,viewpoint就是lookfrom的點,看向的點就是lookat,還須要肯定看過去水平方向的視野寬度,和豎直方向的視野寬度,camera所在平面豎直向上的向量"view up」 vup,經過叉乘,拿到uvw,恰好至關於一個相機的座標系。

class camera
{
    vec3 origin;
    vec3 horizontal;
    vec3 vertical;
    vec3 lower_left_corner;

public :
    camera(vec3 lookfrom, vec3 lookat, vec3 vup, float vfov, float aspect)
    {
        vec3 u,v,w;
        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 = vec3 (-half_width,-half_height,-1.0);
        lower_left_corner = origin - half_width*u - half_height*v - w;
        horizontal = 2*half_width*u;
        vertical = 2*half_height*v;
    }

    ray get_ray(float u,float v)
    {
        return ray(origin,lower_left_corner+u*horizontal + v*vertical - origin);
    }

};

設置新的攝像機,fov分別設置90和30°,獲得的畫面以下:

Chapter11:Defocus Blur

散焦模糊(虛化),拍照的時候,咱們常常會製造出虛化的效果,主題清晰,背景或者前景模糊,這是由於攝像機具備焦距,會有一個成像面,在有效焦距內的物體才能清晰成像,經過光圈控制進光量也能夠控制虛化的範圍。大光圈和長焦端,均可以製造出淺景深的效果。

本章引入aperture(光圈),focus_dist(焦距) 2個參數,來實現畫面的虛化效果。

class camera
{
    vec3 origin;
    vec3 u,v,w;
    vec3 horizontal;
    vec3 vertical;
    vec3 lower_left_corner;
    float len_radius;

public :
    camera(vec3 lookfrom, vec3 lookat, vec3 vup, float vfov, float aspect, float aperture, float focus_dist)
    {
        len_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 = len_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);
    }

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

};

改變camera的參數,設置光圈和焦距

vec3 lookfrom(3,3,2);
    vec3 lookat(0,0,-1);
    float dist_to_focus = (lookfrom-lookat).length();
    float aperture = 2.0;
    camera cam(lookfrom,lookat,vec3(0,1,0),20,float(nx)/float(ny),aperture,dist_to_focus);

拿到的效果以下:

Chapter12:Where next?

這張主要運用本書學到的知識,完成封面上的圖片的渲染。

經過添加一個隨機生成的世界,達到不少個小球的效果

hitable *random_scene() {
    int n = 500;
    hitable **list = new hitable*[n+1];
    list[0] =  new sphere(vec3(0,-700,0), 700, 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 = drand48();
            vec3 center(a+0.9*drand48(),0.2,b+0.9*drand48());
            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(drand48()*drand48(), drand48()*drand48(), drand48()*drand48())));
                }
                else if (choose_mat < 0.95) { // metal
                    list[i++] = new sphere(center, 0.2,
                                           new metal(vec3(0.5*(1 + drand48()), 0.5*(1 + drand48()), 0.5*(1 + drand48())),  0.5*drand48()));
                }
                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(2.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(1, 1, 1), 0.0));

    return new hitable_list(list,i);
}

下一本書Peter Shirley-Ray Tracing The Next Week (2016) 將會從如下幾個方面繼續學習光追。

  • Motion Blur

    運動模糊。跟上面的DOF同樣,暴力多渲幾幀,每幀用不一樣的位置就行。
  • Bounding Volume Hierarchies

    包圍盒樹。加速相交檢測計算的。這方面另外有一大把更好的資料。
  • 貼圖

    形狀裏定義好UV,而後用UV來採樣貼圖。固然採樣方法有多種。
  • Perlin Noise

    柏林噪聲。這裏重點對採樣結果作了Filtering,已經簡單介紹了Turb(N重噪聲產生的大理石紋理)
  • 光源

    把光源當成Emissive材質就搞定了。
  • 物件的擺放

    以前都是寫絕對座標的,這裏在形狀的體系裏面加了一層Transform,只要經過那個Transform把Ray變換到物體的局部空間就能夠像以前同樣進行相交檢測了。
  • 體(Volume / Participating Media)

    依然暴力的把Ray一點點插進Volume裏面進行採樣,每一個採樣點上取得Volume自身的顏色,並把Ray四散開去便可。

相關文章
相關標籤/搜索