Instance(案例)

Cornell Box一般来说还有两个box,这里我们可以撞见六边面来模拟

对于一个立方体,六个面,分别的求每个面的形状就可以。其步骤如下:

  1. 假定立方体就是正对的(轴与x,y,z轴平行)
  2. 利用立方体的每个轴的最小值和最大值来定义边界。
  3. 最后得到这个立方体的每个面,每个面都是一个quad,所以可以定义一个hittable_list去记录所有的quad

定义边界点

auto min = Point3(std::fmin(a.x(),b.x()),std::fmin(a.y(),b.y()),std::min(a.z(),b.z()));
auto max = Point3(std::fmax(a.x(),b.x()),std::fmax(a.y(),b.y()),std::fmax(a.z(),b.z()));

定义轴向量

auto dx = vec3(max.x() - min.x() , 0 , 0 );
auto dy = vec3(0 , max.y() - min.y() , 0 );
auto dz = vec3(0 , 0 , max.z() - min.z());

这里我们按照正对一个面的左边的点来定义起始的位置,然后定义每个面的轴

inline shared_ptr<hittable_list> box(const Point3& a,const Point3& b,shared_ptr<material> mat){
    auto sides = make_shared<hittable_list>();
    auto min = Point3(std::fmin(a.x(),b.x()),std::fmin(a.y(),b.y()),std::min(a.z(),b.z()));
    auto max = Point3(std::fmax(a.x(),b.x()),std::fmax(a.y(),b.y()),std::fmax(a.z(),b.z()));

    auto dx = vec3(max.x() - min.x() , 0 , 0 );
    auto dy = vec3(0 , max.y() - min.y() , 0 );
    auto dz = vec3(0 , 0 , max.z() - min.z());

    sides->add(make_shared<quad>(Point3(min.x(),min.y(),max.z()),dx,dy,mat)); // front
    sides->add(make_shared<quad>(Point3(min.x(),min.y(),min.z()),dz,dy,mat)); //left
    sides->add(make_shared<quad>(Point3(max.x(),min.y(),min.z()),-dx,dy,mat));//back
    sides->add(make_shared<quad>(Point3(max.x(),min.y(),max.z()),-dz,dy,mat));//right
    sides->add(make_shared<quad>(Point3(min.x(),max.y(),max.z()),dx,-dz,mat));//up
    sides->add(make_shared<quad>(Point3(min.x(),min.y(),min.z()),dx,dz,mat)); //bottom
    return sides;
}

在cornell_box中加上两句

void cornell_box(){
    hittable_list world;
    auto red   = make_shared<lambertian>(color(.65, .05, .05));
    auto white = make_shared<lambertian>(color(.73, .73, .73));
    auto green = make_shared<lambertian>(color(.12, .45, .15));
    auto light = make_shared<diffuse_light>(color(15, 15, 15));
    world.add(make_shared<quad>(Point3(555,0,0),vec3(0,555,0),vec3(0,0,555),green));
    world.add(make_shared<quad>(Point3(0,0,0),vec3(0,555,0),vec3(0,0,555),red));
    world.add(make_shared<quad>(Point3(343,544,332),vec3(-130,0,0),vec3(0,0,-105),light));
    world.add(make_shared<quad>(Point3(0,0,0),vec3(555,0,0),vec3(0,0,555),white));
    world.add(make_shared<quad>(Point3(555,555,555),vec3(-555,0,0),vec3(0,0,-555),white));
    world.add(make_shared<quad>(Point3(0,0,555),vec3(555,0,0),vec3(0,555,0),white));
    world.add(box(Point3(130,0,65),Point3(295,165,230),white));
    world.add(box(Point3(265,0,295),Point3(430,330,460),white));

在真实的Cornell Box中这两个盒子是有不同程度的倾斜的。所以我们需要对他进行旋转等操作。

平移

原文中:

在图形学中,通常我们会对对象应用变换(如平移、旋转、缩放)来改变它们在场景中的位置和方向。但在射线追踪中,有一种更加简单且有效的方法:不对对象进行变换,而是对射线进行逆变换。这种方法的优势在于计算上更为简便,特别是在光线与物体的交点测试中。

我的想法是这样。

  1. 假设我们从相机的原点然后在viewport中选择一个像素,射出射线,得到一个方向。这个像素射出的射线假设是没有能够和这个物体形成交点的。
  2. 然后我们的物体假设往这个像素的方向进行了位移,那么这个像素发出的射线就会打到这个物体上。

那么上面的事件等价于什么呢?

  1. 物体不动
  2. 首先明确一点,因为我们还是在做基于相机原点射向物体求交点。那么这个射线方向不能变。此时如果将相机的原点位置向物体平移的反方向位移。那么射线就会整体向反方向移动,就会与物体相交于相同的点。

代码

我们单独定义一个类来记录这样的一个平移的情况

class translate : public hittable{
public:
translate(shared_ptr<hittable> object,const vec3& offset) : object(object),offset(offset){
    bbox = object->bounding_box() + offset;
}
bool hit(const Ray& r,interval ray_t,hit_record& rec) const override{
    Ray offset_r(r.origin()-offset,r.direction(),r.time());
    if(!object->hit(offset_r,ray_t,rec)){
        return false;
    }
    rec.p+=offset;
    return true;
}
aabb bounding_box() const {
    return bbox;
}
private:
shared_ptr<hittable> object;
vec3 offset;
aabb bbox;
};

对于这个物体发生了平移了以后,其包围盒肯定也是要发生平移的。对于包围盒的加。我们需要以下几个重载

aabb operator + (const aabb& bbox,const vec3& offset){
    return aabb(bbox.x+offset.x(),bbox.y+offset.y(),bbox.z+offset.z());
}
aabb operator + (const vec3& offset,const aabb& bbox){
    return bbox + offset;
}
interval operator + (const interval& ival,const double& displacement){
    return interval(ival.min + displacement,ival.max+displacement);
}
interval operator + (const double& displacement,const interval& ival){
    return ival + displacement;
}

旋转

旋转理论上来说可以与轴对齐。

绕着z轴进行旋转。

绕着不同轴的旋转公式如下:

那么就可以得到不同的轴的旋转公式的表达。当绕着Z轴变化的时候:

绕着X轴旋转:

绕着Y轴旋转:

将平移视为初始光线的简单移动是理解发生的事情的一个好方法。但是,对于像旋转这样更复杂的操作,很容易不小心搞混项(或者忘记负号),因此,将旋转视为坐标系的变换会更好。

上述描述了以下移动过程:

  1. 将光线向后移动偏移量。
  2. 确定沿偏移光线是否存在交点(如果存在,在哪里)。
  3. 将交点向前移动偏移量。

但这也可以用坐标系变换来理解:

  1. 将光线从世界空间变换到物体空间。
  2. 确定在物体空间中是否存在交点(如果存在,在哪里)。
  3. 将交点从物体空间变换回世界空间。

旋转一个物体不仅会改变交点的位置,还会改变表面法向量,这将改变反射和折射的方向。所以我们也需要改变法线。幸运的是,法线会像向量一样旋转,所以我们可以使用上面的公式。虽然对于正在旋转和平移的物体,法线和向量看起来可能相同,但是正在缩放的物体需要特别注意以保持法线与表面正交。

我们需要开始将光线从世界空间变换到物体空间,对于旋转来说,这意味着通过-θ旋转。下面是对于y轴旋转,变化到物体的坐标系:

对于这个什么坐标系,我个人认为是保持目标不变,其它物体的相对目标的变化操作。那么我们要保证物体在世界坐标中的旋转,实际上就是我们相对于物体的逆旋转。当我们的射线发生逆旋转得到了物体上的交点以后,那么回到世界坐标系中,实际上就是这个交点发生的正旋转。

代码

接下来在hittable中补上对y轴的旋转。

class rotate_y : public hittable{
public:
bool hit(const Ray& r,interval ray_t,hit_record &rec) const override{
    Point3 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 = Ray(origin,direction,r.time());

    if(!object->hit(rotated_r,ray_t,rec)) {return false;}
    auto p = rec.p;
    p[0] = cos_theta * rec.p[0] + sin_theta * rec.p[2];
    p[2] = - sin_theta * rec.p[0] + cos_theta * rec.p[2];

    auto normal = rec.normal;
    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 = unit_vector(normal);
    return true;
}
private:
shared_ptr<hittable> object;
double sin_theta;
double cos_theta;
aabb bbox;
};

然后就是对传入的object进行修改bbox。修改思路:

  1. 首先初始化一个空的包围盒,也就是初始化最小轴集合,和最大轴集合。
  2. 然后对于之前的包围盒,求每一个边界顶点,计算每个顶点的新的位置。
  3. 然后根据新的顶点位置,更新最小轴集合,和最大轴集合
rotate_y(shared_ptr<hittable> object,double angle) : object(object){
    auto radians = degrees_to_radians(angle);
    sin_theta = std::sin(radians);
    cos_theta = std::cos(radians);
    bbox = object->bounding_box();

    vec3 min(infinity,infinity,infinity);
    vec3 max(-infinity,-infinity,-infinity);
    for(int i=0;i<2;i++){
        for(int j=0;j<2;j++){
            for(int k=0;k<2;k++){
                auto x = bbox.x.min * i + bbox.x.max * (1-i);
                auto y = bbox.y.min * j + bbox.y.max * (1-j);
                auto z = bbox.z.min * k + bbox.z.max * (1-k);

                auto newx = cos_theta * x + sin_theta * z;
                auto newz = -sin_theta * x + cos_theta * z;
                vec3 tester(newx,y,newz);
                for(int c =0 ; c < 3 ; c++){
                    min[c] = std::fmin(min[c] , tester[c]);
                    max[c] = std::fmax(max[c] , tester[c]);
                }
            }
        }
    }
    bbox = aabb(min,max);
}
class rotate_y : public hittable{
public:
rotate_y(shared_ptr<hittable> object,double angle) : object(object){
    auto radians = degrees_to_radians(angle);
    sin_theta = std::sin(radians);
    cos_theta = std::cos(radians);
    bbox = object->bounding_box();

    vec3 min(infinity,infinity,infinity);
    vec3 max(-infinity,-infinity,-infinity);
    for(int i=0;i<2;i++){
        for(int j=0;j<2;j++){
            for(int k=0;k<2;k++){
                auto x = bbox.x.min * i + bbox.x.max * (1-i);
                auto y = bbox.y.min * j + bbox.y.max * (1-j);
                auto z = bbox.z.min * k + bbox.z.max * (1-k);

                auto newx = cos_theta * x + sin_theta * z;
                auto newz = -sin_theta * x + cos_theta * z;
                vec3 tester(newx,y,newz);
                for(int c =0 ; c < 3 ; c++){
                    min[c] = std::fmin(min[c] , tester[c]);
                    max[c] = std::fmax(max[c] , tester[c]);
                }
            }
        }
    }
    bbox = aabb(min,max);
}
bool hit(const Ray& r,interval ray_t,hit_record &rec) const override{
    Point3 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 = Ray(origin,direction,r.time());

    if(!object->hit(rotated_r,ray_t,rec)) {return false;}
    auto p = rec.p;
    p[0] = cos_theta * rec.p[0] + sin_theta * rec.p[2];
    p[2] = - sin_theta * rec.p[0] + cos_theta * rec.p[2];

    auto normal = rec.normal;
    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 = unit_vector(normal);
    return true;
}
aabb bounding_box() const override{ return bbox; }
private:
shared_ptr<hittable> object;
double sin_theta;
double cos_theta;
aabb bbox;
};

修改cornell_box

void cornell_box(){
    hittable_list world;
    auto red   = make_shared<lambertian>(color(.65, .05, .05));
    auto white = make_shared<lambertian>(color(.73, .73, .73));
    auto green = make_shared<lambertian>(color(.12, .45, .15));
    auto light = make_shared<diffuse_light>(color(15, 15, 15));
    world.add(make_shared<quad>(Point3(555,0,0),vec3(0,555,0),vec3(0,0,555),green));
    world.add(make_shared<quad>(Point3(0,0,0),vec3(0,555,0),vec3(0,0,555),red));
    world.add(make_shared<quad>(Point3(343,554,332),vec3(-130,0,0),vec3(0,0,-105),light));
    world.add(make_shared<quad>(Point3(0,0,0),vec3(555,0,0),vec3(0,0,555),white));
    world.add(make_shared<quad>(Point3(555,555,555),vec3(-555,0,0),vec3(0,0,-555),white));
    world.add(make_shared<quad>(Point3(0,0,555),vec3(555,0,0),vec3(0,555,0),white));
    shared_ptr<hittable> box1 = box(Point3(130,0,65),Point3(295,165,230),white);
    box1 = make_shared<rotate_y>(box1,15);
    box1 = make_shared<translate>(box1,vec3(265,0,295));
    world.add(box1);
    shared_ptr<hittable> box2 = box(Point3(0,0,0),Point3(165,165,165),white);
    box2 = make_shared<rotate_y>(box2,-18);
    box2 = make_shared<translate>(box2,vec3(130,0,65));
    world.add(box2);
    bvh_node bvh_root(world.objects,0,world.objects.size()-1);
    camera cam;

    cam.aspect_ratio      = 1.0;
    cam.image_width       = 600;
    cam.samples_per_pixel = 200;
    cam.max_depth         = 50;
    cam.background        = color(0,0,0);

    cam.vfov     = 40;
    cam.lookfrom = Point3(278, 278, -800);
    cam.lookat   = Point3(278, 278, 0);
    cam.vup      = vec3(0,1,0);

    cam.defocus_angle = 0;

    cam.render(bvh_root);
}

最终结果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值