【Ray-Tracing In Next Week】第6章 矩形和灯光

    xiaoxiao2023-11-14  152

    在以前所有的章节,我们都是靠背景来支撑各种变换,也即当光线与场景无交时,取了一个颜色。按说应该取全黑。当取全黑时,场景中没有自发光的物体,那么场景就是全黑的。本章将定义一个发光体。建立一个BOX,里面放一个灯光。

    之所以看到有噪声是因为微平面的随机光线会射到外面无交,置全黑。

    本节代码使用VS2015编写,下载链接如下:

    链接:https://pan.baidu.com/s/14FdUnpnRW4l3B3iQnhf4Nw  提取码:mhfb   

    【实现思路】

    在材质基类中我们添加发光这个属性,默认是不发光全黑:

    class material { public: virtual bool scatter(const ray& r_in, const hit_record& rec, vec3& attenuation, ray& scattered) const = 0; virtual vec3 emitted(float u, float v, const vec3 &p) const { return vec3(0, 0, 0); } };

    而后我们在颜色的求交里,将发光这个影响因素给加上:

    vec3 color(const ray& r, hitable* world, int depth) { hit_record rec; if (world->hit(r, 0.001f, FLT_MAX, 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; //无反射则直接取发光射出的颜色 } return vec3(0.0f, 0.0f, 0.0f); }

    而后我们定义发光材质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; };

    光线到它就不再反射或折射,而是直接受它发光的影响。得到发光属性emit->value

    现在我们来定义墙面,本节的灯光也是个方块形发光体,因此也是个墙面。本节定义平行于XYZ轴的面,易于计算,拿平行于XY轴的面来说,在其上定义一个面需要有以下变量:

    首先z=k,直线方程p(t)=A+Bt,这两个方程一起可以求出。而后再将p(t)=A+Bt代入到与x=xx的相交之中,看看交点是否在[x0, x1]之间,y方向上同理:可以参考【Ray-Tracing In Next Week】第2章 BVH加速结构里与AABB求交的方法。

    比如XY平面的求交代码如下:

    class xy_rect : public hitable { 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){} virtual bool hit(const ray& r, float t0, float t1, hit_record& rec) const { float t = (k - r.origin().z()) / r.direction().z(); if (t < t0 || t>t1) 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; } virtual bool bounding_box(float t0, float t1, aabb& box) const { box = aabb(vec3(x0, y0, k - 0.0001f), vec3(x1, y1, k + 0.0001f)); return true; } material *mp; float x0, x1, y0, y1, k; };

    同种方法定义xz, yz平面上的四边形。同时我们发现,当与一个面求交时,法线可能是反的,尤其平面这种东西,左边有灯的时候,可能法线要向左,右边有灯的时候可能要向右,因此法线需要经常颠倒方向。我们因此做了个专门颠倒法线方向的类:

    class flip_normals : public hitable { public: flip_normals(hitable *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); } hitable* ptr; };

    当然这个类很脆弱,不会自己判断是否应该颠倒,在OptiX中的做法是可以和光线的方向一起判断,其夹角>90度就不需要颠倒。

    最终我们这样定义场景:

    hitable* cornell_box() { hitable** list = new hitable*[6]; int i = 0; material *red = new lambertian(new constant_texture(vec3(0.65f, 0.05f, 0.05f))); material *white = new lambertian(new constant_texture(vec3(0.73f, 0.73f, 0.73f))); material *green = new lambertian(new constant_texture(vec3(0.12f, 0.45f, 0.15f))); material *light = new diffuse_light(new constant_texture(vec3(15, 15, 15))); list[i++] = new flip_normals(new yz_rect(0.0f, 555.0f, 0.0f, 555.0f, 555.0f, green)); list[i++] = new yz_rect(0.0f, 555.0f, 0.0f, 555.0f, 0.0f, red); list[i++] = new xz_rect(213.0f, 343.0f, 227.0f, 332.0f, 554.0f, light); list[i++] = new flip_normals(new xz_rect(0.0f, 555.0f, 0.0f, 555.0f, 555.0f, white)); list[i++] = new xz_rect(0.0f, 555.0f, 0.0f, 555.0f, 0.0f, white); list[i++] = new flip_normals(new xy_rect(0.0f, 555.0f, 0.0f, 555.0f, 555.0f, white)); return new hitable_list(list, i); }

     

    最新回复(0)