Açık Işık Örneklemesi ile Aşamalı Yol İzleme


14

BRDF kısmı için önemli örneklemenin arkasındaki mantığı anladım. Bununla birlikte, ışık kaynaklarını açıkça örneklemeye gelince, hepsi kafa karıştırıcı hale gelir. Örneğin, sahnemde bir nokta ışık kaynağım varsa ve bunu sürekli olarak her karede örneklersem, monte carlo entegrasyonu için bir örnek daha saymalıyım? Yani, bir örnek kosinüs ağırlıklı dağılımdan diğerini ise nokta ışığından alıyorum. Toplamda iki örnek mi yoksa sadece bir örnek mi? Ayrıca, doğrudan örnekten gelen parlaklığı herhangi bir terime bölmeli miyim?

Yanıtlar:


19

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:

  1. Sahnede bir ışın çekin
  2. Bir şeye çarpıp çarpmadığımızı kontrol edin. Değilse, skybox rengini iade edip kırıyoruz.
  3. Bir ışığa çarpıp çarpmadığımızı kontrol edin. Öyleyse, renk birikimimize ışık emisyonu ekliyoruz
  4. Sonraki ışın için yeni bir yön seçin. Bunu tek tip veya BRDF'ye dayalı önem örneği yapabiliriz
  5. 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.
  6. Seçtiğimiz yöne ve nereden geldiğimize bağlı olarak yeni bir ışın oluşturun
  7. [İsteğe Bağlı] Işını sonlandırıp sonlandırmamayı seçmek için Rus Ruletini kullanın
  8. 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:

  1. İlk ışın
  2. 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:

  1. Tüm ışıkların arasında dolaşın
  2. Vurursak ışığı atla
    • Çift daldırma
  3. Tüm ışıklardan doğrudan aydınlatmayı biriktirin
  4. Doğrudan aydınlatmayı iade edin

BSDF(p,ωi,ωo)Li(p,ωi)

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?

BSDF(p,ωi,ωo)Li(p,ωi)

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:

  1. İlk olarak, ışığı örnekliyoruz
    • Bu etkileşimi günceller.
    • Bize ışık için Li verir
    • Ve o noktayı ışık üzerinde seçme pdf'i
  2. PDF'nin geçerli ve parlaklığın sıfır olmadığından emin olun
  3. BSDF'yi örneklenmiş InputDirection kullanarak değerlendirin
  4. Örneklenmiş InputDirection verildiğinde BSDF için pdf değerini hesaplayın
    • Esasen, ışık yerine BSDF kullanarak örneklememiz gerekirse, bu örnek ne kadar muhtemeldir?
  5. 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
  6. 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.
  7. Sonra BRDF'yi örnekliyoruz
    • Bu etkileşimi günceller.
  8. BRDF'yi değerlendirin
  9. BRDF'ye göre bu yönü seçmek için pdf dosyasını alın
  10. Örneklenmiş InputDirection verildiğinde ışık pdf'ini hesaplayın
    • Bu eskisinin aynası. Işığı örnekleyecek olsaydık, bu yön ne kadar muhtemeldir?
  11. LightPdf == 0.0f ise, ışın ışığı kaçırdı, bu yüzden doğrudan aydınlatmayı ışık örneğinden döndürün.
  12. Aksi takdirde, ağırlığı hesaplayın ve BSDF doğrudan aydınlatmasını birikime ekleyin
  13. 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)=1Ni=1Nf(xi)+g(xi)

f(x)g(x)

h(x)=1Ni=1Nr(ζ,x)pdf

ζr(ζ,x)

r(ζ,x)={f(x),0.0ζ<0.5g(x),0.5ζ<1.0

pdf=12

İngilizcede:

  1. f(x)g(x)
  2. 12
  3. 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.


Açıklayıcı cevap için teşekkürler! Açık ışık örneklemesi olmadan bir yol izleyici kullanırsak hiçbir zaman bir nokta ışık kaynağına çarpmayacağımızı anlıyorum. Temel olarak katkısını ekleyebiliriz. Öte yandan, bir alan ışık kaynağını örneklersek, çift daldırmadan kaçınmak için dolaylı aydınlatma ile tekrar vurmamamız gerektiğinden emin olmalıyız
Mustafa Işık

Kesinlikle! Açıklamanız gereken herhangi bir kısım var mı? Yoksa yeterli ayrıntı yok mu?
RichieSams

Ayrıca, çok önemli örnekleme sadece doğrudan aydınlatma hesaplaması için mi kullanılıyor? Belki özledim ama başka bir örneği görmedim. Yol izleyicimde sıçrama başına sadece bir ışın çekersem, dolaylı aydınlatma hesaplaması için bunu yapamıyorum.
Mustafa Işık

2
Çok Önem Örneklemesi, önem örneklemesini kullandığınız her yere uygulanabilir. Çok önemli örneklemenin gücü, çoklu örnekleme tekniklerinin faydalarını birleştirebilmemizdir. Örneğin, bazı durumlarda, ışık önemine sahip örnekleme, BSDF örneklemesinden daha iyi olacaktır. Diğer durumlarda, tam tersi. YBS her iki dünyanın en iyilerini birleştirecektir. Bununla birlikte, BSDF örneklemesi zamanın% 100'ünden daha iyi olacaksa, MIS'in karmaşıklığını eklemek için bir neden yoktur. Bu noktaya genişlemek için cevaba bazı bölümler ekledim
RichieSams

1
Görünüşe göre, gelen ışıma kaynaklarını doğrudan ve dolaylı olarak iki kısma ayırdık. Doğrudan parça için ışıkları açıkça örnekliyoruz ve bu kısmı örneklerken BSDF'lerin yanı sıra ışıkları da örneklemek önemlidir. Bununla birlikte, dolaylı kısım için, hangi yönün bize potansiyel olarak daha yüksek parlaklık değerleri verebileceği hakkında hiçbir fikrimiz yoktur, çünkü çözmek istediğimiz sorunun kendisidir. Bununla birlikte, kosinüs terimine ve BSDF'ye göre hangi yönün daha fazla katkıda bulunabileceğini söyleyebiliriz. Anladığım bu. Yanlışsam beni düzeltin ve harika cevabınız için teşekkür ederim.
Mustafa Işık
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.