pbrt笔记--第五章 颜色和辐射度量学

第5章主要讲了光传输的相关理论以及描述光谱的相关接口。

前言

为了精确描述光如何表达以及在计算机图像中如何被采样,我们需要建立一个radiometry(辐射度量学)--电磁辐射在环境中传输的研究。

人类的可见光的波长大约在380nm~780nm之间。

衡量电磁辐射的四个关键物理量:flux(辐射通量), intensity(辐射强度), irradiance(辐照度), 和 radiance(辐射率)。
这些物理量通过他们的光能量分布spectral power distribution (SPD)来描述,也就是记录每个波长对应的能量。对应的类是SampledSpectrum。

5.1 光谱的展示

SPDs在真实环境中是很复杂的。下图是一个荧光灯的光谱分布和一个柠檬的皮肤的光谱分布。


pbrt笔记--第五章 颜色和辐射度量学_第1张图片

这个光谱对于渲染来说太复杂了,在实践中需要进行一些妥协来平衡精确度和复杂度。有许多函数可以表达光谱的分布,在pbrt中,主要有两中方式,分别对应RGBSpectrum ClassSampledSpectrum Class

5.1.1 The Spectrum Type

对整个分析无影响,直接跳过。

5.1.2 CoefficientSpectrum实现

像上面那两幅光谱图,我们在做渲染计算时肯定是不可能实现的,我们只能用有限个采样值来近似表达它。pbrt中用CoefficientSpectrum的模板类来表达,后面的RGBSpectrum和SampledSpectrum都是继承于它。我们看下它的简化后的代码:

// Spectrum Declarations
template 
class CoefficientSpectrum {
  public:
    // CoefficientSpectrum Public Methods
    CoefficientSpectrum(Float v = 0.f) {
        for (int i = 0; i < nSpectrumSamples; ++i) c[i] = v;
        DCHECK(!HasNaNs());
    }
   //省略..
    
protected:
    // CoefficientSpectrum Protected Data
    Float c[nSpectrumSamples];
}

可以看到它只有一个成员变量c,它是一个数组,存储着代表光谱的有限个采样值。

CoefficientSpectrum的接口都比较简单,都是遍历每个采样值做相对应的运算,不再记录。

5.2 SampledSpectrumass 类

SampledSpectrum在一个波段进行均匀采样来表现一个SPD。这个波段的范围是400nm到700nm--是人类可视光谱。采样数量是60,这个数量在渲染中表示复杂的SPD已经足够。因此,第一个采样值代表的波长范围是 [400, 405),第二个是[405, 410),这个可以根据需要很容易的修改。对应代码如下:

static const int sampledLambdaStart = 400; 
static const int sampledLambdaEnd = 700; 
static const int nSpectralSamples = 60;

SampledSpectrum通过继承CoefficientSpectrum,有了大部分光谱的算法实现。剩下的主要方法是从光谱数据中初始化和把它代表的SPD转化为其他的光谱表达方式(例如RGB)。

我们通常被提供一系列(λi, vi)采样值,第i个采样值vi的波长为λi。一般情况下,这些采样值的波长间隔可能是不规则的,也可能比SampledSpectrum的数量更多或更少。

FromSampled()方法接受一个SPD采样数组v和一个波长数组lambda,用他们构造一个新的SampledSpectrum对象。上面说过,SampledSpectrum对象内存储的波长从400nm~700nm均匀分布的60个采样值,而FromSampled()输入的lambda和v数据有可能是不规则的,FromSampled()就要把这些不规则的采样值它们转换成符合SampledSpectrum的均匀分布的采样值。

FromSampled()方法代码简略如下:

static SampledSpectrum FromSampled(const Float *lambda,
                                      const Float *v, int n) {
    //1.如果输入的光谱数据没有排序,则先排序
    SampledSpectrum r;
    //2.循环生成第i个采样值对应的波长范围,并根据输入的lambda和v计算平均值得到第i个采样值
    for (int i = 0; i < nSpectralSamples; ++i) {
        Float lambda0 = Lerp(Float(i) / Float(nSpectralSamples),
                                 sampledLambdaStart, sampledLambdaEnd);
        Float lambda1 = Lerp(Float(i + 1) / Float(nSpectralSamples),
                                 sampledLambdaStart, sampledLambdaEnd);
        r.c[i] = AverageSpectrumSamples(lambda, v, n, lambda0, lambda1);
    }
} }

首先是用SpectrumSamplesSorted()方法对输入的lambda和v数组进行排序,这个比较简单略过不提。

第二步,首先用Lerp插值接口来得出第i个采样值的波长范围lambda0和lambda1,比如现在默认sampledLambdaStart是400,sampledLambdaEnd是700,nSpectralSamples是60,那么第0个采样值的lambda0就是400,lambda1是405,第1个采样值的lambda0是405,lambda1是410....接下来调用AverageSpectrumSamples()接口来求出波长范围(lambda0,lambda1)的光谱的平均值,如下图:


如上图,上面的实心点就是的波长为lambda,值为v的输入采样值,它们之间的间距可能是不规则的,坐标轴上的波长划分是均匀分布的(每一小段就是lambda0~lambda1),AverageSpectrumSamples()任务就是求出每一个均匀分段对应点平均值。例如,如果要求lambda0=405,lambda1=410范围内的光谱平均值,实际上就是阴影部分的面积除以(lambda1-lambda0)得到。

AverageSpectrumSamples()的内部实现需要考虑许多细节,但仔细阅读的话应该不难,在此略过。

5.2.1 XYZ Color

人类视觉系统有一个显著特性,可以只用三个浮点型数值来描述人类所看到的颜色。三色刺激理论指出,对于人类观察者来说,所有可视SPDs可以用三个值xλ, yλ, and zλ精确代替。给定一个光谱SPD S(λ),这三个值可以分别通过让SPD和三条光谱匹配曲线(spectral matching curves)X(λ), Y (λ), and Z(λ)乘积的积分得到:

pbrt笔记--第五章 颜色和辐射度量学_第2张图片

这三条曲线是国际照明委员会(CIE)通过一系列实现得到的。如下图

pbrt笔记--第五章 颜色和辐射度量学_第3张图片

这三条曲线类似人类视网膜中的三种颜色感知的视锥体。在光谱中分布形状非常不一样的SPDs可能会有非常类似的xλ, yλ, and zλ值。因为对于人类观察者来说,这些SPDs在视觉上看起来是一样的。这叫做条件等色(metamers)。

有了这个理论给了我们表示光能量分布的一个新的思路。许多色彩空间都是在三色刺激理论基础上拓展,使用三个系数来表示来建模人类感知到的颜色。Although XYZ works well ...(后面这段话不太理解,先跳过,影响不大)。

pbrt提供了代表标准X(λ), Y (λ), and Z(λ)曲线的精确数据,它们是波长范围从360nm到830nm以1nm递增的采样值。pbrt工程中以静态数组形式存储,如下:

static const int nCIESamples = 471;
extern const Float CIE_X[nCIESamples];
extern const Float CIE_Y[nCIESamples];
extern const Float CIE_Z[nCIESamples];
extern const Float CIE_lambda[nCIESamples];

const Float CIE_lambda[nCIESamples] = {
    360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374,
    375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389,
    390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404,
    405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419,
    //...
    };

const Float CIE_X[nCIESamples] = {
    // CIE X function values
    0.0001299000f,   0.0001458470f,   0.0001638021f,   0.0001840037f,
    0.0002066902f,   0.0002321000f,   0.0002607280f,   0.0002930750f,
    0.0003293880f,   0.0003699140f,   0.0004149000f,   0.0004641587f,
    0.0005189860f,   0.0005818540f,   0.0006552347f,   0.0007416000f,
    //...
    };
const Float CIE_Y[nCIESamples] = {
    // CIE Y function values
    0.000003917000f,  0.000004393581f,  0.000004929604f,  0.000005532136f,
    0.000006208245f,  0.000006965000f,  0.000007813219f,  0.000008767336f,
    0.000009839844f,  0.00001104323f,   0.00001239000f,   0.00001388641f,
    0.00001555728f,   0.00001744296f,   0.00001958375f,   0.00002202000f,
    0.00002483965f,   0.00002804126f,   0.00003153104f,   0.00003521521f,
        //...
    };
const Float CIE_Z[nCIESamples] = {
    // CIE Z function values
    0.0006061000f,
    0.0006808792f,
    0.0007651456f,
    0.0008600124f,
    //...
    };
    

在没有这种XYZ这种表达方式之前,我们是用SampledSpectrum类的SPD的采样值(成员变量数组c[nSpectrumSamples])来代表我们所看到的某种颜色的,但我们的显示器只能接收RGB数据,因此我们最终是需要把我们的光谱数据转换为RGB数据的。有了上面的XYZ曲线数据,我们就可以先把光谱采样数据转为三个值xλ, yλ, and zλ,然后再把xλ, yλ, and zλ按照某种规则转为RGB数据。

因为上面的曲线数据的波长范围是360nm到830n,采样间隔是1nm,而SampledSpectrum类默认的波长范围是400到700nm,采样间隔是5nm,因此在使用之前还要进行匹配一下,这个工作放在SampledSpectrum的静态函数Init()中,如下:

    static void Init() {
        // Compute XYZ matching functions for _SampledSpectrum_
        for (int i = 0; i < nSpectralSamples; ++i) {
            Float wl0 = Lerp(Float(i) / Float(nSpectralSamples),
                             sampledLambdaStart, sampledLambdaEnd);
            Float wl1 = Lerp(Float(i + 1) / Float(nSpectralSamples),
                             sampledLambdaStart, sampledLambdaEnd);
            X.c[i] = AverageSpectrumSamples(CIE_lambda, CIE_X, nCIESamples, wl0,
                                            wl1);
            Y.c[i] = AverageSpectrumSamples(CIE_lambda, CIE_Y, nCIESamples, wl0,
                                            wl1);
            Z.c[i] = AverageSpectrumSamples(CIE_lambda, CIE_Z, nCIESamples, wl0,
                                            wl1);
        }

        // Compute RGB to spectrum functions for _SampledSpectrum_
        //省略...
    }

匹配方法也是用AverageSpectrumSamples(),与之前类似。计算的结果存在SampledSpectrum的静态变量中,如下:

class SampledSpectrum : public CoefficientSpectrum {
  public:
  //省略

  private:
    // SampledSpectrum Private Data
    static SampledSpectrum X, Y, Z;
    static SampledSpectrum rgbRefl2SpectWhite, rgbRefl2SpectCyan;
    static SampledSpectrum rgbRefl2SpectMagenta, rgbRefl2SpectYellow;
    static SampledSpectrum rgbRefl2SpectRed, rgbRefl2SpectGreen;
    static SampledSpectrum rgbRefl2SpectBlue;
    static SampledSpectrum rgbIllum2SpectWhite, rgbIllum2SpectCyan;
    static SampledSpectrum rgbIllum2SpectMagenta, rgbIllum2SpectYellow;
    static SampledSpectrum rgbIllum2SpectRed, rgbIllum2SpectGreen;
    static SampledSpectrum rgbIllum2SpectBlue;
};

可以看到,除了X, Y, Z,SampledSpectrum中还记录了其他颜色的光谱。

为了计算XYZ系数xλ, yλ, and zλ,根据公式5.1,实际上就是计算一个波长范围内的黎曼积分,如下:


代码如下:

    void ToXYZ(Float xyz[3]) const {
        xyz[0] = xyz[1] = xyz[2] = 0.f;
        for (int i = 0; i < nSpectralSamples; ++i) {
            xyz[0] += X.c[i] * c[i];
            xyz[1] += Y.c[i] * c[i];
            xyz[2] += Z.c[i] * c[i];
        }
        Float scale = Float(sampledLambdaEnd - sampledLambdaStart) /
                      Float(CIE_Y_integral * nSpectralSamples);
        xyz[0] *= scale;
        xyz[1] *= scale;
        xyz[2] *= scale;
    }

其中Y曲线的积分CIE_Y_integral是提前计算好存储起来的。如下:

static const Float CIE_Y_integral = 106.856895;

5.2.2 RGB Color

当我们在一个显示器上显示一种RGB颜色的时候,真正显示出来的颜色的光谱是由显示器的三种光谱响应曲线的加权求和决定的,这三种光谱曲线分别对应红,绿,蓝三种颜色,是由显示器的荧光粉,LED或者LCD单元,或者等离子单元等发射出来的。在图5.4中显示了一个LED显示屏和一个LCD显示屏发射的红,绿,蓝的光谱;它们的形状明显不同。也就是说,同一种RGB颜色值,人们在这两个显示屏上看到的颜色是不一样的。例如图5.5是这两个显示屏显示RGB(0.6, 0.3, 0.2)。不出意外,结果的SPDs非常不一样。


pbrt笔记--第五章 颜色和辐射度量学_第4张图片
pbrt笔记--第五章 颜色和辐射度量学_第5张图片

上面的例子说明,使用用户提供的RGB值来描述一种特定的颜色,只仅仅对他们使用的特定的显示器才有意义

给定一个代表SPD的(xλ, yλ, zλ)系数,我们可以将他们转换为对应的RGB系数,当然还要选定我们想要用的显示器(选定红绿蓝的光谱)。给定一个特定显示器的光谱响应曲线R(λ), G(λ), and B(λ)后,RGB系数可以通过用响应曲线和SPD S(λ)的积分得到:


pbrt笔记--第五章 颜色和辐射度量学_第6张图片

R(λ)X(λ) 乘积的积分可以提前算好,可以写成一个矩阵:


pbrt工程里的RGB光谱用的是高清电视的标准,代码如下:

inline void XYZToRGB(const Float xyz[3], Float rgb[3]) {
    rgb[0] = 3.240479f * xyz[0] - 1.537150f * xyz[1] - 0.498535f * xyz[2];
    rgb[1] = -0.969256f * xyz[0] + 1.875991f * xyz[1] + 0.041556f * xyz[2];
    rgb[2] = 0.055648f * xyz[0] - 0.204043f * xyz[1] + 1.057311f * xyz[2];
}

这个矩阵的逆矩阵可以将RGB转为XYZ,如下:

inline void RGBToXYZ(const Float rgb[3], Float xyz[3]) {
    xyz[0] = 0.412453f * rgb[0] + 0.357580f * rgb[1] + 0.180423f * rgb[2];
    xyz[1] = 0.212671f * rgb[0] + 0.715160f * rgb[1] + 0.072169f * rgb[2];
    xyz[2] = 0.019334f * rgb[0] + 0.119193f * rgb[1] + 0.950227f * rgb[2];
}

有了这些函数,一个SampledSpectrum就可以转换为RGB系数,首先转为XYZ,然后调用XYZToRGB()函数:

    void ToRGB(Float rgb[3]) const {
        Float xyz[3];
        ToXYZ(xyz);
        XYZToRGB(xyz, rgb);
    }

这一节剩下的部分主要是讨论如何将RGB或XYZ转换为一个SPD。书中指出,因为真实世界中的光谱大部分平滑的,所以不能直接将显示器的RGB光谱直接加权求和来得到一个SPD,因为从图5.4中可以看出RGB的光谱是不规则且尖刺的。其实这里我是有些疑惑的,就是如果我就直接对RGB光谱用系数加权求和,得出的颜色看起来是不是正确的呢?我想根据条件等色原则,结果应该也是对的,那么既然是对的,仅仅是应为光谱不平滑,就不能如此计算吗,我猜理由应该没有那么简单,但书中没有细说,查了下资料也没有搜到对这方面详细的介绍,只好先在此记录下疑惑,后面有时间再研究。

后面书中讲了由Smits介绍的将RGB转为SPDs的方法,看了感觉是知其然而不知其所以然,等有时间研究再做详细记录。

5.4 Radiometry(辐射度量学)

Radiometry提供了描述光传播和反射的一系列方法和数学工具。它是本书剩余部分的渲染算法的基础。(可见它的地位是多么重要啊)。当然Radiometry并没有严格遵守物理学,而是基于光的一定的假设和抽象。例如,它没有考虑光的偏振效应。后面巴拉巴拉说了一大堆,直接略过...

在pbrt中,我们假设geometric optics(几何光学)模型已经足够描述光和光的散射。这推导出光的行为一些基本假设,并默认在整个系统中使用:
1.线性两个输入的和 作用于一个光学系统后得到的 输出效果,等于 两个输入 分别作用于一个光学系统所得到的 两个输出效果的和
2.能量守恒:从一个表面或从媒介中反射或折射出来光的能量,不会超过的输入光的能量。
3.忽略光的偏振效应:忽略电磁场的偏振效应,也就是说,光只和波长的分布有关(或频率)。
4.没有荧光性和磷光性:...放弃理解
5.状态稳定:环境中的光已经达到稳定状态,也就是说光辐射分布不会随着时间变化。因为光在真实世界中几乎是瞬时发生的。

5.4.1 基本物理量

有四个光学物理量,是渲染的核心:flux, irradiance/ radiant exitance, intensity, and radiance.它们是通过对能量(单位焦耳)进行时间,面积和方向进行限制而得到。所有这些光学物理量都是依赖于波长。

能量(Energy)

我们从能量开始介绍,能量的单位是焦耳joules(J)。光源发射光子,每个光子有特定的波长,并携带一定的能量。一个波长为λ的光子携带的能量是:


光通量(Flux)

很多时候我们想知道光在一个瞬间的能量。光通量(radiant flux),或者说功率(power),表示在单位时间内通过某个区域的能量。radiant flux可以通过能量变化率除以时间变化率得到:


例如,一个每小时发射能量Q=200000J的灯,如果在这一小时内的所有时间段发射的能量都是相等的,我们可以得到灯的光通量:


相反的,我们可以通过对光通量在一段时间区间做积分,就可以求得总能量:


光源的发射通常是以光通量描述的。图5.6描述从一个点光源发射的光通量,衡量的是穿过包围着光源的一个想象的球面的瞬时能量。注意在5.6图中通过这两个球体的光通量是相同的-----虽然在任意相同面积的区域,通过大球的光通量比小球要小(因为总的光通量相同,大球的面积比小球大)


pbrt笔记--第五章 颜色和辐射度量学_第7张图片

Irradiance and Radiant Exitance(辐照度和辐出度)

光通量是光子在单位时间内通过一定区域的能量。给定一个有限的区域A,我们可以定义通过该区域的平均功率,通过E = / A.。这个物理量叫irradiance(E),到达一个表面的光通量的强度,或者radiant exitance(M),离开一个表面的光通量的强度。它的单位是W/m2。(有时候irradiance也用来表示离开一个表面的光通量,但为了明晰我们使用这两种情况)。

对于图5.6的点光源,在外边的球的某一点上的irradiance比内边的球的一点上的小,因为外边球的表面积更大。如果点光源在所有方向上的光照都相等,那么一个半径为r的球上面的辐照度是


这就解释了从一个点光源出发的能量会随着距离的平方衰减的原因。

更一般的,我们可以用单位面积上的功率来定义某一点的irradiance和Radiant exitance。


可以通过对irradiance在面积上的积分来求功率:


这个irradiance公式也可以帮助我们理解Lamberts law(也就是Lambert漫反射定律),该定律指出到达一个表面的光的能量,和入射光方向及表面法线的夹角的余弦值成正比。(如图5.7)。考虑一个面积为A,光通量为的光源照射到一个表面。如果光垂直向下照到表面上(左图),那么表面上的接收到光的面积A1等于A。在A1内部的任一点的irradiance等于


然而,如果光以某个角度照到表面上,接收到光的区域面积会变大。如果A足够小,那么接收到光通量的面积A2,大约等于A/cosθ。对于在区域A2内的点,它们的irradiance就是


pbrt笔记--第五章 颜色和辐射度量学_第8张图片

Solid Angle and Intensity(立体角和光照强度)

为了定义intensity(光照强度),我们首先需要定义Solid Angle(立体角)。立体角,可以认为是2D中平面上的angles(角度)在3D中球面的扩展。如图5.8,是物体c相对于某个位置p的平面角(planar angle),等于从p处看向c的正好能够包住c的两条边的夹角(原书那句看不懂,这是我自由发挥的...)。或者说,它等于单位圆上的弧线s的长度。
再考虑一个以点p为中心的单位圆,如果我们将某个物体投影到圆弧上,圆上的某段圆弧s会被物体的投影覆盖。这段弧线s的长度(和角度θ相等)就是夹住这个物体的角度。平面角度的单位是radians(弧度)。


pbrt笔记--第五章 颜色和辐射度量学_第9张图片

立体角的概念将2D单位圆扩展到3D单位球面(如图5.9)。物体投影到球面上的面积为s,区域面积s就是夹住物体的立体角。立体角以steradians(sr)立体弧度为单位。整个单位球面的立体角是4π sr,半球对应的立体角是2π sr。


pbrt笔记--第五章 颜色和辐射度量学_第10张图片

在中心为p的单位球面上的点的集合,可以用来表示从点p发射的一系列向量。我们通常用符号ω来表示这些方向,为了方便我们通常把它们转化为normalized vector(也可以理解为单位向量吧)。

考虑现在有一个无穷小的光源发射光子。如果我们把光源放在一个单位球的中心,我们可以计算发射功率的angular density(角密度?)。Intensity,光照强度,符号为I,衡量这个角密度。对于整个球的所有方向,我们有


但通常我们把它限制在某个微小圆锥区域内的方向:


通常我们可以反过来用intensity来求功率,给定以方向ω为自变量的函数I(ωw),我们对I(ω)在一个有限的方向集合 内求积:


Radiance(辐射率)

最后,最重要的 物理量是radiance(辐射率),L。irradianceradiant exitance给出了在某个点p的单位面积的功率,但他们无法区分功率的在方向上的分布。radiance是用来衡量相对于某个立体角的irradianceradiant exitance。定义为:

我们用Eω来标记垂直于入射方向ω的的表面上的irradiance。也就是说,radiance不是衡量irradiance射入的p点所在的表面(而是衡量垂直于入射方向的表面)。实际上,对衡量区域的改变可以减掉来自 Lambert’s law中定义的radiance中的cos θ项。

Radiance是单位面积,单位立体角内的光通量。定义为:

其中dA⊥ 是dA投影在一个假想的垂直于w的平面(如图5.10)。


pbrt笔记--第五章 颜色和辐射度量学_第11张图片

在所有的光学物理量中,radiance是本书剩余部分用的最频繁的。一个直观的原因是,它是所有光学物理量中最基础的,如果给定了radiance,其他所有的值都可以通过对radiance进行在面积或者方向上进行积分。radiancee另一个很好的属性是,它在空间中保持常量。因此在光线跟踪中它一个最本质的物理量。

5.4.2 Incident and Exitant Radiance Functions

只记录一点,就是incident radiance function, Li(p, w)描述到达某一点的radiance,exitant radiance function, Lo(p, ω) 描述离开某一点的radiance。注意这两个方程的w都表示离开表面的方向。如图5.11


pbrt笔记--第五章 颜色和辐射度量学_第12张图片

5.4.3 luminance and photometry(亮度和光度测量学)

所有辐射物理量如光通量,辐射率等等都有对应的photometric measurements (光度测量)。Photometry 是关于与人类视觉系统相关的可见电磁辐射的一门研究。任何光谱的辐射量可以通过与光谱响应曲线V (λ) 的积分来转化为对应的光度值(photometric quantity ),光度值是描述人眼对不同的波长的敏感度。

讲下亮度(Luminance),luminance 亮度描述观察者对一个光谱能量分布有多亮的感觉。例如,亮度考虑到了一个事实:对于有相同能量的绿色波长的光谱看起来比蓝色波长的光谱更亮。

Lumiance Y和光谱的辐射率radiance L(λ) 相关:

有意思的是,其中光谱曲线V (λ) 和XYZ(5.2.1节)中的Y曲线非常近似。上式也可以表示为


(所以绿色(Y曲线)对亮度的贡献很大)。

5.1表列举了一些环境的亮度值。
5.2表列举了和辐射测量和光度量学相关的物理量。


pbrt笔记--第五章 颜色和辐射度量学_第13张图片

5.5 working with radiometric integrals (辐射量的积分)

在渲染中最频繁的任务之一就是计算各种辐射量的积分。这节会展示一些让这个任务更简单的技巧。本章使用计算某一点的irradiance作为例子。在法线为n的表面上的某一点p的irradiance是一系列方向的radiance的积分:

其中Li(p, ω) 是图5.12的入射辐射率,cos θ 是由于在radiance定义中的dA⊥ 。θ 是入射方向ω和法线n的角度。irradiance通常计算一个给定表面的半球范围的方向。

5.5.1 integrals over projected solid angle(投影立体角的积分)

可能是考虑到有人会觉得积分式中的余弦项cos θ不爽吧(其实我觉得还好),因此引入了一个叫投影立体角的东西,如下


就像5.13图,投影立体角是先把物体c投影到单位球上,得到物体的立体角s,然后再把s垂直投影到平面上,投影在平面上的面积就是投影立体角。


pbrt笔记--第五章 颜色和辐射度量学_第14张图片

当然书中也说了,大部分时候书中还是继续用立体角而不是投影立体角,也就是告诉你有这么回事吧…略过。

5.5.2 integrial over spherical coordinates(球面坐标上的积分)

这个比较重要也比较常用。就是把立体角积分转换到球面坐标(θ, φ) 上。首先看图5.14,方向向量(x, y, z) 也可以用球面坐标来表示:


pbrt笔记--第五章 颜色和辐射度量学_第15张图片

为了把立体角的积分转换到(θ , φ) 的积分,首先要弄清楚他们的关系。在单位球面上,dω表示的是一系列方向在球面上构成的一小块平面,那么这小块平面的面积可以用一对(θ , φ) 来表达(如图5.15)。dω对应的微小区域的面积,等于它的两条微小的边的乘积,两条边的长度分别是sin θ dφ 和 dθ,因此,


pbrt笔记--第五章 颜色和辐射度量学_第16张图片

因此,在半球上的irradiance积分可以写成

如果所有的方向的radiance都相等,那么公式可以简化成E = πLi.

为了方便,pbrt定义了两个将θ 和转换到(x, y, z)的函数。直接贴代码了,其中第二个函数是在以向量x,y,z组成的新的坐标轴上进行的转换。

inline Vector3f SphericalDirection(Float sinTheta,
           Float cosTheta, Float phi) {
       return Vector3f(sinTheta * std::cos(phi),
                       sinTheta * std::sin(phi),
                        cosTheta);
}

inline Vector3f SphericalDirection(Float sinTheta, Float cosTheta,
           Float phi, const Vector3f &x, const Vector3f &y,
           const Vector3f &z) {
       return sinTheta * std::cos(phi) * x +
              sinTheta * std::sin(phi) * y + cosTheta * z;
}

5.5.3 integrals over area(在面积上的积分)

最后一个简化积分计算的方法是,将在“方向上的积分”转化为“在面积上的积分”。再次考虑公式5.4,想象有一个四边形的光源,它在所有方向都发射相同的radiance,我们想计算这个光源在表面上的一点p上的irradiance。如果对方向做积分是不直观的,因为不好确定四边形在一个特定的方向是否是可见的。如果在这个四边形的面积上做积分来计算irradiance会更简单。

微分面积和微分立体角的关系为:


其中θ是dA的表面法线和它到点p的向量之间的夹角,r是dA到p的距离(如图5.16)。我们可以这样理解:如果dA和p的距离是1,并且正好垂直于方向dω,那么dω=dA,θ=0。当dA移动到离p很远的地方,或者它旋转了方向不再垂直于方向dω,那么r2和cosθ就相当于补偿dω减少的部分。


pbrt笔记--第五章 颜色和辐射度量学_第17张图片

因此,我们可以把irradiance积分式写为:

image

5.6 surface reflection(表面反射)

个人理解:本章最后一节,终于开始讲到反射,pbrt大部分内容其实就是各种光照反射模型和求反射公式的积分。

下面两段直接翻译书本的,像是个开场白吧...

当光入射到一个表面,表面对光进行散射,将光的一部分反射回环境中。有两种主要效应需要描述和建模:反射光的光谱反射方向的分布。例如,柠檬的皮肤吸收光的蓝色波长的波段,反射大部分红光和绿光。因此当白光照到柠檬上时,它看起来是黄色的。无论你从哪个方向观察它的皮肤,他的颜色基本都是一样的,虽然在某些方向上会出现高光—-在某些区域很亮并且看起来白色多于黄色。作为对比,从镜子某一点反射出来的光几乎完全取决于你的观察方向。在镜子上固定的一点,当观察角度变化时,镜中反射的物体也跟着变化。

对于透明或半透明的反射更加复杂;一系列的材质,如皮肤,花瓣,蜡,液体等会表现光的次表面散射(subsurface light transport),也就是说,光从表面的某一点进入材质,从另一点离开。(例如,你张开嘴巴,用个手电筒照进去,光会穿透你的脸颊的皮肤并从你的脸上发散出光来(小时候经常这么玩!))

有两种描述光反射的机制:BRDFBSSRDF,分别在5.6.1节和5.6.2节介绍。BRDF忽略了次表面散射效应, 主要用在一些次表面散射效应很弱的材质上,这种简化误差小且可以高效实现。BSSRDF包含了BRDF并且描述更多半透明材质的光反射的特性。

5.6.1 The BRDF

个人理解:BRDF其实就是输出辐射率(radiance)和输入辐照度(irradiance)的比值,不同材质有不同的BRDF,套入不同的BRDF公式就可以渲染出不同的表面,至于为什么是radiance与irradiance的比值,而不是radiance与radiance或irradiance与irradiance之类的比值,知乎上大佬有许多文章已经讲了。

BRDF,bidirectional reflectance distribution function 双向反射分布函数,是描述一个表面的反射的一种形式。如图5.18,我们想知道离开表面在观察者方向ω0上的radiance,Lo(p, ωo),它是在输入方向wi的radiance作用的结果,Li(p, ωi) .

pbrt笔记--第五章 颜色和辐射度量学_第18张图片

如果把输入方向ωi看作是一个微分圆锥内的一系列方向,那么p点的微分irradiance就是

在输出方向ωo上的反射微分radiance是由于irradiance的作用。因为在geometric optics 中的线性假设,反射的微分radianceirradiance成正比

这个比例的不变性定义了一对特定方向ωi和ωo上的表面的BRDF:


基于物理的BRDFs有两个重要的特性:

1.互换性(Reciprocity):对于一对方向ωi和ωwo,fr(p,ωi,ωo)=fr(p,ωo,ωi).

2.能量守恒:光反射的能量小于或等于入射光的能量。对于所有的方向ωo


bidirectional transmittance distribution function BTDF ,双向透射分布函数,描述透射光的分布,和BRDF类似。BTDF的符号为ft(p, ωo, ωi) ,其中wi和wo在两个不同的半球。要注意的是,BTDF没有遵循上面的互换性,我们将在8.2和16.1.3节介绍。

为了方便,我们将BRDF和BTDF一起视为f (p, ωo, ωi) ,我们将它称为bidirectional scattering distribution function (BSDF)双向散射分布函数。

根据BSDF的定义,我们有


这里的cosθ项加入了绝对值。这是因为在pbrt中表面法线可能和输入方向wi不是在同一边(许多其他的渲染系统会)。这样做是为了遵循“表面法线假定是指向表面的外侧”。

我们可以对包围p的整个球面的所有输入方向做积分,来计算所有输入方向的输入光作用到p点的总和:


这是渲染的一个最基础的方程;当作用域是整个球面时,通常称为scattering equation 散射方程;当作用域在上半球时,称为reflection equation (反射方程)。求这个积分也是渲染的关键任务,在14章和16章讲解。

The BSSRDF

bidirectional scattering surface reflectance distribution function (BSSRDF)
双向散射表面反射分布函数,还没深入研究过,只简单说下。它和BRDF的一个重要区别就是,它的输入点pi和输出点po并不是在表面的同一点(也就是说一条光线照到表面上一点,会从另一点反射或折射出去)。可想而知它的模型比BRDF更强大,但比BRDF更复杂也更耗时。

你可能感兴趣的:(pbrt笔记--第五章 颜色和辐射度量学)