#1 楼
路径跟踪中有多个区域可以进行重要性采样。此外,这些领域中的每个领域都可以使用Veach和Guibas在1995年的论文中首次提出的多重重要性抽样。为了更好地解释,让我们看一下向后路径跟踪器: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 we hit a light, add the emission
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);
// 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);
}
用英语:
检查我们是否击中了任何东西。如果不是,我们将返回天空盒的颜色并中断。
检查是否击中了灯光。如果是这样,我们将发光添加到颜色累积中
为下一条射线选择新的方向。我们可以统一执行此操作,也可以基于BRDF进行重要性样本
评估BRDF并进行累加。在这里,我们必须除以我们选择的方向的pdf,以便遵循蒙特卡洛算法。
根据我们选择的方向以及我们刚来自的方向创建一条新射线
[可选]使用俄语轮盘选择是否终止光线
转到1
使用此代码,只有在光线最终照射到光线时,我们才会获得颜色。另外,它不支持守时光源,因为它们没有面积。
为解决此问题,我们在每次反弹时直接对灯光进行采样。我们必须做一些小的更改:
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
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);
}
首先,我们添加“颜色+ =吞吐量* SampleLights(...)”。我将详细介绍SampleLights()。但是,从本质上讲,它会循环遍历所有灯光,并返回它们对颜色的贡献,并由BSDF衰减。
这很好,但是我们需要再进行更改以使其正确。 ;具体来说,当我们开灯时会发生什么。在旧代码中,我们将光的发射添加到了颜色累积中。但是现在我们在每次反射时都直接对光进行采样,因此,如果添加光的发射,我们将“两次浸入”。因此,正确的做法是……什么都不做;我们跳过了累积光的发射的过程。
但是有两个极端的情况:
第一束光线
完全是镜面反射(又称镜子)
如果第一束光线照射到光线,则应该直接看到光线的发射。因此,如果我们跳过它,即使它们周围的表面都被照亮,所有的光都将显示为黑色。
当您击中完全镜面的表面时,您将无法直接采样光,因为输入射线只有一个输出。好吧,从技术上讲,我们可以检查输入光线是否会照亮,但是没有意义;无论如何,主要的路径跟踪循环都将这样做。因此,如果我们在击中镜面后立即击中灯光,则需要累积颜色。如果不这样做,那么镜子中的灯光将变黑。
现在,让我们深入研究SampleLights():
在所有灯光下循环
如果碰到灯光,请跳过灯光
不要重复浸入
从所有灯中累积直接照明
返回直接照明
最后,EssentialDirect()只是计算$ BSDF(p,\ omega _ {\ text { i}},\ omega _ {\ text {o}})L _ {\ text {i}}(p,\ omega _ {\ text {i}})$
对于点光源,很简单,例如:因此,完整的定义是:
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;
}
我们可以根据需要实现light-> SampleLi。我们可以统一选择要点或重要性样本。无论哪种情况,我们都将辐射度除以选择点的pdf。同样,为了满足蒙特卡洛的要求。
如果BRDF高度依赖于视图,则最好基于BRDF选择一个点,而不是灯光上的随机点。但是我们如何选择呢?样品是基于光还是基于BRDF?
为什么不兼得?输入多个重要性采样。简而言之,我们评估$ BSDF(p,\ omega _ {\ text {i}},\ omega _ {\ text {o}})L _ {\ text {i}}(p,\ omega _ {\ text {i}} )$,使用不同的采样技术多次,然后使用基于其pdf的权重将它们平均在一起。在代码中是:
float3 EstimateDirect(Light *light, UniformSampler *sampler, SurfaceInteraction &interaction, BSDF *bsdf) const {
// Only sample if the BRDF is non-specular
if ((bsdf->SupportedLobes & ~BSDFLobe::Specular) != 0) {
return float3(0.0f);
}
interaction.InputDirection = normalize(light->Origin - interaction.Position);
return bsdf->Eval(interaction) * light->Li;
}
英语:
首先,我们对光进行采样
这会更新交互。InputDirection
为我们提供灯光的Li
以及在灯光上选择该点的pdf
检查是否pdf有效且辐射度非零
使用采样的InputDirection评估BSDF
在采样到InputDirection的情况下,计算BSDF的pdf
基本上,如何如果我们要使用BSDF而不是轻量进行采样,则此样本很可能
使用轻pdf和BSDF pdf计算重量
Veach和Guibas定义了几种不同的方法来计算重量。通过实验,他们发现2的幂次幂启发法在大多数情况下效果最佳。有关更多详细信息,请参考本文。具体实现如下:
将权重乘以直接照明计算,然后除以pdf。 (对于Monte Carlo)并添加到直接光累积中。
然后,我们对BRDF进行采样
,这将更新交互。InputDirection
评估BRDF
在BRDF的基础上获取pdf以选择该方向
给定采样的InputDirection,计算光pdf
这是之前。如果我们要对光进行采样,那么这个方向的可能性有多大?如果lightPdf == 0.0f,则光线错过了光,因此只需从光采样中返回直接照明即可。
否则,计算重量,然后将BSDF直接照明添加到累积量中
最后,返回累积的直接照明量
。
float3 EstimateDirect(Light *light, UniformSampler *sampler, SurfaceInteraction &interaction, BSDF *bsdf) const {
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;
}
您可以对这些函数进行许多优化/改进,但是我将它们简化了一些,以使它们易于理解。如果您愿意,我可以分享其中的一些改进。
仅采样一盏灯
在SampleLights()中,我们遍历所有灯,并获得它们的贡献。对于少量的灯,这很好,但是对于成百上千的灯,则变得昂贵。幸运的是,我们可以利用蒙特卡洛积分是一个巨大的平均值的事实。示例:
让我们定义
$$ h(x)= f(x)+ g(x)$$
当前,重新估算$ h(x)$的方式为:
$$ h(x)= \ frac {1} {N} \ sum_ {i = 1} ^ N f(x_i)+ g(x_i )$$
但是,同时计算$ f(x)$和$ g(x)$非常昂贵,所以我们这样做:
$$ h(x) = \ frac {1} {N} \ sum_ {i = 1} ^ N \ frac {r(\ zeta,x)} {pdf} $$
其中$ \ zeta $是统一的随机变量,并且$ r(\ zeta,x)$定义为:
$$ r(\ zeta,x)=
\ begin {cases}
f( x),&0.0 \ leq \ zeta \ lt 0.5 \\
g(x),&0.5 \ leq \ zeta \ lt 1.0
\ end {cases)$$
在这种情况下,$ pdf = \ frac {1} {2} $,因为pdf必须集成为1,并且有2个函数可供选择。
英语:
随机选择$ f(x)$或$ g(x)$进行评估。
将结果除以$ \ frac {1} {2} $(因为有两项)
平均
随着N变大,估计将收敛到正确的解。
我们可以将相同的原理应用于光采样。而不是对每个光源进行采样,我们随机选择一个光源,然后将结果乘以光源数量(这与除以分数pdf相同):
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;
}
在此代码中,所有灯光均具有平等的机会被拾取。但是,如果愿意,我们可以重视样本。例如,我们可以为较大的灯光提供更高的被拾取机会,或者使灯光更靠近命中表面。您只需要将结果除以pdf,就不再是$ \ frac {1} {\ text {numLights}} $。
对“ New Ray”方向采样的多重重要性
当前代码仅根据BSDF对“ New Ray”方向进行采样。如果我们也想根据灯光的位置来重视样本又该怎么办?
根据我们从以上中学到的知识,一种方法是拍摄两条“新”射线并根据其pdf进行权重。但是,这既计算量大,又难以递归实现。
要克服这一点,我们可以应用仅采样一个光源所学到的相同原理。也就是说,随机选择一个样本,然后除以选择它的pdf。根据光的方向?对于直接照明,光能传递受表面的BSDF和光的方向的影响。但是对于间接照明,光能传递几乎完全由之前命中的表面的BSDF定义。因此,增加重要度抽样并不能给我们任何好处。
因此,通常仅使用BSDF对“新方向”进行重要性采样,而对直接照明应用多重重要性采样。
评论
$ \ begingroup $
谢谢您的澄清!我知道,如果不使用显式光采样就使用路径跟踪器,我们将永远不会碰到点光源。因此,我们基本上可以添加其贡献。另一方面,如果我们对面光源进行采样,则必须确保不要再用间接照明再次击中它,以免发生两次倾斜
$ \ endgroup $
–穆斯塔法·伊斯克(MustafaIşık)
17年5月25日在15:37
$ \ begingroup $
没错!您是否需要澄清任何部分?还是没有足够的细节?
$ \ endgroup $
–RichieSams
17年5月25日在15:39
$ \ begingroup $
另外,多重重要性采样是否仅用于直接照明计算?也许我错过了,但是我没有看到另一个例子。如果我在路径跟踪器中每次反弹仅发射一束光线,似乎无法进行间接照明计算。
$ \ endgroup $
–穆斯塔法·伊斯克(MustafaIşık)
17年5月25日在15:43
$ \ begingroup $
您可以在使用重要性抽样的任何地方应用多重重要性抽样。多重重要性抽样的力量在于我们可以结合多种抽样技术的优势。例如,在某些情况下,轻度重要性采样将比BSDF采样更好。在其他情况下,反之亦然。 MIS将结合两全其美。但是,如果BSDF采样在100%的时间内会更好,则没有理由增加MIS的复杂性。我在答案中添加了一些部分以扩展这一点
$ \ endgroup $
–RichieSams
17年5月25日在18:53
$ \ begingroup $
似乎我们将进入的辐射源分为直接和间接两部分。我们对直接部分的灯光进行了明确采样,并且在对该部分进行采样时,对灯光以及BSDF进行重要性采样是合理的。但是对于间接部分,我们不知道哪个方向可能会给我们带来更高的辐射值,因为这是我们要解决的问题。但是,根据余弦项和BSDF,我们可以说哪个方向的贡献更大。这是我的理解。如果我错了,请指正我,并感谢您的出色回答。
$ \ endgroup $
–穆斯塔法·伊斯克(MustafaIşık)
17年5月25日19:50