《Fundamentals of Computer Graphics》第十四章 基于物理的渲染
<h1 id="开篇">开篇</h1><p> 虽然所有的渲染在某种程度上都是“基于物理”的。但是在实践中,“基于物理”意味着严格遵循物理模型,而不是“基于现象”,前者可以启发式地捕捉主观的感知特征,比如用经验公式把高光放在“正确”的地方。这章涵盖了基于物理的高级渲染,定义了领域中使用的单位和术语,此外还提供了一个可以非常缓慢地生成非常精确的图像的蛮力“路径追踪”算法。在这里,我们不会深入许多渲染算法的细节,但是它们中的绝大多数都可以被看作是对蛮力算法的改进。</p>
<h1 id="光子photons">光子(Photons)</h1>
<p> 为了帮助我们的直觉,我们将使用大量<strong>光子</strong>(<strong>Photon</strong>)的集合来描述辐射度量学。基于这个上下文,这个部分将建立对光子的认知。这里要注意,在图形学中的“光子”和物理学中的不一样,许多物理学家看到研究计算机图形学的人写的“光子追踪”时会感到困惑。对于我们来说,光子只是一个能量包,它的行为遵循几何光学(光以直线传播而且没有波属性)。<br>
更加精确地说,光子对于我们来说是一束有位置、传播方向、波长<span class="math inline">\(\lambda\)</span>的光。波长的<span class="math inline">\(\mathrm{SI}\)</span>的单位是<strong>纳米</strong>(<strong>nanometer</strong>,<strong>nm</strong>)。一个光子也有着速度<span class="math inline">\(c\)</span>只取决于传播所在的介质的折射率<span class="math inline">\(n\)</span>。有时频率<span class="math inline">\(f=c/\lambda\)</span>也会被使用。这很便捷因为不像<span class="math inline">\(\lambda\)</span>和<span class="math inline">\(c\)</span>,<span class="math inline">\(f\)</span>在光子折射到一个新的介质时会保持不变。另一个不变的度量是光子携带的能量<span class="math inline">\(q\)</span>,可以使用下方的公式求出。</p>
<p></p><div class="math display">\[q = hf =\frac{hc}{\lambda}
\]</div><p></p><p>式中的<span class="math inline">\(h=6.63 \times 10^{-34} \mathrm{J \, s}\)</span>,它是普朗克常量。尽管这些量可以在任意单位系统下被测量,但是我们尽可能使用<span class="math inline">\(\mathrm{SI}\)</span>单位。</p>
<h1 id="光滑金属smooth-metals">光滑金属(Smooth Metals)</h1>
<p> 对于光滑金属来说,光线要不被反射要不在折射后被金属快速吸收。被反射的光的比例取决于<strong>菲涅尔方程</strong>(<strong>Fresnel Equations</strong>)。这些方程很简单,但是很麻烦。此外,它们的值会随着光的偏振变化,这在图形学中通常被忽略。菲涅尔方程的主要视觉效果是反射率随着入射角的增加而增加,特别是在掠射角附近时会接近<span class="math inline">\(100\%\)</span>。<br>
几乎所有的图形程序都使用菲涅尔方程的一个简单的近似,它由Schlick开发而来。对于金属,我们通常指定法向反射率<span class="math inline">\(R_0(\lambda)\)</span>。根据菲涅尔方程,反射率应该变化,Schlick开发的一个好的近似公式如下</p>
<p></p><div class="math display">\[R(\theta,\lambda) = R_0(\lambda) + (1 - R_0(\lambda))(1-\cos(\theta))^5
\]</div><p></p><p>其中<span class="math inline">\(\theta\)</span>为光线入射方向和表面法线的夹角。在这里,近似值让我们可以通过数据或人眼来设置金属的法向反射率。</p>
<h1 id="光滑电介质smooth-dielectrics">光滑电介质(Smooth Dielectrics)</h1>
<p> 电介质是那些透明的并折射光的材料,皮肤、牛奶、头发、衣物以及几乎所有日常看到的物质都是电介质,尽管这可能不那么明显,这是因为它们是有着不同折射率的吸光杂质的混合物。但是那些光滑均匀的电介质是透明的,比如玻璃、水、眼镜镜片。对于光滑电介质,有三个重要的属性:</p>
<ol>
<li>
<p>对于每个入射角和波长,有多少光被折射。</p>
</li>
<li>
<p>当光在物质中传播时,对于给定的传播距离和光的波长,光会衰减多少。</p>
</li>
<li>
<p>被反射和折射的光的方向是什么。</p>
</li>
</ol>
<h2 id="电介质的反射率reflectivity-of-a-dielectric">电介质的反射率(Reflectivity of a Dielectric)</h2>
<p> 光是如何弯曲以及有多少比例被反射/透射(折射)取决于物质的折射率<span class="math inline">\(n(\lambda)\)</span>。对于电介质来说,可以使用相同的菲涅尔方程。但是如果另一个物质为空气,我们可以根据<span class="math inline">\(n(\lambda)\)</span>计算法向反射率<span class="math inline">\(R_0(\lambda)\)</span>。</p>
<p></p><div class="math display">\[R_0(\lambda) = \left(\frac{n(\lambda)-1}{n(\lambda)+1} \right)^2
\]</div><p></p><p>如果另一个物质的折射率不为<span class="math inline">\(1.0\)</span>那么可以使用下方的公式</p>
<p></p><div class="math display">\[R_0(\lambda) = \left(\frac{n_t(\lambda)-n_i(\lambda)}{n_t(\lambda)+n_i(\lambda)}\right)^2
\]</div><p></p><p>通常<span class="math inline">\(n\)</span>不随波长变化,但是在涉及色散的应用中,它会随着波长变化。常用的折射率包括水(<span class="math inline">\(n=1.33\)</span>)、玻璃(<span class="math inline">\(n=1.4\)</span>到<span class="math inline">\(n=1.7\)</span>)、钻石(<span class="math inline">\(n=2.4\)</span>)。那些被透射的为没有被反射的那一部分,所以我们不需要显式计算被透射的那一部分。</p>
<h2 id="折射和比尔定律refraction-and-beers-law">折射和比尔定律(Refraction and Beer's Law)</h2>
<p> 电介质会过滤和折射光线,一些玻璃会过滤更多的红光和蓝光,因此呈现出绿色。当光从一个有着折射率<span class="math inline">\(n\)</span>的介质传播到一个新的有着<span class="math inline">\(n_t\)</span>折射率的介质时,有些光会被透射也就是弯曲。下图展示了<span class="math inline">\(n_t > n\)</span>的一个情况<br>
<img src="https://img2024.cnblogs.com/blog/2774734/202509/2774734-20250906024030893-1549509706.png"></p>
<p>斯涅尔定律告诉我们</p>
<p></p><div class="math display">\[n\sin\theta = n_t\sin\phi
\]</div><p></p><p>计算正弦值通常不如余弦值的计算方便,利用恒等式<span class="math inline">\(\sin^2\theta + \cos^2\theta =1\)</span>,可以得到</p>
<p></p><div class="math display">\[\cos^2\phi = 1 - \frac{n^2(1-\cos^2\theta)}{n^2_t}
\]</div><p></p><p>这里要注意,如果<span class="math inline">\(n\)</span>和<span class="math inline">\(n_t\)</span>是颠倒的,那么<span class="math inline">\(\theta\)</span>和<span class="math inline">\(\phi\)</span>也得是,就如上方那张示例图右边展示的那样。<br>
为了把<span class="math inline">\(\sin\phi\)</span>和<span class="math inline">\(\cos\phi\)</span>转化为三维向量,我们可以在由表面法线<span class="math inline">\(\mathbf{n}\)</span>和光线方向<span class="math inline">\(\mathbf{d}\)</span>定义的平面上建立一个二维标准正交基。<br>
<img src="https://img2024.cnblogs.com/blog/2774734/202509/2774734-20250906030216942-368482241.png"></p>
<p>从上图我们可以知道透射方向<span class="math inline">\(\mathbf{t}\)</span>用<span class="math inline">\(\mathbf{n}\)</span>和<span class="math inline">\(\mathbf{b}\)</span>这两个基向量来表示为</p>
<p></p><div class="math display">\[\mathbf{t} = \sin\phi \mathbf{b} - \cos\phi \mathbf{n}
\]</div><p></p><p>我们可以使用相同的基向量来表示<span class="math inline">\(\mathbf{d}\)</span></p>
<p></p><div class="math display">\[\mathbf{d} = \sin\theta \mathbf{b} - \cos\theta \mathbf{n}
\]</div><p></p><p>既然已知<span class="math inline">\(\mathbf{d}\)</span>,那么可以求出<span class="math inline">\(\mathbf{b}\)</span>为</p>
<p></p><div class="math display">\[\mathbf{b} = \frac{\mathbf{d}+\mathbf{n}\cos\theta}{\sin\theta}
\]</div><p></p><p>利用已知量,我们可以求得<span class="math inline">\(\mathbf{t}\)</span>为</p>
<p></p><div class="math display">\[\begin{align*}
\mathbf{t} &= \sin\phi \mathbf{b} - \mathbf{n} \cos\phi \\
&= \frac{n(\mathbf{d}+\mathbf{n}\cos\theta)}{n_t} - \mathbf{n} \cos\phi \\
&= \frac{n(\mathbf{d}-\mathbf{n}(\mathbf{d} \cdot \mathbf{n}))}{n_t} - \mathbf{n} \sqrt{1 - \frac{n^2(1-(\mathbf{d} \cdot \mathbf{n})^2)}{n^2_t}}
\end{align*}
\]</div><p></p><p>观察这个公式你会想开方里的数如果小于<span class="math inline">\(0\)</span>该怎么办,在这种情况下,没有被折射的光线,所有的光线都被反射了。这被称为全内反射(<strong>Total Internal Reflection</strong>),它造就了玻璃物品的丰富外观。<br>
对于均匀杂质,就像在典型的有色玻璃中发现的那样,光线的强度会根据<strong>比尔定律</strong>(<strong>Beer's Law</strong>)衰减。当光线在介质中传播时,它会根据<span class="math inline">\(dI=-CI dx\)</span>来衰减强度,式中的<span class="math inline">\(dx\)</span>为距离。因此,<span class="math inline">\(dI/dx=-CI\)</span>。我们可以解这个式子得到<span class="math inline">\(I=k \mathrm{exp}(-Cx)\)</span>。衰减的程度用RGB衰减常数<span class="math inline">\(a\)</span>描述,它是单位距离的衰减量。加入边界条件,我们知道<span class="math inline">\(I(0)=I_0\)</span>和<span class="math inline">\(I(1)=aI(0)\)</span>,前者意味着<span class="math inline">\(I(x)=I_0 \mathrm{exp}(-Cx)\)</span>,后者意味着<span class="math inline">\(I_0a=I_0\mathrm{exp}(-C)\)</span>,因此<span class="math inline">\(-C=\ln(a)\)</span>。所以最终的公式为</p>
<p></p><div class="math display">\[I(s)=I(0)e^{\ln(a)s}
\]</div><p></p><p>式中的<span class="math inline">\(I(s)\)</span>是光束在距离界面<span class="math inline">\(s\)</span>处的强度。因为<span class="math inline">\(e^{\ln x} = x\)</span>,这个公式也可以被写作</p>
<p></p><div class="math display">\[I(s) = I(0)a^s
\]</div><p></p><p>在实践中,我们通过眼睛逆向得出<span class="math inline">\(a\)</span>,因为这种数据很难被找到。比尔定律的效果如下图所示,在玻璃呈现绿色的地方。<br>
<img src="https://img2024.cnblogs.com/blog/2774734/202509/2774734-20250906035850383-715521311.png"></p>
<p> 这种衰减的效果也适用于透射光,下图展示了这些思想<br>
<img src="https://img2024.cnblogs.com/blog/2774734/202509/2774734-20250906040638667-541392014.png"></p>
<p>要注意的是,光会反复反射和折射,下方为一张示例图<br>
<img src="https://img2024.cnblogs.com/blog/2774734/202509/2774734-20250906040715834-177099590.png"></p>
<p>通常,只有一个或两个反射成像是可见的。</p>
<h1 id="有着次表面散射的电介质dielectrics-with-subsurface-scattering">有着次表面散射的电介质(Dielectrics with Subsurface Scattering)</h1>
<p> 仅仅使用光滑电介质,我们可以渲染非常多的材料。那些看起来“哑光”和不透明的表面可以用多个电介质来模拟。比如一个完美的冰块,它看上去和一块玻璃一样,只是有更低的折射率。现在在冰块里放置很多小的球形空气袋,随着越来越多的空气泡被添加,冰块会变得越来越不透明。<br>
这种由空气或其它物质形成的散射元素,造成了我们所见的大部分不透明性。另一种让冰块看起来不透明的方式是让表面变粗糙。可以通过对表面进行镶嵌细分,然后随机地小幅度扰动顶点的位置做到。这会和磨砂玻璃的效果相似,扰动越粗糙,不透明度就越高。<br>
进一步的复杂性可以通过插入有颜色的粒子,从而激活比尔定律做到。这是一个相当简单并且非常准确的用来模拟油漆的方法。<br>
有一些材料的复杂性可以用微观结构的显式建模来模拟。例如,人的皮肤可以被建模为一层粗糙的表面和几层折射率略有不同的层,还有色素颗粒和血液。</p>
<h1 id="一个蛮力的光子追踪器a-brute-force-photon-tracer">一个蛮力的光子追踪器(A Brute Force Photon Tracer)</h1>
<p> 假设我们建模一个具有微观结构电介质和一个发光物体的场景,我们怎么样最简单地渲染它并生成一张图像呢?在这个部分,我们将会讨论使用蛮力来模拟光子,以及如何从传感器反向发出光子。在图形学中,这确实可以生成一些最好的图片,只需要写一点代码,但是速度会极慢。</p>
<h2 id="传感器sensor">传感器(Sensor)</h2>
<p> 为了生成一张图像,我们必须对图像捕获设备有概念。一个简单的可以是传感器阵列和一个有着孔的盒子,就像针孔相机那样。在阵列中的每个传感器会本质上会充当一个光子计数器。被光子轰击后,传感器阵列会有值可以被写入到图像。那些接收到少量光子的传感器将会写入黑色,那些接收到大量光子的将会写入白色,还有不同的灰度等级在这两个之间。<br>
为了生成一张有颜色的图像,我们可以按照特定的布局在传感器前放置红、绿、蓝滤光片。最简单的滤光片可以是带通滤光片:</p>
<ul>
<li>
<p><span class="math inline">\(\mathbf{蓝光}\)</span> 只在波长<span class="math inline">\(\lambda \in \)</span>内有全响应,其余无响应。</p>
</li>
<li>
<p><span class="math inline">\(\mathbf{绿光}\)</span> 只在波长<span class="math inline">\(\lambda \in \)</span>内有全响应,其余无响应。</p>
</li>
<li>
<p><span class="math inline">\(\mathbf{红光}\)</span> 只在波长<span class="math inline">\(\lambda \in \)</span>内有全响应,其余无响应。</p>
</li>
</ul>
<p>如果初始化传感器为<span class="math inline">\(0\)</span>,当光子通过滤光片击中传感器时,全响应只意味着存储在传感器上的数字会增加。</p>
<h2 id="光子追踪器photon-tracer">光子追踪器(Photon Tracer)</h2>
<p> 为了追踪光子,可以取光源上的一个随机点,然后取一随机方向和一个在400到700纳米之间的随机波长。接着像第四章描述的那样追踪光线。当光线击中一个表面时,计算它的反射率,然后决定是要反射还是折射。通过波长和入射角计算Schlick近似,我们称之为<span class="math inline">\(R\)</span>,接着再生成一个随机数<span class="math inline">\(\xi \in [0,1)\)</span>,于是我们能用如下的代码进行决定。<br>
<img src="https://img2024.cnblogs.com/blog/2774734/202509/2774734-20250906175539246-924431803.png"></p>
<p>如果表面为金属,光在在折射之后会被直接吸收,我们可以从光源发出一条新的光线。<br>
如果光子进入电介质并且比尔定律系数不是<span class="math inline">\(1\)</span>(意味着着电介质就像绿色玻璃那样吸收光)时,那么光子有可能被吸收。我们可以使用相同的技巧在反射和折射之间进行决定。<br>
<img src="https://img2024.cnblogs.com/blog/2774734/202509/2774734-20250906181219376-1082968353.png"></p>
<p>上述的过程已经可以生成非常棒的图像,但是速度会非常慢。</p>
<h2 id="运动模糊和散焦模糊motion-and-defocus-blur">运动模糊和散焦模糊(Motion and Defocus Blur)</h2>
<p> 上方描述的针孔相机将会生成锐利的图像就像真实的针孔相机那样。但是它需要长时间的曝光,因为只有少数光子有机会能通过针孔。<br>
我们可以为相机增加一个镜片来让针孔变大,即建模一系列真实的复合镜片,或是直接插入一个理想的薄镜片。最简单的镜片可以是两个球的交点(“双凸球面透镜”)。它有非常好的成像性能而且很容易实现其与光相交的代码。<br>
一个薄镜片是理想化的无限薄的镜片,而且只需要使用半径和焦距<span class="math inline">\(f\)</span>进行声明。薄镜片可以通过实现以下几个属性来实现,这些属性为:</p>
<ol>
<li>
<p>一条从<span class="math inline">\(\mathbf{p}\)</span>点离开并经过镜片中心的光线不会被弯曲。</p>
</li>
<li>
<p>所有离开<span class="math inline">\(\mathbf{p}\)</span>点击中镜片的光线会汇聚到<span class="math inline">\(\mathbf{q}\)</span>点。</p>
</li>
<li>
<p>物体点<span class="math inline">\(\mathbf{p}\)</span>沿着镜片的<strong>光轴</strong>(<strong>Optical Axis</strong>)的距离<span class="math inline">\(a\)</span>和成像点<span class="math inline">\(\mathbf{q}\)</span>沿着光轴的距离<span class="math inline">\(b\)</span>满足:<span class="math inline">\(1/a+1/b=1/f\)</span>。</p>
</li>
</ol>
<p>下方为这三个属性的一个示例图<br>
<img src="https://img2024.cnblogs.com/blog/2774734/202509/2774734-20250906201532338-2004465285.jpg"></p>
<p> 这里要注意,拥有一个真实或理想的镜片会自动得到真实照片中的那种模糊,它通常被称为<strong>散焦模糊</strong>(<strong>Defocus Blur</strong>)或<strong>景深</strong>(<strong>Depth of Field</strong>)。<br>
为了得到<strong>运动模糊</strong>(<strong>Motion Blur</strong>),也就是运动的物体在照片中的模糊,或者相机在移动时让那些速度和相机不一样的物体变模糊。如果我们支持以下两个特性,那么就能自动得到它,这两个特性分别为:</p>
<ul>
<li>
<p>光子在相机记录的时间间隔中会在随机时间从光源发出。</p>
</li>
<li>
<p>光线追踪器有移动物体的概念,也就是光线相交代码会以时间为参数。</p>
</li>
</ul>
<p>一个简单的移动的物体可以是一个球,它的中心在直线上随时间变化,即</p>
<p></p><div class="math display">\[\mathbf{c}(\mathrm{time})=\mathbf{g}+\mathrm{time}*(\mathbf{h}-\mathbf{g})
\]</div><p></p><h2 id="逆转时间reversing-time">逆转时间(Reversing Time)</h2>
<p> 上方的光子追踪器会良好地工作,但是还是会非常慢,因为即便是加了镜片,大多数光子也不会击中镜片。有时在计算机图形学中会逆转时间,即从相机发射光线,记录那些击中光源的光线。我们为每个像素发出光子,看它们在哪击中了光源。这些光子的波长是通过把颜色滤光片当作发光曲线决定的,当击中了光源时,我们记录光子,并给它一个权重。</p>
<h1 id="辐射度量学radiometry">辐射度量学(Radiometry)</h1>
<p> 上个部分提到的蛮力渲染器在实践中是不可行的。因此,与其建模微观几何,不如建模大量的行为。这是通过使用测量光的实际问题的工具完成的,通常被称作<strong>辐射度量学</strong>(<strong>Radiometry</strong>)。在辐射度量学中出现的术语以及符号可能看起来有点怪。在这个部分最重要的量是<strong>辐亮度</strong>(<strong>Radiance</strong>),大多数学习计算机图形学的人会把它映射到亮度、颜色、强度这种直观的概念,而在实践中,在<span class="math inline">\(99\%\)</span>的情况下这么理解都是对的。但是有时,需要它的明确定义。<br>
虽然我们可以在许多系统中定义辐射单位,但是我们将会使用<span class="math inline">\(\mathrm{SI}\)</span>单位。熟悉的<span class="math inline">\(SI\)</span>单位包括<strong>米</strong>和<strong>千克</strong>。光是能量的一种基本的传播形式,因此使用的是<strong>焦耳</strong>。</p>
<h2 id="光谱能量spectral-energy">光谱能量(Spectral Energy)</h2>
<p> 如果我们有大量的光子,它们的总能量<span class="math inline">\(Q\)</span>是每个光子的能量<span class="math inline">\(q_i\)</span>的和。一个合理的问题是“能量是如何在波长上分布的?”。这个问题可以转化为求<span class="math inline">\(dQ/d\lambda\)</span>,但是我们一直以来把光子当作单独的个体,因此求出来的结果要不是<span class="math inline">\(0\)</span>要不非常大。有两种方法来解决这个问题,第一个是假设<span class="math inline">\(\Delta\lambda\)</span>很小,但是没有小到光的量子性质发挥作用。第二个方法是假设光是连续体而不是一个个光子,在这种情况下可以用<span class="math inline">\(dQ/d\lambda\)</span>求解。这两种方法都是合理的,而且会导致相同的计算机制。<br>
<span class="math inline">\(Q_\lambda\)</span>被称为<strong>光谱能量</strong>,它是一种<strong>强度量</strong>(<strong>Intensive Quantity</strong>)而不是像能量、长度或质量那种<strong>广延量</strong>(<strong>Extensive Quantity</strong>)。强度量可以被思考成密度函数,它会告诉我们广延量在一个无限小的点上的密度。例如,能量<span class="math inline">\(Q\)</span>在某个波长可能是<span class="math inline">\(0\)</span>,但是光谱能量<span class="math inline">\(Q_\lambda\)</span>是有意义的量。<br>
我们将遵循图形学的惯例,在很多时候使用光谱能量,而不是能量。也就是抛弃下标<span class="math inline">\(\lambda\)</span>,直接使用<span class="math inline">\(Q\)</span>来表示光谱能量,使用小写<span class="math inline">\(q\)</span>来表示能量,因此<span class="math inline">\(Q = \Delta q / \Delta \lambda\)</span>。</p>
<h2 id="功率power">功率(Power)</h2>
<p> 在很多情况下计算光源的能量发生率是有用的,这被称为<strong>功率</strong>,它是以瓦特测量的,也就是<strong>焦耳每秒</strong>。在稳定的状态下很容易理解它,但是由于功率是强度量,当能量的发生率随时间变化时也有明确的定义。<br>
和能量一样,我们也对光谱功率(<span class="math inline">\(\mathrm{W(nm)}^{-1}\)</span>)感兴趣。标准的光谱功率符号是<span class="math inline">\(\Phi_\lambda\)</span>,我们将直接使用<span class="math inline">\(\Phi\)</span>。和光谱能量一样的求法,光谱功率实际上就是小份光谱能量<span class="math inline">\(\Delta Q\)</span>除以短时间<span class="math inline">\(\Delta t\)</span>,即<span class="math inline">\(\Phi = \Delta q /(\Delta t \Delta \lambda)\)</span>。</p>
<h2 id="辐照度irradiance">辐照度(Irradiance)</h2>
<p> 当你想问“短时间<span class="math inline">\(\Delta t\)</span>内有多少光击中了这个点?”时,<strong>辐照度</strong>(<strong>Irradiance</strong>)会自然而然出现。当然了,答案是“没有”,反而应该问短时间<span class="math inline">\(\Delta t\)</span>内有多少光击中了表面。而表面短时间内接收到的光应该是光源辐射到表面上的功率<span class="math inline">\(\Phi\)</span>乘以时间<span class="math inline">\(\Delta t\)</span>,因此“短时间<span class="math inline">\(\Delta t\)</span>内有多少光击中了这个点?”实际上求的是能量密度。把能量密度除以时间就有了我们的辐照度,假设表面上的小份面积为<span class="math inline">\(\Delta A\)</span>,那么辐照度<span class="math inline">\(H\)</span>为</p>
<p></p><div class="math display">\[H = \frac{\Delta q}{\Delta A \; \Delta t \Delta \lambda}
\]</div><p></p><p>可以把它理解为单位时间单位面积内的光击中量。当光离开表面某点时,例如被反射时,我们可以把那部分光“收集”起来,“收集”起来的量被称为<strong>辐射出射度</strong>(<strong>Radiant Exitance</strong>),我们用<span class="math inline">\(E\)</span>来表示它。</p>
<h2 id="辐亮度radiance">辐亮度(Radiance)</h2>
<p> 聪明的你可能想到了,表面上每个点接收到的辐照度实际上是由击中这个点的所有光线贡献的,因此是个积分过程!所以<strong>辐亮度</strong>(<strong>Radiance</strong>)就在这里出现了,假设一份小的立体角为<span class="math inline">\(\Delta \sigma\)</span>,那么我们能求出<span class="math inline">\(\Delta H/\Delta \sigma\)</span>。你可能会认为<span class="math inline">\(\Delta H/\Delta \sigma\)</span>就是辐亮度,但是这是不对的,因为<span class="math inline">\(\Delta H/\Delta \sigma\)</span>还和入射角有关。如果辐亮度的入射角<span class="math inline">\(\theta\)</span>越大,那么它对辐照度的贡献就越小,事实证明各个方向入射的辐亮度对辐照度的贡献和<span class="math inline">\(\cos\theta\)</span>线性相关。令辐亮度为<span class="math inline">\(L\)</span>,那么有<span class="math inline">\(L \cos\theta = \Delta H/\Delta \sigma\)</span>,因此辐亮度<span class="math inline">\(L = \Delta H/(\Delta \sigma \cos\theta)\)</span>,即</p>
<p></p><div class="math display">\[\frac{\Delta q}{\Delta A \cos\theta \Delta\sigma \Delta t \Delta\lambda}
\]</div><p></p><p>和辐照度和辐射出射度一样,我们应该区分入射于表面某点的辐亮度和离开表面某点的辐亮度。在图形领域我们称离开表面某点的辐亮度为<strong>表面辐亮度</strong>(<strong>Surface Radiance</strong>),一般用<span class="math inline">\(L_s\)</span>来表示。而对于入射表面某点的辐亮度,我们称为<strong>场辐亮度</strong>(<strong>Field Radiance</strong>),一般用<span class="math inline">\(L_f\)</span>来表示。<br>
之前提到过表面上每点接收到的辐照度实际上是由击中这个点的所有光线贡献的,那么辐照度<span class="math inline">\(H\)</span>就有如下的积分公式</p>
<p></p><div class="math display">\[H = \int_{\mathrm{all} \; \mathbf{k}} L_f(\mathbf{k}) \; \cos\theta \; d\sigma
\]</div><p></p><p>式中的<span class="math inline">\(\mathbf{k}\)</span>为入射方向。</p>
<h1 id="散射的辐射度量学radiometry-of-scattering">散射的辐射度量学(Radiometry of Scattering)</h1>
<p> 之前的光子追踪假设表面上与光线交互的地方都是光滑的,在这里我们假设有潜在的复杂几何体,这种几何体有着精细的几何细节。在实践中,一个区域的大量属性会被平均化来让这个区域表现得像精细的几何体那样,从而无需存储非常多的属性。这个部分最重要的概念就是,对于粗糙表面,与其使用实际的几何来表示所有小的划痕,不如统计地描述划痕,让光滑的表面随机向多个方向反射光线,就好像在表面上有许多不可见的小细节一样。这个函数被称为<strong>双向反射分布函数</strong>(<strong>Bidirectional Reflectance Distribution Function</strong>,<strong>BRDF</strong>)。</p>
<h2 id="brdf">BRDF</h2>
<p> 因为我们对表面的外表感兴趣,因此要描述表面是如何反射光的。在直觉层面上,任何从<span class="math inline">\(\mathbf{k}_i\)</span>方向入射的光都有一部分会被散射到<span class="math inline">\(\mathbf{k}_o\)</span>出射方向的一个小立体角区域。于是我们可以向下方那样进行测量,把小的光源放到<span class="math inline">\(\mathbf{k}_i\)</span>方向,把测量仪放到<span class="math inline">\(\mathbf{k}_o\)</span>方向,对于每一对<span class="math inline">\((\mathbf{k}_i,\mathbf{k}_o)\)</span>,我们取测量仪的读数进行计算。<br>
<img src="https://img2024.cnblogs.com/blog/2774734/202509/2774734-20250907014035739-921523767.png"></p>
<p>此外我们还要在被测量的点放置另一个辐照度测量仪来获取光源的强度,因此反射率为</p>
<p></p><div class="math display">\[\rho = \frac{L_s}{H}
\]</div><p></p><p>比例<span class="math inline">\(\rho\)</span>会随着入射方向<span class="math inline">\(\mathbf{k}_i\)</span>和出射方向<span class="math inline">\(\mathbf{k}_o\)</span>变化,如果我们对所有可能的方向进行测量,那么最终会得到一个四维函数<span class="math inline">\(\rho(\mathbf{k}_i,\mathbf{k}_o)\)</span>。这个函数被称为<strong>双向反射分布函数</strong>(<strong>BRDF</strong>),我们只需要知道这个函数就能描述表面反射光线的方向属性。</p>
<h3 id="方向-半球反射率directional-hemispherical-reflectance">方向-半球反射率(Directional Hemispherical Reflectance)</h3>
<p> 给定一个BRDF,我们就能直接问“入射光有多少比例被反射了?”。然而答案并不简单,被反射的比例还取决于入射光的方向分布。正因为这样,我们可以固定光线入射方向,计算辐射出射度占辐照度的比例。这个比例被称为<strong>方向-半球反射率</strong>(<strong>Directional Hemisphere Reflectance</strong>),我们称之为<span class="math inline">\(R(\mathbf{k}_i)\)</span>,它是通过如下的公式定义的</p>
<p></p><div class="math display">\[R(\mathbf{k}_i) = \frac{E}{H}
\]</div><p></p><p>对于给定的辐照度<span class="math inline">\(H\)</span>,从BRDF的定义我们能知道方向<span class="math inline">\(\mathbf{k}_o\)</span>上的表面辐亮度的计算公式为</p>
<p></p><div class="math display">\[L(\mathbf{k}_o) = H \rho(\mathbf{k}_i,\mathbf{k}_o)
\]</div><p></p><p>此外,通过辐亮度的定义,我们也有</p>
<p></p><div class="math display">\[L(\mathbf{k}_o) = \frac{\Delta E}{\Delta \sigma_o \cos\theta_o}
\]</div><p></p><p>整理一下就能得到</p>
<p></p><div class="math display">\[\frac{\Delta E}{H} = \rho(\mathbf{k}_i,\mathbf{k}_o) \Delta \sigma_o \cos\theta_o
\]</div><p></p><p>为了得到<span class="math inline">\(R(\mathbf{k}_i)\)</span>,我们对所有的出射方向<span class="math inline">\(\mathbf{k}_o\)</span>进行积分</p>
<p></p><div class="math display">\[R(\mathbf{k}_i) = \int_{\mathrm{all} \; \mathbf{k}_o} \rho(\mathbf{k}_i,\mathbf{k}_o) \cos\theta_o \; d\sigma_o
\]</div><p></p><h3 id="理想漫反射brdfideal-diffuse-brdf">理想漫反射BRDF(Ideal Diffuse BRDF)</h3>
<p> 一个理想的漫反射表面被称作<strong>朗伯表面</strong>(<strong>Lambertian Surface</strong>),这种表面会向四周均匀散射入射的光线,也就是说朗伯表面的BRDF为常量。当常量为<span class="math inline">\(C\)</span>时,如果我们要为这个表面计算<span class="math inline">\(R(\mathbf{k}_i)\)</span>,那么有</p>
<p></p><div class="math display">\[\begin{align*}
R(\mathbf{k}_i) &= \int_{\mathrm{all} \; \mathbf{k}_o} C \cos\theta_o \; d\sigma_o \\
&= \int_{\phi_o=0}^{2\pi} \int_{\theta_o=0}^{\pi/2} C \cos\theta_o \sin\theta_o \; d\theta_o \; d\phi_o \\
&= \pi C
\end{align*}
\]</div><p></p><h1 id="传输方程transport-equation">传输方程(Transport Equation)</h1>
<p> 利用BRDF,我们能根据不同方向上的入射辐亮度计算表面的辐亮度。取一小份立体角<span class="math inline">\(\Delta \sigma_i\)</span>,可以知道入射方向<span class="math inline">\(\mathbf{k}_i\)</span>小范围内的辐亮度对表面上某点的辐照度的贡献<span class="math inline">\(H=L_i\cos\theta_i\Delta\sigma_i\)</span>。在这种情况下,BRDF为</p>
<p></p><div class="math display">\[\rho = \frac{\Delta L_o}{L_i\cos\theta_i\Delta\sigma_i}
\]</div><p></p><p>因此我们能求得入射方向<span class="math inline">\(\mathbf{k}_i\)</span>小范围内的辐亮度对出射方向<span class="math inline">\(\mathbf{k}_o\)</span>的辐亮度的贡献</p>
<p></p><div class="math display">\[\Delta L_0 = \rho(\mathbf{k}_i,\mathbf{k}_o) L_i \cos\theta_i \Delta\sigma_i
\]</div><p></p><p>有了这个,我们能计算所有方向上的辐亮度对出射方向<span class="math inline">\(\mathbf{k}_o\)</span>的辐亮度的贡献</p>
<p></p><div class="math display">\[L_s(\mathbf{k}_o) = \int_{\mathrm{all} \; \mathbf{k}_i} \rho(\mathbf{k}_i,\mathbf{k}_o) L_f(\mathbf{k}_i) \cos\theta_i \; d\sigma_i
\]</div><p></p><p>它在计算机图形学中通常被称为<strong>渲染方程</strong>(<strong>Rendering Equation</strong>)。有时,场辐亮度<span class="math inline">\(L_f(\mathbf{k}_i)\)</span>可能来自环境中别的表面,就像下图展示的那样(<span class="math inline">\(L_f(\mathbf{k}_i)=L_s(-\mathbf{k}_i)\)</span>)。<br>
<img src="https://img2024.cnblogs.com/blog/2774734/202509/2774734-20250907050033766-1706694225.png"></p>
<p>图中点<span class="math inline">\(\mathbf{x}^\prime\)</span>对应的立体角公式为</p>
<p></p><div class="math display">\[\Delta \sigma_i = \frac{\Delta A^\prime\cos\theta^\prime}{||\mathbf{x}-\mathbf{x}^\prime||^2}
\]</div><p></p><p>式中的<span class="math inline">\(\Delta A^\prime\)</span>是与<span class="math inline">\(\mathbf{x}^\prime\)</span>相关联的面积,把<span class="math inline">\(\Delta\sigma_i\)</span>替换成<span class="math inline">\(\Delta A^\prime\)</span>可以得到下方的传输方程。</p>
<p></p><div class="math display">\[L_s(\mathbf{x},\mathbf{k}_o) = \int_{\mathrm{all} \; \mathbf{x}^\prime \; \mathrm{visible\;to} \; \mathbf{x}} \frac{\rho(\mathbf{k}_i,\mathbf{k}_o)L_s(\mathbf{x}^\prime,\mathbf{x-x}^\prime)\cos\theta_i\cos\theta^\prime}{||\mathbf{x-x}^\prime||^2} dA^\prime
\]</div><p></p><p>要注意的是,我们使用了非归一化的向量<span class="math inline">\(\mathbf{x-x}^\prime\)</span>来表示从<span class="math inline">\(\mathbf{x}^\prime\)</span>到<span class="math inline">\(\mathbf{x}\)</span>的方向。此外还要注意我们把<span class="math inline">\(L_s\)</span>写成了位置和方向的函数。<br>
上方积分公式的唯一问题就是积分域看起来怪怪的。如果我们能引入一个可见度函数,那么就能在积分域的复杂度和被积函数的复杂度之间权衡。</p>
<p></p><div class="math display">\[L_s(\mathbf{x},\mathbf{k}_o) = \int_{\mathrm{all} \; \mathbf{x}^\prime} \frac{\rho(\mathbf{k}_i,\mathbf{k}_o)L_s(\mathbf{x}^\prime,\mathbf{x-x}^\prime)v(\mathbf{x},\mathbf{x}^\prime)\cos\theta_i\cos\theta^\prime}{||\mathbf{x-x}^\prime||^2} dA^\prime
\]</div><p></p><p></p><div class="math display">\[v(\mathbf{x},\mathbf{x}^\prime) = \begin{cases} 1,如果x和x^\prime是互相可见的 \\ 0,其它情况 \end{cases}
\]</div><p></p><h1 id="蒙特卡洛光线追踪monte-carlo-ray-tracing">蒙特卡洛光线追踪(Monte Carlo Ray Tracing)</h1>
<p> 一旦我们使用积分来表达光照计算,那么我们就能使用<strong>蒙特卡洛积分</strong>来解决它。回想之前对它的描述,蒙特卡洛积分只是许多随机样本的平均值。在光线追踪的情况下,就是为每个像素发出随机光线,模拟其与环境的交互,最终计算出一些辐亮度,把平均辐亮度(颜色)写入图像我们就得到了使用蒙特卡洛光线追踪计算出的图像。具体怎么做可以看看下方提供的伪代码<br>
<img src="https://img2024.cnblogs.com/blog/2774734/202509/2774734-20250907045143701-881076401.png"></p>
<p>上述代码描述了为每个像素发出光线,如果没击中物体那么返回环境颜色,如果击中了物体那么就计算直接光照也就是光源的贡献,此外还要加上周围的物体对被击中物体的贡献,这正是因为</p>
<p></p><div class="math display">\[L_s(\mathbf{x},\mathbf{k}_o) = \int_{\mathrm{all} \; \mathbf{x}^\prime \; \mathrm{visible\;to} \; \mathbf{x}} \frac{\rho(\mathbf{k}_i,\mathbf{k}_o)L_s(\mathbf{x}^\prime,\mathbf{x-x}^\prime)\cos\theta_i\cos\theta^\prime}{||\mathbf{x-x}^\prime||^2} dA^\prime
\]</div><p></p><p>为了计算周围物体对被击中物体的贡献,我们应该先从击中点发射次级光线找到周围的物体,可以像之前提供的代码那样调用函数本身。次级光线击中周围物体后,我们就能计算周围物体对被击中物体的贡献。稍微想一想就会发现,这实际上是另外一些传输方程的积分过程,涉及到发射更低一级的光线。这个过程可以一直进行下去,直到所有光线未击中场景中的物体,返回一个背景颜色。因此蒙特卡洛光线追踪实际上是个递归的积分过程,它模拟的就是现实世界中,从光源出发的光线进入眼睛的逆过程。我们要做的就是不断发出随机光线,让光线在物体之间弹射,只要发出的光线数量够多,求出的平均颜色就能逼近真实值。</p>
<h1 id="小节">小节</h1>
<p> 我滴个妈呀,居然真的写完了。好像花了我45天还多,终于在暑假结束前一天写完了。这下终于可以长叹一口气了,这真得休息一下吧。但是接下来,我想在这个部分总结下。<br>
第一当然是关于收获。翻译完这些重要的章节后,我感觉收获是真的多呀!通过翻译,我发现有一些地方我理解错了,然后还看懂了一些原先不懂的内容,此外对书本知识的掌握也更扎实了。看来读书真的是不止要读,还要理解并记录知识。真的是不积跬步无以至千里,不积小流无以成江河。<br>
第二是关于这本书。一直到基于物理的渲染的十几章感觉还是不错的,我感觉教了很多有用的知识,比如光线追踪、光栅化、图形管线什么的。不过后面的某些章节是真的拉跨,就比如接下来的曲线章节,感觉真的好垃圾,说了半天p话就讲了那么一点知识,真是服了,我在图书馆都看急眼了。而且这章还有好多晦涩的知识,比如<span class="math inline">\(\mathrm{NURBS}\)</span>,作者好像也没怎么解释。除了这个之外,后面的有些章节的一些地方我也看不太懂,特别是隐式建模(Implicit Modeling)这一章,读的我怀疑人生了。应该是自己能力不太行,只能把能掌握的抓牢了。<br>
第三就是关于写作能力。我发现我的表达能力是真的不行,感觉脑袋相关些地方真的是生锈了,有些地方感觉实在是太难翻译了就借助了ChatGPT和有道。不过这也和没时间有关,主要是这次给自己下了死命令必须暑假结束前写完。暑假放松了好久,一时没缓过来,结果只有20多来天时才开始写,于是在某些地方用了ChatGPT和有道。我感觉这样不行,以后应该多靠自己,所以真得猛猛写文章,还要多推敲怎么写,让自己的死脑转起来。不过这次翻译完,感觉写作能力还是提升了不少,明显感觉后面翻译的比前面好。后面得抽点时间,修改一些我不满意的地方。<br>
现在没什么想说的了,此时此刻我只想附上一首诗:</p>
<p><strong>《七律·长征》</strong><br>
红军不怕远征难,万水千山只等闲。<br>
五岭逶迤腾细浪,乌蒙磅礴走泥丸。<br>
金沙水拍云崖暖,大渡桥横铁索寒。<br>
更喜岷山千里雪,三军过后尽开颜。</p>
</div>
<div id="MySignature" role="contentinfo">
<p>本文来自博客园,作者:TiredInkRaven,转载请注明原文链接:https://www.cnblogs.com/TiredInkRaven/p/19076123</p><br><br>
来源:https://www.cnblogs.com/TiredInkRaven/p/19076123
頁:
[1]