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


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)=1N∑i=1Nf(xi)+g(xi)

f(x)g(x)

h(x)=1N∑i=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.