【Ray-Tracing In Next Week】第7章 添加实例(instance)

    xiaoxiao2024-11-12  71

    本节会在场景中添加两个盒子,并实现盒子的移动和旋转。

    代码使用VS2015编写,可从如下链接下载:

    链接:https://pan.baidu.com/s/1Fi_pICX1zJyYHiknoznHvw  提取码:kxa9  【实现】

    由于有上一章添加面的基础,因此box类很容易实现,添加六个面一组合就可以了:

    class box : public hitable { public: box(){} box(const vec3& p0, const vec3& p1, material *ptr) { pmin = p0; pmax = p1; hitable** list = new hitable*[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 hitable_list(list, 6); } virtual bool box::hit(const ray& r, float t0, float t1, hit_record& rec) const { return list_ptr->hit(r, t0, t1, rec); } virtual bool bounding_box(float t0, float t1, aabb& box) const { box = aabb(pmin, pmax); return true; } vec3 pmin, pmax; hitable* list_ptr; };

    可以看到其中list中有六个面组成一个BOX。

    【盒子的移动】

    移动对于光线追踪系统来说非常简单,光线的起点移动也可以达到实现物体移动的效果。注意不是顶点移动了,而是光线移动了。

    class translate : public hitable { public: translate(hitable* p, const vec3& displacement) : ptr(p), offset(displacement) {} virtual bool 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; } virtual bool 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; } hitable* ptr; vec3 offset; };

    物体移动offset,则使用translate来把这个物体放进去。在hit中对光线进行移动。物体和光线的移动是相反的方向,因此moved_r是r.origin()-offset而并非加。

    【盒子的旋转】

    旋转比平移要复杂,先谈计算过程就需要一点绕绕。首先我们来看旋转的过程:

    为了方便说明,我们拿一个弯管来说明,因为球旋转并不直观。上图左光线照向弯管的一个黄点,则求出了该黄点处的坐标和法线。右图在弯管旋转了90度以后,光线照向另外一点,求得坐标和法线,可以清晰的看到该点的坐标与法线与左图的一点不同。那么关键的地方来了,右图中的交点在左图中的物体的相同位置坐标和法线也不相同,因为什么呢?因为从左图到右图该位置发生了旋转。

    以上的物体向左旋转90度的操作,我们把求交点分为了两步:

    第一步,物体不动,对光线进行反方向旋转,也即光线向右旋转90度,此时求出了顶点正确。(物体顶点变换没有变换光线那么方便)

    第二步,对顶点和法线进行向左旋转90度。得到物体旋转后的顶点和法线。

    【具体实现】

    本节只考虑绕三轴旋转。下图是绕Z轴旋转,可以看到Z不会变化,XY会发生变化,具体会变化多少呢,有公式:

      (公式一)

    这个证明起来也很简单:使用圆的参数方程,从(x, y)到(x', y')可以看成共圆的一个操作。

    设:

      (公式二)

    按三角函数展开再将(公式二)带入,便可得(公式一)。

    具体实现中有个技巧,就是对物体旋转时,对光线的旋转是,则旋转公式为:

    同理绕x轴,y轴都有类似公式。下面我们来实现一个绕rotate_y旋转的。有了以上的铺垫,代码是很容易的。

    class rotate_y : public hitable { public: rotate_y(hitable* p, float angle):ptr(p) { //求包围盒 float radians = (float(M_PI) / 180.0f) * 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); 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); } virtual bool bounding_box(float t0, float t1, aabb& box) const { box = bbox; return hasbox; } virtual bool 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; } hitable* ptr; float sin_theta; float cos_theta; bool hasbox; aabb bbox; };

     

    最新回复(0)