我正在尝试在raytracer中实现微面BRDF,但遇到了一些问题。我阅读的许多论文和文章都将局部几何术语定义为视图和半矢量的函数:G1(v,h)。但是,实施此方法时,我得到以下结果:



(下排是电介质,粗糙度为1.0-0.0,上排是金属,粗糙度为1.0-0.0)

在边缘周围有一个怪异的高光,在nl == 0处有一个截止点。我使用Unity作为参考来检查我的渲染,因此我检查了它们的着色器源,以查看它们的用途,并从中可以看出它们的几何项根本没有被半向量参数化!因此,我尝试了相同的代码,但使用了宏观表面法线而不是半向量,并得到以下结果:



对我而言,未经训练的眼睛似乎更接近于预期的结果。但是我有感觉这是不正确的?我阅读的大多数文章都使用半向量,但不是全部。

我有以下原因吗?

我将以下代码用作我的几何术语:

正态分布函数:

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


#1 楼

TL; DR:您的$ G1 $公式错误。


为了避免混淆,我假设是BRDF的各向同性版本,Smith微面模型(与V腔模型相对)和GGX微面分布。

根据Heitz 2014,掩蔽/阴影术语$ G1 $是

$$
\ chi ^ {+} \ left(\ omega_ {v} \ cdot \ omega_ {m} \ right)
\ frac {2} {1+ \ sqrt {1+ \ alpha_ {o} ^ {2} \ tan ^ {2} \ theta_ {v}}}
$$

,根据Walter 2007,公式为

$$
\ chi ^ {+} \ left(\ frac {\ omega_ {v } \ cdot \ omega_ {g}} {\ omega_ {v} \ cdot \ omega_ {m}} \ right)\ frac {2} {1+ \ sqrt {1+ \ alpha ^ {2} \ tan ^ {2 } \ theta_ {v}}}
$$

其中$ \ omega_ {m} $是微面法线方向(半矢量),$ \ omega_ {g} $是主要(几何)法线方向(法线),$ \ omega_ {v} $是传入或传出方向,$ \ alpha $是各向同性粗糙度参数,而$ \ chi ^ {+} \ left(a \ right)$是正特征函数或Heaviside阶跃函数(如果$ a> 0 $等于1,否则等于0)。

您会注意到,如果几何构型被禁止,则半矢量$ \ omega_ {m} $仅用于确保$ G1 $为零。更准确地说,它可以确保从宏观表面的$ omega_ {v} $方向上看不到微观表面的背面,反之亦然(后一种情况仅在还支持折射时才有意义)。如果调用代码保证了这一点,那么您显然可以忽略此参数。另一方面,这可能就是他们在Unity中这样做的原因。
另一方面,您的实现使用半向量来计算$ \ omega_ {v} $方向相对于microfacet,这会导致计算除所提供公式之外的其他内容。

如果有帮助,那么这是我对$ 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;
}


评论


$ \ begingroup $
感谢您的答复。我已经实现了您提供的公式,并且得到的结果与我自己的公式相同(使用宏观曲面法线时)。因此,似乎这只是一种不同的形式(我来自:graphicrants.blogspot.nl/2013/08/specular-brdf-reference.html)我对半向量感到困惑,因为SIGGRAPH 2015 PBS数学课程专门说明了几何功能取决于视图,光和半向量。所以这是幻灯片中的错误?
$ \ endgroup $
–欧文
16年5月29日在8:26



$ \ begingroup $
@Erwin,现在您还提供了公式本身,因此更加清楚。下次在开始时做对它有帮助。是的,两个版本(mine和您的版本)都是等效的,但是它们都不使用中途向量来计算正弦或切线函数。它使用$ n \ cdot v $而不是您在实现中使用的$ h \ cdot v $-这似乎是错误的。我怀疑您在新实现中也犯了同样的错误。
$ \ endgroup $
– ivokabel
16年5月29日在10:01



$ \ begingroup $
我在新实现中确实使用了N点V,这与我发布的第二张图片的结果相同。但是我仍然不清楚为什么PBS课程幻灯片会指出应该使用中途向量(请参阅:blog.selfshadow.com/publications/s2015-shading-course/hoffman/…,幻灯片88)。
$ \ endgroup $
–欧文
16年5月30日在15:24

$ \ begingroup $
我是否正确理解使用$ h \ cdot v $而不是$ n \ cdot v $是问题所在?关于在$ G_1 $中使用中途向量:实际上,我在发布的两个版本中都使用了中途向量(构造LaTeX公式时我犯了一个错误,并在第一个中写入了几何法线,我会尽快修复),但要点是,中途向量不用于计算余弦值(即不使用$ h \ cdot v $)。
$ \ endgroup $
– ivokabel
16年5月30日在21:28

$ \ begingroup $
是的,这就是问题所在。但是我的主要问题是:半向量用于什么,因为它出现在函数定义中。据我所知,它仅用于检查H点V是否为正。感谢您抽出宝贵的时间写答案。
$ \ endgroup $
–欧文
16年6月6日在8:00