【Ray Tracing The Next Week 超详解】 光线追踪2-8 Volume
Preface
今天有两个东东,一个是体积烟雾,一个是封面图
下一篇我们总结项目代码
Chapter 8:Volumes
我们需要为我们的光线追踪器添加新的物体——烟、雾,也称为participating media。 我们还需要补充一个材质——次表面散射材质,它有点像物体内的浓雾。
体渲染通常的做法是,在体的内部有很多随机表面,来实现散射的效果。比如一束烟可以表示为,在这束烟的内部任意位置,都可以存在一个面,以此来实现烟、雾
对于一个恒定密度体,一条光线通过其中的时候,在烟雾体中传播的时候也会发生散射,光线在烟雾体中能传播多远,也是由烟雾体的密度决定的,密度越高,光线穿透性越差,光线传播的距离也越短。从而实现烟雾的透光性。
引用书中一张图(光线可穿透可散射)
当光线通过体积时,它可能在任何点散射。 光线在任何小距离dL中散射的概率为:
概率= C * dL,其中C与体积的光密度成比例。
对于恒定体积,我们只需要密度C和边界。
/// isotropic.hpp // ----------------------------------------------------- // [author] lv // [begin ] 2019.1 // [brief ] the isotropic-class for the ray-tracing project // from the 《ray tracing the next week》 // ----------------------------------------------------- #pragma once namespace rt { class isotropic :public material { public: isotropic(texture* tex) :_albedo(tex) { } virtual bool scatter(const ray& InRay, const hitInfo& info, rtvec& attenuation, ray& scattered)const override { scattered = ray(info._p, lvgm::random_unit_sphere()); attenuation = _albedo->value(info._u, info._v, info._p); return true; } private: texture * _albedo; }; } // rt namespace
这个材质的散射原理和漫反射磨砂材质的大同小异,均属于碰撞点转换为新视点,沿任意方向发射新的视线,只不过漫反射的视线方向向量指向外相切球体表面,而isotropic的视线方向指向以碰撞点为球心的单位球体表面
区别就在于
漫反射的散射光线不可能指到物体内部,它一定是散射到表面外部(视线方向指向外切球体表面)
isotropic材质的散射光线可以沿原来的方向一往前,以此视线透光性
因为烟雾内部只是颗粒而不存在真正不可穿透的几何实体,所以漫反射实体不可穿透,只能散射到表面外部,而烟雾可穿透
接下来我们看一下烟雾体
/// constant_medium.hpp // ----------------------------------------------------- // [author] lv // [begin ] 2019.1 // [brief ] the constant_dedium-class for the ray-tracing project // from the 《ray tracing the next week》 // ----------------------------------------------------- #pragma once namespace rt { class constant_medium :public intersect { public: constant_medium(intersect* p, rtvar d, texture* tex); virtual bool hit(const ray& sight, rtvar t_min, rtvar t_max, hitInfo& info)const override; virtual aabb getbox()const override; private: intersect* _item; rtvar _density; //烟雾密度 material* _materialp; }; inline constant_medium::constant_medium(intersect* p, rtvar d, texture* tex) :_item(p) ,_density(d) ,_materialp(new isotropic(tex)) { } aabb constant_medium::getbox()const { return _item->getbox(); } bool constant_medium::hit(const ray& sight, rtvar t_min, rtvar t_max, hitInfo& info)const { hitInfo info1, info2; if (_item->hit(sight, -rtInf(), rtInf(), info1)) { if (_item->hit(sight, info1._t + 0.0001, rtInf(), info2)) { if (info1._t < t_min) info1._t = t_min; if (info2._t > t_max) info2._t = t_max; if (info1._t >= info2._t) return false; if (info1._t < 0) info1._t = 0; float distance_inside_boundary = (info2._t - info1._t)*sight.direction().normal(); float hit_distance = -(1 / _density)*log(lvgm::rand01()); if (hit_distance < distance_inside_boundary) { info._t = info1._t + hit_distance / sight.direction().normal(); info._p = sight.go(info._t); info._n = rtvec(1, 0, 0); // arbitrary info._materialp = _materialp; return true; } } } return false; } } // rt namespace
hit函数里面是一些边界合法性检测
场景测试代码
intersect* cornell_smoke() { intersect ** list = new intersect*[9]; int cnt = 0; material* red = new lambertian(new constant_texture(rtvec(0.65, 0.05, 0.05))); material * blue = new lambertian(new constant_texture(rtvec(0.05, 0.05, 0.73))); material* white = new lambertian(new constant_texture(rtvec(0.73, 0.73, 0.73))); material* green = new lambertian(new constant_texture(rtvec(0.12, 0.45, 0.15))); material* light = new areaLight(new constant_texture(rtvec(7, 7, 7))); list[cnt++] = new xz_rect(113, 443, 127, 432, 550, light); list[cnt++] = new flip_normal(new xz_rect(113, 443, 127, 432, 550, light)); list[cnt++] = new flip_normal(new yz_rect(0, 555, 0, 555, 555, green)); list[cnt++] = new yz_rect(0, 555, 0, 555, 0, red); list[cnt++] = new flip_normal(new xz_rect(0, 555, 0, 555, 555, white)); list[cnt++] = new xz_rect(0, 555, 0, 555, 0, white); list[cnt++] = new flip_normal(new xy_rect(0, 555, 0, 555, 555, blue)); intersect* box1 = new translate(new rotate_y(new box(rtvec(), rtvec(165, 165, 165), white), -18), rtvec(130, 0, 65)); intersect* box2 = new translate(new rotate_y(new box(rtvec(), rtvec(165, 320, 165), white), 15), rtvec(265, 0, 295)); list[cnt++] = new constant_medium(box2, 0.006, new constant_texture(rtvec(0.8, 0.58, 0.))); list[cnt++] = new constant_medium(box1, 0.008, new constant_texture(rtvec(0.9, 0.2, 0.72))); return new intersections(list, cnt); }
下面是效果:sample -> 1000
注释 // arbitrary处为随机方向,之前为(1,0,0)
我觉得改为(rand01(),rand01(),rand01())较为合适,下面是改之后的效果
Chapter 9:A Scene Testing All New Features
最后的封面图是这样一个场景:
体积雾:有一个蓝色的次表面散射球体,但是这个东西并没有单独实现,而是把它包裹在了一个玻璃球内。
体积雾:整个场景是由层薄薄的雾气笼盖着的
长方体:地面是一堆随机长方体
玻璃球:一个作为蓝色烟雾的容器,一个是单纯的玻璃球
映射纹理:地球纹理球体
过程纹理:柏林噪声纹理球体
运动模糊球体:有一个棕色的运动球体
镜面球体:银白色的镜面球
区域光照:顶部是一个长方形的区域光源
其他未说明材质的都是漫反射
渲染器中剩下的最大缺陷是没有阴影射线,但这就是为什么我们容易获得焦散和次表面散射效果的原因。
下面是渲染代码,关于相机参数设置还需等待渲染结果出来才能公布
VS四开,渲染了一天还没完,我也实属无奈
intersect* finalScene() { int nb = 20; intersect ** list = new intersect*[30]; intersect ** boxlist = new intersect*[2000]; intersect ** boxlist2 = new intersect*[2000]; material * white = new lambertian(new constant_texture(rtvec(0.73, 0.73, 0.73))); material * ground = new lambertian(new constant_texture(rtvec(0.48, 0.83, 0.53))); int b = 0; for (int i = 0; i < nb; ++i) for (int j = 0; j < nb; ++j) { rtvar w = 100; rtvar x0 = -1000 + i*w; rtvar z0 = -1000 + j*w; rtvar y0 = 0; rtvar x1 = x0 + w; rtvar y1 = 100 * (lvgm::rand01() + 0.01); rtvar z1 = z0 + w; boxlist[b++] = new box(rtvec(x0, y0, z0), rtvec(x1, y1, z1), ground); } int l = 0; list[l++] = new bvh_node(boxlist, b, 0, 1); material * light = new areaLight(new constant_texture(rtvec(10, 10, 10))); list[l++] = new xz_rect(123, 423, 147, 412, 550, light); rtvec center(400, 400, 200); list[l++] = new moving_sphere(center, center + rtvec(30, 0, 0), 0, 1, 50, new lambertian(new constant_texture(rtvec(0.7, 0.3, 0.1)))); list[l++] = new sphere(rtvec(260, 150, 45), 50, new dielectric(1.5)); list[l++] = new sphere(rtvec(0, 150, 145), 50, new metal(new constant_texture(rtvec(0.8, 0.8, 0.9)), 10.0)); intersect * boundary = new sphere(rtvec(360, 150, 145), 70, new dielectric(1.5)); list[l++] = boundary; list[l++] = new constant_medium(boundary, 0.2, new constant_texture(rtvec(0.2, 0.4, 0.9))); boundary = new sphere(rtvec(), 5000, new dielectric(1.5)); list[l++] = new constant_medium(boundary, 0.0011, new constant_texture(rtvec(1., 1., 1.))); int x, y, n; unsigned char * tex = stbi_load("earthmap.jpg", &x, &y, &n, 0); material * emat = new lambertian(new image_texture(tex, x, y)); list[l++] = new sphere(rtvec(400, 200, 400), 100, emat); texture * pertext = new noise_texture(0.1); list[l++] = new sphere(rtvec(220, 280, 300), 80, new lambertian(pertext)); int ns = 1000; for (int j = 0; j < ns; ++j) boxlist2[j] = new sphere(rtvec(165 * lvgm::rand01(), 165 * lvgm::rand01(), lvgm::rand01()), 10, white); list[l++] = new translate(new rotate_y(new bvh_node(boxlist2, ns, 0, 1), 15), rtvec(-100, 270, 395)); return new intersections(list, l); }
/-----------------------更新线-------------------------------/
对应的相机参数
效果
其中,薄雾效果太重了,雾气参数应该小一点,大约在1e-4左右较好
vfov可能太大了,45°应该更好一点吧
镜头应该更靠近些
感谢您的阅读,生活愉快~