pbrt笔记--第九章 材质

前言

上一章的BRDFs和BTDFs描述的是光在表面的一点上如何发散,但渲染器需要知道表面上的一点对应的是哪些BRDFs和BTDFs,以及它们的参数是什么。本章讨论的就是如何解决这些问题。

基本的思路是对场景中的每个primitive(几何图元)绑定一个surface shader。这个surface shader用一个Material接口类,它有一个方法,给定该方法一个suface的指针,该方法就会创建一个描述该点的光如何scatting的BsDF对象。BsDF class持有一系列BxDFs,它们的作用会被累加起来加到full scattering function中。而Materials对象,使用Texture class的实例(下一章介绍)来决定在每一个点的材质的参数。

9.1 BsDFs

BsDFs class 是BRDFs和BTDFs的集合。并且它会隐藏一些shading normals(着色法线)的细节,无论是来自三角形网格中的per-vertex normals(逐顶点法线)或者从bump mapping,shading normals都可以显著提高渲染表面的丰富性。

BSDF构造函数

BSDF的构造函数需要一个SurfaceInterfact对象(包含有一个表面某一点的几何信息),以及一个表示边界折射率的参数eta。对于不透明的物体,eta是不会使用的(默认为1)。

BSDF会计算出一个坐标系统(也就是上一章BxDF使用的local坐标系统!),该坐标系统以shading normal作为其中一个坐标轴(z轴)。为了方便,我们用ns标记shading normal,用ng标记geometric normal(如图9.1)。


pbrt笔记--第九章 材质_第1张图片

BSDF构造函数及相关成员变量如下:

BSDF(const SurfaceInteraction &si, Float eta = 1)
       : eta(eta), ns(si.shading.n), ng(si.n),
         ss(Normalize(si.shading.dpdu)), ts(Cross(ns, ss)) { }

//BSDF Public Data
const Float eta;

//BSDF Private Data
const Normal3f ns, ng; const Vector3f ss, ts;
bxdfs数组

BSDF内部存储了有限数量的BxDF组件,目前限制8个,如需要可以扩展。相关代码如下:

void Add(BxDF *b) {
       Assert(nBxDFs < MaxBxDFs);
       bxdfs[nBxDFs++] = b;
   }

//BSDF Private Data
int nBxDFs = 0;
static constexpr int MaxBxDFs = 8; 
BxDF *bxdfs[MaxBxDFs];

int NumComponents(BxDFType flags = BSDF_ALL) const;
WorldToLocal()和LocalToWorld()

BSDF提供将世界坐标系转换为Local reflection坐标系(或反过来)。回忆第8章,因为在local reflection坐标系中,法线正好是z轴((0, 0, 1),表面上的一条切线是x轴(1, 0, 0),另一条切线是y轴(0, 1, 0),如果给定了在世界坐标系的这三个向量nst,那么从世界坐标系到local reflection坐标系的变换矩阵M就是:

可以验证一下上面的推论是否正确,例如,用M乘以表面法线nMn=(s·n, t·n, n·n)。因为stn是相互垂直的,因此Mn的x和y向量都是0,因为n是normalized的,因此nn = 1。因此,Mn= (0,0,1)。

当从Local reflection坐标系转换到世界坐标系时,我们不需要计算M的逆矩阵,因为M是正交矩阵,它的逆矩阵等于它的转置矩阵。对应的代码如下:

Vector3f WorldToLocal(const Vector3f &v) const {
       return Vector3f(Dot(v, ss), Dot(v, ts), Dot(v, ns));
   }

Vector3f LocalToWorld(const Vector3f &v) const {
       return Vector3f(ss.x * v.x + ts.x * v.y + ns.x * v.z,
                       ss.y * v.x + ts.y * v.y + ns.y * v.z,
                       ss.z * v.x + ts.z * v.y + ns.z * v.z);
}
Shading normals带来的问题

Shading normals可能会引起不希望发生的错误。如图9.2,图a展示了一个 light leak(光线泄露):如果用geometric normal计算,ωi和ωo处在表面的不同侧,因此如果表面没有透射的话,这条光线是没有贡献的。然而,如果用shading normal的话,ωi和ωo处在表面的同一侧,会把ωi的影响计算进去。这是情况告诉我们,不能直接用ns替换ng

pbrt笔记--第九章 材质_第2张图片

图b展示了类似的问题:shading normal指示没有光会反射到观察者那里,因为观察者和输入光不在表面的同一个半球,但shading normal指示它们在同一个半球。当这种情况发生是,直接用ns会导致表面上有难看的黑点。

幸运的是,有一个优雅的方法解决这些问题。当计算BSDF的时候,我们可以用geometric normal来决定我们应该计算反射还是透射:在以ng为准的表面上,如果ωi and ωo实在同一个半球,则只计算BRDFs,如果不在同一半球,则值计算BTDFs。

BSDF::f()接口

BSDF::f()接口先通过上面的方法判断是反射还是透射,然后把多个bxdf是累加起来。

Spectrum BSDF::f(const Vector3f &woW, const Vector3f &wiW,
                    BxDFType flags) const {
       Vector3f wi = WorldToLocal(wiW), wo = WorldToLocal(woW);
       bool reflect = Dot(wiW, ng) * Dot(woW, ng) > 0;
       Spectrum f(0.f);
       for (int i = 0; i < nBxDFs; ++i)
           if (bxdfs[i]->MatchesFlags(flags) &&
               ((reflect && (bxdfs[i]->type & BSDF_REFLECTION)) ||
               (!reflect && (bxdfs[i]->type & BSDF_TRANSMISSION))))
               f += bxdfs[i]->f(wo, wi);
return f; }
Spectrum BSDF::rho()接口

主要用在蒙特卡洛采样算法,后面再分析。

9.1.1 BsDF Memory Management

内存管理相关,略过...

9.2 Material Interface and Implementations

Material class是一个虚基类,如下:

class Material {
  public:
    virtual void ComputeScatteringFunctions(SurfaceInteraction *si,
                                            MemoryArena &arena,
                                            TransportMode mode,
                                            bool allowMultipleLobes) const = 0;
    virtual ~Material();
    static void Bump(const std::shared_ptr> &d,
                     SurfaceInteraction *si);
};

Material的派生类只有一个接口必须要实现:Materials: ComputeScatteringFunctions()。输入参数是一个SurfaceInteraction对象,它包含了在表面上的一个交点的集合属性。该方法的任务是,确定在该point的反射属性,以及用一个BSDF实例初始化SurfaceInteraction::bsdf成员变量。(如果材质包含次表面散射, SurfaceInteraction::bssrdf成员变量也应该被初始化,在此先不管)。

另外三个参数,arena,mode,allowMultipleLobes先不管。

ComputeScatteringFunctions()的调用堆栈

因为上层的积分器(如WhittedIntegrator)只能访问到SurfaceInteraction对象,这里介绍一下积分器是如何调用到Material::ComputeScatteringFunctions()接口的,直接贴下相关代码:

Spectrum WhittedIntegrator::Li(const RayDifferential &ray, const Scene &scene,
                               Sampler &sampler, MemoryArena &arena,
                               int depth) const {
    SurfaceInteraction isect;
    if (!scene.Intersect(ray, &isect)) {
        for (const auto &light : scene.lights) L += light->Le(ray);
        return L;
    }

    // Compute scattering functions for surface interaction
    isect.ComputeScatteringFunctions(ray, arena);
    if (!isect.bsdf)
        return Li(isect.SpawnRay(ray.d), scene, sampler, arena, depth);

//........
}

void SurfaceInteraction::ComputeScatteringFunctions(const RayDifferential &ray,
                                                    MemoryArena &arena,
                                                    bool allowMultipleLobes,
                                                    TransportMode mode) {
    ComputeDifferentials(ray);
    primitive->ComputeScatteringFunctions(this, arena, mode,
                                          allowMultipleLobes);
}

void GeometricPrimitive::ComputeScatteringFunctions(
    SurfaceInteraction *isect, MemoryArena &arena, TransportMode mode,
    bool allowMultipleLobes) const {
    if (material)
        material->ComputeScatteringFunctions(isect, arena, mode,
                                             allowMultipleLobes);
}

因为现在还没介绍积分器以及整个渲染流程,这个大致了解一下就可以了吧。

9.2.1 Matte Material(无光泽的材质)

MatteMaterial class如下:

class MatteMaterial : public Material {
  public:
    // MatteMaterial Public Methods
    MatteMaterial(const std::shared_ptr> &Kd,
                  const std::shared_ptr> &sigma,
                  const std::shared_ptr> &bumpMap)
        : Kd(Kd), sigma(sigma), bumpMap(bumpMap) {}
    void ComputeScatteringFunctions(SurfaceInteraction *si, MemoryArena &arena,
                                    TransportMode mode,
                                    bool allowMultipleLobes) const;

  private:
    // MatteMaterial Private Data
    std::shared_ptr> Kd;
    std::shared_ptr> sigma, bumpMap;
};

MatteMaterial需要一个漫反射光谱参数Kd,以及一个代表粗糙度的参数sigma。如果sigma为0,则MatteMaterial会创建一个LambertianReflection BRDF;否则,使用OrenNayar模型。还有一个可选的参数bumpMap,它可以改变表面的法线(也就是shading normal),在9.3节介绍。

MatteMaterial::ComputeScatteringFunctions()

代码如下:

void MatteMaterial::ComputeScatteringFunctions(SurfaceInteraction *si,
                                               MemoryArena &arena,
                                               TransportMode mode,
                                               bool allowMultipleLobes) const {
    // Perform bump mapping with _bumpMap_, if present
    if (bumpMap) Bump(bumpMap, si);

    // Evaluate textures for _MatteMaterial_ material and allocate BRDF
    si->bsdf = ARENA_ALLOC(arena, BSDF)(*si);
    Spectrum r = Kd->Evaluate(*si).Clamp();
    Float sig = Clamp(sigma->Evaluate(*si), 0, 90);
    if (!r.IsBlack()) {
        if (sig == 0)
            si->bsdf->Add(ARENA_ALLOC(arena, LambertianReflection)(r));
        else
            si->bsdf->Add(ARENA_ALLOC(arena, OrenNayar)(r, sig));
    }
}

从代码可以看出,ComputeScatteringFunctions()先判断是否有bumpMap,如果有则根据它计算该点的shading normal(9.3节介绍)。然后从Kd纹理中读取该点的漫反射光谱r,以及从sigma纹理中读出粗糙度sig(Texture相关在下一章介绍)。根据sig来对BSDF添加LambertianReflection或OrenNayar,并返回给si的bsdf参数。

Plastic Material (塑料材质)

Plastic可以用一个diffuse(漫反射)和glossy scattering(高光散射)的混合来建模,通过参数控制特定的颜色(diffuse参数)和镜面高光的大小(glossy scattering参数)。代码如下:

class PlasticMaterial : public Material {
  public:
    // PlasticMaterial Public Methods
    PlasticMaterial(const std::shared_ptr> &Kd,
                    const std::shared_ptr> &Ks,
                    const std::shared_ptr> &roughness,
                    const std::shared_ptr> &bumpMap,
                    bool remapRoughness)
        : Kd(Kd),
          Ks(Ks),
          roughness(roughness),
          bumpMap(bumpMap),
          remapRoughness(remapRoughness) {}
    void ComputeScatteringFunctions(SurfaceInteraction *si, MemoryArena &arena,
                                    TransportMode mode,
                                    bool allowMultipleLobes) const;

  private:
    // PlasticMaterial Private Data
    std::shared_ptr> Kd, Ks;
    std::shared_ptr> roughness, bumpMap;
    const bool remapRoughness;
};

Kd and Ks参数分别控制diffuse reflection和glossy specular reflection的大小。

roughness参数控制specular highlight的大小。有两种方式。第一种,如果remapRoughness参数为true,那么roughness的范围是0到1,roughness越大,则高光的范围越大。另一种,如果remapRoughness为false,roughness参数就直接做为microfacet distribution的α的参数(8.4.2节)。

PlasticMaterial::ComputeScatteringFunctions()

代码还是比较简单,直接贴代码,不分析了:

void PlasticMaterial::ComputeScatteringFunctions(
    SurfaceInteraction *si, MemoryArena &arena, TransportMode mode,
    bool allowMultipleLobes) const {
    // Perform bump mapping with _bumpMap_, if present
    if (bumpMap) Bump(bumpMap, si);
    si->bsdf = ARENA_ALLOC(arena, BSDF)(*si);
    // Initialize diffuse component of plastic material
    Spectrum kd = Kd->Evaluate(*si).Clamp();
    if (!kd.IsBlack())
        si->bsdf->Add(ARENA_ALLOC(arena, LambertianReflection)(kd));

    // Initialize specular component of plastic material
    Spectrum ks = Ks->Evaluate(*si).Clamp();
    if (!ks.IsBlack()) {
        Fresnel *fresnel = ARENA_ALLOC(arena, FresnelDielectric)(1.5f, 1.f);
        // Create microfacet distribution _distrib_ for plastic material
        Float rough = roughness->Evaluate(*si);
        if (remapRoughness)
            rough = TrowbridgeReitzDistribution::RoughnessToAlpha(rough);
        MicrofacetDistribution *distrib =
            ARENA_ALLOC(arena, TrowbridgeReitzDistribution)(rough, rough);
        BxDF *spec =
            ARENA_ALLOC(arena, MicrofacetReflection)(ks, distrib, fresnel);
        si->bsdf->Add(spec);
    }
}
后面的9.2.3节的Mix Material,9.2.4节的Fourier Material,9.2.5节的Additional Material先不分析了,需要再回头看。

9.3 Bump Mapping(凹凸贴图)

你可能感兴趣的:(pbrt笔记--第九章 材质)