Vector3f Scene::castRay(const Ray &ray, int depth) const
输入为一个光线,一个深度。
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中编写的函数)。
新增了Vector3f tcoords、 Vector3f emit;
但并没赋值使用。
bool happened; //是否发生碰撞
Vector3f coords;//碰撞发生的坐标
Vector3f tcoords;
Vector3f normal;//相交三角形的法线
Vector3f emit;
double distance;//碰撞点距光源的距离
Object* obj;
Material* m;
如果光线与场景中物体有交点,则
如果射线第一次打到光源,则直接返回光源颜色。
如果射线打到光源,但不是该像素的直接光照,则返回0。该问题在交点为物体时求解。
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;
}
}
}
}
即:假设该物体接收到来自这个随机方向的光照,再判断是否真的在这个方向有光照照入。
// 物体表面法线
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;
}
}
//最后返回直接光照和间接光照
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);
}
获得光线击中某点后随机弹射的某个方向
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;
}
}
}
将半球坐标转化为世界坐标。
也即:
将法线为(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;
}
这里应该为获取服从均匀分布范围为[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