Yol izlemede önemle örneklenebilecek çok sayıda alan vardır. Ayrıca, bu alanların her biri ilk olarak Veach ve Guibas'ın 1995 tarihli makalesinde önerilen Çok Önem Örneklemesini de kullanabilir . Daha iyi açıklamak için, geriye doğru bir yol izleyicisine bakalım:
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);
SurfaceInteraction interaction;
// Bounce the ray around the scene
const uint maxBounces = 15;
for (uint bounces = 0; bounces < maxBounces; ++bounces) {
m_scene->Intersect(ray);
// The ray missed. Return the background color
if (ray.GeomID == INVALID_GEOMETRY_ID) {
color += throughput * m_scene->BackgroundColor;
break;
}
// 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 emission
if (light != nullptr) {
color += throughput * light->Le();
}
interaction.Position = ray.Origin + ray.Direction * ray.TFar;
interaction.Normal = normalize(m_scene->InterpolateNormal(ray.GeomID, ray.PrimID, ray.U, ray.V));
interaction.OutputDirection = normalize(-ray.Direction);
// Get the new ray direction
// Choose the direction based on the bsdf
material->bsdf->Sample(interaction, sampler);
float pdf = material->bsdf->Pdf(interaction);
// Accumulate the weight
throughput = throughput * material->bsdf->Eval(interaction) / pdf;
// Shoot a new ray
// Set the origin at the intersection point
ray.Origin = interaction.Position;
// Reset the other ray properties
ray.Direction = interaction.InputDirection;
ray.TNear = 0.001f;
ray.TFar = infinity;
// Russian Roulette
if (bounces > 3) {
float p = std::max(throughput.x, std::max(throughput.y, throughput.z));
if (sampler->NextFloat() > p) {
break;
}
throughput *= 1 / p;
}
}
m_scene->Camera->FrameBufferData.SplatPixel(x, y, color);
}
İngilizcede:
- Sahnede bir ışın çekin
- Bir şeye çarpıp çarpmadığımızı kontrol edin. Değilse, skybox rengini iade edip kırıyoruz.
- Bir ışığa çarpıp çarpmadığımızı kontrol edin. Öyleyse, renk birikimimize ışık emisyonu ekliyoruz
- Sonraki ışın için yeni bir yön seçin. Bunu tek tip veya BRDF'ye dayalı önem örneği yapabiliriz
- BRDF'yi değerlendirin ve biriktirin. Burada Monte Carlo Algoritmasını takip etmek için seçtiğimiz yönün pdf'sine bölünmeliyiz.
- Seçtiğimiz yöne ve nereden geldiğimize bağlı olarak yeni bir ışın oluşturun
- [İsteğe Bağlı] Işını sonlandırıp sonlandırmamayı seçmek için Rus Ruletini kullanın
- Git 1
Bu kodla, yalnızca ışın sonunda bir ışığa çarparsa renk alırız. Buna ek olarak, alanı olmadığı için dakik ışık kaynaklarını desteklemez.
Bunu düzeltmek için, ışıkları her sıçramada doğrudan örnekliyoruz. Birkaç küçük değişiklik yapmalıyız:
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);
SurfaceInteraction interaction;
// Bounce the ray around the scene
const uint maxBounces = 15;
for (uint bounces = 0; bounces < maxBounces; ++bounces) {
m_scene->Intersect(ray);
// The ray missed. Return the background color
if (ray.GeomID == INVALID_GEOMETRY_ID) {
color += throughput * m_scene->BackgroundColor;
break;
}
// 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 this is the first bounce or if we just had a specular bounce,
// we need to add the emmisive light
if ((bounces == 0 || (interaction.SampledLobe & BSDFLobe::Specular) != 0) && light != nullptr) {
color += throughput * light->Le();
}
interaction.Position = ray.Origin + ray.Direction * ray.TFar;
interaction.Normal = normalize(m_scene->InterpolateNormal(ray.GeomID, ray.PrimID, ray.U, ray.V));
interaction.OutputDirection = normalize(-ray.Direction);
// Calculate the direct lighting
color += throughput * SampleLights(sampler, interaction, material->bsdf, light);
// Get the new ray direction
// Choose the direction based on the bsdf
material->bsdf->Sample(interaction, sampler);
float pdf = material->bsdf->Pdf(interaction);
// Accumulate the weight
throughput = throughput * material->bsdf->Eval(interaction) / pdf;
// Shoot a new ray
// Set the origin at the intersection point
ray.Origin = interaction.Position;
// Reset the other ray properties
ray.Direction = interaction.InputDirection;
ray.TNear = 0.001f;
ray.TFar = infinity;
// Russian Roulette
if (bounces > 3) {
float p = std::max(throughput.x, std::max(throughput.y, throughput.z));
if (sampler->NextFloat() > p) {
break;
}
throughput *= 1 / p;
}
}
m_scene->Camera->FrameBufferData.SplatPixel(x, y, color);
}
İlk olarak "color + = throughput * SampleLights (...)" ekliyoruz. SampleLights () hakkında biraz ayrıntıya gireceğim. Ancak, esasen, tüm ışıklardan geçer ve BSDF tarafından zayıflatılan renge olan katkılarını döndürür.
Bu harika, ama düzeltmek için bir değişiklik daha yapmamız gerekiyor; özellikle, bir ışığa çarptığımızda ne olur. Eski kodda, ışığın emisyonunu renk birikimine ekledik. Ama şimdi ışığı her sıçramada doğrudan örnekliyoruz, bu yüzden ışığın emisyonunu eklersek "çift daldırırız". Bu nedenle, yapılacak doğru şey ... hiçbir şey; ışığın emisyonunu biriktirmeyi atlıyoruz.
Ancak, iki köşe vakası vardır:
- İlk ışın
- Mükemmel speküler sekmeler (diğer adıyla aynalar)
İlk ışın ışığa çarparsa, ışığın emisyonunu doğrudan görmelisiniz. Bu yüzden atlarsak, etraflarındaki yüzeyler yanmış olsa bile tüm ışıklar siyah olarak görünür.
Mükemmel speküler yüzeylere çarptığınızda, doğrudan bir ışık örnekleyemezsiniz, çünkü bir giriş ışınının yalnızca bir çıkışı vardır. Aslında teknik biz olabilir giriş ışını ışık çarpacak olup olmadığını kontrol ama anlamı yok; ana Yol İzleme döngüsü bunu zaten yapacak. Bu nedenle, aynasal bir yüzeye çarptıktan hemen sonra bir ışığa çarparsak, rengi biriktirmeliyiz. Yapmazsak, aynalarda ışıklar siyah olacaktır.
Şimdi SampleLights () konusunu inceleyelim:
float3 SampleLights(UniformSampler *sampler, SurfaceInteraction interaction, BSDF *bsdf, Light *hitLight) const {
std::size_t numLights = m_scene->NumLights();
float3 L(0.0f);
for (uint i = 0; i < numLights; ++i) {
Light *light = &m_scene->Lights[i];
// Don't let a light contribute light to itself
if (light == hitLight) {
continue;
}
L = L + EstimateDirect(light, sampler, interaction, bsdf);
}
return L;
}
İngilizcede:
- Tüm ışıkların arasında dolaşın
- Vurursak ışığı atla
- Tüm ışıklardan doğrudan aydınlatmayı biriktirin
- Doğrudan aydınlatmayı iade edin
B SD F( p , ωben, ωÖ) Lben( p , ωben)
Dakik ışık kaynakları için bu basittir:
float3 EstimateDirect(Light *light, UniformSampler *sampler, SurfaceInteraction &interaction, BSDF *bsdf) const {
// Only sample if the BRDF is non-specular
if ((bsdf->SupportedLobes & ~BSDFLobe::Specular) != 0) {
return float3(0.0f);
}
interaction.InputDirection = normalize(light->Origin - interaction.Position);
return bsdf->Eval(interaction) * light->Li;
}
Ancak, ışıkların alana sahip olmasını istiyorsak, ilk önce ışıktaki bir noktayı örneklememiz gerekir. Bu nedenle, tam tanım:
float3 EstimateDirect(Light *light, UniformSampler *sampler, SurfaceInteraction &interaction, BSDF *bsdf) const {
float3 directLighting = float3(0.0f);
// Only sample if the BRDF is non-specular
if ((bsdf->SupportedLobes & ~BSDFLobe::Specular) != 0) {
float pdf;
float3 Li = light->SampleLi(sampler, m_scene, interaction, &pdf);
// Make sure the pdf isn't zero and the radiance isn't black
if (pdf != 0.0f && !all(Li)) {
directLighting += bsdf->Eval(interaction) * Li / pdf;
}
}
return directLighting;
}
İstediğimiz şekilde light-> SampleLi uygulayabiliriz; noktayı eşit olarak veya önem örneğini seçebiliriz. Her iki durumda da, telsizliği noktayı seçme pdf'sine böleriz. Yine, Monte Carlo'nun gereksinimlerini karşılamak için.
BRDF yüksek oranda görünüme bağımlıysa, ışıkta rastgele bir nokta yerine BRDF'ye dayalı bir nokta seçmek daha iyi olabilir. Fakat nasıl seçeriz? Işığa dayalı veya BRDF'ye dayalı örnek?
B SD F( p , ωben, ωÖ) Lben( p , ωben)
float3 EstimateDirect(Light *light, UniformSampler *sampler, SurfaceInteraction &interaction, BSDF *bsdf) const {
float3 directLighting = float3(0.0f);
float3 f;
float lightPdf, scatteringPdf;
// Sample lighting with multiple importance sampling
// Only sample if the BRDF is non-specular
if ((bsdf->SupportedLobes & ~BSDFLobe::Specular) != 0) {
float3 Li = light->SampleLi(sampler, m_scene, interaction, &lightPdf);
// Make sure the pdf isn't zero and the radiance isn't black
if (lightPdf != 0.0f && !all(Li)) {
// Calculate the brdf value
f = bsdf->Eval(interaction);
scatteringPdf = bsdf->Pdf(interaction);
if (scatteringPdf != 0.0f && !all(f)) {
float weight = PowerHeuristic(1, lightPdf, 1, scatteringPdf);
directLighting += f * Li * weight / lightPdf;
}
}
}
// Sample brdf with multiple importance sampling
bsdf->Sample(interaction, sampler);
f = bsdf->Eval(interaction);
scatteringPdf = bsdf->Pdf(interaction);
if (scatteringPdf != 0.0f && !all(f)) {
lightPdf = light->PdfLi(m_scene, interaction);
if (lightPdf == 0.0f) {
// We didn't hit anything, so ignore the brdf sample
return directLighting;
}
float weight = PowerHeuristic(1, scatteringPdf, 1, lightPdf);
float3 Li = light->Le();
directLighting += f * Li * weight / scatteringPdf;
}
return directLighting;
}
İngilizcede:
- İlk olarak, ışığı örnekliyoruz
- Bu etkileşimi günceller.
- Bize ışık için Li verir
- Ve o noktayı ışık üzerinde seçme pdf'i
- PDF'nin geçerli ve parlaklığın sıfır olmadığından emin olun
- BSDF'yi örneklenmiş InputDirection kullanarak değerlendirin
- Örneklenmiş InputDirection verildiğinde BSDF için pdf değerini hesaplayın
- Esasen, ışık yerine BSDF kullanarak örneklememiz gerekirse, bu örnek ne kadar muhtemeldir?
- Hafif pdf ve BSDF pdf kullanarak ağırlığı hesaplayın
- Veach ve Guibas, ağırlığı hesaplamak için birkaç farklı yol tanımlar. Deneysel olarak, çoğu durumda en iyi sonucu vermek için gücü 2 sezgisel olarak buldular. Daha fazla bilgi için sizi makaleye yönlendiriyorum. Uygulama aşağıda
- Ağırlığı doğrudan aydınlatma hesaplamasıyla çarpın ve ışık pdf'e bölün. (Monte Carlo için) Ve doğrudan ışık birikimine ekleyin.
- Sonra BRDF'yi örnekliyoruz
- BRDF'yi değerlendirin
- BRDF'ye göre bu yönü seçmek için pdf dosyasını alın
- Örneklenmiş InputDirection verildiğinde ışık pdf'ini hesaplayın
- Bu eskisinin aynası. Işığı örnekleyecek olsaydık, bu yön ne kadar muhtemeldir?
- LightPdf == 0.0f ise, ışın ışığı kaçırdı, bu yüzden doğrudan aydınlatmayı ışık örneğinden döndürün.
- Aksi takdirde, ağırlığı hesaplayın ve BSDF doğrudan aydınlatmasını birikime ekleyin
- Son olarak, biriken doğrudan aydınlatmayı iade edin
.
inline float PowerHeuristic(uint numf, float fPdf, uint numg, float gPdf) {
float f = numf * fPdf;
float g = numg * gPdf;
return (f * f) / (f * f + g * g);
}
Bu işlevlerde yapabileceğiniz bir dizi optimizasyon / iyileştirme var, ancak daha kolay anlaşılmasını sağlamak için bunları ayrıştırdım. İsterseniz, bu geliştirmelerden bazılarını paylaşabilirim.
Sadece Bir Işık Örnekleme
SampleLights () 'da tüm ışıkların arasında dolaşıp katkılarını alıyoruz. Az sayıda ışık için bu iyi, ancak yüzlerce veya binlerce ışık için bu pahalı hale geliyor. Neyse ki, Monte Carlo Entegrasyonunun dev bir ortalama olduğu gerçeğinden faydalanabiliriz. Misal:
Tanımlayalım
h ( x ) = f( x ) + g( x )
h ( x )
h ( x ) = 1N-Σi = 1N-f( xben) + g( xben)
f( x )g( x )
h ( x ) = 1N-Σi = 1N-r ( ζ, x )p df
ζr(ζ,x)
r(ζ,x)={f(x),g(x),0.0≤ζ<0.50.5≤ζ<1.0
pdf=12
İngilizcede:
- f(x)g(x)
- 12
- Ortalama
N büyüdükçe, tahmin doğru çözüme yaklaşacaktır.
Aynı prensibi ışık örneklemesine de uygulayabiliriz. Her ışığı örneklemek yerine, rastgele bir tane seçeriz ve sonucu ışık sayısı ile çarparız (Bu, kesirli pdf'ye bölmekle aynıdır):
float3 SampleOneLight(UniformSampler *sampler, SurfaceInteraction interaction, BSDF *bsdf, Light *hitLight) const {
std::size_t numLights = m_scene->NumLights();
// Return black if there are no lights
// And don't let a light contribute light to itself
// Aka, if we hit a light
// This is the special case where there is only 1 light
if (numLights == 0 || numLights == 1 && hitLight != nullptr) {
return float3(0.0f);
}
// Don't let a light contribute light to itself
// Choose another one
Light *light;
do {
light = m_scene->RandomOneLight(sampler);
} while (light == hitLight);
return numLights * EstimateDirect(light, sampler, interaction, bsdf);
}
1numLights
"Yeni Işın" Yönünü Örnekleyen Çok Önem
Mevcut kod sadece BSDF'ye dayalı "Yeni Işın" yönünü örnekler. Işıkların konumuna göre numuneye de önem vermek istiyorsak ne olur?
Yukarıda öğrendiklerimizden yola çıkarak, bir yöntem iki "yeni" ışın ve her birinin kendi pdf'lerine göre ağırlık çekmek olacaktır . Bununla birlikte, bu hem hesaplama açısından pahalıdır ve özyineleme olmadan uygulanması zordur.
Bunun üstesinden gelmek için, sadece bir ışık örnekleyerek öğrendiğimiz prensipleri uygulayabiliriz. Yani, rastgele örneklemek için seçin ve seçerek pdf'ye bölün.
// Get the new ray direction
// Randomly (uniform) choose whether to sample based on the BSDF or the Lights
float p = sampler->NextFloat();
Light *light = m_scene->RandomLight();
if (p < 0.5f) {
// Choose the direction based on the bsdf
material->bsdf->Sample(interaction, sampler);
float bsdfPdf = material->bsdf->Pdf(interaction);
float lightPdf = light->PdfLi(m_scene, interaction);
float weight = PowerHeuristic(1, bsdfPdf, 1, lightPdf);
// Accumulate the throughput
throughput = throughput * weight * material->bsdf->Eval(interaction) / bsdfPdf;
} else {
// Choose the direction based on a light
float lightPdf;
light->SampleLi(sampler, m_scene, interaction, &lightPdf);
float bsdfPdf = material->bsdf->Pdf(interaction);
float weight = PowerHeuristic(1, lightPdf, 1, bsdfPdf);
// Accumulate the throughput
throughput = throughput * weight * material->bsdf->Eval(interaction) / lightPdf;
}
Tüm bunlar dedi, gerçekten ışığa dayalı "Yeni ışın" yönünü örneklemek istiyor musunuz? İçin direkt aydınlatma radiosity yüzeyinin BSDF ve ışığın yönü hem de etkilenir. Ancak dolaylı aydınlatma için, radyoloji neredeyse sadece daha önce vurulan yüzeyin BSDF'si tarafından tanımlanır. Bu nedenle, ışık önemine sahip örnekleme eklemek bize hiçbir şey vermez.
Bu nedenle, BSDF ile sadece "Yeni Yön" ün örneklemenin önemini sağlamak yaygındır, fakat doğrudan aydınlatmaya Çok Önemli Örnekleme uygulayın.