Truy tìm đường dẫn lũy tiến với Lấy mẫu ánh sáng rõ ràng


14

Tôi hiểu logic đằng sau việc lấy mẫu quan trọng cho phần BRDF. Tuy nhiên, khi nói đến việc lấy mẫu nguồn sáng một cách rõ ràng, tất cả trở nên khó hiểu. Ví dụ: nếu tôi có một nguồn sáng một điểm trong cảnh của mình và nếu tôi trực tiếp lấy mẫu ở mỗi khung hình, tôi có nên tính nó là một mẫu nữa cho tích hợp monte carlo không? Đó là, tôi lấy một mẫu từ phân phối có trọng số cosin và mẫu khác từ ánh sáng điểm. Đây có phải là hai mẫu trong tổng số hoặc chỉ một? Ngoài ra, tôi có nên chia sự rạng rỡ đến từ mẫu trực tiếp cho bất kỳ thuật ngữ nào không?

Câu trả lời:


19

Có nhiều khu vực trong theo dõi đường dẫn có thể được lấy mẫu quan trọng. Ngoài ra, mỗi khu vực đó cũng có thể sử dụng Lấy mẫu nhiều lần quan trọng, lần đầu tiên được đề xuất trong bài báo năm 1995 của Veach và Guibas . Để giải thích rõ hơn, chúng ta hãy nhìn vào một đường dẫn ngược:

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

Bằng tiếng Anh:

  1. Bắn một tia qua cảnh
  2. Kiểm tra nếu chúng tôi nhấn bất cứ điều gì. Nếu không chúng tôi trả lại màu skybox và phá vỡ.
  3. Kiểm tra nếu chúng ta nhấn một ánh sáng. Nếu vậy, chúng ta thêm sự phát xạ ánh sáng vào sự tích lũy màu của chúng ta
  4. Chọn một hướng mới cho tia tiếp theo. Chúng tôi có thể làm điều này thống nhất, hoặc mẫu quan trọng dựa trên BRDF
  5. Đánh giá BRDF và tích lũy nó. Ở đây chúng ta phải chia theo pdf theo hướng đã chọn, để tuân theo Thuật toán Monte Carlo.
  6. Tạo một tia mới dựa trên hướng đã chọn của chúng tôi và nơi chúng tôi vừa đến
  7. [Tùy chọn] Sử dụng Roulette Nga để chọn xem chúng tôi có nên chấm dứt tia không
  8. Đi 1

Với mã này, chúng ta chỉ nhận được màu nếu tia cuối cùng phát sáng. Ngoài ra, nó không hỗ trợ các nguồn sáng đúng giờ, vì chúng không có khu vực.

Để khắc phục điều này, chúng tôi lấy mẫu đèn trực tiếp tại mỗi lần nảy. Chúng tôi phải thực hiện một vài thay đổi nhỏ:

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

Đầu tiên, chúng tôi thêm "color + = thông lượng * SampleLights (...)". Tôi sẽ đi vào chi tiết về SampleLights () một chút. Nhưng, về cơ bản, nó lặp qua tất cả các đèn và trả lại sự đóng góp của chúng cho màu sắc, bị suy giảm bởi BSDF.

Điều này thật tuyệt, nhưng chúng ta cần thực hiện thêm một thay đổi để làm cho nó chính xác; cụ thể, những gì xảy ra khi chúng ta nhấn một ánh sáng. Trong mã cũ, chúng tôi đã thêm sự phát xạ của ánh sáng vào sự tích lũy màu. Nhưng bây giờ chúng tôi trực tiếp lấy mẫu ánh sáng mỗi lần nảy, vì vậy nếu chúng tôi thêm phát xạ của ánh sáng, chúng tôi sẽ "nhúng đôi". Do đó, điều chính xác cần làm là ... không có gì; chúng ta bỏ qua việc tích lũy phát xạ của ánh sáng.

Tuy nhiên, có hai trường hợp góc:

  1. Tia đầu tiên
  2. Bounce hoàn toàn đặc biệt (còn gọi là gương)

Nếu tia đầu tiên chiếu vào ánh sáng, bạn sẽ thấy sự phát xạ của ánh sáng trực tiếp. Vì vậy, nếu chúng ta bỏ qua nó, tất cả các đèn sẽ hiển thị màu đen, mặc dù các bề mặt xung quanh chúng được thắp sáng.

Khi bạn chạm vào một bề mặt đặc biệt hoàn hảo, bạn không thể lấy mẫu trực tiếp ánh sáng, vì một tia đầu vào chỉ có một đầu ra. Chà, về mặt kỹ thuật, chúng ta có thể kiểm tra xem tia đầu vào có bị sáng hay không, nhưng không có điểm nào; vòng lặp Path Trace chính sẽ làm điều đó. Do đó, nếu chúng ta đánh một ánh sáng ngay sau khi chúng ta chạm vào một bề mặt cụ thể, chúng ta cần tích lũy màu sắc. Nếu chúng ta không, đèn sẽ có màu đen trong gương.

Bây giờ, hãy đi sâu vào 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;
}

Bằng tiếng Anh:

  1. Vòng qua tất cả các đèn
  2. Bỏ qua ánh sáng nếu chúng ta nhấn nó
    • Đừng nhúng đôi
  3. Tích lũy ánh sáng trực tiếp từ tất cả các đèn
  4. Trả lại ánh sáng trực tiếp

BSDF(p,ωTôi,ωo)LTôi(p,ωTôi)

Đối với các nguồn sáng đúng giờ, điều này đơn giản như:

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

Tuy nhiên, nếu chúng ta muốn đèn có diện tích, trước tiên chúng ta cần lấy mẫu một điểm trên ánh sáng. Do đó, định nghĩa đầy đủ là:

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

Chúng tôi có thể thực hiện ánh sáng-> SampleLi theo cách chúng tôi muốn; chúng ta có thể chọn điểm đồng nhất, hoặc mẫu quan trọng. Trong cả hai trường hợp, chúng tôi chia độ phóng xạ cho pdf khi chọn điểm. Một lần nữa, để đáp ứng các yêu cầu của Monte Carlo.

Nếu BRDF phụ thuộc nhiều vào chế độ xem, có thể tốt hơn là chọn một điểm dựa trên BRDF, thay vì một điểm ngẫu nhiên trên ánh sáng. Nhưng làm thế nào để chúng ta chọn? Mẫu dựa trên ánh sáng, hoặc dựa trên BRDF?

BSDF(p,ωTôi,ωo)LTôi(p,ωTô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;
}

Bằng tiếng Anh:

  1. Đầu tiên, chúng tôi lấy mẫu ánh sáng
    • Điều này cập nhật tương tác.InputDirection
    • Cung cấp cho chúng tôi Li cho ánh sáng
    • Và pdf của việc chọn điểm đó trên ánh sáng
  2. Kiểm tra xem pdf có hợp lệ không và độ sáng là khác không
  3. Đánh giá BSDF bằng cách sử dụng InputDirection được lấy mẫu
  4. Tính toán pdf cho BSDF được đưa vào InputDirection được lấy mẫu
    • Về cơ bản, mẫu này có khả năng như thế nào, nếu chúng ta lấy mẫu bằng BSDF, thay vì ánh sáng
  5. Tính trọng lượng, sử dụng pdf ánh sáng và pdf BSDF
    • Veach và Guibas định nghĩa một vài cách khác nhau để tính trọng lượng. Thực nghiệm, họ đã tìm thấy sức mạnh heuristic với sức mạnh là 2 để hoạt động tốt nhất trong hầu hết các trường hợp. Tôi giới thiệu bạn đến bài báo để biết thêm chi tiết. Việc thực hiện dưới đây
  6. Nhân trọng lượng với tính toán ánh sáng trực tiếp và chia cho ánh sáng pdf. (Đối với Monte Carlo) Và thêm vào sự tích lũy ánh sáng trực tiếp.
  7. Sau đó, chúng tôi lấy mẫu BRDF
    • Điều này cập nhật tương tác.InputDirection
  8. Đánh giá BRDF
  9. Nhận pdf để chọn hướng này dựa trên BRDF
  10. Tính pdf ánh sáng, được đưa vào InputDirection được lấy mẫu
    • Đây là tấm gương của trước đây. Hướng này có khả năng như thế nào, nếu chúng ta lấy mẫu ánh sáng
  11. Nếu lightPdf == 0,0f, thì tia bị mất ánh sáng, vì vậy chỉ cần trả lại ánh sáng trực tiếp từ mẫu ánh sáng.
  12. Mặt khác, tính toán trọng lượng và thêm ánh sáng trực tiếp BSDF vào tích lũy
  13. Cuối cùng, trả lại ánh sáng trực tiếp tích lũy

.

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

Có một số tối ưu hóa / cải tiến bạn có thể thực hiện trong các chức năng này, nhưng tôi đã phân tích chúng để cố gắng làm cho chúng dễ hiểu hơn. Nếu bạn muốn, tôi có thể chia sẻ một số cải tiến này.

Chỉ lấy mẫu một ánh sáng

Trong SampleLights () chúng tôi lặp qua tất cả các đèn và nhận được sự đóng góp của chúng. Đối với một số lượng nhỏ đèn, điều này là tốt, nhưng đối với hàng trăm hoặc hàng ngàn đèn, điều này trở nên đắt đỏ. May mắn thay, chúng ta có thể khai thác thực tế rằng Monte Carlo Integration là một mức trung bình khổng lồ. Thí dụ:

Hãy xác định

h(x)= =f(x)+g(x)

h(x)

h(x)= =1NΣTôi= =1Nf(xTôi)+g(xTôi)

f(x)g(x)

h(x)= =1NΣTôi= =1Nr(ζ,x)pdf

ζr(ζ,x)

r(ζ,x)= ={f(x),0,0ζ<0,5g(x),0,5ζ<1

pdf= =12

Bằng tiếng Anh:

  1. f(x)g(x)
  2. 12
  3. Trung bình cộng

Khi N trở nên lớn, ước tính sẽ hội tụ đến giải pháp chính xác.

Chúng ta có thể áp dụng nguyên tắc tương tự này để lấy mẫu ánh sáng. Thay vì lấy mẫu mỗi ánh sáng, chúng tôi chọn ngẫu nhiên một và nhân kết quả với số lượng đèn (Điều này giống như chia cho pdf phân số):

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

1chữ số

Nhiều tầm quan trọng Lấy mẫu theo hướng "Tia mới"

Mã hiện tại chỉ quan trọng lấy mẫu theo hướng "Tia mới" dựa trên BSDF. Điều gì sẽ xảy ra nếu chúng ta muốn lấy mẫu quan trọng dựa trên vị trí của đèn?

Lấy từ những gì chúng ta đã học ở trên, một phương pháp sẽ là bắn hai tia "mới" và trọng lượng mỗi tia dựa trên pdf của chúng. Tuy nhiên, điều này vừa tốn kém về mặt tính toán, vừa khó thực hiện mà không cần đệ quy.

Để khắc phục điều này, chúng ta có thể áp dụng các nguyên tắc tương tự mà chúng ta đã học bằng cách chỉ lấy một ánh sáng. Đó là, chọn ngẫu nhiên một mẫu để lấy mẫu và chia cho pdf chọn 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;
}

Như đã nói, chúng ta có thực sự muốn coi trọng hướng "Tia mới" dựa trên ánh sáng không? Đối với ánh sáng trực tiếp , độ phóng xạ bị ảnh hưởng bởi cả BSDF của bề mặt và hướng của ánh sáng. Nhưng đối với ánh sáng gián tiếp , độ phóng xạ hầu như chỉ được xác định bởi BSDF của bề mặt trước đó. Vì vậy, việc thêm mẫu quan trọng nhẹ không cung cấp cho chúng tôi bất cứ điều gì.

Do đó, thông thường chỉ lấy mẫu quan trọng "Hướng mới" với BSDF, nhưng áp dụng Lấy mẫu nhiều lần quan trọng cho chiếu sáng trực tiếp.


Cảm ơn bạn đã trả lời làm rõ! Tôi hiểu rằng nếu chúng ta sử dụng một bộ dò đường mà không lấy mẫu ánh sáng rõ ràng, chúng ta sẽ không bao giờ chạm vào nguồn sáng điểm. Vì vậy, về cơ bản chúng ta có thể thêm đóng góp của nó. Mặt khác, nếu chúng ta lấy mẫu nguồn sáng khu vực, chúng ta phải đảm bảo rằng chúng ta không nên đánh lại nó với ánh sáng gián tiếp để tránh nhúng đôi
Mustafa Işık

Chính xác! Có phần nào bạn cần làm rõ không? Hoặc không có đủ chi tiết?
RichieSams

Ngoài ra, việc lấy mẫu nhiều tầm quan trọng chỉ được sử dụng để tính toán ánh sáng trực tiếp? Có lẽ tôi đã bỏ lỡ nhưng tôi đã không thấy một ví dụ khác về nó. Nếu tôi chỉ bắn một tia mỗi lần nảy trong đường đi của mình, có vẻ như tôi không thể làm điều đó để tính toán ánh sáng gián tiếp.
Mustafa Işık

2
Lấy mẫu nhiều tầm quan trọng có thể được áp dụng bất cứ nơi nào bạn sử dụng lấy mẫu quan trọng. Sức mạnh của việc lấy mẫu quan trọng là chúng ta có thể kết hợp các lợi ích của nhiều kỹ thuật lấy mẫu. Ví dụ, trong một số trường hợp, lấy mẫu quan trọng nhẹ sẽ tốt hơn lấy mẫu BSDF. Trong các trường hợp khác, ngược lại. MIS sẽ kết hợp tốt nhất của cả hai thế giới. Tuy nhiên, nếu lấy mẫu BSDF sẽ tốt hơn 100% thời gian, không có lý do gì để thêm độ phức tạp của MIS. Tôi đã thêm một số phần vào câu trả lời để mở rộng vào thời điểm này
RichieSams

1
Có vẻ như chúng tôi đã tách các nguồn bức xạ đến thành hai phần là trực tiếp và gián tiếp. Chúng tôi lấy mẫu đèn rõ ràng cho phần trực tiếp và trong khi lấy mẫu phần này, việc lấy mẫu đèn cũng như BSDF là điều hợp lý. Tuy nhiên, đối với phần gián tiếp, chúng tôi không biết hướng nào có khả năng mang lại cho chúng tôi giá trị bức xạ cao hơn vì đó là vấn đề mà chúng tôi muốn giải quyết. Tuy nhiên, chúng ta có thể nói hướng nào có thể đóng góp nhiều hơn theo thuật ngữ cosin và BSDF. Đây là những gì tôi hiểu. Sửa lỗi cho tôi nếu tôi sai và cảm ơn bạn vì câu trả lời tuyệt vời của bạn.
Mustafa Işık
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.