需要编写的函数
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();}//若非射线经弹射打到光源,则在打到物体是判断,这里不做处理,返回0else 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//条件判断应该是为了防止除0if (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