Games101,作业7(作业代码分析)

需要编写的函数

Vector3f Scene::castRay(const Ray &ray, int depth) const

输入为一个光线,一个深度。

1.求出该光线与场景的交点

Intersection inter = intersect(ray);

该函数调用场景bvh类中的求交函数

Intersection Scene::intersect(const Ray &ray) const
{
    return this->bvh->Intersect(ray);
}

最终获取场景中某个三角形和该光线的交点信息。

Intersection isect = BVHAccel::getIntersection(root, ray);
return isect;

getIntersection(root, ray)是求交函数,返回的是Intersection 类(在作业6中编写的函数)。

Intersection 类

新增了Vector3f tcoords、 Vector3f emit;但并没赋值使用。

    bool happened;  //是否发生碰撞
    Vector3f coords;//碰撞发生的坐标
    Vector3f tcoords;
    Vector3f normal;//相交三角形的法线
    Vector3f emit;
    double distance;//碰撞点距光源的距离
    Object* obj;
    Material* m;

如果光线与场景中物体有交点,则

2.如果交点为光源

如果射线第一次打到光源,则直接返回光源颜色。
如果射线打到光源,但不是该像素的直接光照,则返回0。该问题在交点为物体时求解。

3.如果交点为物体

使用函数Scene::sampleLight(Intersection &pos, float &pdf)得到lightInter(场景中光源区域的任意一点),pdf(该光源的密度)。

		Intersection lightInter;
		float pdf_light = 0.0f;
		sampleLight(lightInter, pdf_light);

sampleLight(lightInter, pdf_light)函数内容

void Scene::sampleLight(Intersection &pos, float &pdf) const
{
    float emit_area_sum = 0; //保存总的光源面积
    for (uint32_t k = 0; k < objects.size(); ++k) {
        if (objects[k]->hasEmit()){
            emit_area_sum += objects[k]->getArea();
        }
    }
    //get_random_float()随机生成一个服从[0,1]的均匀分布的数
    float p = get_random_float() * emit_area_sum;
    emit_area_sum = 0;
    for (uint32_t k = 0; k < objects.size(); ++k) {
        if (objects[k]->hasEmit()){
            emit_area_sum += objects[k]->getArea();
            if (p <= emit_area_sum){//按光源面积比例,随机找到一个光源面,再在这个光源面中找到一个点
                objects[k]->Sample(pos, pdf);//pos为该光源面中随机找到的一个点,pdf为 1/该模型的面积
                break;
            }
        }
    }
}
生成一条由该物体指向随机生成的光源的一条光线,与场景求交,交点为light2obj 。

即:假设该物体接收到来自这个随机方向的光照,再判断是否真的在这个方向有光照照入。

		// 物体表面法线
		auto& N = inter.normal;
		// 灯光表面法线
		auto& NN = lightInter.normal;

		auto& objPos = inter.coords;
		auto& lightPos = lightInter.coords;

		auto diff = lightPos - objPos;
		auto lightDir = diff.normalized();
		float lightDistance = diff.x * diff.x + diff.y * diff.y + diff.z * diff.z;

		Ray light(objPos, lightDir);
		Intersection light2obj = intersect(light);
如果该光线击中光源(及该光源可以直接照射到该点),计算直接光照值
		// 如果反射击中光源
		if (light2obj.happened && (light2obj.coords - lightPos).norm() < 1e-2)
		{
			//获取改材质的brdf,这里的brdf为漫反射(brdf=Kd/pi)
			Vector3f f_r = inter.m->eval(ray.direction, lightDir, N);
			//直接光照光 = 光源光 * brdf * 光线和物体角度衰减 * 光线和光源法线角度衰减 / 光线距离 / 该点的概率密度(1/该光源的面积)
			L_dir = lightInter.emit * f_r * dotProduct(lightDir, N) * dotProduct(-lightDir, NN) / lightDistance / pdf_light;
		}

f_r为BRDF,这里用的是简单漫反射,f_r= 漫 反 射 系 数 ( K d ) π 漫反射系数(Kd)\over \pi π(Kd)
lightInter.emit是光照强度
dotProduct(lightDir, N)是物体非正向接收光照造成的能量衰减。
dotProduct(-lightDir, NN) 是光源非正向光照传播造成的能量衰减。
lightDistance 是光照传播的距离,除以lightDistance 及光照的距离衰减。
pdf_light是小数,(通过代码分析知此处)为该光源的面积的倒数。

光线是否继续弹射,计算间接光照?(俄罗斯轮盘赌)

递归计算

//俄罗斯轮盘赌,确定是否继续弹射光线
if (get_random_float() < RussianRoulette)
{
	//获取半平面上的随机弹射方向
	Vector3f nextDir = inter.m->sample(ray.direction, N).normalized();
	//定义弹射光线
	Ray nextRay(objPos, nextDir);
	//获取相交点
	Intersection nextInter = intersect(nextRay);
	//如果有相交,且是与物体相交
	if (nextInter.happened && !nextInter.m->hasEmission())
	{
		//该点间接光= 弹射点反射光 * brdf * 角度衰减 / pdf(认为该点四面八方都接收到了该方向的光强,为1/(2*pi)) / 俄罗斯轮盘赌值(强度矫正值)
		float pdf = inter.m->pdf(ray.direction, nextDir, N);
		Vector3f f_r = inter.m->eval(ray.direction, nextDir, N);
		L_indir = castRay(nextRay, depth + 1) * f_r * dotProduct(nextDir, N) / pdf / RussianRoulette;
	}
}

4.返回得到的光线值

//最后返回直接光照和间接光照
return L_dir + L_indir;

完整代码

来自GAMES101-现代计算机图形学学习笔记(作业07)

// Implementation of Path Tracing
Vector3f Scene::castRay(const Ray& ray, int depth) const
{
	Intersection inter = intersect(ray);

	if (inter.happened)//如果光线与场景有交点
	{
		//如果打到光源
		if (inter.m->hasEmission())
		{
			// 如果射线第一次打到光源,直接返回光源光
			if (depth == 0)
			{
				return inter.m->getEmission();
			}
			//若非射线经弹射打到光源,则在打到物体是判断,这里不做处理,返回0
			else return Vector3f(0, 0, 0);
		}

		// 如果打到物体
		Vector3f L_dir(0, 0, 0);
		Vector3f L_indir(0, 0, 0);

		//均匀采样光源物体,取光源上一点
		Intersection lightInter;
		float pdf_light = 0.0f;
		sampleLight(lightInter, pdf_light);

		// 物体表面法线
		auto& N = inter.normal;
		// 灯光表面法线
		auto& NN = lightInter.normal;
		//物体点坐标
		auto& objPos = inter.coords;
		//光源点坐标
		auto& lightPos = lightInter.coords;

		auto diff = lightPos - objPos;
		auto lightDir = diff.normalized();
		float lightDistance = diff.x * diff.x + diff.y * diff.y + diff.z * diff.z;

		//从物体打向光源的感光线(感光线为光线传播的逆方向)
		Ray light(objPos, lightDir);
		//该光线与场景求交
		Intersection light2obj = intersect(light);

		// 如果反射击中光源
		if (light2obj.happened && (light2obj.coords - lightPos).norm() < 1e-2)
		{
			//获取改材质的brdf,这里的brdf为漫反射(brdf=Kd/pi)
			Vector3f f_r = inter.m->eval(ray.direction, lightDir, N);
			//直接光照光 = 光源光 * brdf * 光线和物体角度衰减 * 光线和光源法线角度衰减 / 光线距离 / 该点的概率密度(1/该光源的面积)
			L_dir = lightInter.emit * f_r * dotProduct(lightDir, N) * dotProduct(-lightDir, NN) / lightDistance / pdf_light;
		}

		//俄罗斯轮盘赌,确定是否继续弹射光线
		if (get_random_float() < RussianRoulette)
		{
			//获取半平面上的随机弹射方向
			Vector3f nextDir = inter.m->sample(ray.direction, N).normalized();
			//定义弹射光线
			Ray nextRay(objPos, nextDir);
			//获取相交点
			Intersection nextInter = intersect(nextRay);
			//如果有相交,且是与物体相交
			if (nextInter.happened && !nextInter.m->hasEmission())
			{
				//该点间接光= 弹射点反射光 * brdf * 角度衰减 / pdf(认为该点四面八方都接收到了该方向的光强,为1/(2*pi)) / 俄罗斯轮盘赌值(强度矫正值)
				float pdf = inter.m->pdf(ray.direction, nextDir, N);
				Vector3f f_r = inter.m->eval(ray.direction, nextDir, N);
				L_indir = castRay(nextRay, depth + 1) * f_r * dotProduct(nextDir, N) / pdf / RussianRoulette;
			}
		}
		//最后返回直接光照和间接光照
		return L_dir + L_indir;
	}
	//如果光线与场景无交点
	return Vector3f(0, 0, 0);
}

补充该函数中用到的函数

sample(ray.direction, N);

获得光线击中某点后随机弹射的某个方向

Vector3f nextDir = inter.m->sample(ray.direction, N).normalized();
Vector3f Material::sample(const Vector3f &wi, const Vector3f &N){
    switch(m_type){
        case DIFFUSE:
        {
            // uniform sample on the hemisphere在半球上均匀采样
            float x_1 = get_random_float(), x_2 = get_random_float();
            //z∈[0,1],是随机半球方向的z轴向量
            float z = std::fabs(1.0f - 2.0f * x_1);
            //r是半球半径随机向量以法线为旋转轴的半径
            //phi是r沿法线旋转轴的旋转角度
            float r = std::sqrt(1.0f - z * z), phi = 2 * M_PI * x_2;//phi∈[0,2*pi]
            Vector3f localRay(r*std::cos(phi), r*std::sin(phi), z);//半球面上随机的光线的弹射方向
            return toWorld(localRay, N);//转换到世界坐标
            break;
        }
    }
}

toWorld(localRay, N);

将半球坐标转化为世界坐标。
也即:
将法线为(0,0,1)的localRay转换为法线为N的向量。

Vector3f toWorld(const Vector3f &a, const Vector3f &N){
        Vector3f B, C;
        //将N分解为B和C
        //条件判断应该是为了防止除0
        if (std::fabs(N.x) > std::fabs(N.y)){//C为与x,z平面上N分量及N垂直的单位向量
            float invLen = 1.0f / std::sqrt(N.x * N.x + N.z * N.z);
            C = Vector3f(N.z * invLen, 0.0f, -N.x *invLen);
        }
        else {//C为与y,z平面上N分量及N垂直的单位向量
            float invLen = 1.0f / std::sqrt(N.y * N.y + N.z * N.z);
            C = Vector3f(0.0f, N.z * invLen, -N.y *invLen);
        }
        B = crossProduct(C, N);
        return a.x * B + a.y * C + a.z * N;
    }

get_random_float()

这里应该为获取服从均匀分布范围为[0,1]的浮点数。
程序源注释// distribution in range [1, 6]应该是错的。

inline float get_random_float()
{
    std::random_device dev;//生成一个随机数,随机数服从均匀分布
    std::mt19937 rng(dev());
    std::uniform_real_distribution<float> dist(0.f, 1.f); // distribution in range [1, 6]

    return dist(rng);
}

运行结果

32spp

256spp

你可能感兴趣的:(Games101,games101,图形学,path,tracing)