我已经看到,在“路径跟踪”的某些实现中,使用了一种名为“俄罗斯轮盘赌”的方法来剔除某些路径并在其他路径之间共享它们的贡献。当其下降到某个贡献阈值以下时,然后放弃该阈值,使用另一个阈值,并且贡献小于该阈值的路径仅以很小的概率终止。其他路径的贡献增加了相应于共享来自终止路径的损耗能量的量。我不清楚这是否是要纠正该技术带来的偏差,还是整个技术本身是否有必要避免偏差。


俄罗斯轮盘赌给出的结果是否公正?
是否需要使用俄罗斯轮盘来获得公正的结果?

,即使用较小的阈值并在其跌落到该阈值以下时终止一条路径会产生更大的偏差或更少的偏差?

给出任意数量的样本,这两种方法都可以收敛到无偏的结果图像上吗? 。在速度或质量上有明显差异吗?但是,如果光线下降到固定阈值以下而终止,而不是在达到该阈值后具有随机确定的寿命,那么是否仍无法进行重新分配?通过终止光线而不重新分配其能量而丢失的光最终还是会丢失(因为最终将其重新分布到的光线也将终止),这如何改善这种情况?

#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);

    // Bounce the ray around the scene
    for (uint bounces = 0; bounces < 10; ++bounces) {
        m_scene->Intersect(ray);

        // The ray missed. Return the background color
        if (ray.geomID == RTC_INVALID_GEOMETRY_ID) {
            color += throughput * float3(0.846f, 0.933f, 0.949f);
            break;
        }

        // We hit an object

        // 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 emmisive light
        if (light != nullptr) {
            color += throughput * light->Le();
        }

        float3 normal = normalize(ray.Ng);
        float3 wo = normalize(-ray.dir);
        float3 surfacePos = ray.org + ray.dir * ray.tfar;

        // Get the new ray direction
        // Choose the direction based on the material
        float3 wi = material->Sample(wo, normal, sampler);
        float pdf = material->Pdf(wi, normal);

        // Accumulate the brdf attenuation
        throughput = throughput * material->Eval(wi, wo, normal) / pdf;


        // Shoot a new ray

        // Set the origin at the intersection point
        ray.org = surfacePos;

        // Reset the other ray properties
        ray.dir = wi;
        ray.tnear = 0.001f;
        ray.tfar = embree::inf;
        ray.geomID = RTC_INVALID_GEOMETRY_ID;
        ray.primID = RTC_INVALID_GEOMETRY_ID;
        ray.instID = RTC_INVALID_GEOMETRY_ID;
        ray.mask = 0xFFFFFFFF;
        ray.time = 0.0f;
    }

    m_scene->Camera.FrameBuffer.SplatPixel(x, y, color);
}


IE。我们在场景中弹跳,并在进行过程中累积颜色和光衰减。为了在数学上完全无偏,反弹应该达到无穷大。但是,这是不现实的,而且正如您所指出的,这在视觉上不是必需的。在大多数场景中,经过一定数量的反弹(例如10次)后,对最终颜色的贡献量非常非常小。跳动的次数。这增加了偏见。

也就是说,很难选择该硬限制应该是什么。反弹2次后,某些场景看起来不错;其他像素(例如,带有传输或SSS的像素)最多可能需要10或20。
如果我们选择的值太低,图像将明显偏斜。但是,如果选择的太高,则会浪费计算能力和时间。

解决此问题的一种方法,如您所指出的,是在达到一定衰减阈值后终止路径。这也增加了偏见。

阈值后夹紧是可行的,但是再次,我们如何选择阈值?如果选择的太大,图像将明显偏斜,太小,并且浪费资源。首先,下面是代码:

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);

    // Bounce the ray around the scene
    for (uint bounces = 0; bounces < 10; ++bounces) {
        m_scene->Intersect(ray);

        // The ray missed. Return the background color
        if (ray.geomID == RTC_INVALID_GEOMETRY_ID) {
            color += throughput * float3(0.846f, 0.933f, 0.949f);
            break;
        }

        // We hit an object

        // 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 emmisive light
        if (light != nullptr) {
            color += throughput * light->Le();
        }

        float3 normal = normalize(ray.Ng);
        float3 wo = normalize(-ray.dir);
        float3 surfacePos = ray.org + ray.dir * ray.tfar;

        // Get the new ray direction
        // Choose the direction based on the material
        float3 wi = material->Sample(wo, normal, sampler);
        float pdf = material->Pdf(wi, normal);

        // Accumulate the brdf attenuation
        throughput = throughput * material->Eval(wi, wo, normal) / pdf;


        // Russian Roulette
        // Randomly terminate a path with a probability inversely equal to the throughput
        float p = std::max(throughput.x, std::max(throughput.y, throughput.z));
        if (sampler->NextFloat() > p) {
            break;
        }

        // Add the energy we 'lose' by randomly terminating paths
        throughput *= 1 / p;


        // Shoot a new ray

        // Set the origin at the intersection point
        ray.org = surfacePos;

        // Reset the other ray properties
        ray.dir = wi;
        ray.tnear = 0.001f;
        ray.tfar = embree::inf;
        ray.geomID = RTC_INVALID_GEOMETRY_ID;
        ray.primID = RTC_INVALID_GEOMETRY_ID;
        ray.instID = RTC_INVALID_GEOMETRY_ID;
        ray.mask = 0xFFFFFFFF;
        ray.time = 0.0f;
    }

    m_scene->Camera.FrameBuffer.SplatPixel(x, y, color);
}


俄罗斯轮盘随机终止路径的概率与吞吐量成反比。因此,吞吐率低且不会对场景产生太大影响的路径更有可能被终止。

如果我们停在那里,我们仍然有偏见。我们“失去”了我们随机终止的路径的能量。为了使其无偏,我们通过终止路径的可能性来增强未终止路径的能量。这加上随机性,使俄罗斯轮盘赌没有偏见。

回答您的最后一个问题:


俄罗斯轮盘能否给出公正的结果?


是的


俄罗斯轮盘是否需要公正的结果?
取决于您的偏见。如果您的数学意思是,那么可以。但是,如果您的意思是视觉上的话,那就没有。您只需要非常仔细地选择最大路径深度和截止阈值即可。这可能非常乏味,因为它可能因场景而异。


您可以使用固定的概率(截止),然后重新分配“丢失”的能量。这是公正的吗?


如果您使用固定的概率,则会添加偏差。通过重新分配“丢失”的能量,可以减少偏差,但在数学上仍然有偏差。要完全无偏,它必须是随机的。也最终终止了),这将如何改善这种情况?它不能完全去除样品。同样,“丢失”的能量在反弹到终止为止。因此,无论如何,最终“失去能量”的唯一方法就是拥有一个完全黑的房间。使用很少量额外计算资源的算法。作为交换,它可以节省大量的计算资源。因此,我看不出不使用它的原因。

评论


$ \ begingroup $
老实说,我不确定要完全公正,它一定是随机的。我认为您仍然可以通过使用样本加权的小数权重而不是俄罗斯轮盘赌强加的二进制通过/丢弃来获得数学确定的结果,只是因为轮盘赌正在运行一个非常重要的抽样,因此轮盘会收敛得更快。
$ \ endgroup $
–v.oddou
17-10-30在2:36

#2 楼

只是为了扩展其他一些答案,证明俄罗斯轮盘赌不会给出有偏差的结果非常简单。

假设您有一些随机变量$ F $,它是多个项的总和:

$$ F = F_1 + \ cdots + F_N $$

将每一项替换为:

$$ F'_i = \ left \ {
\ begin {array} {ll}
\ frac {1} {p_i} F_i&\ hbox {有概率} p_i \\
0&\ hbox {否则}
\ end {array}
\ right。$$

然后:

$$ E [F'_i] = p_i \ times \ frac {1 } {p_i} E [F_i] +(1-p_i)\ times 0 = E [F_i] $$

请注意,选择$ p_i $的概率并不重要。条款的期望值,因此$ F $的期望值是相同的。

#3 楼

俄罗斯轮盘赌技术本身就是一种在不引入系统偏见的情况下终止路径的方法。原理很简单:如果在特定的顶点处有10%的机会将能量任意替换为0,并且您进行了无数次操作,则能量将减少10%。能量提升可以弥补这一点。如果您不能补偿由于路径终止而造成的能量损失,那么俄罗斯轮盘赌会产生偏差,但是整个技术都是避免偏差的有用方法。当“贡献小于某些固定值的终结路径”技术出现偏差时,我将构建一个灯光昏暗的场景,以使贡献路径始终小于该值。

但是,您当然可以始终将固定值作为可调整的参数提供给用户,因此,即使他们的场景碰巧是固定的,他们也可以将其丢弃弱光。因此,让我们先忽略一下该示例。低能耗路径不一定会以您完全忽略的方式来回跳动。类似的推理也适用于,例如,在固定的反弹次数之后切断路径:您可以构建一个场景,该场景的路径可以在击中对象之前从一系列20个镜子反射出来。

假设:如果您将路径的贡献值降低到某个固定epsil以下,然后将其贡献设置为0,您如何校正该能量损失?您并不是简单地将总能量降低一小部分。您对忽略多少能量一无所知,因为您在知道另一个因素(入射能量)之前就已在某个贡献阈值处切断。