명시 적 광 샘플링을 사용한 프로그레시브 경로 추적


14

BRDF 부품에 대한 중요도 샘플링의 논리를 이해했습니다. 그러나 광원을 명시 적으로 샘플링 할 때는 모든 것이 혼란스러워집니다. 예를 들어, 장면에 하나의 포인트 광원이 있고 각 프레임에서 지속적으로 직접 샘플링하는 경우 몬테 카를로 통합을 위해 하나의 샘플로 계산해야합니까? 즉, 하나는 코사인 가중 분포에서 샘플을 가져오고 다른 하나는 포인트 라이트에서 가져옵니다. 총 두 개의 샘플입니까, 아니면 하나의 샘플입니까? 또한 직접 시료에서 나오는 빛을 임의의 항으로 나눠야합니까?

답변:


19

경로 추적에는 중요도를 샘플링 할 수있는 여러 영역이 있습니다. 또한 각 영역은 Veach and Guibas의 1995 년 논문 에서 처음 제안 된 다중 중요도 샘플링을 사용할 수 있습니다 . 더 잘 설명하기 위해 뒤로 경로 추적기를 살펴 보겠습니다.

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);
}

영어로:

  1. 장면을 통해 광선을 쏴
  2. 우리가 무언가를 쳤는지 확인하십시오. 그렇지 않으면 스카이 박스 색상을 반환하고 끊습니다.
  3. 우리가 불을 켰는 지 확인하십시오. 그렇다면 색 축적에 빛을 방출합니다.
  4. 다음 광선의 새 방향을 선택하십시오. BRDF를 기반으로 균일하게 또는 중요도 샘플을 수행 할 수 있습니다.
  5. BRDF를 평가하고 누적하십시오. 여기에서 우리는 Monte Carlo Algorithm을 따르기 위해 선택한 방향의 pdf로 나눕니다.
  6. 선택한 방향과 방금 출발 한 위치를 기준으로 새 광선을 만듭니다.
  7. [선택 사항] 러시아 룰렛을 사용하여 광선을 종료해야하는지 선택합니다
  8. 고토 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);
    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);
}

먼저 "color + = throughput * SampleLights (...)"를 추가합니다. SampleLights ()에 대해 조금 자세히 살펴 보겠습니다. 그러나 본질적으로 모든 조명을 반복하고 BSDF에 의해 감쇠 된 색상에 기여를 반환합니다.

이 방법은 훌륭하지만, 수정하려면 하나 더 변경해야합니다. 구체적으로, 우리가 빛을 비추면 어떻게됩니까? 이전 코드에서는 빛의 방출을 색 축적에 추가했습니다. 그러나 이제 우리는 바운스마다 빛을 직접 샘플링하므로 빛의 방출을 추가하면 "더블 딥"이됩니다. 그러므로 옳은 일은 ... 아무것도 아닙니다. 우리는 빛의 방출을 건너 뜁니다.

그러나 두 가지 경우가 있습니다.

  1. 첫 번째 광선
  2. 완벽한 반사 바운스 (일명 미러)

첫 번째 광선이 빛을 비추면 빛의 방출을 직접 볼 수 있습니다. 따라서 건너 뛰면 주변의 표면이 밝아 지더라도 모든 표시등이 검은 색으로 표시됩니다.

완벽하게 반사되는 표면에 닿으면 입력 광선에 출력이 하나만 있기 때문에 빛을 직접 샘플링 할 수 없습니다. 글쎄, 기술적으로, 우리는 할 수 입력 광선이 빛을 칠 것입니다 있는지 확인하지만 아무 소용이 없다; 주요 경로 추적 루프는 어쨌든 그렇게 할 것입니다. 따라서, 정반사 표면에 닿은 직후 빛을 비추면 색상을 축적해야합니다. 그렇지 않으면 조명이 거울에 검은 색이됩니다.

이제 SampleLights ()에 대해 알아 보겠습니다.

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;
}

영어로:

  1. 모든 조명을 반복
  2. 우리가 그것을 때 빛을 건너 뛰십시오
    • 두 배로 담그지 마십시오
  3. 모든 조명에서 직접 조명을 축적
  4. 직접 조명을 반환

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

지키는 광원의 경우 다음과 같이 간단합니다.

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;
}

그러나 조명에 면적이 생기려면 먼저 조명의 점을 샘플링해야합니다. 따라서 전체 정의는 다음과 같습니다.

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;
}

우리는 원하는 Light-> SampleLi를 구현할 수 있습니다. 점을 균일하게 선택하거나 중요도 샘플을 선택할 수 있습니다. 어느 경우이든, 우리는 라디오 시티를 포인트를 선택하는 pdf로 나눕니다. 다시 한 번 Monte Carlo의 요구 사항을 충족합니다.

BRDF가보기에 크게 의존하는 경우 조명의 임의의 점 대신 BRDF를 기반으로 점을 선택하는 것이 좋습니다. 그러나 우리는 어떻게 선택합니까? 빛 또는 BRDF에 기초한 샘플?

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;
}

영어로:

  1. 먼저, 우리는 빛을 샘플링합니다
    • 이것은 상호 작용을 업데이트합니다.
    • 우리에게 빛을위한 Li를줍니다
    • 그리고 빛에 그 지점을 선택하는 PDF
  2. PDF가 유효하고 광도가 0이 아닌지 확인하십시오.
  3. 샘플링 된 InputDirection을 사용하여 BSDF 평가
  4. 샘플링 된 InputDirection이 주어진 경우 BSDF에 대한 pdf를 계산하십시오.
    • 기본적으로 빛 대신 BSDF를 사용하여 샘플링하려는 경우이 샘플의 가능성
  5. 가벼운 pdf 및 BSDF pdf를 사용하여 무게 계산
    • Veach와 Guibas는 무게를 계산하는 몇 가지 다른 방법을 정의합니다. 실험적으로 그들은 대부분의 경우 가장 잘 작동하는 2의 거듭 제곱으로 휴리스틱을 발견했습니다. 자세한 내용은 논문을 참조하십시오. 구현은 다음과 같습니다
  6. 직접 조명 계산에 가중치를 곱하고 빛 pdf로 나눕니다. (몬테 카를로의 경우) 그리고 직접적인 빛 축적에 추가하십시오.
  7. 그런 다음 BRDF를 샘플링합니다.
    • 이것은 상호 작용을 업데이트합니다.
  8. BRDF 평가
  9. BRDF를 기반으로이 방향을 선택하기위한 pdf를 얻으십시오
  10. 샘플링 된 InputDirection이 주어지면 라이트 PDF를 계산하십시오.
    • 이것은 이전의 거울입니다. 만약 빛을 샘플링한다면이 방향은 얼마나 될까요?
  11. lightPdf == 0.0f이면 광선이 빛을 놓치므로 빛 샘플에서 직접 조명을 반환하십시오.
  12. 그렇지 않으면, 무게를 계산하고 축적에 BSDF 직접 조명을 추가하십시오
  13. 마지막으로 축적 된 직접 조명을 반환

.

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);
}

이 기능들에서 수행 할 수있는 여러 가지 최적화 / 개선 사항이 있지만 이해하기 쉽게하기 위해 그것들을 분석했습니다. 원하는 경우 이러한 개선 사항 중 일부를 공유 할 수 있습니다.

샘플링 하나의 빛만

SampleLights ()에서 모든 라이트를 반복하여 기여합니다. 적은 수의 조명에는 문제가 없지만 수백 또는 수천 개의 조명에 대해서는 비용이 많이 듭니다. 다행히도 Monte Carlo Integration이 평균이라는 사실을 이용할 수 있습니다. 예:

정의하자

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

영어로:

  1. f(x)g(x)
  2. 결과를 나눕니다.12
  3. 평균

N이 커지면 추정값이 올바른 솔루션으로 수렴됩니다.

우리는이 동일한 원리를 광 샘플링에 적용 할 수 있습니다. 모든 라이트를 샘플링하는 대신 무작위로 하나를 선택하고 그 결과에 라이트 수를 곱합니다 (이는 분수 pdf로 나누는 것과 같습니다).

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);
}

이 코드에서 모든 조명은 선택 될 기회가 동일합니다. 그러나 원한다면 중요도 샘플을 볼 수 있습니다. 예를 들어, 큰 조명에 더 높은 피킹 기회를 주거나 히트 표면에 더 가까운 조명을 제공 할 수 있습니다. 더 이상 이 아닌 pdf로 결과를 나눠야합니다.1numLights

"새로운 광선"방향을 샘플링하는 다중 중요성

현재 코드는 BSDF를 기반으로 한 "New Ray"방향 만 샘플링합니다. 조명의 위치에 따라 샘플을 중요하게하려면 어떻게해야합니까?

위에서 배운 것을 바탕으로 한 가지 방법은 각각의 PDF를 기반으로 두 개의 "새로운"광선과 무게 를 촬영하는 것 입니다. 그러나 이것은 계산 비용이 많이 들고 재귀없이 구현하기가 어렵습니다.

이를 극복하기 위해 우리는 하나의 빛만 샘플링하여 배운 것과 동일한 원리를 적용 할 수 있습니다. 즉, 무작위로 샘플링 할 샘플을 선택하고 pdf로 선택하십시오.

// 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;
}

다 말했듯이, 우리 는 빛을 기준으로 "New Ray"방향을 중요하게 샘플링하고 습니까? 들면 직접 조명의 난반사 표면의 BSDF 및 광의 방향 모두에 의해 영향을 받는다. 그러나 간접 조명의 경우 라디오 시티는 거의 전적으로 히트 한 표면의 BSDF에 의해 정의됩니다. 따라서 중요도 샘플링을 추가해도 아무런 효과가 없습니다.

따라서 BSDF로 "새 방향"을 중요하게 샘플링하는 것이 일반적이지만 직접 조명에는 다중 중요도 샘플링을 적용하는 것이 일반적입니다.


명확하게 답변 해 주셔서 감사합니다! 명시적인 광 샘플링없이 경로 추적기를 사용하는 경우 절대 포인트 광원에 도달하지 않습니다. 따라서 기본적으로 기여를 추가 할 수 있습니다. 반면에, 지역 광원을 샘플링하는 경우, 이중 딥을 피하기 위해 간접 조명으로 다시 닿지 않도록해야합니다.
Mustafa Işık

바로 그거죠! 설명이 필요한 부분이 있습니까? 아니면 세부 사항이 충분하지 않습니까?
RichieSams

또한 다중 중요도 샘플링은 직접 조명 계산에만 사용됩니까? 어쩌면 내가 놓쳤지만 다른 예를 보지 못했습니다. 경로 추적 프로그램에서 바운스 당 하나의 광선 만 촬영하면 간접 조명 계산에 광선을 사용할 수없는 것 같습니다.
Mustafa Işık

2
중요도 샘플링을 사용하는 모든 위치에 다중 중요도 샘플링을 적용 할 수 있습니다. 다중 중요도 샘플링의 장점은 다중 샘플링 기술의 이점을 결합 할 수 있다는 것입니다. 예를 들어, 경우에 따라 빛 중요도 샘플링이 BSDF 샘플링보다 낫습니다. 다른 경우에는 그 반대도 마찬가지입니다. MIS는 두 세계의 최고를 결합 할 것입니다. 그러나 BSDF 샘플링이 시간의 100 % 향상 될 경우 MIS의 복잡성을 추가 할 이유가 없습니다. 이 시점에서 확장하기 위해 답변에 일부 섹션을 추가했습니다
RichieSams

1
들어오는 복사 소스를 직접 및 간접의 두 부분으로 분리 한 것 같습니다. 직접 부품에 대해 조명을 명시 적으로 샘플링하고이 부품을 샘플링하는 동안 조명과 BSDF를 샘플링하는 것이 중요합니다. 그러나 간접적 인 부분의 경우, 우리가 해결하고자하는 문제 자체이기 때문에 어느 방향으로 잠재적으로 더 높은 방사능 값을 줄 수 있는지에 대해서는 전혀 알지 못합니다. 그러나 코사인 항과 BSDF에 따라 어느 방향이 더 많은 기여를 할 수 있는지 말할 수 있습니다. 이것이 내가 이해하는 것입니다. 내가 틀렸다면 정정하고 당신의 멋진 답변에 감사드립니다.
Mustafa Işık
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.