【图形学】【Ray Tracing The Next Week】总结

Ray Tracing: The Next Week总结

前言

本文为Ebook, “Ray Tracing: The Next Week” by Peter Shirley的总结。主要内容包括动态模糊,BVH,纹理贴图,柏林噪声,局部光照,物体移动。

最终场景图如下:

动态模糊

实现原理:对于光线,我们引入光线达到物体的时间 tm, t m ∈ [ 0 , 1 ] tm \in [0, 1] tm[0,1]

double time() const { return tm; }

对于物体,我们引入两个中心点 center1,center2,分别表示在 tm = 0 时物体的中心点坐标和 tm = 1时

sphere(const point3& center1, const point3& center2, double radius, shared_ptr<material> mat) : center1(center1), radius(fmax(0, radius)), mat(mat), is_moving(true) {
    vec3 rvec = vec3(radius, radius, radius);
    aabb box1(center1 - rvec, center1 + rvec);
    aabb box2(center2 - rvec, center2 + rvec);
    bbox = aabb(box1, box2);

    center_vec = center2 - center1;
}

当计算光线与物体交点时,取当前物体中心(线性插值)

point3 sphere_center(double time) const {
    return center1 + time * center_vec;
}

效果图:

BVH (Bounding Volume Hierarchies)

对于光线与物体求交,我们之前的方法是遍历所有物体,求交点,取离屏幕最近的展示到屏幕上。这种方法的时间效率太低了。
为此我们引入包围盒的概念,其核心思想为:对于包围盒内的物体,如果光线与其没有交点,那么光线与其里面的物体都没有交点。
那么,对于包围盒,如何划分?我可以使用BVH。
BVH的思想类似二分,将所有物体分为左包围盒和右包围盒,求光线与左右包围盒的交点。重复这个过程,直到求完所有交点。

bvh_node(std::vector<shared_ptr<hittable>>& objects, size_t start, size_t end) {
    bbox = aabb::empty;
    for (size_t object_index = start; object_index < end; ++object_index)
        bbox = aabb(bbox, objects[object_index]->bounding_box());
    int axis = bbox.longest_axis();
    size_t object_span = end - start;
    // in the bbox, if only have 1 or 2 object(s), we use the object itself rather than a bbox
    if (object_span == 1) {
        left = right = objects[start];
    } else if (object_span == 2) {
        left = objects[start];
        right = objects[start + 1];
    } else {
        auto comparator = (axis == 0) ? box_x_compare : ((axis == 1) ? box_y_compare : box_z_compare);
        std::sort(objects.begin() + start, objects.begin() + end, comparator);
        int mid = start + object_span / 2;
        left = make_shared<bvh_node>(objects, start, mid);
        right = make_shared<bvh_node>(objects, mid, end);
    }
}
boolhit(const ray& r, interval ray_t, hit_record& rec) const override {
    // puts("enter bvh hit");
    if (!bbox.hit(r, ray_t)) {
        // puts("exit bvh hit");
        return false;
    }
    // if size_t became 0, we check sphere hit, rather than bvh_node hit.
    bool hit_left = left->hit(r, ray_t, rec);
    bool hit_right = right->hit(r, interval(ray_t.min, hit_left ? rec.t : ray_t.max), rec);
    // puts("exit bvh hit");
    return hit_left || hit_right;
}

纹理贴图

空间纹理

空间纹理直接对3d空间中的每个点进行着色。本次,实现的是一个网格纹理, ( ⌊ x ⌋ + ⌊ y ⌋ + ⌊ z ⌋ )   &   1 (\lfloor x \rfloor + \lfloor y \rfloor + \lfloor z \rfloor) \ \& \ 1 (⌊x+y+z⌋) & 1

color value(double u, double v, const point3& p) const override {
     int xInteger = int(std::floor(inv_scale * p.x()));
     int yInteger = int(std::floor(inv_scale * p.y()));
     int zInteger = int(std::floor(inv_scale * p.z()));
     bool isOdd = (xInteger + yInteger + zInteger) % 2;
     return isOdd ? odd->value(u, v, p) : even->value(u, v, p);
 }

平面纹理

对于平面纹理,本次实现的是一个地球的贴图
考虑如何建立二维平面到三维物体的映射。
θ \theta θ为从 -y 轴开始,逆时针旋转的角度;
ϕ \phi ϕ为从-x轴开始,逆时针旋转的角度;
y = − cos ⁡ ( θ ) ; x = − cos ⁡ ( ϕ ) sin ⁡ ( θ ) ; z = sin ⁡ ( ϕ ) sin ⁡ ( θ ) y = -\cos(\theta); x = -\cos(\phi)\sin(\theta);z=\sin(\phi)\sin(\theta) y=cos(θ);x=cos(ϕ)sin(θ);z=sin(ϕ)sin(θ),得 ϕ = a t a n 2 ( − z , x ) + π ; θ = a c r c o s ( − y ) \phi=atan2(-z,x)+\pi;\theta=acrcos(-y) ϕ=atan2(z,x)+π;θ=acrcos(y)

static void get_sphere_uv(const point3& p, double& u, double& v) {
    double theta = acos(-p.y());
    double phi = atan2(-p.z(), p.x()) + pi;
    u = phi / (2 * pi);
    v = theta / pi;
}

柏林噪声

通过随机化,来模拟固体纹理,如大理石等
实现原理:
对于纹理颜色,通过设置noise值来控制其灰度的大小

class noise_texture : public texture {
   public:
    noise_texture() {}
    noise_texture(double scale) : scale(scale) {}

    color value(double u, double v, const point3& p) const override {
        // return color(1, 1, 1) * 0.5 * (1.0 + noise.noise(scale * p));
        // return color(1, 1, 1) * noise.turb(p, 7);
        return color(1, 1, 1) * 0.5 * (1 + sin(scale * p.z() + 8 * noise.turb(p, 7)));
    }

   private:
    perlin noise;
    double scale;
};

为了使生成的纹理在一定程度上连续,模拟湍流效果,我们设置turb函数,其主要功能在于扩大noise的随机范围,使得周围噪声对当前噪声产生影响

double turb(const point3& p, int depth) const {
    double accum = 0.0;
    point3 temp_p = p;
    double weight = 1.0;

    for (int i = 0; i < depth; ++i) {
        accum += weight * noise(temp_p);
        weight *= 0.5;
        temp_p *= 2;
    }

    return fabs(accum);
}

局部光照

本次的光照是以材质的形式存在的。
其具体作用过程为:为某个物体(本例中为一个平行四边形)添加diffuse_light材质,设置emitted函数,作为其的发射光。
在get_color中,更新得到的颜色为 color_from_emission + color_from_scatter

color ray_color(const ray& r, int depth, const hittable& world) {
    // puts("enter the ray_color");
    if (depth <= 0)
        return color(0, 0, 0);  // all abosorb
    hit_record rec;
    if (!world.hit(r, interval(0.001, infinity), rec))
        return background;

    ray scattered;
    color attenuation;
    color color_from_emission = rec.mat->emitted(rec.u, rec.v, rec.p);

    if (!rec.mat->scatter(r, rec, attenuation, scattered))
        return color_from_emission;
    color color_from_scatter = attenuation * ray_color(scattered, depth - 1, world);

    return color_from_emission + color_from_scatter;
}

物体移动

本次主要实现了位移和旋转。在实现过程中,我们并没有改变实际物体的位置,而是通过改变入射光线来获得交点,然后再改变交点。

位移

其实现细节主要分为:1. 将光线向后移动补偿值 2. 求移动后光线与当前物体的交点 3. 将交点往补偿值的方向移动(补偿值为我们指定的位移向量)

class translate : public hittable {
   public:
    translate(shared_ptr<hittable> object, const vec3& offset) : object(object), offset(offset) {
        bbox = object->bounding_box() + offset;
    }

    const char* className() const override {
        return "translate : hittable";
    }

    bool hit(const ray& r, interval ray_t, hit_record& rec) const override {
        // Move the ray backwards by the offset
        ray offset_r(r.origin() - offset, r.direction(), r.time());
        if (!object->hit(offset_r, ray_t, rec))  // ?
            return false;

        // move the intersection point forwards by the offset
        rec.p += offset;
        return true;
    }

    aabb bounding_box() const override { return bbox; }

   private:
    shared_ptr<hittable> object;
    vec3 offset;
    aabb bbox;
};

旋转

其实现细节主要分为3步:1. 将光线从世界坐标改到物体坐标(即反方向旋转入射光线) 2. 求旋转后光线与当前物体的交点 3. 往正方向旋转交点和法线

bool hit(const ray& r, interval ray_t, hit_record& rec) const override {
    // Change the ray from world space to object space
    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(origin, direction, r.time());

    // Detemine whether an intersection exists in object space
    if (!object->hit(rotated_r, ray_t, rec)) return false;

    // Change the point and normal from object space to world space
    point3 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];

    vec3 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 = normal;

    return true;
}

The Whole Code

My Code

参考资料

Ray Tracing The Next Week

  • 22
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值