Para entender a roleta russa, vejamos um rastreador de caminho muito básico para trás:
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. pulamos pela cena, acumulando cores e atenuação de luz à medida que avançamos. Para ser completamente matematicamente imparcial, os saltos devem ir para o infinito. Mas isso é irreal e, como você observou, não é visualmente necessário; para a maioria das cenas, após um certo número de rejeições, digamos 10, a quantidade de contribuição para a cor final é muito, muito mínima.
Portanto, para economizar recursos de computação, muitos rastreadores de caminho têm um limite rígido para o número de rejeições. Isso adiciona viés.
Dito isto, é difícil escolher qual deve ser esse limite. Algumas cenas ficam ótimas após 2 saltos; outros (digamos, com transmissão ou SSS) podem levar até 10 ou 20.
Se escolhermos muito baixo, a imagem será visivelmente distorcida. Mas se escolhermos muito alto, estamos desperdiçando energia e tempo de computação.
Uma maneira de resolver isso, como você observou, é finalizar o caminho depois de atingirmos algum limiar de atenuação. Isso também adiciona viés.
A fixação de um limite funcionará , mas novamente, como escolhemos o limite? Se escolhermos muito grande, a imagem será visivelmente tendenciosa, muito pequena e estamos desperdiçando recursos.
A Roleta Russa tenta resolver esses problemas de maneira imparcial. Primeiro, aqui está o 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);
}
A Roleta Russa termina aleatoriamente um caminho com uma probabilidade inversamente igual à taxa de transferência. Portanto, caminhos com baixo rendimento que não contribuem muito para a cena têm mais probabilidade de serem finalizados.
Se pararmos por aí, ainda somos tendenciosos. Nós "perdemos" a energia do caminho que terminamos aleatoriamente. Para torná-lo imparcial, aumentamos a energia dos caminhos não terminados pela probabilidade de serem terminados. Isso, além de aleatório, torna a Roleta Russa imparcial.
Para responder às suas últimas perguntas:
- A Roleta Russa oferece um resultado imparcial?
- A Roleta Russa é necessária para obter um resultado imparcial?
- Depende do que você quer dizer com imparcial. Se você quer dizer matematicamente, então sim. No entanto, se você quer dizer visualmente, então não. Você só precisa escolher a profundidade máxima do caminho e o limite de corte com muito cuidado. Isso pode ser muito entediante, pois pode mudar de cena para cena.
- Você pode usar uma probabilidade fixa (corte) e depois redistribuir a energia 'perdida'. Isso é imparcial?
- Se você usa uma probabilidade fixa, está adicionando viés. Ao redistribuir a energia 'perdida', você reduz o viés, mas ainda é matematicamente tendencioso. Para ser completamente imparcial, deve ser aleatório.
- Se a energia que seria perdida ao terminar um raio sem redistribuí-lo é eventualmente perdida (como os raios aos quais é redistribuído também acabam), como isso melhora a situação?
- A Roleta Russa só para de saltar. Não remove a amostra completamente. Além disso, a energia 'perdida' é contabilizada nos saltos até o término. Portanto, a única maneira de a energia ser "eventualmente perdida de qualquer maneira" seria ter um quarto completamente preto.
No final, a Roleta Russa é um algoritmo muito simples que utiliza uma quantidade muito pequena de recursos computacionais extras. Em troca, ele pode economizar uma grande quantidade de recursos computacionais. Portanto, não vejo realmente um motivo para não usá-lo.
to be completely unbiased it must be random
. Acho que você ainda pode obter resultados matemáticos, usando pesos fracionados de amostras, em vez da passagem / queda binária que a roleta russa impõe, é só que a roleta convergirá mais rapidamente porque está operando uma amostragem de importância perfeita.A técnica da roleta russa em si é uma maneira de terminar caminhos sem introduzir viés sistêmico. O princípio é bastante direto: se em um vértice específico você tiver 10% de chance de substituir arbitrariamente a energia por 0, e se fizer isso um número infinito de vezes, verá 10% menos energia. O aumento de energia compensa isso. Se você não compensasse a energia perdida devido ao término do caminho, a roleta russa seria distorcida, mas toda a técnica é um método útil para evitar distorções.
Se eu fosse um adversário procurando provar que a técnica "encerra caminhos cuja contribuição é menor que um pequeno valor fixo" é tendenciosa, eu construí uma cena com luzes tão escuras que os caminhos contribuintes são sempre inferiores a esse valor. Talvez eu esteja simulando uma câmera com pouca luz.
Mas é claro que você sempre pode expor o valor fixo como um parâmetro ajustável para o usuário, para que ele possa cair ainda mais se a cena estiver com pouca luz. Então, vamos desconsiderar esse exemplo por um minuto.
O que acontece se eu considerar um objeto iluminado por muitos caminhos de energia muito baixa coletados por um refletor parabólico ? Os caminhos de baixa energia não necessariamente oscilam indiscriminadamente de uma maneira que você pode negligenciar completamente. Da mesma forma, o raciocínio se aplica a, por exemplo, cortar caminhos após um número fixo de saltos: você pode construir uma cena com um caminho que salta uma série de 20 espelhos antes de atingir um objeto.
Outra maneira de ver: se você define a contribuição de um caminho para 0 depois que ele cai abaixo de algum epsilon fixo, como você corrige essa perda de energia? Você não está simplesmente reduzindo a energia total em alguma fração. Você não sabe nada sobre quanta energia está negligenciando, porque está cortando em algum limiar de contribuição antes de conhecer o outro fator: a energia incidente.
fonte
Apenas para expandir algumas das outras respostas, a prova de que a Roleta Russa não fornece um resultado tendencioso é muito simples.
Suponha que você tenha alguma variável aleatória que é a soma de vários termos:F
Substitua cada termo por:
Então:
Observe que não importa quais probabilidades você escolhe para . O valor esperado dos termos e, portanto, o valor esperado de , é o mesmo.pi F
fonte