【光线追踪系列十七】直接光源采样

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

在这里插入图片描述

前面文章中我们也提到要减少噪点,就是图片上的黑点点。简单说一下为什么会有那么多小点点,就是因为光线路径中没有触碰到光源,路径计算之后就会是黑色的点,我们可以通过发射大量的光线,比如计算每个像素点的时候发射8k~1w条采样光线进行路径计算;也可以路径计算方面做文章:比如加深路径计算递归深度;等等诸如此类。但是上述方法都是暴力解决法,相当耗时,我们可以运用数学对其进行优化,从而实现画质和效率的双面提升,即直接对光源进行采样!

一、理论

我们朝光源方向发送光线或者生成朝向光源的随机方向都是很容易实现的,但是我们需要知道的是,pdf(direction)是什么,如下图对光源采样。
在这里插入图片描述
对于一个光源区域A,如果我们均匀采样该区域,那么这个pdf就等于1/A(意思就是每个点的概率均等)。

因此,我们希望尽可能地往重要的方向采样,但在各方向均匀采样,并不会使得重要方向的采样数量变多。接下来,我们人为地让更多的射线往光照的方向采样。

我们可以向着光源的位置生成一个随机的方向,只需在光源材质上随机选取一个点,然后向着这个点的位置生成射线。但我们还需要知道这个pdf(direction)。假设光源的面积为A,如果对这个光源表面随机采样,则该光源表面的pdf为1/A,但如果要从一个定义了方向的单位球的球心为起点,发射射线到这个光源表面,则需要求解对应的pdf公式。

如上图所示,dA 表示光源表面面积的一小部分,设采样到这一小部分面积的概率为 p_q(q) * dA(采样比例乘以微分区域),也就是 dA/A

对于单位球而言,dw 为对应 dA 的单位球表面面积的一小部分,设采样到这一小部分的概率 p(direction) ⋅dw

dwdA 有如下对应关系:
在这里插入图片描述
即:方位角微分区域:光源微分区域分成(球心到A中心距离平方)份,取其中的 cosα 代表的份额数。

因为采样到 dwdA 的概率必须相等,则有:
在这里插入图片描述
故,
在这里插入图片描述

二、代码实现

上述理论整合为代码,如下:

float lightStartX = 213;
float lightEndX = 343;
float lightStartZ = 227;
float lightEndZ = 332;
float lightY = 554;

hittable *random_scene() {
    ...
    list[i++] = new xz_rect(lightStartX, lightEndX, lightStartZ, lightEndZ, lightY, light);
    ...
}

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);
        float pdf;
        vec3 albedo;
        if (depth < 50 && rec.mat_ptr->scatter(r, rec, albedo, scattered, pdf)) {
        
            //new start
            float sizeX = lightEndX - lightStartX;
            float sizeZ = lightEndZ - lightStartZ;
            float light_area = (lightEndX-lightStartX)*(lightEndZ-lightStartZ);
            vec3 on_light = vec3(lightStartX + random_double()*sizeX, 
                                lightY, 
                                lightStartZ + random_double()*sizeZ);
            vec3 to_light = on_light - rec.p;
            float distance_squared = to_light.squared_length();
            to_light.make_unit_vector();
            if (dot(to_light, rec.normal) < 0) return emitted;
            float light_cosine = fabs(to_light.y());
            if (light_cosine < 0.000001) return emitted;
            pdf = distance_squared / (light_cosine * light_area);
            scattered = ray(rec.p, to_light, r.time());
            //new end
            
            return emitted + albedo*rec.mat_ptr->scattering_pdf(r, rec, scattered)
                *color(scattered, world, depth+1) / pdf;
        }
        else
            return emitted;
    }
    else
        return vec3(0,0,0);
}

渲染结果如下:
在这里插入图片描述

上图为每像素采样5次,运行时间与噪点大幅减少,这个效果相当惊艳。这应该是渲染Cornell Box以来最大的突破了,利用重要性采样,将所有Lambertian材质的散射光线指向了光源的方向,减少了大量噪点,且当散射光线直射光源的时候,不再发生散射,大幅减少了射线转折的次数。

不过,天花板下面的的光源周围出现了一些噪点,因为光源是一个两面矩形,距离天花板还有一定的空间。我们可以将其修改为只向下发光:

修改material的虚函数:

class material {
public:
	virtual bool scatter(const ray& r_in,
		const hit_record& rec, vec3& albedo, ray& scattered, float& pdf) const {
		return false;
	}
	virtual float scattering_pdf(const ray& r_in, const hit_record& rec,
		const ray& scattered) const {
		return 0;
	}
	/*virtual vec3 emitted(float u, float v, const vec3& p) const {
		return vec3(0, 0, 0);
	}*/
	virtual vec3 emitted(const ray& r_in, const hit_record& rec, float u, float v, const vec3& p) const {
		return vec3(0, 0, 0);
	}
};

修改color函数:

vec3 color(const ray& r, hittable *world, int depth) {
	hit_record rec;
	if (world->hit(r, 0.001, FLT_MAX, rec)) {
		ray scattered;
		vec3 attenuation;
		//vec3 emitted = rec.mat_ptr->emitted(rec.u, rec.v, rec.p);
		vec3 emitted = rec.mat_ptr->emitted(r, rec, rec.u, rec.v, rec.p);
		float pdf;
		vec3 albedo;
		if (depth < 50 && rec.mat_ptr->scatter(r, rec, albedo, scattered, pdf)) {

			//new start
			float sizeX = lightEndX - lightStartX;
			float sizeZ = lightEndZ - lightStartZ;
			float light_area = (lightEndX - lightStartX)*(lightEndZ - lightStartZ);
			vec3 on_light = vec3(lightStartX + random_double()*sizeX,
				lightY,
				lightStartZ + random_double()*sizeZ);
			vec3 to_light = on_light - rec.p;
			float distance_squared = to_light.squared_length();
			to_light.make_unit_vector();
			if (dot(to_light, rec.normal) < 0) return emitted;
			float light_cosine = fabs(to_light.y());
			if (light_cosine < 0.000001) return emitted;
			pdf = distance_squared / (light_cosine * light_area);
			scattered = ray(rec.p, to_light, r.time());
			//new end

			return emitted + albedo * rec.mat_ptr->scattering_pdf(r, rec, scattered)
				*color(scattered, world, depth + 1) / pdf;
		}
		else
			return emitted;
	}
	else
		return vec3(0, 0, 0);
}

修改diffuse_light:

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(const ray& r_in, const hit_record& rec, float u, float v, const vec3& p) const {
		
		if (dot(rec.normal, r_in.direction()) >= 0.0)
			return emit->value(u, v, p);
		else
			return vec3(0, 0, 0);
	}
	/*virtual vec3 emitted(float u, float v, const vec3& p) const {
		return emit->value(u, v, p);
	}*/
};

效果如下(采样数50,光源周围的噪点明显减少了):
在这里插入图片描述

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
蒙特卡洛光线追踪算法是一种基于渲染方程的全局光照算法,也可以称为路径追踪算法。其原理是从摄像机的视角开始,通过逆向追踪光线的路径,模拟光线在场景中的传播和交互,最终计算出每个像素的颜色值。 在蒙特卡洛光线追踪算法中,我们使用随机采样的方法来估计光线的传播。通过随机选择光线的起点和方向,并计算它们与场景中的物体的交点,从而获得光线路径。在路径追踪的过程中,我们可以考虑直接光照和间接光照两个问题。 直接光照是指光线直接光源射向物体表面并被反射到摄像机上的光照。为了计算直接光照,我们需要发射一条光线从物体表面的某一点,通过交点处的法线和光源的位置来确定光线的方向和强度。 间接光照是指光线在场景中多次弹射后到达物体表面的光照。为了计算间接光照,我们会对每个交点进行递归追踪,发射一条新的光线并计算它与场景中其他物体的交点,直到达到一定的追踪深度或光线能量过低时终止追踪。 蒙特卡洛光线追踪算法的效率并不高,尤其是在计算直接光照时。因为每次只能随机射出一根光线,很难保证光线能够准确地打到光源上,导致计算效率较低。为了提高效率,可以采用一些优化技术,比如使用重要性采样和加速结构等方法来减少不必要的计算。 总之,蒙特卡洛光线追踪算法是一种基于渲染方程的全局光照算法,通过逆向追踪光线的路径来模拟光线在场景中的传播和交互。它可以计算出每个像素的颜色值,包括直接光照和间接光照。然而,由于其计算效率较低,可以采用一些优化方法来提高效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值