《用一周学习光线追踪》6.光照和轴对齐矩形

本项目参考自《Ray Tracing in One Weekend》系列
上接《用两天学习光线追踪》,继续学习光线追踪。
项目链接:https://github.com/maijiaquan/ray-tracing-with-imgui

目录:
《用两天学习光线追踪》1.项目介绍和ppm图片输出
《用两天学习光线追踪》2.射线、简单相机和背景输出
《用两天学习光线追踪》3.球体和表面法向量
《用两天学习光线追踪》4.封装成类
《用两天学习光线追踪》5.抗锯齿
《用两天学习光线追踪》6.漫反射材质
《用两天学习光线追踪》7.反射向量和金属材质
《用两天学习光线追踪》8.折射向量和电介质
《用两天学习光线追踪》9.可放置相机
《用两天学习光线追踪》10.散焦模糊

《用一周学习光线追踪》1.动态模糊
《用一周学习光线追踪》2.BVH树、AABB相交检测
《用一周学习光线追踪》3.纯色纹理和棋盘纹理
《用一周学习光线追踪》4.柏林噪声
《用一周学习光线追踪》5.球面纹理贴图
《用一周学习光线追踪》6.光照和轴对齐矩形
《用一周学习光线追踪》7.长方体和平移旋转


本节将会新增自发光材质和轴对齐矩形,并渲染图形学教材中的经典场景Cornell Box。


自发光材质

首先,定义自发光材质,该材质不提供散射,仅提供一个发光函数emitted()

//material.h
class diffuse_light : public material  {
    public:
        texture *emit;
        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); 
        }
};

material基类也加上emitted()函数:

//material.h
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);
        }
};

修改color函数

新版的color函数修改如下:

vec3 color(const ray& r, hittable *world, int depth) {
    hit_record rec;
    if (world->hit(r, 0.001, MAXFLOAT, 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;
    }
    else 
        return vec3(0,0,0);
}

新版color()函数,跟旧版color函数的区别如下:
1.取消了“天空盒”,当射线没有命中任何物体的时候,直接返回纯黑色(r,g,b均为0)。这也说明,之前的“天空盒”的本质,其实是一个铺满场景的巨型发光材质,将其取消了之后,四周一片漆黑。
2.如果递归深度超过50,或者命中点没有发生散射(例如射线命中发光材质了),则color返回发光材质emitted()函数的返回值(即发光材质表面某个被射线所命中的点,所对应的发射光的颜色),然后乘以attenuation,叠加到上一层递归的的emitted中(如果有emitted的话,即物体自发光的话),作为上一层color()的返回值。

以上图为例解释一下:我们看到场景中间的柏林噪声材质球体的右侧,明显被自发光矩形照亮了。假设一条射线从我们的眼睛为起点出发,第一次命中结果为P点,在P点发生了散射,产生第二条射线(图中绿色射线)进入下一层递归采样,并命中发光矩形,则该射线完整路径的color返回值递归展开伪代码为:

retColor = emittedPerlin + attenuationP*emittedRect

由于柏林噪声材质球体不发光,即emittedPerlin = 0,所以有

retColor = attenuationP*emittedRect

也就是之前所说的 光源照到物体表面材质的颜色 = 材质表面颜色(即各通道反射率)* 光源颜色


轴对齐矩形

如上图所示,假设在xy平面有个一矩形,我们只需要一个z参数就能够确定该矩形的所在的平面,例如z=k,其边由以下4个变量确定:

  • x = x 0 x=x_0 x=x0
  • x = x 1 x=x_1 x=x1
  • y = y 0 y=y_0 y=y0
  • y = y 1 y=y_1 y=y1

判断一条射线是否命中一个xy平面上的轴对齐矩形,我们可以先计算该射线和该矩形所在的平面的交点。

射线方程为:
p ( t ) = a + t b p(t) = a + tb p(t)=a+tb

单独将z轴的分量抽离出来,则有
z ( t ) = a z + t b z z(t) = a_z + tb_z z(t)=az+tbz

z ( t ) = k z(t) = k z(t)=k 代入上式,有

t = ( k − a z ) / b z t = (k-a_z) / b_z t=(kaz)/bz

有了 t t t的值,可以计算出交点的 x x x y y y值:

x = a x + t b x x = a_x + tb_x x=ax+tbx

y = a y + t b y y = a_y + tb_y y=ay+tby

则有射线命中轴对齐矩形的判断条件为: x 0 < x < x 1 x_0 < x < x_1 x0<x<x1 y 0 < y < y 1 y_0 < y < y_1 y0<y<y1.

将xy平面的轴对齐矩形,写成代码如下:

//aarect.h
class xy_rect: public hittable  {
    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;
        virtual bool bounding_box(float t0, float t1, aabb& box) const {
               box =  aabb(vec3(x0,y0, k-0.0001), vec3(x1, y1, k+0.0001));
               return true; }
        material  *mp;
        float x0, x1, y0, y1, k;
};

bool xy_rect::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);	//默认法线为(0,0,1),但会有潜在的朝向问题
    return true;
}

xz平面和yz平面的定义原理一样,详见代码。

在场景中使用diffuse_ligh材质定义一个发光的球和一个发光的轴对齐矩形:

hittable *simple_light() {
    vec3 lookfrom(13, 2, 3);
    vec3 lookat(0,2,0);
    cam.SetCamParams(lookfrom, lookat, vec3(0, 1, 0), 40, float(nx) / float(ny), 0.0, 10.0, 0.0, 1.0);
    texture *pertext = new noise_texture(4);
    hittable **list = new hittable*[4];
    int i = 0;
    list[i++] = new sphere(vec3(0,-1000, 0), 1000, new lambertian(new constant_texture(vec3(0.4, 0.2, 0.1))));
    list[i++] = new sphere(vec3(0, 2, 0), 2, new lambertian(pertext));
    list[i++] = new sphere(vec3(0, 7, 0), 2, new diffuse_light(new constant_texture(vec3(4,4,4))));
    list[i++] = new xy_rect(3, 5, 1, 3, -2, new diffuse_light(new constant_texture(vec3(4,4,4))));

    return new hittable_list(list,i);
}

效果如下:


Cornell Box

在场景中增加5个普通矩形,1个发光矩形:

hittable *cornell_box() {
    cam.SetCamParams(vec3(278, 278, -800), vec3(278,278,0), vec3(0, 1, 0), 40, float(nx) / float(ny), 0.0, 10.0, 0.0, 1.0);
    hittable **list = new hittable*[6];
    int i = 0;
    material *red = new lambertian(new constant_texture(vec3(0.65, 0.05, 0.05)));
    material *white = new lambertian(new constant_texture(vec3(0.73, 0.73, 0.73)));
    material *green = new lambertian(new constant_texture(vec3(0.12, 0.45, 0.15)));
    material *light = new diffuse_light(new constant_texture(vec3(15, 15, 15)));

    list[i++] = new yz_rect(0, 555, 0, 555, 555, green);
    list[i++] = new yz_rect(0, 555, 0, 555, 0, red);
    list[i++] = new xz_rect(213, 343, 227, 332, 554, light);
    list[i++] = new xz_rect(0, 555, 0, 555, 555, white);
    list[i++] = new xz_rect(0, 555, 0, 555, 0, white);
    list[i++] = new xy_rect(0, 555, 0, 555, 555, white);

    return new hittable_list(list,i);
}

结果出现了绿色矩形和2个白色矩形的缺失。原因是部分轴对齐矩形的法线朝向出错了(因为法线是写死在轴对齐矩形里面的)。

接下来定义一个新的hittable,作用是将另外一个hittable的法线置反。

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

使用flip_normals,将缺失的3个矩形的法线置反:

hittable *cornell_box() {
    ns = 10000;
    cam.SetCamParams(vec3(278, 278, -800), vec3(278,278,0), vec3(0, 1, 0), 40, float(nx) / float(ny), 0.0, 10.0, 0.0, 1.0);
    hittable **list = new hittable*[6];
    int i = 0;
    material *red = new lambertian(new constant_texture(vec3(0.65, 0.05, 0.05)));
    material *white = new lambertian(new constant_texture(vec3(0.73, 0.73, 0.73)));
    material *green = new lambertian(new constant_texture(vec3(0.12, 0.45, 0.15)));
    material *light = new diffuse_light(new constant_texture(vec3(15, 15, 15)));

    list[i++] = new flip_normals(new yz_rect(0, 555, 0, 555, 555, green));
    list[i++] = new yz_rect(0, 555, 0, 555, 0, red);
    list[i++] = new xz_rect(213, 343, 227, 332, 554, light);
    list[i++] = new flip_normals(new xz_rect(0, 555, 0, 555, 555, white));
    list[i++] = new xz_rect(0, 555, 0, 555, 0, white);
    list[i++] = new flip_normals(new xy_rect(0, 555, 0, 555, 555, white));

    return new hittable_list(list,i);
}

每像素采样1000次效果如下:

每像素采样1万次的高清无码渲染效果如下:


参考资料:https://raytracing.github.io/books/RayTracingTheNextWeek.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值