ロシアンルーレットを理解するために、非常に基本的なバックワードパストレーサーを見てみましょう。
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かかります。
低すぎる値を選択すると、画像に視覚的な偏りが生じます。しかし、あまりに高い値を選択すると、計算のエネルギーと時間が無駄になります。
これを解決する1つの方法は、既に述べたように、減衰のしきい値に達した後にパスを終了することです。これもバイアスを追加します。
しきい値後クランプ、だろう仕事が、再び、どのように我々は、しきい値を選ぶのですか?選択したサイズが大きすぎると、画像に視覚的な偏りが生じ、小さすぎるため、リソースが無駄になります。
ロシアンルーレットは、これらの問題を公平に解決しようとします。まず、コードは次のとおりです。
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);
}
ロシアンルーレットは、スループットと逆の確率でパスをランダムに終了します。そのため、シーンにあまり寄与しないスループットの低いパスは、終了する可能性が高くなります。
そこで停止しても、まだ偏見があります。ランダムに終了するパスのエネルギーを「失います」。偏りをなくすために、終端される確率によって終端されていないパスのエネルギーを高めます。これは、ランダムであることに加えて、ロシアンルーレットを公平なものにします。
最後の質問に答えるには:
- ロシアンルーレットは公平な結果をもたらしますか?
- ロシアンルーレットは公平な結果を得るために必要ですか?
- 偏りがないという意味によって異なります。あなたが数学的に意味するなら、はい。ただし、視覚的に意味する場合、いいえ。最大パス深度とカットオフしきい値を非常に慎重に選択する必要があります。シーンごとに変化する可能性があるため、これは非常に退屈です。
- 固定確率(カットオフ)を使用して、「失われた」エネルギーを再配分できますか。これは公平ですか?
- 固定確率を使用すると、バイアスが追加されます。「失われた」エネルギーを再分配することで、バイアスを減らしますが、それでも数学的にバイアスがかかります。完全に公平になるには、ランダムでなければなりません。
- エネルギーを再分配せずにレイを終了することによって失われるエネルギーが最終的に失われる場合(再分配されるレイも最終的に終了するため)、これは状況をどのように改善しますか?
- ロシアンルーレットはバウンドを停止するだけです。サンプルが完全に削除されるわけではありません。また、「失われた」エネルギーは、終了までの跳ね返りで考慮されます。したがって、エネルギーが「最終的にはとにかく失われる」ための唯一の方法は、完全に黒い部屋を持つことです。
最終的に、ロシアンルーレットは非常に単純なアルゴリズムであり、非常に少量の追加の計算リソースを使用します。代わりに、大量の計算リソースを節約できます。したがって、私はそれを使用しない理由を本当に見ることができません。
to be completely unbiased it must be random
。ロシアンルーレットが課すバイナリパス/ドロップではなく、サンプルのフラクショナルウェイグシングを使用することで、数学的な結果を得ることができると思います。完全に重要なサンプリングを実行しているため、ルーレットはより速く収束するだけです。