Lấy mẫu quan trọng là gì?


33

Lấy mẫu quan trọng là gì? Mỗi bài viết tôi đọc về nó đều đề cập đến 'PDF' đó là gì?

Từ những gì tôi thu thập được, lấy mẫu quan trọng là một kỹ thuật chỉ lấy mẫu các khu vực trên một bán cầu quan trọng hơn các khu vực khác. Vì vậy, lý tưởng nhất, tôi nên lấy mẫu tia tới các nguồn sáng để giảm nhiễu và tăng tốc độ. Ngoài ra, một số BRDF ở góc chăn thả có chút khác biệt trong tính toán, vì vậy sử dụng lấy mẫu quan trọng để tránh điều đó có tốt không?

Nếu tôi thực hiện lấy mẫu quan trọng cho BRDF Cook-Torrance, làm thế nào tôi có thể làm điều này?


Đây là liên kết một bài đọc tốt giải thích PDF là gì. TL; DR a PDF là một hàm mô tả xác suất của các số ngẫu nhiên (liên tục hay còn gọi là dấu phẩy động). Tạo số ngẫu nhiên từ một tệp PDF cụ thể có thể khó khăn và có một vài kỹ thuật để làm như vậy. Điều này nói về một trong số họ. Bài báo sau bài này nói về một cách khác. blog.demofox.org/2017/08/05/ Từ
Alan Wolfe

Câu trả lời:


51

Câu trả lời ngắn:

Lấy mẫu quan trọng là một phương pháp để giảm phương sai trong Tích hợp Monte Carlo bằng cách chọn một công cụ ước tính gần với hình dạng của hàm thực tế.

PDF là tên viết tắt của Hàm xác suất mật độ . Một pdf(x) đưa ra xác suất của một mẫu ngẫu nhiên được tạo là x .

Câu trả lời dài:

Để bắt đầu, hãy xem lại Tích hợp Monte Carlo là gì và nó trông như thế nào về mặt toán học.

Tích hợp Monte Carlo là một kỹ thuật để ước tính giá trị của tích phân. Nó thường được sử dụng khi không có giải pháp dạng đóng cho tích phân. Nó trông như thế này:

f(x)dx1NΣtôi= =1Nf(xtôi)pdf(xtôi)

Trong tiếng Anh, điều này nói rằng bạn có thể tính gần đúng một tích phân bằng cách lấy trung bình các mẫu ngẫu nhiên liên tiếp từ hàm. Khi N trở nên lớn, phép tính gần đúng càng ngày càng gần với giải pháp. pdf(xtôi) đại diện cho hàm mật độ xác suất của từng mẫu ngẫu nhiên.

Chúng ta hãy làm một ví dụ: Tính giá trị của tích phân tôi .

tôi= =02πe-xtội(x)dx

Hãy sử dụng tích hợp Monte Carlo:

tôi1NΣtôi= =1Ne-xtội(xtôi)pdf(xtôi)

Một chương trình python đơn giản để tính toán điều này là:

import random
import math

N = 200000
TwoPi = 2.0 * math.pi

sum = 0.0

for i in range(N):
    x = random.uniform(0, TwoPi)

    fx = math.exp(-x) * math.sin(x)
    pdf = 1 / (TwoPi - 0.0)

    sum += fx / pdf

I = (1 / N) * sum
print(I)

Nếu chúng tôi chạy chương trình, chúng tôi nhận được tôi= =0,4986941

Sử dụng phân tách theo các phần, chúng ta có thể có được giải pháp chính xác:

tôi= =12(1-e-2π)= =0,4990663

Bạn sẽ nhận thấy rằng Giải pháp Monte Carlo không hoàn toàn chính xác. Điều này là bởi vì nó là một ước tính. Điều đó nói rằng, khi N đi đến vô cùng, ước tính sẽ ngày càng gần hơn với câu trả lời đúng. Đã ở N= =2000 một số lần chạy gần như giống hệt với câu trả lời đúng.

Lưu ý về PDF: Trong ví dụ đơn giản này, chúng tôi luôn lấy một mẫu ngẫu nhiên thống nhất. Một mẫu ngẫu nhiên thống nhất có nghĩa là mọi mẫu đều có cùng xác suất được chọn. Chúng tôi lấy mẫu trong phạm vi [0,2π] vì vậy, pdf(x)= =1/(2π-0)

Lấy mẫu quan trọng hoạt động bằng cách không lấy mẫu thống nhất. Thay vào đó, chúng tôi cố gắng chọn nhiều mẫu đóng góp nhiều vào kết quả (quan trọng) và ít mẫu hơn chỉ đóng góp một ít vào kết quả (ít quan trọng hơn). Do đó tên, lấy mẫu quan trọng.

ffSo sánh lấy mẫu tốt và lấy mẫu xấu

Một ví dụ về lấy mẫu quan trọng trong Truy tìm đường dẫn là cách chọn hướng của tia sau khi chiếu vào bề mặt. Nếu bề mặt không hoàn toàn đặc biệt (ví dụ như gương hoặc kính), thì tia sáng đi ra có thể ở bất cứ đâu trong bán cầu.

Tia đi có thể đi bất cứ nơi nào trong bán cầu

Chúng ta có thể lấy mẫu đồng nhất bán cầu để tạo ra tia mới. Tuy nhiên, chúng ta có thể khai thác thực tế là phương trình kết xuất có yếu tố cosin trong đó:

Lo(p,ωo)= =Le(p,ωo)+Ωf(p,ωtôi,ωo)Ltôi(p,ωtôi)|cosθtôi|dωtôi

cos(x)

Để chống lại điều này, chúng tôi sử dụng lấy mẫu quan trọng. Nếu chúng ta tạo ra các tia theo một bán cầu có trọng số cosin, chúng tôi đảm bảo rằng nhiều tia được tạo ra tốt hơn phía trên đường chân trời và ít gần đường chân trời hơn. Điều này sẽ làm giảm phương sai và giảm tiếng ồn.

Trong trường hợp của bạn, bạn đã chỉ định rằng bạn sẽ sử dụng BRDF dựa trên Cook-Torrance, microfacet. Hình thức phổ biến là:

f(p,ωtôi,ωo)= =F(ωtôi,h)G(ωtôi,ωo,h)D(h)4cos(θtôi)cos(θo)

Ở đâu

F(ωtôi,h)= =Hàm FresnelG(ωtôi,ωo,h)= =Chức năng Tạo hình và Tạo bóngD(h)= =Chức năng phân phối bình thường

Blog "Ghi chú của một đồ họa" có một bài viết tuyệt vời về cách lấy mẫu BRDF của Cook-Torrance. Tôi sẽ giới thiệu bạn đến bài viết trên blog của anh ấy . Điều đó nói rằng, tôi sẽ cố gắng tạo ra một cái nhìn tổng quan ngắn gọn dưới đây:

NDF nói chung là phần chiếm ưu thế của BRDF Cook-Torrance, vì vậy nếu chúng ta sẽ lấy mẫu quan trọng, chúng ta nên lấy mẫu dựa trên NDF.

Cook-Torrance không chỉ định một NDF cụ thể để sử dụng; chúng tôi có thể tự do lựa chọn bất cứ ai phù hợp với ưa thích của chúng tôi. Điều đó nói rằng, có một vài NDF phổ biến:

  • GGX
  • Beckmann
  • Blinn

Mỗi NDF có công thức riêng, do đó mỗi công thức phải được lấy mẫu khác nhau. Tôi sẽ chỉ hiển thị chức năng lấy mẫu cuối cùng cho mỗi. Nếu bạn muốn xem công thức có nguồn gốc như thế nào, hãy xem bài đăng trên blog.

GGX được định nghĩa là:

DGGX(m)= =α2π((α2-1)cos2(θ)+1)2

θ

θ= =hồ quang(α2ξ1(α2-1)+1)

ξ

φ

φ= =ξ2

Beckmann được định nghĩa là:

DBeckmmộtnn(m)= =1πα2cos4(θ)e-tan2(θ)α2

Mà có thể được lấy mẫu với:

θ= =hồ quang(11= =α2ln(1-ξ1))φ= =ξ2

Cuối cùng, Blinn được định nghĩa là:

DBtôitôinn(m)= =α+22π(cos(θ))α

Mà có thể được lấy mẫu với:

θ= =hồ quang(1ξ1α+1)φ= =ξ2

Đưa nó vào thực tế

Chúng ta hãy xem xét một đường dẫn ngược cơ bản:

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

    // Bounce the ray around the scene
    for (uint bounces = 0; bounces < 10; ++bounces) {
        m_scene->Intersect(ray);

        // The ray missed. Return the background color
        if (ray.geomID == RTC_INVALID_GEOMETRY_ID) {
            color += throughput * float3(0.846f, 0.933f, 0.949f);
            break;
        }

        // We hit an object

        // 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 emmisive light
        if (light != nullptr) {
            color += throughput * light->Le();
        }

        float3 normal = normalize(ray.Ng);
        float3 wo = normalize(-ray.dir);
        float3 surfacePos = ray.org + ray.dir * ray.tfar;

        // Get the new ray direction
        // Choose the direction based on the material
        float3 wi = material->Sample(wo, normal, sampler);
        float pdf = material->Pdf(wi, normal);

        // Accumulate the brdf attenuation
        throughput = throughput * material->Eval(wi, wo, normal) / pdf;


        // Shoot a new ray

        // Set the origin at the intersection point
        ray.org = surfacePos;

        // Reset the other ray properties
        ray.dir = wi;
        ray.tnear = 0.001f;
        ray.tfar = embree::inf;
        ray.geomID = RTC_INVALID_GEOMETRY_ID;
        ray.primID = RTC_INVALID_GEOMETRY_ID;
        ray.instID = RTC_INVALID_GEOMETRY_ID;
        ray.mask = 0xFFFFFFFF;
        ray.time = 0.0f;
    }

    m_scene->Camera.FrameBuffer.SplatPixel(x, y, color);
}

I E. chúng tôi tung lên xung quanh khung cảnh, tích lũy màu sắc và suy giảm ánh sáng khi chúng tôi đi. Ở mỗi lần nảy, chúng ta phải chọn một hướng mới cho tia. Như đã đề cập ở trên, chúng ta có thể lấy mẫu đồng nhất bán cầu để tạo ra tia mới. Tuy nhiên, mã thông minh hơn; tầm quan trọng của mẫu là hướng đi mới dựa trên BRDF. (Lưu ý: Đây là hướng đầu vào, bởi vì chúng tôi là người theo dõi đường dẫn ngược)

// Get the new ray direction
// Choose the direction based on the material
float3 wi = material->Sample(wo, normal, sampler);
float pdf = material->Pdf(wi, normal);

Mà có thể được thực hiện như:

void LambertBRDF::Sample(float3 outputDirection, float3 normal, UniformSampler *sampler) {
    float rand = sampler->NextFloat();
    float r = std::sqrtf(rand);
    float theta = sampler->NextFloat() * 2.0f * M_PI;

    float x = r * std::cosf(theta);
    float y = r * std::sinf(theta);

    // Project z up to the unit hemisphere
    float z = std::sqrtf(1.0f - x * x - y * y);

    return normalize(TransformToWorld(x, y, z, normal));
}

float3a TransformToWorld(float x, float y, float z, float3a &normal) {
    // Find an axis that is not parallel to normal
    float3a majorAxis;
    if (abs(normal.x) < 0.57735026919f /* 1 / sqrt(3) */) {
        majorAxis = float3a(1, 0, 0);
    } else if (abs(normal.y) < 0.57735026919f /* 1 / sqrt(3) */) {
        majorAxis = float3a(0, 1, 0);
    } else {
        majorAxis = float3a(0, 0, 1);
    }

    // Use majorAxis to create a coordinate system relative to world space
    float3a u = normalize(cross(normal, majorAxis));
    float3a v = cross(normal, u);
    float3a w = normal;


    // Transform from local coordinates to world coordinates
    return u * x +
           v * y +
           w * z;
}

float LambertBRDF::Pdf(float3 inputDirection, float3 normal) {
    return dot(inputDirection, normal) * M_1_PI;
}

Sau khi chúng tôi lấy mẫu inputDirection ('wi' trong mã), chúng tôi sử dụng điều đó để tính giá trị của BRDF. Và sau đó chúng tôi chia cho pdf theo công thức Monte Carlo:

// Accumulate the brdf attenuation
throughput = throughput * material->Eval(wi, wo, normal) / pdf;

Trong đó Eval () chỉ là hàm BRDF (Lambert, Blinn-Phong, Cook-Torrance, v.v.):

float3 LambertBRDF::Eval(float3 inputDirection, float3 outputDirection, float3 normal) const override {
    return m_albedo * M_1_PI * dot(inputDirection, normal);
}

Câu trả lời tốt đẹp. OP cũng đã hỏi về việc lấy mẫu quan trọng của Cook-Torrance mà câu trả lời này không liên quan.
PeteUK

6
Tôi đã cập nhật câu trả lời để thêm một phần về Cook-Torrance
RichieSams

Ví dụ GGX, để lấy mẫu tọa độ hình cầu góc cos (θ) chúng ta sử dụng công thức lấy mẫu quan trọng để tính góc và sử dụng góc đó trong GGX như bình thường phải không? Hay công thức thay thế hoàn toàn GGX?
Arjan Singh

3
Tôi đã thêm một phần để giúp trả lời câu hỏi của bạn. Nhưng, trong ngắn hạn, phương pháp đầu tiên của bạn là chính xác. Bạn sử dụng công thức lấy mẫu để tạo hướng, sau đó bạn sử dụng hướng mới đó trong công thức GGX thông thường để lấy pdf cho công thức Monte Carlo.
RichieSams

Đối với GGX, tôi sẽ tính toán / mẫu winhư thế nào? Tôi hiểu làm thế nào để lấy mẫu góc tọa độ hình cầu θ nhưng đối với vectơ chỉ hướng thực tế thì làm thế nào?
Arjan Singh

11

f(x)f(x)

f(x)

f= =FGπ(nωtôi)(nωo)
2π

Đối với NDF, bạn cần tính toán Hàm phân phối tích lũy của PDF để chuyển đổi vị trí mẫu được phân phối đồng đều sang vị trí mẫu có trọng số PDF. Đối với NDF đẳng hướng, điều này đơn giản hóa thành hàm 1D do tính đối xứng của hàm. Để biết thêm chi tiết về việc phái sinh CDF, bạn có thể kiểm tra bài viết GPU Gems cũ này .

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.