Um das russische Roulette zu verstehen, schauen wir uns einen sehr einfachen Rückwärtspfad-Tracer an:
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. Wir hüpfen durch die Szene und akkumulieren dabei Farbe und Lichtschwächung. Um vollständig mathematisch unbefangen zu sein, sollten Bounces ins Unendliche gehen. Aber das ist unrealistisch und, wie Sie bemerkt haben, visuell nicht notwendig. Bei den meisten Szenen ist der Beitrag zur endgültigen Farbe nach einer bestimmten Anzahl von Sprüngen, z. B. 10, sehr, sehr gering.
Um Rechenressourcen zu sparen, ist die Anzahl der Bounces bei vielen Pfadverfolgern stark begrenzt. Dies fügt Voreingenommenheit hinzu.
Das heißt, es ist schwer zu entscheiden, was dieses harte Limit sein soll. Einige Szenen sehen nach zwei Bounces großartig aus. andere (etwa mit Übertragung oder SSS) können bis zu 10 oder 20 dauern.
Wenn wir zu niedrig wählen, wird das Bild sichtbar verzerrt. Wenn wir jedoch zu hoch wählen, verschwenden wir Rechenleistung und Zeit.
Eine Möglichkeit, dies zu lösen, besteht darin, den Pfad zu beenden, nachdem wir eine Dämpfungsschwelle erreicht haben. Dies fügt auch Voreingenommenheit hinzu.
Das Klemmen nach einer Schwelle wird funktionieren , aber wie wählen wir die Schwelle aus? Wenn wir zu groß wählen, wird das Bild sichtbar verzerrt, zu klein und es werden Ressourcen verschwendet.
Russian Roulette versucht, diese Probleme unvoreingenommen zu lösen. Hier ist zunächst der Code:
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);
}
Russisches Roulette beendet einen Pfad zufällig mit einer Wahrscheinlichkeit, die umgekehrt dem Durchsatz entspricht. Daher ist es wahrscheinlicher, dass Pfade mit geringem Durchsatz, die nicht viel zur Szene beitragen, beendet werden.
Wenn wir dort aufhören, sind wir immer noch voreingenommen. Wir verlieren die Energie des Weges, den wir zufällig beenden. Um es unparteiisch zu machen, erhöhen wir die Energie der nicht abgeschlossenen Pfade um ihre Abschlusswahrscheinlichkeit. Dies, zusammen mit dem Zufall, macht russisches Roulette unvoreingenommen.
So beantworten Sie Ihre letzten Fragen:
- Gibt das russische Roulette ein unbefangenes Ergebnis?
- Ist russisches Roulette für ein unvoreingenommenes Ergebnis notwendig?
- Kommt darauf an, was du mit unvoreingenommen meinst. Wenn Sie mathematisch meinen, dann ja. Wenn Sie jedoch visuell meinen, dann nein. Sie müssen nur die maximale Pfadtiefe und die Grenzschwelle sehr sorgfältig auswählen. Dies kann sehr mühsam sein, da es sich von Szene zu Szene ändern kann.
- Können Sie eine feste Wahrscheinlichkeit (Cut-Off) verwenden und dann die "verlorene" Energie neu verteilen? Ist das unvoreingenommen?
- Wenn Sie eine feste Wahrscheinlichkeit verwenden, fügen Sie eine Verzerrung hinzu. Indem Sie die "verlorene" Energie umverteilen, reduzieren Sie die Verzerrung, aber sie ist immer noch mathematisch verzerrt. Um völlig unvoreingenommen zu sein, muss es zufällig sein.
- Wenn die Energie, die durch das Beenden eines Strahls verloren gehen würde, ohne seine Energie neu zu verteilen, irgendwann ohnehin verloren geht (da die Strahlen, auf die es neu verteilt wird, auch irgendwann beendet werden), wie verbessert dies die Situation?
- Russisches Roulette stoppt nur das Hüpfen. Die Probe wird nicht vollständig entfernt. Außerdem wird die "verlorene" Energie in den Bounces bis zur Beendigung berücksichtigt. Die einzige Möglichkeit, dass die Energie „irgendwann sowieso verloren geht“, wäre ein komplett schwarzer Raum.
Letztendlich ist Russisches Roulette ein sehr einfacher Algorithmus, der nur sehr wenig zusätzliche Rechenressourcen benötigt. Im Gegenzug kann eine große Menge an Rechenressourcen eingespart werden. Daher sehe ich keinen Grund, es nicht zu benutzen.
to be completely unbiased it must be random
. Ich denke, Sie können immer noch mathematisch einwandfreie Ergebnisse erzielen, wenn Sie eine Teilgewichtung der Stichproben verwenden, anstatt den binären Durchgang / Abfall, den das russische Roulette auferlegt. Es ist nur so, dass das Roulette schneller konvergiert, weil es eine Stichprobe von perfekter Wichtigkeit ausführt.