【光线追踪系列十二】自发光与轴对齐矩形

本文主要参照 Ray Tracing: The Next Week,其中只是主要精炼光追相关理论,具体实现可参照原文。

在这里插入图片描述

一、自发光

1.1 自发光材质类

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

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()函数:

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);
        }
};

1.2 颜色采样修改

新版color()函数,跟旧版color函数的区别如下:

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

即,光源照到物体表面材质的颜色 = 材质表面颜色(即各通道反射率)* 光源颜色

二、轴对齐矩形

在这里插入图片描述

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

x=x0,x = x1,y = y0,y=y1;

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

射线方程为:

​ p(t) = a + t * b

单独将z轴的分量抽离出来,则有:

z(t)=az+t * bz

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

t=(k−az)/bz

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

x = ax + t * bx

y = ay + t * by

则有射线命中轴对齐矩形的判断条件为:x0 < x < x1 且 y0 < y < y1 ,将xy平面的轴对齐矩形,写成代码如下:

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);	
    return true;
}

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

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

hittable *random_scene() {
	texture *pertext = new noise_texture();
	hittable **list = new hittable*[4];
	int i = 0;
	list[i++] = new sphere(vec3(0, -1000, 0), 1000, new lambertian(new constant_texture(vec3(0.7, 0.5, 0.3))));
	
	int nn;
	unsigned char *Cristiano = stbi_load("F://Cristiano.jpg", &nx, &ny, &nn, 0);
	material *CristianoJpg = new lambertian(new image_texture(Cristiano, nx, ny));
	list[i++] = new sphere(vec3(0, 2, 0), 2, CristianoJpg);
	list[i++] = new sphere(vec3(0, 6, 0), 1, 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 bvh_node(list, i, 0.0, 1.0);
}

效果如下(每个像素采样2000次):
在这里插入图片描述

三、Cornell Box

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

class xz_rect : public hittable {
public:
	xz_rect() {}
	xz_rect(float _x0, float _x1, float _z0, float _z1, float _k, material *mat) :x0(_x0), x1(_x1), z0(_z0), z1(_z1), k(_k), mp(mat) {};
	virtual bool hit(const ray& r, float t_min, float t_max, hit_record& rec) const override;

	virtual bool bounding_box(float time0, float time1, aabb& output_box) const override {
		// The bounding box must have non-zero width in each dimension, so pad the Y
		// dimension a small amount.
		output_box = aabb(vec3(x0, k - 0.0001, z0), vec3(x1, k + 0.0001, z1));
		return true;
	}
	material  *mp;
	double x0, x1, z0, z1, k;
};

bool xz_rect::hit(const ray& r, float t_min, float t_max, hit_record& rec) const {
	auto t = (k - r.origin().y()) / r.direction().y();
	if (t < t_min || t > t_max)
		return false;
	auto x = r.origin().x() + t * r.direction().x();
	auto z = r.origin().z() + t * r.direction().z();
	if (x < x0 || x > x1 || z < z0 || z > z1)
		return false;
	rec.u = (x - x0) / (x1 - x0);
	rec.v = (z - z0) / (z1 - z0);
	rec.t = t;
	rec.normal = vec3(0, 1, 0);
	rec.mat_ptr = mp;
	rec.p = r.point_at_parameter(t);
	return true;
}

class yz_rect : public hittable {
public:

	yz_rect(float _y0, float _y1, float _z0, float _z1, float _k,material *mat): y0(_y0), y1(_y1), z0(_z0), z1(_z1), k(_k), mp(mat) {};

	virtual bool hit(const ray& r, float t_min, float t_max, hit_record& rec) const override;

	virtual bool bounding_box(float time0, float time1, aabb& output_box) const override {
		// The bounding box must have non-zero width in each dimension, so pad the X
		// dimension a small amount.
		output_box = aabb(vec3(k - 0.0001, y0, z0), vec3(k + 0.0001, y1, z1));
		return true;
	}

	material  *mp;
	double y0, y1, z0, z1, k;
};

bool yz_rect::hit(const ray& r, float t_min, float t_max, hit_record& rec) const {
	auto t = (k - r.origin().x()) / r.direction().x();
	if (t < t_min || t > t_max)
		return false;
	auto y = r.origin().y() + t * r.direction().y();
	auto z = r.origin().z() + t * r.direction().z();
	if (y < y0 || y > y1 || z < z0 || z > z1)
		return false;
	rec.u = (y - y0) / (y1 - y0);
	rec.v = (z - z0) / (z1 - z0);
	rec.t = t;
	rec.normal = vec3(1, 0, 0);

	rec.mat_ptr = mp;
	rec.p = r.point_at_parameter(t);
	return true;
}

因为法线是写死在轴对齐矩形里面的,所以接下来定义一个新的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;
};

在场景中增加5个普通矩形,1个发光矩形,一个纹理圆形(其中使用flip_normals,将三个对面的法线取反):

hittable *random_scene() {

	//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*[7];
	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));
	int nn;
	unsigned char *Cristiano = stbi_load("F://Cristiano.jpg", &nx, &ny, &nn, 0);
	material *CristianoJpg = new lambertian(new image_texture(Cristiano, nx, ny));
	//list[i++] = new sphere(vec3(0, 2, 0), 2, new lambertian(pertext));
	list[i++] = new sphere(vec3(200, 130, 330), 130, CristianoJpg);
	return new bvh_node(list, i, 0.0, 1.0);
}

每像素采样1000次效果如下:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值