러시아어 룰렛을 이해하기 위해 매우 기본적인 역방향 경로 추적기를 살펴 보겠습니다.
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을 차지할 수 있습니다.
너무 낮게 선택하면 이미지가 눈에 띄게 편향됩니다. 그러나 너무 높게 선택하면 계산 에너지와 시간이 낭비됩니다.
이 문제를 해결하는 한 가지 방법은 감쇠 임계 값에 도달 한 후 경로를 종료하는 것입니다. 이것은 또한 편견을 추가합니다.
임계 값 이후 클램핑은 작동 하지만 다시 임계 값을 어떻게 선택합니까? 너무 크게 선택하면 이미지가 눈에 띄게 바이어스되고 너무 작아 져 리소스를 낭비하게됩니다.
Russian Roulette는 이러한 문제를 편견없이 해결하려고 시도합니다. 먼저 코드는 다음과 같습니다.
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);
}
러시아어 룰렛은 처리량과 반비례 할 확률로 경로를 임의로 종료합니다. 따라서 장면에 많은 영향을 미치지 않는 처리량이 적은 경로는 종료 될 가능성이 높습니다.
우리가 거기서 멈춰도 여전히 편견입니다. 우리는 우리가 무작위로 끝내는 길의 에너지를 잃어 버립니다. 이를 편향시키지 않기 위해 종료되지 않은 경로의 에너지를 종료 확률로 높입니다. 이것은 무작위로, 러시아 룰렛을 편견으로 만듭니다.
마지막 질문에 대답하려면 :
- 러시아 룰렛은 편견없는 결과를 제공합니까?
- 편견없는 결과에 러시아 룰렛이 필요합니까?
- 편견없는 의미에 따라 다릅니다. 수학적으로 의미한다면, 그렇습니다. 그러나 시각적으로 의미한다면, 아닙니다. 최대 경로 깊이와 컷오프 임계 값을 매우 신중하게 선택하면됩니다. 장면마다 바뀔 수 있기 때문에 매우 지루할 수 있습니다.
- 고정 확률 (컷오프)을 사용한 다음 '손실 된'에너지를 재분배 할 수 있습니까? 이 편견이 있습니까?
- 고정 확률을 사용하면 편향이 추가됩니다. '잃어버린'에너지를 재분배함으로써 바이어스를 줄일 수 있지만 여전히 수학적으로 바이어스됩니다. 완전히 편향되지 않으려면 무작위 여야합니다.
- 재분배하지 않고 광선을 종료하여 손실되는 에너지가 어쨌든 결국 (분배되는 광선도 결국 종료 됨) 손실되면 어떻게 상황이 개선됩니까?
- 러시아어 룰렛은 수신 거부 만 중지합니다. 샘플을 완전히 제거하지는 않습니다. 또한 '잃어버린'에너지는 종료까지의 반송에서 설명됩니다. 따라서 에너지가 '결국 어쨌든 손실'되는 유일한 방법은 완전히 검은 방을 갖는 것입니다.
결국 Russian Roulette는 매우 적은 양의 추가 계산 리소스를 사용하는 매우 간단한 알고리즘입니다. 그 대가로 많은 양의 계산 리소스를 절약 할 수 있습니다. 따라서 실제로 사용 하지 않는 이유를 알 수 없습니다 .
to be completely unbiased it must be random
. 러시아 룰렛이 부과하는 이진 패스 / 드롭이 아니라 샘플의 분수 적 조각을 사용하여 여전히 수학 결과를 얻을 수 있다고 생각합니다. 룰렛은 완벽한 중요성 샘플링을 운영하기 때문에 룰렛이 더 빨리 수렴한다는 것입니다.