pbrt笔记--第八章 反射模型

写在前面的总结

以前学OpenGL的时候,光照模型基本都是用环境光+漫反射+镜面反射的套路,这样渲染出来的模型往往显得“不够真实”。而这一章中,pbrt会讲述许多关于光在表面反射的物理原理(比如,光的菲涅尔效应),基于这些原理建立的模型渲染出来可以更加真实。

书中的后部分讲了一个非常重要的模型:微表面模型。现在游戏开发中大部分用的Cook-Torrance BRDF模型就是一种微表面模型。但因我对微表面模型的数学理论及推导还不够理解透彻,因此并没有写在笔记中。当然,相比于弄清这些模型背后的数学理论,我觉得更重要的是弄清楚如何使用这些模型,它们在pbrt中扮演的是什么角色,因此我觉得本章最为重要的事情是,弄清楚BxDF class的每个接口是干嘛的!

前言

BRDF,BTDF,BSDF

BRDF,bidirectional reflectance distribution function(双向反射分布函数),BTDF,bidirectional transmittance distribution function(双向透射分布函数),BSDF,bidirectional scattering distribution function(双向散射分布函数),BSDF包含了BRDF和BTDF。在第五章有对他们的完整介绍。

表面反射模型的来源

略过...

基本术语

四种反射:

大部分真实的反射可以由下面四种反射组合而成的。
diffuse(漫反射):光的所有反射方向的强度都是一样的,虽然现实中不存在理想漫反射,但许多表面,如粗糙的黑板,无光泽的油漆,都近似漫反射。
glossy specular(高光镜面反射):比如塑料或高光油漆,在某些方向会反射光,并且模糊的显示反射的物体。
perfect specular(理想镜面反射):只有一个方向有反射光。镜子和玻璃很近似理想镜面反射。
retro-reflective(姑且翻译成回射吧):反射光会沿着入射光的相反方向射回来。比如天鹅绒,月球。
图8.1展示了四种反射:

pbrt笔记--第八章 反射模型_第1张图片

isotropic (各向同性)和anisotropic(各向异性)

各向同性就是说,入射光射到表面的一点,如果入射光和反射光以表面的法线为中轴转圈圈,它们之间的比例和相对方向保持不变。相反,各向异性就是...。各向异性的例子,包括Brushed Metal(拉丝金属?),各种类型的衣服,以及光碟。

Geometric Setting(几何设置)

pbrt中的反射计算是在一个反射坐标系中,反射坐标系由经过着色点的两个切线向量和一个法线向量组成(着色点也就是光线和表面的交点),两个且切线向量和法线分别对应着x,y和z轴。如图8.2。所有BRDF和BTDF中的方向向量都是在这个坐标系中定义的。这个点很重要。


pbrt笔记--第八章 反射模型_第2张图片

也可以用球面坐标(θ , φ);θ表示给定的向量和z轴的角度,φ向量表示投影到xy平面后,和x轴的角度。给定在该坐标系的一个方向向量ω,可以很容易计算它和法线形成的角度的余弦值:


pbrt工程中提供了许多工具函数来计算相关的值,这里就不列举了...

书中提醒,阅读后面章节的BRDFs和BTDFs时应注意的几点:
1.在变换到local coordinate system(就是上面的反射坐标系),入射光向量ωi和输出的观察方向ωo 都是normalized的,并且方向都是向外的;
2.pbrt中为了方便,法线向量n总是指向物体的外面(outside),这样可以很容易的确定光是进入还是离开物体;如果入射光ωi和n是用一个半球,那么光就是entering的;反之,就是exiting;
3.local coordinate system可能和Shape::Intersect()函数返回的坐标系统不一样,比如他可以被bump mapping修改;
4.先略过,后面会再遇到的。

8.1 Basic Interface(基本接口)

BxDF

BRDFs和BTDFs共享一个通用的基类,BxDF。

class BxDF { public:
//BxDF Interface
//BxDF Public Data
}; 
BxDFType

BxDF用BxDFType来区分各种反射类型:

// BSDF Declarations
enum BxDFType {
    BSDF_REFLECTION = 1 << 0,
    BSDF_TRANSMISSION = 1 << 1,
    BSDF_DIFFUSE = 1 << 2,
    BSDF_GLOSSY = 1 << 3,
    BSDF_SPECULAR = 1 << 4,
    BSDF_ALL = BSDF_DIFFUSE | BSDF_GLOSSY | BSDF_SPECULAR | BSDF_REFLECTION |
               BSDF_TRANSMISSION,
};

//BxDF Interface
BxDF(BxDFType type) : type(type) { }

//BxDF Public Data
const BxDFType type;
BxDF::f()

BxDF::f()是BxDFs一个关键的接口。如下:

//BxDF Interface
virtual Spectrum f(const Vector3f &wo, const Vector3f &wi) const = 0;

其实这个接口对应的正是某个光照模型的BRDF(或BTDF)公式,回忆第五章,BRDF表示的是输出辐射率Lo和输入光照强度E的比值,


BxDF::f()的任务就是,给定一个输入光方向wi和一个输出光方向wo,返回它们的比值,也就是BRDF公式左侧的fr。

BxDF::Sample_f()

不是所有的BxDF都可以用f()计算。例如,理想镜面反射,比如镜子,玻璃,或水,对应一个单一的入射光,只有一个单一的反射方向。这样的BxDFs会使用delta distributions(狄克拉分布,也就是脉冲)来描述,除了单一的反射方向外,其他方向的光都为0。pbrt中对这些BxDFs需要特殊处理,提供的接口是BxDF::Sample_f()。这个方法既可以处理狄克拉分布,也可以处理反射有多个随机分布的方向光的BxDF(在14.1章介绍)

//BxDF Interface
virtual Spectrum Sample_f(const Vector3f &wo, Vector3f *wi,
       const Point2f &sample, Float *pdf,
       BxDFType *sampledType = nullptr) const;

BxDF::Sample_f()是,调用方给定一个输出方向ωo,BxDF::Sample_f()会计算出一个输入方向wi(注意输入参数是ωo,返回值是wi),并返回BRDF的fr。其他参数后面再解释。

8.1.1 Reflectance (反射比)

BxDF::rho()

下面这个是我自己的理解(不一定准确,但书中讲的太委婉了受不了...)
rho()接口对应计算的是下面的公式:


BxDF::f()或BxDF::Sample_f()对应的就是公式中的fr(p,ωo,ωi),而ρhd是对fr(p,ωo,ωi)在所有的输入方向wi上做积分。我们看看rho接口:

virtual Spectrum rho(const Vector3f &wo, int nSamples,
                        const Point2f *samples) const;

rho接口主要是后面蒙特卡洛积分中会用到,现在暂不分析。

书中还介绍了一个表面的hemispherical-hemispherical reflectance,先不管。

8.1.2 BxDF Scaling Adapter

这里举了BxDF的一个简单的子类,不多说了直接贴代码了,可以提前感受一下BxDF的用法:

class ScaledBxDF : public BxDF {
 public:
       //ScaledBxDF Public Methods
 private:
       BxDF *bxdf;
       Spectrum scale;
};

ScaledBxDF(BxDF *bxdf, const Spectrum &scale)
       : BxDF(BxDFType(bxdf->type)), bxdf(bxdf), scale(scale) {
}

Spectrum ScaledBxDF::f(const Vector3f &wo, const Vector3f &wi) const
{
   return scale * bxdf->f(wo, wi);
}

8.2 Specular Reflection and Transmission(镜面反射和透射)

tip:感觉书中这节讲的理论过多了,因此下面删掉书中许多对我没多大用处的细节。

镜面反射或折射中,一条入射光ωi到达表面后只有一条反射或折射光ωo。对于镜面反射,outgoing direction与法线的夹角,跟incoming direction和法线的夹角相同:


而φo = φi + π。对于折射,依然是 φo = φi + π,但outgoing direction θt则根据Snell’s law(菲涅尔定律),如下:


η表示折射率。

8.2.1 Fresnel Reflectance(菲涅尔反射)

上面讲的是incoming light和reflected or transmitted light的方向的关系,现在它们的大小的关系。它们的比值由Fresnel equations公式决定。

有三种材质:
1.dielectrics(绝缘体):不导电,它们的折射率是实数(通常是1-3),这种材质会产生折射,也就是输入光的一部分能量会透射进入介质中。例如玻璃,矿物油,水,空气;
2.conductors(导体):如金属,这种材质是不透明的,输入光的几乎所有能量都被反射出去。
3.Semiconductors(半导体):不讲。

Dielectric的菲涅尔公式:

具体理论不记录了,对于两种介质都是dielectric的公式为:


pbrt笔记--第八章 反射模型_第3张图片

这是unpolarized light(非偏振光)的菲涅尔公式(至于什么是unpolarized light我也不清楚)。Fr表示的是反射光能量和输入光能量的比值。公式中,ηi and ηt 是入射光和折射光所在传输介质的折射率。

由能量守恒定律,一个dielectric的折射的能量为1 − Fr。

FrDielectric()函数计算这个公式:

// BxDF Utility Functions
Float FrDielectric(Float cosThetaI, Float etaI, Float etaT) {
    cosThetaI = Clamp(cosThetaI, -1, 1);
    // Potentially swap indices of refraction
    bool entering = cosThetaI > 0.f;
    if (!entering) {
        std::swap(etaI, etaT);
        cosThetaI = std::abs(cosThetaI);
    }

    // Compute _cosThetaT_ using Snell's law
    Float sinThetaI = std::sqrt(std::max((Float)0, 1 - cosThetaI * cosThetaI));
    Float sinThetaT = etaI / etaT * sinThetaI;

    // Handle total internal reflection
    if (sinThetaT >= 1) return 1;
    Float cosThetaT = std::sqrt(std::max((Float)0, 1 - sinThetaT * sinThetaT));
    Float Rparl = ((etaT * cosThetaI) - (etaI * cosThetaT)) /
                  ((etaT * cosThetaI) + (etaI * cosThetaT));
    Float Rperp = ((etaI * cosThetaI) - (etaT * cosThetaT)) /
                  ((etaI * cosThetaI) + (etaT * cosThetaT));
    return (Rparl * Rparl + Rperp * Rperp) / 2;
}

要注意的是,当输入光的角度达到critical angle时,只有反射,没有折射:

if (sinThetaT >= 1)
return 1;
Conductor的菲涅尔公式:

对于一边为conductor一边为dielectric介质的菲涅尔公式,因为conductor会吸收一部分光的能量并转化为热量,因此增加一个absorption coefficient(吸收系数)。公式如下:


pbrt笔记--第八章 反射模型_第4张图片

函数FrConductor()计算这个公式:

Spectrum FrConductor(Float cosThetaI, const Spectrum &etaI,
       const Spectrum &etaT, const Spectrum &k);
Fresnel class

Fresnel class为基类,绝缘体和导体材质分别对应两个子类,FresnelDielectric和FresnelConductor。不再详述,直接贴代码:

class Fresnel {
  public:
    // Fresnel Interface
    virtual ~Fresnel();
    virtual Spectrum Evaluate(Float cosI) const = 0;
    virtual std::string ToString() const = 0;
};

class FresnelConductor : public Fresnel {
  public:
    // FresnelConductor Public Methods
    Spectrum Evaluate(Float cosThetaI) const;
    FresnelConductor(const Spectrum &etaI, const Spectrum &etaT,
                     const Spectrum &k)
        : etaI(etaI), etaT(etaT), k(k) {}
    std::string ToString() const;

  private:
    Spectrum etaI, etaT, k;
};

class FresnelDielectric : public Fresnel {
  public:
    // FresnelDielectric Public Methods
    Spectrum Evaluate(Float cosThetaI) const;
    FresnelDielectric(Float etaI, Float etaT) : etaI(etaI), etaT(etaT) {}
    std::string ToString() const;

  private:
    Float etaI, etaT;
};

8.2.2 Specular Reflection(镜面反射)

SpecularReflection class

现在我们来实现SpecularReflection class。先看代码:

class SpecularReflection : public BxDF {
  public:
    // SpecularReflection Public Methods
    SpecularReflection(const Spectrum &R, Fresnel *fresnel)
        : BxDF(BxDFType(BSDF_REFLECTION | BSDF_SPECULAR)),
          R(R),
          fresnel(fresnel) {}
    Spectrum f(const Vector3f &wo, const Vector3f &wi) const {
        return Spectrum(0.f);
    }
    Spectrum Sample_f(const Vector3f &wo, Vector3f *wi, const Point2f &sample,
                      Float *pdf, BxDFType *sampledType) const;
    Float Pdf(const Vector3f &wo, const Vector3f &wi) const { return 0; }

  private:
    // SpecularReflection Private Data
    const Spectrum R;
    const Fresnel *fresnel;
};

SpecularReflection中有两个成员变量,Spectrum R是用来调节输出color的,Fresnel是用来计算菲涅尔公式的。从代码也可以看出,其实SpecularReflection的接口不多,就是f(),Sample_f(),Pdf()。Pdf()这里先不讨论。f()和Sample_f()粗略的说都是求BRDF中的fr(p,ωo,ωi)(也就是输出光和输入光的比值)。

求fr(p,ωo,ωi)

从上面所学的知识,我们知道菲涅尔公式就给出了输出光和输出光的比值,也就是Fr(ω)。因此BRDF公式可以写成:


其中ωr = R(ωo, n)是输出方向ωo 对应的输入光。(因为镜面反射中θr = θo,因此Fr(ωo) = Fr(ωr))。记住我们的目的是求fr(wo, wi)。虽然我们已经有了Fr(ωr),但还得继续....

根据镜面反射的特点,我们可以把上面的公式用狄克拉公式来表达。首先看下狄克拉分布的一个特性:


(书中这里有一段讲delta distribution,没明白讲的是啥,貌似跳过也没影响)

因为镜面反射除了理想反射的方向光外,其他方向都为0,因此我们可以用delta distribution来表达:


放入上面的pbrt公式,得到


我们的目的是求fr(p,ωo,ωi),如下:


SpecularReflection::f()接口

SpecularReflection的f()接口直接返回0,因为对于任意一对方向光,delta function都不会有反射。(这里书中在底下的小字做了解释,主要解释为什么如果wo正好是理想反射的方向时,为什么f()不是返回1。我还不是特别明白,先截下来)


Spectrum f(const Vector3f &wo, const Vector3f &wi) const {
       return Spectrum(0.f);
   }
SpecularReflection::Sample_f()接口

Sample_f()接口根据wo计算出wi,并且返回fr(p,ωo,ωi)。代码如下:

Spectrum SpecularReflection::Sample_f(const Vector3f &wo,
           Vector3f *wi, const Point2f &sample, Float *pdf,
           BxDFType *sampledType) const {
//Compute perfect specular reflection direction
*wi = Vector3f(-wo.x, -wo.y, wo.z);
*pdf = 1;
return fresnel->Evaluate(CosTheta(*wi)) * R / AbsCosTheta(*wi);
}

从代码可以看出,求wi很简单,因为所有向量都在反射模型坐标系中,法线n = (0, 0, 1),而wi和wo相对于法线n有相同的夹角。

任意坐标系的反射向量计算

对于任意坐标系的计算,书中也给出了推导。首先假设n和ω都是normalized的,推导过程直接截图了:



pbrt笔记--第八章 反射模型_第5张图片
pbrt笔记--第八章 反射模型_第6张图片

相应的代码如下:

//BSDF Inline Functions
inline Vector3f Reflect(const Vector3f &wo, const Vector3f &n) {
       return -wo + 2 * Dot(wo, n) * n;
   }

8.2.3 Specular Transmission

镜面折射和镜面反射差不多,这里就不再记录了,只贴下代码吧:

class SpecularTransmission : public BxDF {
  public:
    // SpecularTransmission Public Methods
    SpecularTransmission(const Spectrum &T, Float etaA, Float etaB,
                         TransportMode mode)
        : BxDF(BxDFType(BSDF_TRANSMISSION | BSDF_SPECULAR)),
          T(T),
          etaA(etaA),
          etaB(etaB),
          fresnel(etaA, etaB),
          mode(mode) {}
    Spectrum f(const Vector3f &wo, const Vector3f &wi) const {
        return Spectrum(0.f);
    }
    Spectrum Sample_f(const Vector3f &wo, Vector3f *wi, const Point2f &sample,
                      Float *pdf, BxDFType *sampledType) const;
    Float Pdf(const Vector3f &wo, const Vector3f &wi) const { return 0; }

  private:
    // SpecularTransmission Private Data
    const Spectrum T;
    const Float etaA, etaB;
    const FresnelDielectric fresnel;
    const TransportMode mode;
};

Spectrum SpecularTransmission::Sample_f(const Vector3f &wo, Vector3f *wi,
                                        const Point2f &sample, Float *pdf,
                                        BxDFType *sampledType) const {
    // Figure out which $\eta$ is incident and which is transmitted
    bool entering = CosTheta(wo) > 0;
    Float etaI = entering ? etaA : etaB;
    Float etaT = entering ? etaB : etaA;

    // Compute ray direction for specular transmission
    if (!Refract(wo, Faceforward(Normal3f(0, 0, 1), wo), etaI / etaT, wi))
        return 0;
    *pdf = 1;
    Spectrum ft = T * (Spectrum(1.) - fresnel.Evaluate(CosTheta(*wi)));
    // Account for non-symmetry with transmission to different medium
    if (mode == TransportMode::Radiance) ft *= (etaI * etaI) / (etaT * etaT);
    return ft / AbsCosTheta(*wi);
}

8.2.4 Fresnel-Modulated Specular Reflection and Transmission

为了在某些蒙特卡洛传输算法中能更高效(14章到16章),用一个单一的BxDF类代表镜面反射和镜面折射,具体不多说了,直接贴代码:

class FresnelSpecular : public BxDF {
  public:
    // FresnelSpecular Public Methods
    FresnelSpecular(const Spectrum &R, const Spectrum &T, Float etaA,
                    Float etaB, TransportMode mode)
        : BxDF(BxDFType(BSDF_REFLECTION | BSDF_TRANSMISSION | BSDF_SPECULAR)),
          R(R),
          T(T),
          etaA(etaA),
          etaB(etaB),
          mode(mode) {}
    Spectrum f(const Vector3f &wo, const Vector3f &wi) const {
        return Spectrum(0.f);
    }
    Spectrum Sample_f(const Vector3f &wo, Vector3f *wi, const Point2f &u,
                      Float *pdf, BxDFType *sampledType) const;
    Float Pdf(const Vector3f &wo, const Vector3f &wi) const { return 0; }

  private:
    // FresnelSpecular Private Data
    const Spectrum R, T;
    const Float etaA, etaB;
    const TransportMode mode;
};

8.3 Lambertian Reflection(朗伯反射)

Lambertian model是最简单的一种BRDFs。它描述了一种理想漫反射,也就是反射光在所有方向上都相等。虽然它在物理上是不存在的,但它对真实世界的许多物体有很好的近似,比如matte paint(无光油漆?)

因为比较简单,也直接贴代码了:

class LambertianReflection : public BxDF {
  public:
    // LambertianReflection Public Methods
    LambertianReflection(const Spectrum &R)
        : BxDF(BxDFType(BSDF_REFLECTION | BSDF_DIFFUSE)), R(R) {}
    Spectrum f(const Vector3f &wo, const Vector3f &wi) const;
    Spectrum rho(const Vector3f &, int, const Point2f *) const { return R; }
    Spectrum rho(int, const Point2f *, const Point2f *) const { return R; }

  private:
    // LambertianReflection Private Data
    const Spectrum R;
};

Spectrum LambertianTransmission::f(const Vector3f &wo,
                                   const Vector3f &wi) const {
    return T * InvPi;
}

8.4 Microfacet Models(微表面模型)

微表面模型的思想是:可以把粗糙的表面看做是由一系列微小的表面(microfacets)组成的,微表面的朝向分布是服从某种概率统计的。如图8.12,图a是模拟比较粗糙的表面,图b是模拟较为平滑的表面。


pbrt笔记--第八章 反射模型_第7张图片
microsurface和macrosurface

有两个术语:microsurface(微观表面),macrosurface(宏观表面)。根据我自己的理解,比如我们之前讲的光通量时的微分区域dA,它属于macrosurface;而单个微表面属于microsurface,它相对于dA来说是尺寸更小的级别。

microfacet model的两个组成部分

微表面模型,要为两个部分建模:一个是这些微表面如何分布(比如说法线方向是n1的占比是多少,n2的占比多少...);另一个是,这些微表面是如何反射光的(比如把微表面当做是Lambertian反射,或者把微表面当做理想镜面反射)。大部分模型会把微表面当做Perfect mirror reflection,但也有部分是用Lambertian reflector的,比如下一节的Oren–Nayar model。

微表面模型的三种几何效应

微表面的数学模型要描述三种情况,如图8.13:


pbrt笔记--第八章 反射模型_第8张图片

图a是Masking(遮挡),b是Shadowing(阴影),c是Interreflection(间接反射)

8.4.1 Oren-Nayar Diffuse Reflection

因本人目前尚无能力去分析Oren-Nayar背后的数学理论,只简略过一下代码接口。对Oren-Nayar理论感兴趣的同学可以去看SIGGRAPH论文《Generalization of Lambert’s Reflectance Model》(Oren_SIGGRAPH94.pdf)。书中也做了介绍,但没有论文讲的基础和深入。

Oren-Nayar数学理论虽然不容易看懂,但对于我们做工程的人来说,完全可以把它当做黑盒子,照抄公式就是了。看下OrenNayar的Class:

class OrenNayar : public BxDF {
  public:
    // OrenNayar Public Methods
    Spectrum f(const Vector3f &wo, const Vector3f &wi) const;
    OrenNayar(const Spectrum &R, Float sigma)
        : BxDF(BxDFType(BSDF_REFLECTION | BSDF_DIFFUSE)), R(R) {
        sigma = Radians(sigma);
        Float sigma2 = sigma * sigma;
        A = 1.f - (sigma2 / (2.f * (sigma2 + 0.33f)));
        B = 0.45f * sigma2 / (sigma2 + 0.09f);
    }

  private:
    // OrenNayar Private Data
    const Spectrum R;
    Float A, B;
};

OrenNayar构造函数只需要一个参数sigma(R和OrenNayar的计算无关),姑且把它理解为粗糙度吧(实际上应该不是,还没仔细了研究)。从代码可以看出,
OrenNayar和上面的LambertianReflection,FresnelSpecular等等一样,也继承BxDF,只是它的f()和Sample_f()等接口实现不同罢了。

书中还展示了用LambertianReflection和用sigma=20的OrenNayar做出来的模型的效果对比:


可以看出OrenNayar的光照模型更为真实。

未完待续

书中后续的内容主要讲了一个Torrance–Sparrow Model,它和我们现在熟知的Cook-Torrance微表面模型类似,我们可以看下Cook-Torrance公式:



Cook-Torrance 微表面模型,它兼具漫反射和镜面反射特性的,其中镜面反射部分fcook−torrance,它具体的形式为:


其中D是一个分布函数,它表示在dA微分区域内的所有微表面中,法线为某个向量wh的微表面占总面积dA的比例;G表示一个几何函数,它是描述微表面模型中的遮蔽和阴影;F为菲涅尔反射方程.....因为自己目前还不能把他们背后的数学理论完全弄明白,因此也无法对书中的公式推导做出很好的说明,等有时间有需要的时候再补上吧....

你可能感兴趣的:(pbrt笔记--第八章 反射模型)