Dạng chính xác của thuật ngữ hình học GGX


9

Tôi đang cố gắng triển khai BRDF microfacet trong raytracer của mình nhưng tôi đang gặp phải một số vấn đề. Rất nhiều bài báo và bài báo tôi đã đọc xác định thuật ngữ hình học một phần là một hàm của khung nhìn và một nửa vectơ: G1 (v, h). Tuy nhiên, khi thực hiện điều này tôi nhận được kết quả như sau:

Thuật ngữ hình học GGX sử dụng nửa vector

(Hàng dưới cùng là điện môi có độ nhám 1.0 - 0.0, Hàng trên cùng là kim loại có độ nhám 1.0 - 0.0)

Có một điểm nổi bật kỳ lạ xung quanh các cạnh và một điểm cắt xung quanh nl == 0. Tôi thực sự không thể tìm ra nơi này đến từ đâu. Tôi đang sử dụng Unity làm tài liệu tham khảo để kiểm tra kết xuất của mình vì vậy tôi đã kiểm tra nguồn shader của họ để xem những gì họ sử dụng và từ những gì tôi có thể nói thuật ngữ hình học của họ không bị tham số bởi một nửa vector! Vì vậy, tôi đã thử cùng một mã nhưng được sử dụng để macro bề mặt bình thường thay vì một nửa vectơ và nhận được kết quả như sau:

Thuật ngữ hình học GGX sử dụng bề mặt macro bình thường

Đối với mắt chưa được huấn luyện của tôi, điều này dường như gần với kết quả mong muốn. Nhưng tôi có cảm giác điều này không đúng? Phần lớn các bài báo tôi đọc sử dụng một nửa vectơ nhưng không phải tất cả chúng. Có một lý do cho sự khác biệt này?

Tôi sử dụng mã sau đây làm thuật ngữ hình học của mình:

float RayTracer::GeometryGGX(const Vector3& v, const Vector3& l, const Vector3& n, const Vector3& h, float a)
{
    return G1GGX(v, h, a) * G1GGX(l, h, a);
}

float RayTracer::G1GGX(const Vector3& v, const Vector3& h, float a)
{
    float NoV = Util::Clamp01(cml::dot(v, h));
    float a2 = a * a;

    return (2.0f * NoV) / std::max(NoV + sqrt(a2 + (1.0f - a2) * NoV * NoV), 1e-7f);
}

Và để tham khảo, đây là chức năng phân phối bình thường của tôi:

float RayTracer::DistributionGGX(const Vector3& n, const Vector3& h, float alpha)
{
    float alpha2 = alpha * alpha;
    float NoH = Util::Clamp01(cml::dot(n, h));
    float denom = (NoH * NoH * (alpha2 - 1.0f)) + 1.0f;
    return alpha2 / std::max((float)PI * denom * denom, 1e-7f);
}

Câu trả lời:


5

TL; DR: Công thức của bạn sai.G1


Để tránh nhầm lẫn, tôi giả sử phiên bản đẳng hướng của BRDF, mô hình microfacet Smith (trái ngược với mô hình khoang V) và phân phối microfacet GGX.

Theo Heitz 2014 , thuật ngữ mặt nạ / bóng làG1

χ+(ωvωm)21+1+αo2tan2θv

và theo Walter 2007 , công thức là

χ+(ωvωgωvωm)21+1+α2tan2θv

Trong đó là hướng thông thường của microfacet (vectơ nửa chừng), là hướng chính (hình học) chính (bình thường), là hướng đến hoặc đi, là hướng đẳng hướng Tham số độ nhám và là hàm đặc tính dương hoặc hàm bước Heaviside (bằng một nếu và khác 0).ωmωgωvαχ+(một)một>0

Như bạn có thể nhận thấy, một nửa vectơ chỉ được sử dụng để đảm bảo bằng 0 nếu cấu hình hình học bị cấm. Chính xác hơn, nó đảm bảo rằng mặt sau của microsurface không bao giờ có thể nhìn thấy được từ hướng ở mặt trước của macrosurface và ngược lại (trường hợp sau chỉ có ý nghĩa khi cũng có hỗ trợ khúc xạ). Nếu mã gọi đảm bảo điều này, thì rõ ràng bạn có thể bỏ qua tham số này. Đó có lẽ là lý do tại sao họ đã làm như vậy trong Unity.ωmG1ωv

Mặt khác, việc triển khai của bạn sử dụng một nửa vectơ để tính cosin của hướng đối với microfacet, dẫn đến tính toán một cái gì đó khác với các công thức được trình bày.ωv

Nếu nó có ích gì thì đây là cách tôi thực hiện yếu tố :G1

float SmithMaskingFunctionGgx(
    const Vec3f &aDir,  // the direction to compute masking for (either incoming or outgoing)
    const Vec3f &aMicrofacetNormal,
    const float  aRoughnessAlpha)
{
    PG3_ASSERT_VEC3F_NORMALIZED(aDir);
    PG3_ASSERT_VEC3F_NORMALIZED(aMicrofacetNormal);
    PG3_ASSERT_FLOAT_NONNEGATIVE(aRoughnessAlpha);

    if (aMicrofacetNormal.z <= 0)
        return 0.0f;

    const float cosThetaVM = Dot(aDir, aMicrofacetNormal);
    if ((aDir.z * cosThetaVM) < 0.0f)
        return 0.0f; // up direction is below microfacet or vice versa

    const float roughnessAlphaSqr = aRoughnessAlpha * aRoughnessAlpha;
    const float tanThetaSqr = Geom::TanThetaSqr(aDir);
    const float root = std::sqrt(1.0f + roughnessAlphaSqr * tanThetaSqr);

    const float result = 2.0f / (1.0f + root);

    PG3_ASSERT_FLOAT_IN_RANGE(result, 0.0f, 1.0f);

    return result;
}

Cảm ơn bạn đã trả lời của bạn. Tôi đã thực hiện công thức bạn đã cung cấp và tôi nhận được kết quả giống hệt như của riêng tôi (khi sử dụng macrosurface bình thường). Vì vậy, có vẻ như đó chỉ là một hình thức khác (tôi đã nhận nó từ: graphrant.blogspot.nl/2013/08/specular-brdf-reference.html ) Tôi đã nhầm lẫn về một nửa vectơ vì khóa học toán học PBS SIGGRAPH 2015 nêu cụ thể hình học chức năng phụ thuộc vào chế độ xem, ánh sáng và nửa vectơ. Vì vậy, đây là một lỗi trong các slide?
Erwin

@Erwin, bây giờ bạn cũng đã cung cấp công thức, nó rõ ràng hơn nhiều. Lần sau làm điều đó ngay từ đầu, nó sẽ giúp. Có, cả hai phiên bản (của tôi và của bạn) đều tương đương, nhưng cả hai đều không sử dụng vectơ nửa chừng để tính toán hàm sin hoặc hàm tiếp tuyến. Nó sử dụng chứ không phải như bạn đã thực hiện - đó dường như là một sai lầm. Tôi nghi ngờ bạn cũng đã làm sai với việc thực hiện mới. nvhv
ivokabel

Tôi đã sử dụng N dot V trong triển khai mới của mình, điều đó mang lại cho tôi kết quả giống hệt với hình ảnh thứ hai tôi đã đăng. Nhưng tôi vẫn chưa rõ lý do tại sao khóa học PBS trình bày rằng nên sử dụng vectơ nửa chừng (Xem: blog.elfshadow.com/publications/s2015-shading-cference/hoffman/iêu , Slide 88).
Erwin

hvnvG1hv

Vâng, đó là vấn đề. Nhưng câu hỏi chính của tôi là: nửa vectơ được sử dụng để làm gì, vì nó xuất hiện trong định nghĩa hàm. Theo tôi hiểu bây giờ nó chỉ được sử dụng trong kiểm tra nếu H chấm V dương tính. Cảm ơn bạn đã dành thời gian để viết câu trả lời.
Erwin
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.