shade(p, wo)
// 1. 直接光照
sampleLight(inter , pdf_light)
Get x, ws, NN, emit form inter
Shoot a ray form p to x
If the ray is not blocked in the middle
L_dir = emit // Emitssion
* eval(wo, ws, N) // BRDF 描述光从 ws → wo 在表面如何反射
* dot(ws, N) // theta
* dot(ws, NN) // theta_prime
/ |x - p|^2 // r^2
/ pdf_light // Monte Carlo
// 2. 间接光照
L_indir = 0.0
// 用俄罗斯轮盘赌决定是否继续递归 (防止无线递归,保证最终结果能量无损失)
Test Russain Roulette with probability RussainRoulette
wi = sample(wo, N) // 方向采样(BRDF Sampling)
// 根据 BRDF 分布采样一个入射方向 wi, 比均匀采样效率高
Trace a ray r(p, wi)
If ray r hit a non-emitting object at q
L_indir = shade(q, wi) * eval(ow, wi, N) * dot(wi, N) / pdf(wo, wi, N)
/ RussainRoulette
Return L_dir + L_indir
解析
| shade(p, wo) | 这个函数本质上是在数值求解渲染方程 | $Lo(p,ωo)=L_{dir}+L_{indir}$ |
|---|---|---|
| p | 当前着色点(光线与物体的交点) | |
| wo | 从点 p 指向相机的出射方向(outgoing direction) | |
本次实验中只需要修改一个函数 CastRay(const Ray ray, int depth) in scene.cpp
结果图添加到 readme
(我也要做一个酷酷的图)

多线程
check
// 光线与场景求交
Intersection inter = Intersect(ray);
if (!inter.happened)
return Vector3f(0.0f);
// 如果打到光源,直接返回光源的 emission
// 也就是说我们直接看到光源本身的颜色
if (inter.m->HasEmission())
return inter.m->GetEmission();
// ----------------------------------------------------------------------------: 直接光照
Vector3f dir_light(0.0f);
// 在光源上采样一个点
Intersection light_pos;
float light_pdf;
SampleLight(light_pos, light_pdf); // out parameters
// 计算交点 p 到光源采样点的方向
Vector3f p = inter.coords;
Vector3f N = inter.normal;
Vector3f light_dir = light_pos.coords - p; // p 指向光源的向量
float light_distance = light_dir.Norm(); // 距离
light_dir = Normalize(light_dir); // 单位向量 ws
// 发射一条 shadow ray, 检查中间是否有遮挡物
// 添加偏移避免自相交
Ray shadow_ray(p + N * 0.0001f, light_dir);
Intersection shadow_inter = Intersect(shadow_ray);
// 如果没有遮挡,直接计算光照贡献
// 注意:需要检查 shadow_inter.happened,否则 distance 可能未定义
if (!shadow_inter.happened || shadow_inter.distance >= light_distance - 0.001f) {
auto ws = light_dir;
auto NN = light_pos.normal; // 光源的法线
auto wo = -ray.direction; // 出射方向 (指向眼睛,相机)
Vector3f brdf = inter.m->Eval(wo, ws, N);
float cos_theta = std::max(0.0f, DotProduct(ws, N)); // 光线方向与交点法的夹角余弦
float cos_theta_prime = std::max(0.0f, DotProduct(-ws, NN)); // 光线方向与光源法线的夹角余弦
dir_light = light_pos.emit * brdf * cos_theta * cos_theta_prime / (light_distance * light_distance) / light_pdf;
}
// ----------------------------------------------------------------------------: 间接光照
Vector3f light_indir(0.0f);
// 俄罗斯轮盘赌决定是否继续追踪 (用随机数和 russian_roulette 0.8 比较)
if (GetRandomFloat() < russian_roulette) {
// 在加点处采样一个反射方向
Vector3f wo = -ray.direction;
Vector3f wi = inter.m->Sample(wo, N); // 在半球上随机采样一个出射方向 wi
// 发射发新的光线
// 添加偏移避免自相交
Ray new_ray(p + N * 0.0001f, wi);
Intersection new_inter = Intersect(new_ray);
// 如果打到了物体,且不是光源,计算间接光照
if (new_inter.happened && !new_inter.m->HasEmission()) {
float pdf = inter.m->Pdf(wo, wi, N);
if (pdf > 0.001f) { // 避免除以0
Vector3f brdf = inter.m->Eval(wo, wi, N);
float cos_theta = std::max(0.0f, DotProduct(wi, N));
light_indir = CastRay(new_ray, depth + 1) * brdf * cos_theta / pdf / russian_roulette;
}
}
} // end rr
return dir_light + light_indir;