v4vendeta's homepage

home | project | blog| other

Ray Tracing in One Weekend Summary XIV - Rectangles & Lights & Translation

矩形 光照 变换


矩形和光照。如何做一个自发光的材质,首先须要在hit_record里面加一个 emitted的方法。比如说背景如果是纯黑的话,就相当于光线来了的时候,他不反射任何光线。

自发光

在material类中添加虚函数emitted,默认返回黑色

virtual vec3 emitted(float u, float v, const vec3& p) const {
    return vec3(0,0,0);
}

diffuse_light类

class diffuse_light : public material {
    public:
        diffuse_light(texture *a) : emit(a) {}
        virtual bool scatter(const ray& r_in, const hit_record& rec,
            vec3& attenuation, ray& scattered) const { return false; }
        virtual vec3 emitted(float u, float v, const vec3& p) const {
            return emit->value(u, v, p);
        }
        texture *emit;
};

color函数

vec3 color(const ray& r, hittable *world, int depth) {
    hit_record rec;
    if (world->hit(r, 0.001, MAXFLOAT, rec)) {
        ray scattered;
        vec3 attenuation;
        vec3 emitted = rec.mat_ptr->emitted(rec.u, rec.v, rec.p);
        if (depth < 50 && rec.mat_ptr->scatter(r, rec, attenuation, scattered))
             return emitted + attenuation*color(scattered, world, depth+1);
        else
            return emitted;
    }
    else
        return vec3(0,0,0);
}

矩形

以xy平面上的矩形为例,在 z=k时,x0x1y0y1可以定义一个矩形平面区域

计算光线和矩形相交的方程如下

p(t)=a+t*b

z(t)=az+t*bz

z=k

解得:

t=(k−az)/bz

x=ax+t∗bx

y=ay+t∗by

如果同时x在区间[x0,x1],y在[y0,y1]上,光线就和矩形相交

xyz_rec.h

#ifndef XYZRECT
#define XYZRECT 

#include "hittable.h"

class xy_rect:public hittable
{
    public:
        xy_rect();
        xy_rect(float _x0, float _x1, float _y0, float _y1, float _k, material *mat)
                : x0(_x0), x1(_x1), y0(_y0), y1(_y1), k(_k), mp(mat) {};
        ~xy_rect();
        virtual bool hit(const ray& r,float t_min,float t_max,hit_record& rec)const;
        virtual bool bounding_box(float t_min,float t_max,aabb& box)const;

        float x0,x1,y0,y1,k;
        material *mp;
};

class xz_rect: public hittable {
    public:
        xz_rect() {}
        xz_rect(float _x0, float _x1, float _z0, float _z1, float _k, material *mat)
            : x0(_x0), x1(_x1), z0(_z0), z1(_z1), k(_k), mp(mat) {};
        virtual bool hit(const ray& r, float t_min, float t_max, hit_record& rec) const;
        virtual bool bounding_box(float t_min, float t_max, aabb& box) const;
        
        material *mp;
        float x0, x1, z0, z1, k;
};

class yz_rect: public hittable {
    public:
        yz_rect() {}
        yz_rect(float _y0, float _y1, float _z0, float _z1, float _k, material *mat)
            : y0(_y0), y1(_y1), z0(_z0), z1(_z1), k(_k), mp(mat) {};
        virtual bool hit(const ray& r, float t_min, float t_max, hit_record& rec) const;
        virtual bool bounding_box(float t_min, float t_max, aabb& box) const;

        material  *mp;
        float y0, y1, z0, z1, k;
};
bool xy_rect::hit(const ray& r, float t_min, float t_max, hit_record& rec) const {
    float t = (k-r.origin().z()) / r.direction().z();
    if (t < t_min || t > t_max)
        return false;
    float x = r.origin().x() + t*r.direction().x();
    float y = r.origin().y() + t*r.direction().y();
    if (x < x0 || x > x1 || y < y0 || y > y1)
        return false;
    rec.u = (x-x0)/(x1-x0);
    rec.v = (y-y0)/(y1-y0);
    rec.t = t;
    rec.mat_ptr = mp;
    rec.p = r.point_at_parameter(t);
    rec.normal = vec3(0, 0, 1);
    return true;
}
bool xy_rect::bounding_box(float t_min,float t_max,aabb& box)const{
    box =  aabb(vec3(x0,y0, k-0.0001), vec3(x1, y1, k+0.0001));
    return true;
}

bool xz_rect::hit(const ray& r, float t_min, float t_max, hit_record& rec) const {
    float t = (k-r.origin().y()) / r.direction().y();
    if (t < t_min || t > t_max)
        return false;
    float x = r.origin().x() + t*r.direction().x();
    float z = r.origin().z() + t*r.direction().z();
    if (x < x0 || x > x1 || z < z0 || z > z1)
        return false;
    rec.u = (x-x0)/(x1-x0);
    rec.v = (z-z0)/(z1-z0);
    rec.t = t;
    rec.mat_ptr = mp;
    rec.p = r.point_at_parameter(t);
    rec.normal = vec3(0, 1, 0);
    return true;
}

bool xz_rect::bounding_box(float t_min, float t_max, aabb& box) const {
    box =  aabb(vec3(x0,k-0.0001,z0), vec3(x1, k+0.0001, z1));
    return true;
}

bool yz_rect::hit(const ray& r, float t_min, float t_max, hit_record& rec) const {
    float t = (k-r.origin().x()) / r.direction().x();
    if (t < t_min || t > t_max)
        return false;
    float y = r.origin().y() + t*r.direction().y();
    float z = r.origin().z() + t*r.direction().z();
    if (y < y0 || y > y1 || z < z0 || z > z1)
        return false;
    rec.u = (y-y0)/(y1-y0);
    rec.v = (z-z0)/(z1-z0);
    rec.t = t;
    rec.mat_ptr = mp;
    rec.p = r.point_at_parameter(t);
    rec.normal = vec3(1, 0, 0);
    return true;
}

bool yz_rect::bounding_box(float t_min, float t_max, aabb& box) const {
    box =  aabb(vec3(k-0.0001, y0, z0), vec3(k+0.0001, y1, z1));
    return true;
}
#endif

类中有hit和bounding_box两个成员函数

###flip_normals.h

在前一步做完后,渲染出的cornell box有一个bug,我们所建立的矩形是有朝向的,光照射到矩形背面时只会渲染出黑色,因此,我们要对一些矩形进行法向量的翻转

flip_normals.h

class flip_normals : public hittable {
    public:
        flip_normals(hittable *p) : ptr(p) {}

        virtual bool hit(
            const ray& r, float t_min, float t_max, hit_record& rec) const {

            if (ptr->hit(r, t_min, t_max, rec)) {
                rec.normal = -rec.normal;
                return true;
            }
            else
                return false;
        }

        virtual bool bounding_box(float t0, float t1, aabb& box) const {
            return ptr->bounding_box(t0, t1, box);
        }

        hittable *ptr;
};

test

接下来渲染一个cornell_box

hittable *cornell_box() {
    hittable **list = new hittable*[50];
    int i = 0;
    material *red = new lambertian(new constant_texture(vec3(0.65, 0.05, 0.05)));
    material *white = new lambertian(new constant_texture(vec3(0.73, 0.73, 0.73)));
    material *green = new lambertian(new constant_texture(vec3(0.12, 0.45, 0.15)));
    material *light = new diffuse_light(new constant_texture(vec3(15, 15, 15)));
    material *spec = new metal(vec3(1,1,1),0);

    list[i++] = new flip_normals(new yz_rect(0, 555, 0, 555, 555, green));
    list[i++] = new yz_rect(0, 555, 0, 555, 0, red);
    list[i++] = new xz_rect(213, 343, 227, 332, 554, light);
    list[i++] = new flip_normals(new xz_rect(0, 555, 0, 555, 555, white));
    list[i++] = new xz_rect(0, 555, 0, 555, 0, white);
    list[i++] = new flip_normals(new xy_rect(0, 555, 0, 555, 555, white));

    list[i++] = new sphere(vec3(150, 150, 350), 100, spec);
    list[i++] = new sphere(vec3(350, 100, 500), 100, green);

    return new hittable_list(list,i);
}
vec3 lookfrom(278, 278, -800);
vec3 lookat(278,278,0);
float dist_to_focus = 10.0;
float aperture = 0.0;
float vfov = 40.0;
camera cam(lookfrom, lookat, vec3(0,1,0), vfov, float(nx)/float(ny),aperture, dist_to_focus, 0.0, 1.0);

渲染结果如下

长方体

长方体类的定义只需要定义8个相邻的矩形即可

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);
}

变换

平移

translate.h

#ifndef TRANSLATE
#define TRANSLATE 

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;
}
#endif

旋转

物体绕z轴旋转的示意图如下

旋转角度为θ

x'=cos(θ)*x-sin(θ)*y
y'=sin(θ)*x+cos(θ)*y

rotate.h

#ifndef ROTATE
#define ROTATE 

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);
                //重新计算bounding_box
                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;
}
#endif

test

hittable *cornell_box() {
    hittable **list = new hittable*[50];
    int i = 0;
    material *red = new lambertian(new constant_texture(vec3(0.65, 0.05, 0.05)));
    material *white = new lambertian(new constant_texture(vec3(0.73, 0.73, 0.73)));
    material *green = new lambertian(new constant_texture(vec3(0.12, 0.45, 0.15)));
    material *light = new diffuse_light(new constant_texture(vec3(15, 15, 15)));
    material *spec = new metal(vec3(1,1,1),0);

    list[i++] = new flip_normals(new yz_rect(0, 555, 0, 555, 555, green));
    list[i++] = new yz_rect(0, 555, 0, 555, 0, red);
    list[i++] = new xz_rect(213, 343, 227, 332, 554, light);
    list[i++] = new flip_normals(new xz_rect(0, 555, 0, 555, 555, white));
    list[i++] = new xz_rect(0, 555, 0, 555, 0, white);
    list[i++] = new flip_normals(new xy_rect(0, 555, 0, 555, 555, white));

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

    return new hittable_list(list,i);
}

渲染出的图像如下

.. ... ...