我开发了使用标准Phong / Blinn Phong照明模型的光线跟踪器。现在,我正在对其进行修改以支持基于物理的渲染,因此我正在实现各种BRDF模型。目前,我专注于Oren-Nayar和Torrance-Sparrow模型。这些中的每一个均基于用于表示入射光和出射光方向的球坐标。

我的问题是:正确的方法是将wi和wo从笛卡尔坐标转换为球坐标?

我正在应用这里报告的标准公式https://en.wikipedia.org/wiki/Spherical_coordinate_system#Coordinate_system_conversions,但是我不确定我做对了,因为我的向量是不是在笛卡尔坐标系的原点带有尾巴,而是以射线与对象的交点为中心。

在这里您可以找到我当前的实现:


https://github.com/chicio/Multispectral-Ray-tracing/tree/brdf/RayTracing/ RayTracer / Objects / BRDF
https://github.com/chicio/Multispectral-Ray-tracing/blob/brdf/RayTracing/RayTracer/Math/Vector3D.cpp

谁能帮助我给出将wi和wo向量从笛卡尔坐标转换为球坐标的正确方法的解释吗?

UPDATE

我在这里复制代码的相关部分:

球面坐标计算

float Vector3D::sphericalTheta() const {

    float sphericalTheta = acosf(Utils::clamp(y, -1.f, 1.f));

    return sphericalTheta;
}

float Vector3D::sphericalPhi() const {

    float phi = atan2f(z, x);

    return (phi < 0.f) ? phi + 2.f * M_PI : phi;
}


Oren Nayar

OrenNayar::OrenNayar(Spectrum<constant::spectrumSamples> reflectanceSpectrum, float degree) : reflectanceSpectrum{reflectanceSpectrum} {

    float sigma = Utils::degreeToRadian(degree);
    float sigmaPowerTwo = sigma * sigma;

    A = 1.0f - (sigmaPowerTwo / 2.0f * (sigmaPowerTwo + 0.33f));
    B = 0.45f * sigmaPowerTwo / (sigmaPowerTwo + 0.09f);
};

Spectrum<constant::spectrumSamples> OrenNayar::f(const Vector3D& wi, const Vector3D& wo, const Intersection* intersection) const {

    float thetaI = wi.sphericalTheta();
    float phiI = wi.sphericalPhi();

    float thetaO = wo.sphericalTheta();
    float phiO = wo.sphericalPhi();

    float alpha = std::fmaxf(thetaI, thetaO);
    float beta = std::fminf(thetaI, thetaO);

    Spectrum<constant::spectrumSamples> orenNayar = reflectanceSpectrum * constant::inversePi * (A + B * std::fmaxf(0, cosf(phiI - phiO) * sinf(alpha) * tanf(beta)));

    return orenNayar;
}


Torrance-Sparrow

float TorranceSparrow::G(const Vector3D& wi, const Vector3D& wo, const Vector3D& wh, const Intersection* intersection) const {

    Vector3D normal = intersection->normal;
    normal.normalize();

    float normalDotWh = fabsf(normal.dot(wh));
    float normalDotWo = fabsf(normal.dot(wo));
    float normalDotWi = fabsf(normal.dot(wi));
    float woDotWh = fabsf(wo.dot(wh));

    float G = fminf(1.0f, std::fminf((2.0f * normalDotWh * normalDotWo)/woDotWh, (2.0f * normalDotWh * normalDotWi)/woDotWh));

    return G;
}

float TorranceSparrow::D(const Vector3D& wh, const Intersection* intersection) const {

    Vector3D normal = intersection->normal;
    normal.normalize();

    float cosThetaH = fabsf(wh.dot(normal));

    float Dd = (exponent + 2) * constant::inverseTwoPi * powf(cosThetaH, exponent);

    return Dd;
}

Spectrum<constant::spectrumSamples> TorranceSparrow::f(const Vector3D& wi, const Vector3D& wo, const Intersection* intersection) const {

    Vector3D normal = intersection->normal;
    normal.normalize();

    float thetaI = wi.sphericalTheta();
    float thetaO = wo.sphericalTheta();

    float cosThetaO = fabsf(cosf(thetaO));
    float cosThetaI = fabsf(cosf(thetaI));

    if(cosThetaI == 0 || cosThetaO == 0) {

        return reflectanceSpectrum * 0.0f;
    }

    Vector3D wh = (wi + wo);
    wh.normalize();

    float cosThetaH = wi.dot(wh);

    float F = Fresnel::dieletricFresnel(cosThetaH, refractiveIndex);
    float g = G(wi, wo, wh, intersection);
    float d = D(wh, intersection);

    printf("f %f g %f d %f \n", F, g, d);
    printf("result %f \n", ((d * g * F) / (4.0f * cosThetaI * cosThetaO)));

    Spectrum<constant::spectrumSamples> torranceSparrow = reflectanceSpectrum * ((d * g * F) / (4.0f * cosThetaI * cosThetaO));

    return torranceSparrow;
}


更新2

经过一番搜索,我发现了Oren-Nayar BRDF的这种实现。

http:// content。 gpwiki.org/index.php/D3DBook :(照明)_Oren-Nayar

在上面的实现中,只需做arccos(wo.dotProduct(Normal))和arccos(wi.dotProduct(Normal)),即可获得wi和wo的theta。这对我来说似乎很合理,因为我们可以将交点的法线用作球坐标系的天顶方向并进行计算。 γ= cos(phi_wi-phi_wo)的计算对wi和wo进行了所谓的“切线空间”的投影。假设此实现中的一切正确,我是否可以使用公式| View-Normal x(View.dotProduct(Normal))|和| Light-普通x(Light.dotProduct(Normal))|获得phi坐标(而不是使用arctan(“ something”))?


N.B .:我还在阅读“基于物理的渲染:从理论到实现”,这是发布给pbrt的书(http://www.pbrt.org)。在此实现中,相交点的坐标系发生了某种变化(使用偏导数和曲面的参数坐标)(我现在正在阅读,因此我所说的可能不准确)。我想找到一种直截了当的方法(如果有人可以确认的话,UPDATE 2上面的方法就是我要寻找的方法)。

评论

我已经在这里问了这个问题gamedev.stackexchange.com/questions/112165/…。似乎没人回答。我认为我的“ UPDATE 2”中的参数可能是遵循的方法(因为似乎以这种方式描述的矢量项目可能是计算方位角的正确方法)。任何人都可以帮助我给出答案,也许可以作为典范参考用作学习参考?

#1 楼

BRDF定义中常用的极坐标系是相对于要着色的表面(即在切线空间中)设置的。 “ $ \ theta $”角用于度量您距表面法线的距离,而“ $ \ phi $”是相对于某个参考方向围绕表面平面的方位角(除非BRDF是各向异性的,这无关紧要)。因此,如果要转换为这些坐标,则必须首先在切线空间中获取矢量(原点在相交点,并且两个轴与曲面对齐),然后应用常规的笛卡尔到球面变换。
但是,通常可以并且应该完全不使用极坐标,三角函数或角度而仅使用矢量数学基元(例如点积)来评估BRDF。这通常会更有效且更健壮,因为您不必处理角度回绕,pi的因数,反三角函数的参数超出范围等问题。例如,您可能知道向量之间的角度的余弦值可以通过仅对(标准化的)向量进行求点而获得。正弦和正切可以通过余弦(即点积)的三角恒等式获得。
Fabian Giesen撰写了一篇有关该主题的文章,请完成您的推导,请参阅您确切的Oren-Nayar文章。链接,并给出另一种免触发的形式:

该重新整理一下三角身份了。在这里可以找到一个特别糟糕的罪犯–简化着色器的相关部分是:
float alpha = max( acos( dot( v, n ) ), acos( dot( l, n ) ) );
float beta  = min( acos( dot( v, n ) ), acos( dot( l, n ) ) );
C = sin(alpha) * tan(beta);

太好了!如果您使用一些触发身份,并且acos在其范围内单调递减,则可以简化为:
float vdotn = dot(v, n);
float ldotn = dot(l, n);
C = sqrt((1.0 - vdotn*vdotn) * (1.0 - ldotn*ldotn))
  / max(vdotn, ldotn);

..突然之间,就不再需要使用查找纹理(顺便说一句,这也有更高的精度)。

并请注意,此公式仅具有一个先验功能(sqrt)来代替四个(两个acossintan)。

评论


$ \ begingroup $
非常感谢@NathanReed的帮助。最后一个问题:关于如何计算切线空间并将向量wi和wo转换为用于射线跟踪/ BRDF的坐标空间,您是否有任何参考资料?目前,我找不到任何有用的工具。这样,我就能在切线空间方式和矢量数学方式之间进行一些比较。
$ \ endgroup $
–Fabrizio Duroni
2015年12月11日12:19



$ \ begingroup $
@FabrizioDuroni我想您通常已经熟悉如何在坐标系之间转换?对于切线空间,您只需要使用表面法线和垂直于它的一些两个向量作为轴来设置坐标。对于法线贴图,通常选择两个向量以匹配纹理空间的U轴和V轴(映射到特定表面)。对于没有法线映射的各向同性BRDF来说,这并不重要。
$ \ endgroup $
–内森·里德(Nathan Reed)
2015年12月11日19:11



$ \ begingroup $
是的,我熟悉如何在坐标系之间进行转换(因此,我知道必须使用使用基矢量成分的特定矩阵),但是我对切线空间和纹理映射不太熟悉。在网上搜索时,似乎对象类型(球形,三角形...)的计算方法有所不同。您说:“对于没有法线映射的各向同性BRDF来说,这并不重要。”,这是什么意思?那么我该如何选择呢?再次感谢您,我接受了您的回答,因为您是唯一真正给我一些很好提示的人。
$ \ endgroup $
–Fabrizio Duroni
15年12月12日在17:43