Para entender la ruleta rusa, echemos un vistazo a un trazador de ruta hacia atrás muy básico:
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);
}
ES DECIR. saltamos alrededor de la escena, acumulando color y atenuación de luz a medida que avanzamos. Para ser completamente imparcial matemáticamente, los rebotes deben ir al infinito. Pero esto no es realista y, como notó, no es visualmente necesario; Para la mayoría de las escenas, después de un cierto número de rebotes, digamos 10, la cantidad de contribución al color final es muy, muy mínima.
Entonces, para ahorrar recursos informáticos, muchos trazadores de ruta tienen un límite estricto para la cantidad de rebotes. Esto agrega sesgo.
Dicho esto, es difícil elegir cuál debería ser ese límite difícil. Algunas escenas se ven geniales después de 2 rebotes; otros (digamos con transmisión o SSS) pueden tomar hasta 10 o 20.
Si elegimos demasiado bajo, la imagen estará visiblemente sesgada. Pero si elegimos demasiado alto, estamos desperdiciando energía y tiempo de cálculo.
Como notó, una forma de resolver esto es terminar la ruta después de que alcancemos un umbral de atenuación. Esto también agrega sesgo.
Sujetar después de un umbral funcionará , pero de nuevo, ¿cómo elegimos el umbral? Si elegimos demasiado grande, la imagen estará visiblemente sesgada, demasiado pequeña, y estamos desperdiciando recursos.
La ruleta rusa intenta resolver estos problemas de manera imparcial. Primero, aquí está el código:
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 ruleta rusa termina aleatoriamente un camino con una probabilidad inversamente igual al rendimiento. Por lo tanto, es más probable que se terminen las rutas con bajo rendimiento que no contribuirán mucho a la escena.
Si nos detenemos allí, todavía estamos sesgados. 'Perdemos' la energía del camino que terminamos al azar. Para hacerlo imparcial, aumentamos la energía de los caminos no terminados por su probabilidad de terminar. Esto, junto con ser aleatorio, hace que la ruleta rusa sea imparcial.
Para responder a sus últimas preguntas:
- ¿La ruleta rusa da un resultado imparcial?
- ¿Es necesaria la ruleta rusa para obtener un resultado imparcial?
- Depende de lo que quieras decir con imparcial. Si quieres decir matemáticamente, entonces sí. Sin embargo, si te refieres a lo visual, entonces no. Solo tiene que elegir la profundidad máxima de la ruta y el umbral de corte con mucho cuidado. Esto puede ser muy tedioso ya que puede cambiar de una escena a otra.
- ¿Puede usar una probabilidad fija (corte) y luego redistribuir la energía 'perdida'? ¿Es esto imparcial?
- Si usa una probabilidad fija, está agregando sesgo. Al redistribuir la energía 'perdida', se reduce el sesgo, pero todavía está sesgado matemáticamente. Para ser completamente imparcial, debe ser aleatorio.
- Si la energía que se perdería al terminar un rayo sin redistribuir su energía finalmente se pierde de todos modos (ya que los rayos a los que se redistribuye también terminan finalmente), ¿cómo mejora esto la situación?
- La ruleta rusa solo detiene el rebote. No elimina la muestra por completo. Además, la energía "perdida" se tiene en cuenta en los rebotes hasta la terminación. Entonces, la única forma de que la energía se 'pierda eventualmente de todos modos' sería tener una habitación completamente negra.
Al final, la ruleta rusa es un algoritmo muy simple que utiliza una cantidad muy pequeña de recursos computacionales adicionales. A cambio, puede ahorrar una gran cantidad de recursos computacionales. Por lo tanto, realmente no puedo ver una razón para no usarlo.
to be completely unbiased it must be random
. Creo que aún puede obtener resultados matemáticos aceptables utilizando el peso fraccional de las muestras, en lugar del pase / caída binario que impone la ruleta rusa, es solo que la ruleta convergerá más rápido porque está operando un muestreo de importancia perfecta.La técnica de la ruleta rusa en sí misma es una forma de terminar caminos sin introducir sesgos sistémicos. El principio es bastante sencillo: si en un vértice particular tiene un 10% de posibilidades de reemplazar arbitrariamente la energía con 0, y si lo hace un número infinito de veces, verá un 10% menos de energía. El aumento de energía solo compensa eso. Si no compensó la energía perdida debido a la terminación del camino, la ruleta rusa estaría sesgada, pero toda la técnica es un método útil para evitar el sesgo.
Si fuera un adversario que buscara demostrar que la técnica de "terminar caminos cuya contribución es menor que un valor fijo pequeño" está sesgada, construiría una escena con luces tan tenues que los caminos contribuyentes siempre son menores que ese valor. Quizás estoy simulando una cámara con poca luz.
Pero, por supuesto, siempre puede exponer el valor fijo como un parámetro modificable para el usuario, por lo que puede soltarlo aún más si su escena es de poca luz. Así que ignoremos ese ejemplo por un minuto.
¿Qué sucede si considero un objeto que está iluminado por muchos caminos de muy baja energía que son recogidos por un reflector parabólico ? Los caminos de baja energía no necesariamente rebotan indiscriminadamente de una manera que puedes descuidar por completo. Del mismo modo, el razonamiento se aplica, por ejemplo, a la eliminación de rutas después de un número fijo de rebotes: puede construir una escena con una ruta que rebota en una serie de 20 espejos antes de golpear un objeto.
Otra forma de verlo: si establece la contribución de una ruta a 0 después de que cae por debajo de un épsilon fijo, ¿cómo corrige esa pérdida de energía? No estás simplemente reduciendo la energía total en alguna fracción. No sabe nada acerca de cuánta energía está descuidando, porque está cortando en algún umbral de contribución antes de conocer el otro factor: la energía incidente.
fuente
Solo para ampliar algunas de las otras respuestas, la prueba de que la ruleta rusa no da un resultado sesgado es muy simple.
Reemplace cada término con:
Luego:
fuente