《用一週學習光線追蹤》7.長方體和平移旋轉

本項目參考自《Ray Tracing in One Weekend》系列
上接《用兩天學習光線追蹤》,繼續學習光線追蹤。
項目鏈接: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.長方體和平移旋轉


實現長方體

首先,實現一個軸對齊的長方體box,同樣繼承自hittable,軸對齊長方體有6個面,塞到一個hittable_list數組裏面,這6個面的數組構成了一個box。

class box: public hittable  {
    public:
        box() {}
        box(const vec3& p0, const vec3& p1, material *ptr);
        virtual bool hit(const ray& r, float t0, float t1, hit_record& rec) const;
        virtual bool bounding_box(float t0, float t1, aabb& box) const {
               box =  aabb(pmin, pmax);
               return true; }
        vec3 pmin, pmax;
        hittable *list_ptr;
};

box::box(const vec3& p0, const vec3& p1, material *ptr) {
    pmin = p0;
    pmax = p1;
    hittable **list = new hittable*[6];
    list[0] = new xy_rect(p0.x(), p1.x(), p0.y(), p1.y(), p1.z(), ptr);
    list[1] = new flip_normals(new xy_rect(p0.x(), p1.x(), p0.y(), p1.y(), p0.z(), ptr));
    list[2] = new xz_rect(p0.x(), p1.x(), p0.z(), p1.z(), p1.y(), ptr);
    list[3] = new flip_normals(new xz_rect(p0.x(), p1.x(), p0.z(), p1.z(), p0.y(), ptr));
    list[4] = new yz_rect(p0.y(), p1.y(), p0.z(), p1.z(), p1.x(), ptr);
    list[5] = new flip_normals(new yz_rect(p0.y(), p1.y(), p0.z(), p1.z(), p0.x(), ptr));
    list_ptr = new hittable_list(list,6);
}

bool box::hit(const ray& r, float t0, float t1, hit_record& rec) const {
    return list_ptr->hit(r, t0, t1, rec);
}

場景中增加兩個軸對齊長方體:

list[i++] = new box(vec3(130, 0, 65), vec3(295, 165, 230), white);
list[i++] = new box(vec3(265, 0, 295), vec3(430, 330, 460), white);

效果如下:


平移

平移是相對的,如果要射線採樣一個平移的box,只需要在射線命中檢測之前,將射線反向平移。

舉個例子:如果要對一個box在x軸上平移2個單位,從(1,1)平移到(3,1),並不需要老老實實地平移box,只需要在檢查射線是否命中之前,將射線反向平移,然後再做真正的射線命中判斷。若命中,則將命中點正向平移。

代碼中,需要包多一層translate,用反向平移後的射線moved_r來進行命中檢測:

class translate : public hittable {
    public:
        translate(hittable *p, const vec3& displacement)
            : ptr(p), offset(displacement) {}
        virtual bool hit(
            const ray& r, float t_min, float t_max, hit_record& rec) const;
        virtual bool bounding_box(float t0, float t1, aabb& box) const;
        hittable *ptr;
        vec3 offset;
};

bool translate::hit(const ray& r, float t_min, float t_max, hit_record& rec) const {
    ray moved_r(r.origin() - offset, r.direction(), r.time());
    if (ptr->hit(moved_r, t_min, t_max, rec)) {
        rec.p += offset;
        return true;
    }
    else
        return false;
}

bool translate::bounding_box(float t0, float t1, aabb& box) const {
    if (ptr->bounding_box(t0, t1, box)) {
        box = aabb(box.min() + offset, box.max() + offset);
        return true;
    }
    else
        return false;
}

旋轉

接下來實現旋轉,旋轉的原理跟平移非常相似。下面以繞z軸旋轉爲例:

繞z軸逆時針旋轉θ\theta,則x和y分量的變化公式爲:
x=cos(θ)xsin(θ)y x' = cos(\theta) \cdot x - sin(\theta) \cdot y

y=sin(θ)x+cos(θ)y y' = sin(\theta) \cdot x + cos(\theta) \cdot y

同理,繞y軸旋轉公式爲:
x=cos(θ)x+sin(θ)zx' = cos(\theta) \cdot x + sin(\theta) \cdot z

z=sin(θ)x+cos(θ)z z' = -sin(\theta) \cdot x + cos(\theta) \cdot z

繞x軸旋轉公式爲:
y=cos(θ)ysin(θ)z y' = cos(\theta) \cdot y - sin(\theta) \cdot z

z=sin(θ)y+cos(θ)z z' = sin(\theta) \cdot y + cos(\theta) \cdot z

如果反向旋轉,則只需要將 θ\theta 替換成 θ-\theta ,同時運用 cos(θ)=cos(θ)cos(\theta) = cos(-\theta)sin(θ)=sin(θ)sin(-\theta) = -sin(\theta) 即可。

如果要旋轉射線,則同樣要在射線命中檢測之前,同時反向旋轉 射線的起點和方向。若命中,則跟平移類似,變換命中點的座標,此外,還需要額外對法線執行旋轉。繞y軸旋轉寫成代碼如下:

class rotate_y : public hittable {
    public:
        rotate_y(hittable *p, float angle);
        virtual bool hit(const ray& r, float t_min, float t_max, hit_record& rec) const;
        virtual bool bounding_box(float t0, float t1, aabb& box) const {
            box = bbox; return hasbox;}
        hittable *ptr;
        float sin_theta;
        float cos_theta;
        bool hasbox;
        aabb bbox;
};

rotate_y::rotate_y(hittable *p, float angle) : ptr(p) {
    float radians = (M_PI / 180.) * angle;
    sin_theta = sin(radians);
    cos_theta = cos(radians);
    hasbox = ptr->bounding_box(0, 1, bbox);
    vec3 min(FLT_MAX, FLT_MAX, FLT_MAX);
    vec3 max(-FLT_MAX, -FLT_MAX, -FLT_MAX);
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 2; j++) {
            for (int k = 0; k < 2; k++) {
                float x = i*bbox.max().x() + (1-i)*bbox.min().x();
                float y = j*bbox.max().y() + (1-j)*bbox.min().y();
                float z = k*bbox.max().z() + (1-k)*bbox.min().z();
                float newx = cos_theta*x + sin_theta*z;
                float newz = -sin_theta*x + cos_theta*z;
                vec3 tester(newx, y, newz);
                for ( int c = 0; c < 3; c++ )
                {
                    if ( tester[c] > max[c] )
                        max[c] = tester[c];
                    if ( tester[c] < min[c] )
                        min[c] = tester[c];
                }
            }
        }
    }
    bbox = aabb(min, max);
}   

bool rotate_y::hit(const ray& r, float t_min, float t_max, hit_record& rec) const {
    vec3 origin = r.origin();
    vec3 direction = r.direction();
    origin[0] = cos_theta*r.origin()[0] - sin_theta*r.origin()[2];
    origin[2] =  sin_theta*r.origin()[0] + cos_theta*r.origin()[2];
    direction[0] = cos_theta*r.direction()[0] - sin_theta*r.direction()[2];
    direction[2] = sin_theta*r.direction()[0] + cos_theta*r.direction()[2];
    ray rotated_r(origin, direction, r.time());
    if (ptr->hit(rotated_r, t_min, t_max, rec)) {
        vec3 p = rec.p;
        vec3 normal = rec.normal;
        p[0] = cos_theta*rec.p[0] + sin_theta*rec.p[2];	//命中點座標需要旋轉
        p[2] = -sin_theta*rec.p[0] + cos_theta*rec.p[2];
        normal[0] = cos_theta*rec.normal[0] + sin_theta*rec.normal[2];	//命中點法線也要旋轉
        normal[2] = -sin_theta*rec.normal[0] + cos_theta*rec.normal[2];
        rec.p = p;
        rec.normal = normal;
        return true;
    }
    else 
        return false;
}

平移和旋轉的結合

在場景中,應用剛剛實現的平移和變換:

	hittable *box1 = new box(vec3(0,0,0), vec3(165,165,165), white);
	hittable *box2 = new box(vec3(0,0,0), vec3(165,330,165), white);
	list[i++] = new translate(new rotate_y(box1, -18), vec3(130,0,65));
	list[i++] = new translate(new rotate_y(box2, 15), vec3(265,0,295));

注意:上述旋轉是對物體的局部座標的旋轉,要等局部變換執行完之後才平移到世界座標。(也就是說,旋轉和平移的順序不能搞反,作者沒有提到這個潛規則,特此補充)

平移和旋轉的效果如下:


參考資料:https://raytracing.github.io/books/RayTracingTheNextWeek.html

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