Aby zrozumieć rosyjską ruletkę, spójrzmy na bardzo podstawowy znacznik ścieżki wstecznej:
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);
}
TO ZNACZY. odbijamy się wokół sceny, gromadząc w miarę upływu czasu kolor i tłumienie światła. Aby być całkowicie matematycznie bezstronnym, odbicia powinny przechodzić w nieskończoność. Jest to jednak nierealne i, jak zauważyłeś, nie jest konieczne wizualnie; w większości scen, po pewnej liczbie odrzuceń, powiedzmy 10, wkład w końcowy kolor jest bardzo bardzo minimalny.
Tak więc, aby zaoszczędzić zasoby komputerowe, wiele znaczników ścieżki ma twarde ograniczenie liczby odrzuceń. To dodaje stronniczości.
To powiedziawszy, trudno wybrać, jaki powinien być ten twardy limit. Niektóre sceny wyglądają świetnie po 2 odbiciach; inne (powiedzmy z transmisją lub SSS) mogą zająć do 10 lub 20.
Jeśli wybierzemy zbyt nisko, obraz będzie wyraźnie stronniczy. Ale jeśli wybierzemy zbyt wysoką wartość, marnujemy energię obliczeniową i czas.
Jak zauważyłeś, jednym ze sposobów rozwiązania tego problemu jest zakończenie ścieżki po osiągnięciu pewnego progu tłumienia. To także dodaje stronniczości.
Zaciskanie po progu zadziała , ale znowu, jak wybrać próg? Jeśli wybierzemy zbyt duży, obraz będzie wyraźnie stronniczy, zbyt mały i marnujemy zasoby.
Rosyjska ruletka próbuje rozwiązać te problemy w sposób bezstronny. Po pierwsze, oto kod:
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);
}
Rosyjska ruletka losowo kończy ścieżkę z prawdopodobieństwem odwrotnie równym przepustowości. Dlatego ścieżki o niskiej przepustowości, które nie przyczynią się znacząco do sceny, są bardziej prawdopodobne, że zostaną zakończone.
Jeśli się tam zatrzymamy, nadal będziemy stronniczy. „Tracimy” energię ścieżki, którą losowo kończymy. Aby uczynić to bezstronnym, zwiększamy energię nieterminicznych ścieżek o prawdopodobieństwo ich zakończenia. To, oprócz losowości, sprawia, że rosyjska ruletka jest bezstronna.
Aby odpowiedzieć na ostatnie pytania:
- Czy Russian Roulette daje obiektywny wynik?
- Czy rosyjska ruletka jest konieczna do uzyskania obiektywnego wyniku?
- Zależy od tego, co rozumiesz przez obiektywne. Jeśli masz na myśli matematykę, to tak. Jeśli jednak masz na myśli wizualnie, to nie. Musisz bardzo ostrożnie wybrać maksymalną głębokość ścieżki i próg odcięcia. Może to być bardzo uciążliwe, ponieważ może zmieniać się w zależności od sceny.
- Czy możesz użyć ustalonego prawdopodobieństwa (odcięcia), a następnie rozdzielić „utraconą” energię. Czy to jest bezstronne?
- Jeśli używasz stałego prawdopodobieństwa, dodajesz uprzedzenie. Poprzez redystrybucję „utraconej” energii zmniejszasz stronniczość, ale nadal jest ona matematycznie tendencyjna. Aby być całkowicie bezstronnym, musi być losowy.
- Jeżeli energia, która zostałaby utracona przez zakończenie promienia bez redystrybucji jej energii, jest ostatecznie i tak tracona (ponieważ promienie, na które jest ona redystrybuowana, również są ostatecznie przerywane), jak to poprawia sytuację?
- Rosyjska ruletka tylko zatrzymuje podskakiwanie. Nie usuwa całkowicie próbki. Również „utracona” energia jest uwzględniana w odbiciach aż do zakończenia. Tak więc jedynym sposobem na „utratę energii” będzie całkowicie czarny pokój.
Ostatecznie Rosyjska Ruletka jest bardzo prostym algorytmem, który wykorzystuje bardzo niewielką ilość dodatkowych zasobów obliczeniowych. W zamian może zaoszczędzić dużą ilość zasobów obliczeniowych. Dlatego tak naprawdę nie widzę powodu, aby go nie używać.
to be completely unbiased it must be random
. Myślę, że nadal możesz uzyskać wyniki matematyczne, stosując częściowe ważenie próbek, zamiast binarnego podania / upuszczenia narzuconego przez rosyjską ruletkę, po prostu ruletka zbiegnie się szybciej, ponieważ działa z próbkowaniem o doskonałej ważności.