Чи справді це російська рулетка?


21

Я бачив, що в деяких реалізаціях Path Tracing застосовується підхід під назвою «Руська рулетка», щоб обміняти деякі шляхи та розподілити їх внесок серед інших шляхів.

Я розумію, що замість того, щоб дотримуватися контуру, поки він не опуститься нижче певного порогового значення внеску, а потім відмовиться від нього, використовується інший поріг, а шляхи, внесок яких нижче цього порогу, припиняються лише з невеликою ймовірністю. Інші шляхи збільшують свій внесок на суму, що відповідає розподілу втраченої енергії з припиненого шляху. Мені не зрозуміло, чи це виправлення зміщення, введене технікою, чи необхідна сама техніка, щоб уникнути упередженості.

  • Чи дає російська рулетка неупереджений результат?
  • Чи потрібна російська рулетка для неупередженого результату?

Тобто, чи використання крихітного порогу та просто припинення шляху в момент, коли він опуститься нижче цього порогу, дасть більш упереджений чи менш упереджений результат?

З огляду на довільно велику кількість зразків, чи будуть обидва підходи сходитись на неупередженому отриманому зображенні?

Я хочу зрозуміти основну причину використання підходу російської рулетки. Чи є значна різниця у швидкості чи якості?


Я розумію, що енергія перерозподіляється серед інших променів, щоб зберегти загальну енергію. Однак, чи не може це перерозподіл все-таки зробити, якби промінь припинився при падінні нижче встановленого порогу, а не після випадкового визначення терміну життя після досягнення цього порогу?

І навпаки, якщо енергія, яку було б втрачено, припинивши промінь, не перерозподіляючи його енергію, з часом все-таки втрачається (оскільки промені, на які вона перерозподіляється, також врешті-решт припиняються), як це покращує ситуацію?

Відповіді:


26

Для того, щоб зрозуміти російську рулетку, давайте подивимося на дуже основний відстежуючий зворотній шлях:

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. 2 відскоку від великого героя Діснея 6 9 відмов від великого героя Діснея 6

Якщо ми виберемо занадто низьке, зображення буде помітно упередженим. Але якщо ми обираємо занадто високу, ми витрачаємо на обчислювальну енергію та час.

Як ви зазначили, один із способів вирішити це - припинити шлях після досягнення певного порогу ослаблення. Це також додає упередженості.

Затискання після порогу спрацює , але знову ж таки, як ми обираємо поріг? Якщо ми виберемо занадто великі, зображення буде помітно упередженим, занадто малим, і ми витрачаємо ресурси.

Російська рулетка намагається вирішити ці проблеми неупереджено. По-перше, ось код:

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

Російська рулетка випадковим чином завершує шлях з ймовірністю, обернено рівною пропускній здатності. Тож шляхи з низькою пропускною здатністю, які не сприятимуть великій кількості сцени, швидше за все припиняються.

Якщо ми зупинимось там, ми все ще упереджені. Ми «втрачаємо» енергію шляху, який ми випадково закінчуємо. Щоб зробити його неупередженим, ми збільшуємо енергію неприпинених контурів, імовірність їх припинення. Це, поряд із випадковістю, робить російську рулетку неупередженою.

Щоб відповісти на ваші останні запитання:

  1. Чи дає російська рулетка неупереджений результат?
    • Так
  2. Чи потрібна російська рулетка для неупередженого результату?
    • Залежить від того, що ви маєте на увазі неупереджено. Якщо ви маєте на увазі математично, то так. Однак якщо ви маєте на увазі візуально, то ні. Вам потрібно дуже обережно вибрати максимальну глибину шляху та поріг обрізання. Це може бути дуже нудним, оскільки може змінюватися від сцени до сцени.
  3. Чи можете ви скористатися фіксованою ймовірністю (скорочення), а потім перерозподілити «втрачену» енергію. Це неупереджено?
    • Якщо ви використовуєте фіксовану ймовірність, ви додаєте упередженість. Перерозподіляючи «втрачену» енергію, ви зменшуєте упередження, але воно все ще є математично упередженим. Щоб бути абсолютно неупередженим, це повинно бути випадковим.
  4. Якщо енергія, яка була б втрачена при припиненні променя, не перерозподіляючи його енергію, врешті-решт втрачається (оскільки промені, на які він перерозподіляється, також врешті-решт припиняються), як це покращує ситуацію?
    • Російська рулетка лише зупиняє підстрибування. Це не видаляє зразок повністю. Крім того, «втрачена» енергія враховується в очікуванні до припинення. Тож єдиним способом енергії, яка в остаточному підсумку все-таки втрачається, було б мати повністю чорну кімнату.

Зрештою, російська рулетка - це дуже простий алгоритм, який використовує дуже малу кількість додаткових обчислювальних ресурсів. В обмін це може заощадити велику кількість обчислювальних ресурсів. Тому я не можу реально побачити причину не використовувати його.


чесно кажучи, я не зовсім впевнений to be completely unbiased it must be random. Я думаю, що ви все одно можете отримати математичні результати, використовуючи дробове зважування зразків, а не двійковий пропуск / краплю, яку нав'язує російська рулетка, це просто те, що рулетка збіжиться швидше, оскільки вона має ідеальну вибірку важливості.
v.oddou

9

Сама техніка російської рулетки - це спосіб припинення шляхів без введення системного зміщення. Принцип досить простий: якщо у певної вершини у вас є 10% шанс довільної заміни енергії на 0, і якщо ви зробите це нескінченну кількість разів, ви побачите на 10% менше енергії. Посилення енергії просто компенсує це. Якщо ви не компенсували втрачену енергію через припинення шляху, то російська рулетка була б упередженою, але вся техніка - корисний метод уникнення упередженості.

Якби я був противником, який прагнув довести, що техніка "припинення шляхів, чий внесок менший, ніж деяка невелика фіксована величина", є упередженою, я б сконструював сцену з вогнями настільки тьмяними, що контури, що вносяться, завжди менші за це значення. Можливо, я імітую камеру із слабким освітленням.

Але, звичайно, ви завжди можете піддавати фіксованому значенню параметр, який можна змінити користувачеві, тому він може скинути його ще більше, якщо їх сцена буде слабкою. Тож давайте на хвилину нехтувати цим прикладом.

Що станеться, якщо я вважаю предмет, освітлений безліччю дуже низьких енергійних шляхів, зібраних параболічним відбивачем ? Шляхи з низькою енергією не обов’язково відбиватися без розбору таким чином, що ви можете повністю нехтувати. Аналогічне міркування стосується, наприклад, обрізання контурів через фіксовану кількість відмов: ви можете сконструювати сцену з контуру, який відскакує від серії з 20 дзеркал, перш ніж потрапити на об’єкт.

Інший спосіб його погляду: якщо ви встановите внесок шляху до 0 після того, як він опуститься нижче деякого фіксованого епсилона, як ви виправите цю втрату енергії? Ви не просто зменшуєте загальну енергію на якусь частку. Ви нічого не знаєте про те, якою кількістю енергії ви нехтуєте, тому що ви скорочуєте деякий поріг внеску, перш ніж дізнаєтесь про інший фактор: енергію, що падає.


8

Просто для розширення деяких інших відповідей, доказ того, що російська рулетка не дає упередженого результату, дуже простий.

Ж

Ж=Ж1++ЖN

Замініть кожен термін на:

Жi'={1piЖiз вірогідністю pi0інакше

Потім:

Е[Жi']=pi×1piЕ[Жi]+(1-pi)×0=Е[Жi]

piЖ

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.