Russian Roulette è davvero la risposta?


21

Ho visto che in alcune implementazioni di Path Tracing, un approccio chiamato Russian Roulette viene utilizzato per eliminare alcuni dei percorsi e condividere il loro contributo tra gli altri percorsi.

Comprendo che invece di seguire un percorso fino a quando non scende al di sotto di un determinato valore di soglia del contributo, e quindi di abbandonarlo, viene utilizzata una soglia diversa e i percorsi il cui contributo è al di sotto di tale soglia vengono chiusi solo con una piccola probabilità. Gli altri percorsi hanno il loro contributo aumentato di un importo corrispondente alla condivisione dell'energia persa dal percorso terminato. Non mi è chiaro se si tratta di correggere un pregiudizio introdotto dalla tecnica, o se l'intera tecnica è essa stessa necessaria per evitare il pregiudizio.

  • La roulette russa dà un risultato imparziale?
  • La roulette russa è necessaria per un risultato imparziale?

Cioè, usare una soglia minuscola e terminare un percorso nel momento in cui scende al di sotto di tale soglia darebbe un risultato più distorto o meno distorto?

Dato un numero arbitrariamente elevato di campioni, entrambi gli approcci converrebbero su un'immagine risultante imparziale?

Sto cercando di capire il motivo alla base dell'utilizzo dell'approccio alla roulette russa. C'è una differenza significativa nella velocità o nella qualità?


Capisco che l'energia viene ridistribuita tra gli altri raggi al fine di preservare l'energia totale. Tuttavia, questa ridistribuzione non potrebbe ancora essere eseguita se il raggio fosse interrotto al di sotto di una soglia fissa, anziché avere una durata determinata in modo casuale dopo aver raggiunto quella soglia?

Viceversa, se l'energia che si perderebbe terminando un raggio senza ridistribuire la sua energia alla fine viene persa comunque (man mano che i raggi a cui viene ridistribuito vengono infine interrotti), in che modo ciò migliora la situazione?

Risposte:


26

Per capire la roulette russa, diamo un'occhiata a un tracciatore di percorsi all'indietro di base:

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. rimbalziamo intorno alla scena, accumulando colore e attenuazione della luce mentre procediamo. Per essere completamente matematicamente imparziali, i rimbalzi dovrebbero andare all'infinito. Ma questo non è realistico e, come hai notato, non è visivamente necessario; per la maggior parte delle scene, dopo un certo numero di rimbalzi, diciamo 10, la quantità di contributo al colore finale è molto minima.

Quindi, al fine di risparmiare risorse di calcolo, molti tracciatori di percorsi hanno un limite rigido al numero di rimbalzi. Questo aggiunge parzialità.

Detto questo, è difficile scegliere quale dovrebbe essere quel limite. Alcune scene sembrano fantastiche dopo 2 rimbalzi; altri (diciamo con trasmissione o SSS) possono richiedere fino a 10 o 20. 2 rimbalzi da Disney's Big Hero 6 9 rimbalzi da Disney's Big Hero 6

Se scegliamo troppo in basso, l'immagine sarà visibilmente distorta. Ma se scegliamo troppo in alto, stiamo sprecando energia e tempo di calcolo.

Un modo per risolverlo, come hai notato, è terminare il percorso dopo aver raggiunto una certa soglia di attenuazione. Questo aggiunge anche parzialità.

Il serraggio dopo una soglia funzionerà , ma ancora una volta, come possiamo scegliere la soglia? Se scegliamo troppo grande, l'immagine sarà visibilmente distorta, troppo piccola e stiamo sprecando risorse.

La roulette russa tenta di risolvere questi problemi in modo imparziale. Innanzitutto, ecco il codice:

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

La roulette russa termina casualmente un percorso con una probabilità inversamente uguale alla velocità effettiva. Quindi è più probabile che vengano interrotti i percorsi con un throughput basso che non contribuiranno molto alla scena.

Se ci fermiamo lì, siamo ancora di parte. 'Perdiamo' l'energia del percorso che terminiamo casualmente. Per renderlo imparziale, aumentiamo l'energia dei percorsi non terminati dalla loro probabilità di essere terminati. Questo, oltre ad essere casuale, rende la roulette russa imparziale.

Per rispondere alle tue ultime domande:

  1. La roulette russa dà un risultato imparziale?
  2. La roulette russa è necessaria per un risultato imparziale?
    • Dipende da cosa intendi per imparziale. Se intendi matematicamente, allora sì. Tuttavia, se intendi visivamente, allora no. Devi solo scegliere la profondità massima del percorso e la soglia di taglio con molta attenzione. Questo può essere molto noioso poiché può cambiare da una scena all'altra.
  3. Puoi usare una probabilità fissa (cut-off) e quindi ridistribuire l'energia "persa". È imparziale?
    • Se usi una probabilità fissa, stai aggiungendo distorsione. Ridistribuendo l'energia "persa", si riduce il pregiudizio, ma è ancora matematicamente distorto. Per essere completamente imparziale, deve essere casuale.
  4. Se l'energia che andrebbe persa terminando un raggio senza ridistribuire la sua energia alla fine viene persa comunque (man mano che i raggi a cui viene ridistribuita vengono infine interrotti), in che modo ciò migliora la situazione?
    • La roulette russa ferma solo il rimbalzo. Non rimuove completamente il campione. Inoltre, l'energia "persa" è spiegata nei rimbalzi fino alla fine. Quindi l'unico modo in cui l'energia "alla fine potrebbe essere persa" sarebbe quella di avere una stanza completamente nera.

Alla fine, la roulette russa è un algoritmo molto semplice che utilizza una quantità molto piccola di risorse di calcolo extra. In cambio, può risparmiare una grande quantità di risorse computazionali. Pertanto, non riesco davvero a vedere un motivo per non usarlo.


onestamente non ne sono completamente sicuro to be completely unbiased it must be random. Penso che puoi ancora ottenere risultati matematici ok usando la distribuzione frazionaria dei campioni, piuttosto che il passaggio / drop binario imposto dalla roulette russa, è solo che la roulette converge più velocemente perché sta eseguendo un campionamento di importanza perfetta.
v

9

La stessa tecnica della roulette russa è un modo per terminare i percorsi senza introdurre una distorsione sistemica. Il principio è abbastanza semplice: se in un particolare vertice hai una probabilità del 10% di sostituire arbitrariamente l'energia con 0, e se lo fai un numero infinito di volte, vedrai il 10% in meno di energia. L'incremento di energia compensa solo questo. Se non compensassi l'energia persa a causa della fine del percorso, la roulette russa sarebbe distorta, ma l'intera tecnica è un metodo utile per evitare la distorsione.

Se fossi un avversario che cerca di dimostrare che la tecnica "termina i percorsi il cui contributo è inferiore a qualche piccolo valore fisso" è distorta, costruirò una scena con luci così deboli che i percorsi che contribuiscono sono sempre inferiori a quel valore. Forse sto simulando una fotocamera con poca luce.

Ma ovviamente potresti sempre esporre all'utente il valore fisso come parametro modificabile, in modo che possano farlo cadere ulteriormente se la loro scena risulta scarsamente illuminata. Quindi ignoriamo quell'esempio per un minuto.

Cosa succede se considero un oggetto illuminato da molti percorsi a bassissima energia raccolti da un riflettore parabolico ? I percorsi a bassa energia non rimbalzano necessariamente indiscriminatamente in un modo che puoi trascurare completamente. Allo stesso modo si applica il ragionamento, ad esempio, per tagliare percorsi dopo un numero fisso di rimbalzi: puoi costruire una scena con un percorso che rimbalza su una serie di 20 specchi prima di colpire un oggetto.

Un altro modo di vederlo: se imposti il ​​contributo di un percorso su 0 dopo che è sceso sotto un epsilon fisso, come correggi quella perdita di energia? Non stai semplicemente riducendo l'energia totale di una frazione. Non sai nulla di quanta energia stai trascurando, perché stai tagliando a una certa soglia di contributo prima di conoscere l'altro fattore: l'energia incidente.


8

Solo per espandere alcune delle altre risposte, la prova che la roulette russa non dà un risultato biassoso è molto semplice.

F

F=F1++FN

Sostituisci ogni termine con:

Fio'={1pioFiocon probabilità pio0altrimenti

Poi:

E[Fio']=pio×1pioE[Fio]+(1-pio)×0=E[Fio]

Nota che non importa per quali probabilità sceglipio. Il valore atteso dei termini, e quindi il valore atteso diF, è la stessa.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.