Чтобы понять русскую рулетку, давайте рассмотрим очень простой трассировщик обратного пути:
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);
}
Русская рулетка случайным образом завершает путь с вероятностью, обратно равной пропускной способности. Таким образом, пути с низкой пропускной способностью, которые не будут сильно влиять на сцену, с большей вероятностью будут завершены.
Если мы остановимся там, мы все еще предвзяты. Мы «теряем» энергию пути, который мы случайным образом заканчиваем. Чтобы сделать его беспристрастным, мы повышаем энергию неразорванных путей за счет их вероятности обрыва. Это, наряду со случайностью, делает русскую рулетку беспристрастной.
Чтобы ответить на ваши последние вопросы:
- Русская рулетка дает непредвзятый результат?
- Нужна ли русская рулетка для непредвзятого результата?
- Зависит от того, что вы подразумеваете под объективным. Если вы имеете в виду математически, то да. Однако, если вы имеете в виду визуально, то нет. Вам просто нужно очень тщательно выбрать максимальную глубину пути и порог отсечки. Это может быть очень утомительно, поскольку может меняться от сцены к сцене.
- Можете ли вы использовать фиксированную вероятность (отсечение), а затем перераспределить «потерянную» энергию. Это беспристрастно?
- Если вы используете фиксированную вероятность, вы добавляете смещение. Перераспределяя «потерянную» энергию, вы уменьшаете смещение, но оно все еще математически смещено. Чтобы быть абсолютно беспристрастным, оно должно быть случайным.
- Если энергия, которая будет потеряна при прекращении луча без перераспределения его энергии, в конечном итоге будет потеряна (так как лучи, на которые он перераспределяется, также в конечном итоге прекращаются), как это улучшит ситуацию?
- Русская рулетка только останавливает подпрыгивание. Это не удаляет образец полностью. Кроме того, «потерянная» энергия учитывается в скачках до завершения. Таким образом, единственный способ для энергии, которая в конечном итоге «все равно будет потеряна», - это иметь полностью черную комнату.
В конце концов, Русская рулетка - это очень простой алгоритм, который использует очень небольшое количество дополнительных вычислительных ресурсов. В обмен на это можно сэкономить большое количество вычислительных ресурсов. Поэтому я не вижу причины не использовать его.
to be completely unbiased it must be random
. Я думаю, что вы все еще можете получить математические результаты, используя дробное взвешивание сэмплов, а не двоичную передачу / раздачу, которую навязывает русская рулетка, просто рулетка будет сходиться быстрее, потому что она выполняет выборку идеальной важности.