Progressive Path Tracing with Explicit Light Sampling
Progressive Path Tracing with Explicit Light Sampling
There are multiple areas in path tracing that can be importance sampled. In addition, each of those areas can also use Multiple Importance Sampling,
first proposed in Veach and Guibas's 1995 paper. To better explain, let's look at a backwards path tracer:
void RenderPixel(uint x, uint y, UniformSampler* sampler) {
Ray ray = m_scene->Camera->CalculateRayFromPixel(x, y, sampler);
float3 color(0.0f);
float3 throughput(1.0f);
SurfaceInteraction interaction;
// Bounce the ray around the scene
const uint maxBounces = 15;
for (uint bounces = 0; bounces < maxBounces; ++bounces) {
m_scene->Intersect(ray);
// 如果没有求到交集,则直接返回背景颜色
if (ray.GeomID == INVALID_GEOMETRY_ID) {
color += throughput * m_scene->BackgroundColor;
break;
}
// Fetch the material
Material* material = m_scene->GetMaterial(ray.GeomID);
// 这个时候,可能交到的是一个(Area)光源,则直接获取光源的emission
// Otherwise, GetLight will return nullptr
Light* light = m_scene->GetLight(ray.GeomID);
if (light != nullptr) {
color += throughput * light->Le();
}
interaction.Position = ray.Origin + ray.Direction * ray.TFar;
interaction.Normal = normalize(m_scene->InterpolateNormal(ray.GeomID, ray.PrimID, ray.U, ray.V));
interaction.OutputDirection = normalize(-ray.Direction);
// 根据bsdf获取一个新的方向
material->bsdf->Sample(interaction, sampler);
float pdf = material->bsdf->Pdf(interaction);
// Accumulate the weight (这个是在path tracing的时候,来累计当前点经过前面路径的比重,可以参考pbrt4 里的公式(13.6)
throughput = throughput * material->bsdf->Eval(interaction) / pdf;
// 将新的交点作为新的ray 出发点
ray.Origin = interaction.Position;
// Reset the other ray properties
ray.Direction = interaction.InputDirection;
ray.TNear = 0.001f;
ray.TFar = infinity;
// 这块主要是使用 俄罗斯转盘(Russian Roulette)算法来提前结束碰撞
if (bounces > 3) {
float p = std::max(throughput.x, std::max(throughput.y, throughput.z));
if (sampler->NextFloat() > p) {
break;
}
throughput *= 1 / p;
}
}
m_scene->Camera->FrameBufferData.SplatPixel(x, y, color);
}
因为光源有多种,包括面光源,点光源还有的光源是没有面的(infiniteLights),
但上面的只是和面光源(或者其他可以求得交点的光源)求得交点后在通过Le函数实现光照的,对于根本存在交点的光源没有计算辐照
void RenderPixel(uint x, uint y, UniformSampler* sampler) {
Ray ray = m_scene->Camera->CalculateRayFromPixel(x, y, sampler);
float3 color(0.0f);
float3 throughput(1.0f);
SurfaceInteraction interaction;
// Bounce the ray around the scene
const uint maxBounces = 15;
for (uint bounces = 0; bounces < maxBounces; ++bounces) {
m_scene->Intersect(ray);
// The ray missed. Return the background color
if (ray.GeomID == INVALID_GEOMETRY_ID) {
color += throughput * m_scene->BackgroundColor;
break;
}
// Fetch the material
Material* material = m_scene->GetMaterial(ray.GeomID);
// The object might be emissive. If so, it will have a corresponding light
// Otherwise, GetLight will return nullptr
Light* light = m_scene->GetLight(ray.GeomID);
//此处修改了
// If this is the first bounce or if we just had a specular bounce,
// we need to add the emmisive light
if ((bounces == 0 || (interaction.SampledLobe & BSDFLobe::Specular) != 0) && light != nullptr) {
color += throughput * light->Le();
}
interaction.Position = ray.Origin + ray.Direction * ray.TFar;
interaction.Normal = normalize(m_scene->InterpolateNormal(ray.GeomID, ray.PrimID, ray.U, ray.V));
interaction.OutputDirection = normalize(-ray.Direction);
/*
新增内容:(Calculate the direct lighting)
计算光源的直接光照(因为是直接照射到当前点上的,而不是经过多个path路径获取到的光照,所以叫 direct lighting )
SampleLights返回的是对当前点的光照, 当然乘上前面的throughtput得到的就是最后返回给人眼方向的光照
*/
//
color += throughput * SampleLights(sampler, interaction, material->bsdf, light);
// Get the new ray direction
// Choose the direction based on the bsdf
material->bsdf->Sample(interaction, sampler);
float pdf = material->bsdf->Pdf(interaction);
// Accumulate the weight
throughput = throughput * material->bsdf->Eval(interaction) / pdf;
// Shoot a new ray
// Set the origin at the intersection point
ray.Origin = interaction.Position;
// Reset the other ray properties
ray.Direction = interaction.InputDirection;
ray.TNear = 0.001f;
ray.TFar = infinity;
// Russian Roulette
if (bounces > 3) {
float p = std::max(throughput.x, std::max(throughput.y, throughput.z));
if (sampler->NextFloat() > p) {
break;
}
throughput *= 1 / p;
}
}
m_scene->Camera->FrameBufferData.SplatPixel(x, y, color);
}
因为之前是直接返回光照的强度,现在使用了采用光源方式,所以上面的(Le)直接返回光源就应该省略掉了。
但有两点需要调整:
- 首次光线。如果首次光线和光源有交点,则可以接受到光源的 emission。如果我们省略掉就看不见光源了。
- 对于 specular 平面,则光影采样是无效的。因为对于specualr平面它的入射光是特定的唯一的,对于光源采样是失效的。(采样是一个随机的)
float3 EstimateDirect(Light* light, UniformSampler* sampler, SurfaceInteraction& interaction, BSDF* bsdf) const {
// Only sample if the BRDF is non-specular
float3 directLighting = float3(0.0f);
// Only sample if the BRDF is non-specular
if ((bsdf->SupportedLobes & ~BSDFLobe::Specular) != 0) {
float pdf;
float3 Li = light->SampleLi(sampler, m_scene, interaction, &pdf);
// Make sure the pdf isn't zero and the radiance isn't black
if (pdf != 0.0f && !all(Li)) {
directLighting += bsdf->Eval(interaction) * Li / pdf;
}
}
return directLighting;
}
float3 SampleLights(UniformSampler* sampler, SurfaceInteraction interaction, BSDF* bsdf, Light* hitLight) const {
std::size_t numLights = m_scene->NumLights();
float3 L(0.0f);
for (uint i = 0; i < numLights; ++i) {
Light* light = &m_scene->Lights[i];
// Don't let a light contribute light to itself
if (light == hitLight) {
continue;
}
L = L + EstimateDirect(light, sampler, interaction, bsdf);
}
return L;
}
对于光源的SampleLi 函数, 我们可以使用均匀采样,也可以使用importance 采样。对于任何满足Monte Carlo采样方式都可以。
因为BRDF 是和视角相关联的,所以可以可以优先考虑使用BRDF在光源上进行采样。 这样存在两个采样:BRDF的采样和光源的采样。应该
使用那个呢?可以将这两个结合起来(Multiple Importance Sampling)。
float3 EstimateDirect(Light* light, UniformSampler* sampler, SurfaceInteraction& interaction,
BSDF* bsdf) const {
float3 directLighting = float3(0.0f);
float3 f;
float lightPdf, scatteringPdf;
// Sample lighting with multiple importance sampling
// Only sample if the BRDF is non-specular
if ((bsdf->SupportedLobes & ~BSDFLobe::Specular) != 0) {
float3 Li = light->SampleLi(sampler, m_scene, interaction, &lightPdf);
// Make sure the pdf isn't zero and the radiance isn't black
if (lightPdf != 0.0f && !all(Li)) {
// Calculate the brdf value
f = bsdf->Eval(interaction);
scatteringPdf = bsdf->Pdf(interaction);
if (scatteringPdf != 0.0f && !all(f)) {
float weight = PowerHeuristic(1, lightPdf, 1, scatteringPdf);
directLighting += f * Li * weight / lightPdf;
}
}
}
// Sample brdf with multiple importance sampling
bsdf->Sample(interaction, sampler);
f = bsdf->Eval(interaction);
scatteringPdf = bsdf->Pdf(interaction);
if (scatteringPdf != 0.0f && !all(f)) {
lightPdf = light->PdfLi(m_scene, interaction);
if (lightPdf == 0.0f) {
// We didn't hit anything, so ignore the brdf sample
return directLighting;
}
float weight = PowerHeuristic(1, scatteringPdf, 1, lightPdf);
float3 Li = light->Le();
directLighting += f * Li * weight / scatteringPdf;
}
return directLighting;
}
-
对光进行采样
- 更新入射光线方向
- 获取了该方向的Li
- 在光源上的的pdf
-
判断获取的Li是否为0
-
获取该入射方向光线的BSDF值(f)
-
获取该入射方向光线的BSDF的pdf值
-
使用光源的pdf和BSDF的pdf来计算比重(weight),此处使用PowerHeuristic来计算比重
-
将该比重作用与Li,并且除于光源的pdf,得到光照
-
对BSDF进行采样
- 获取入手光线方向
-
计算BRDF的值(f)
-
或该方向上BRDF的pdf
-
这样可以使用该入射方向作用与光源,获取光源的pdf
-
计算BSDF的比重
SampleLights函数里遍历了所有的光源,但是如果有上千个光源会很耗时的。
我们可以使用 Only Sampling One Light
例如:
如果我们要estimating \(h(x)\) by:
但是计算\(f(x_{i})\)和\(g(x_{i})\)会很耗时,所以我是使用:
其中\(\zeta\) 是个随机变量
posted on 2025-03-06 17:52 Ultraman_X 阅读(31) 评论(0) 收藏 举报
浙公网安备 33010602011771号