<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[max's playground.]]></title><description><![CDATA[Though it was hard from the start, we were able to double in size.]]></description><link>http://xx-ma.com/</link><image><url>http://xx-ma.com/favicon.png</url><title>max&apos;s playground.</title><link>http://xx-ma.com/</link></image><generator>Ghost 2.0</generator><lastBuildDate>Mon, 11 May 2026 15:34:39 GMT</lastBuildDate><atom:link href="http://xx-ma.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Realtime Rendering 4th notes - chapter10]]></title><description><![CDATA[<blockquote>
<p>&quot;Light makes right. &quot; — Andrew Glassner</p>
</blockquote>
<p>在第9章中，我们讨论了基于物理的材质理论，以及如何用点源光对它们进行计算。通过此内容，我们可以通过模拟灯光与曲面的交互方式来执行着色计算，以便测量在给定方向上向虚拟相机发送多少辐亮度。 该光谱辐射亮度是场景参考像素颜色，将被转换（第8.2节）到给定像素在最终图像中将具有的显示参考颜色。</p>
<p>实际上，我们需要考虑的互动从不是点源的。我们在第9.13.1节中已经看到，为了正确计算着色，我们必须依照像素的足迹（pixel footprint）计算整个像素覆盖区域上表面BRDF的积分，这是像素区域在表面上的投影。这种集成过程也可以被认为是一种抗锯齿解决方案。我们预先集成，而不是对其频率成分没有约束的着色函数进行采样。</p>
<p>到目前为止，仅呈现了点光源和定向光源的效果，这限制了表面从少数离散方向接收光。这种照明描述不完整。实际上，表面接收来自所有传入方向的光。户外场景不仅仅是被太阳照亮。若是这样，阴影中或背离太阳的所有表面都将是黑色的。天空是由大气中的阳光散射引起的重要光源。通过观察月球的图片可以看到天空的重要性，因为它没有大气层，因此缺少天光。见图10.1。</p>
<p>在阴天，黄昏或黎明时，户外照明都是天空之光。</p>]]></description><link>http://xx-ma.com/rtr4-10/</link><guid isPermaLink="false">5c3ea62f74cbc057b22c32d2</guid><category><![CDATA[notes]]></category><category><![CDATA[graphics]]></category><category><![CDATA[realtime-rendering]]></category><category><![CDATA[code]]></category><dc:creator><![CDATA[Marianax]]></dc:creator><pubDate>Wed, 16 Jan 2019 03:40:16 GMT</pubDate><media:content url="http://xx-ma.com/content/images/2019/01/serge-kutuzov-305979-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<blockquote>
<img src="http://xx-ma.com/content/images/2019/01/serge-kutuzov-305979-unsplash.jpg" alt="Realtime Rendering 4th notes - chapter10"><p>&quot;Light makes right. &quot; — Andrew Glassner</p>
</blockquote>
<p>在第9章中，我们讨论了基于物理的材质理论，以及如何用点源光对它们进行计算。通过此内容，我们可以通过模拟灯光与曲面的交互方式来执行着色计算，以便测量在给定方向上向虚拟相机发送多少辐亮度。 该光谱辐射亮度是场景参考像素颜色，将被转换（第8.2节）到给定像素在最终图像中将具有的显示参考颜色。</p>
<p>实际上，我们需要考虑的互动从不是点源的。我们在第9.13.1节中已经看到，为了正确计算着色，我们必须依照像素的足迹（pixel footprint）计算整个像素覆盖区域上表面BRDF的积分，这是像素区域在表面上的投影。这种集成过程也可以被认为是一种抗锯齿解决方案。我们预先集成，而不是对其频率成分没有约束的着色函数进行采样。</p>
<p>到目前为止，仅呈现了点光源和定向光源的效果，这限制了表面从少数离散方向接收光。这种照明描述不完整。实际上，表面接收来自所有传入方向的光。户外场景不仅仅是被太阳照亮。若是这样，阴影中或背离太阳的所有表面都将是黑色的。天空是由大气中的阳光散射引起的重要光源。通过观察月球的图片可以看到天空的重要性，因为它没有大气层，因此缺少天光。见图10.1。</p>
<p>在阴天，黄昏或黎明时，户外照明都是天空之光。即使在晴朗的日子，当从地球上看时，太阳会对着一个圆锥体，所以它不是无限小的。奇怪的是，太阳和月亮都有相似的角度，大约半度，尽管它们的巨大尺寸差异：太阳的半径比月亮大两个数量级。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C10/10.1.png" alt="Realtime Rendering 4th notes - chapter10"></p>
<center>图10.1</center>
<p>实际上，照明永远不会是点光。无穷小实体在某些情况下可用作廉价近似值，或用作更完整模型的构建块。为了形成更逼真的照明模型，我们需要将BRDF响应整合到表面上的整个入射方向半球上。 在实时渲染中，我们更倾向于通过找到闭合形式的解或其近似来求解渲染方程（第11.1节）所需的积分。 我们通常避免平均多个样本（光线），因为这种方法往往要慢得多。 见图10.2。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C10/10.2.png" alt="Realtime Rendering 4th notes - chapter10"></p>
<center>图10.2</center>
<p>本章致力于探索此类解决方案。特别是，我们希望通过使用各种非功能性光源计算BRDF来扩展我们的着色模型。通常，为了找到廉价的解决方案（或任何根本），我们需要近似光发射器，BRDF或两者。重要的是在感知框架中评估最终的着色结果，了解最终图像中哪些元素最重要，从而为这些元素分配更多的努力。</p>
<p>我们从本章开始，使用公式来集成分析区域光源。这种发射器是场景中的主要光源，负责大部分直接照明强度，因此对于这些，我们需要保留所有选定的材质属性。应该为这样的发射器计算阴影，因为光泄漏将导致明显的伪影。然后，我们研究了表示更一般照明环境的方法，这些环境由进入的半球上的任意分布组成。在这些情况下，我们通常接受更接近的解决方案。环境照明用于大型，复杂但不太强烈的光源。例子包括从天空和云层散射的光，间接光从场景中的大物体反射，以及调光器直接区域光源。这样的发射器对于图像的正确平衡是重要的，否则图像将显得太暗。即使我们考虑间接光源的影响，我们仍然不在全局照明领域（第11章），这取决于场景中其他表面的知识。</p>
<h3 id="101arealightsources">10.1 Area Light Sources（区域光源）</h3>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C10/10.3.png" alt="Realtime Rendering 4th notes - chapter10"></p>
<center>图10.3</center>
<p>在第9章中，我们描述了理想化的无限小光源：点源和定向。图10.3显示了表面点上的入射半球，以及无穷小源和具有非零尺寸的区域光源之间的差异。左侧的光源使用第9.4节中讨论的定义。它从单个方向$l_c$照亮表面。它的亮度由$\boldsymbol{c}_{light}$表示，定义为白色的朗伯表面朝向光线的反射辐射。点方向或方向光对方向$\boldsymbol{v}$的出射辐射$L_o(\boldsymbol{v})$的贡献是$πf(l_c, v)c_{light}(n·l_c)^+$。或者，区域光源（右侧）的亮度由其辐射亮度$L_1$表示。区域光从表面位置对着一个立体角。它对方向 $\boldsymbol{v}$ 的输出辐射的贡献是在$w_l$上的$f(l, v)L_l(n\cdot l)^+$的积分。</p>
<p>无穷小光源背后的基本近似表达式如下：</p>
<p>$$<br>
L_o(\boldsymbol{v})=\int_{\boldsymbol{l}\in\omega_l}f(\boldsymbol{l,v})L_l(\boldsymbol{n\cdot l})^+d\boldsymbol{l}\approx\pi f(\boldsymbol{l}_c,\boldsymbol{v})\boldsymbol{c}_{light}(\boldsymbol{n\cdot l_c})^+.\tag{10.1}<br>
$$</p>
<p>区域光源对表面位置的照射有贡献的量是其辐射亮度($L_1$)和从该位置看到的尺寸($\omega_l$)的函数。正如我们在9.4节中看到的那样，点和方向光源是在实践中无法实现的近似值，因为它们的零立体角意味着无限的辐射。理解近似带来的视觉误差将有助于知道何时使用它，以及在不能使用它时采取的其他方法。这些误差取决于两个因素：光源的大小，从阴影点覆盖的立体角度，以及表面的光泽度。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C10/10.4.png" alt="Realtime Rendering 4th notes - chapter10"></p>
<center>图10.4 从左到右，使用GGX BRDF，球体材质的表面粗糙度增加。最右边的图像复制系列中的第一个，垂直翻转。请注意，低粗糙度材质上的大圆盘光引起的高光和阴影看起来与粗糙材料上较小光源引起的高光相似。</center>
<p>图10.4显示了表面上的镜面高光尺寸和形状如何被材质粗糙度和光源尺寸影响。对于小光源，与视角相比，对着一个微小立体角的光源，误差很小。粗糙表面也倾向于显示光源尺寸小于抛光表面的效果。通常，朝向表面点的区域光发射和表面BRDF的镜面波瓣都是球面函数。如果我们考虑这两个函数的贡献是重要的方向集，我们获得两个立体角。误差的决定因素与发射角的相对大小与BRDF镜面高光立体角大小相比成比例。</p>
<p>最后，请注意区域光的高光可以通过使用点源光并增加表面粗糙度来近似。该观察结果对于获得对区域光积分的成本较低的近似是有用的。它还解释了为什么在实践中许多实时渲染系统仅使用点源光来产生合理的结果：艺术家可以弥补错误。然而，这样做是有害的，因为它将材质特性与特定的照明设置相结合。在改变照明场景时，以这种方式创建的内容看起来不正确。</p>
<p>对于特殊情况下的Lambertian表面，使用点光表示区域光可以是精确的。对于此类表面，出射辐射与辐照度成正比：</p>
<p>$$<br>
L_o(\boldsymbol{v})=\frac{\rho_{ss}}{\pi}E,\tag{10.2}<br>
$$</p>
<p>其中$ρ_{ss}$是次表面散射的反照率或漫反射颜色（第9.9.1节）。这种关系让我们使用等式10.1来计算辐照度更简单：</p>
<p>$$<br>
E=\int_{\boldsymbol{l}\in\omega_l}L_l(\boldsymbol{n\cdot l})^+d\boldsymbol{l}\approx\pi\boldsymbol{c}_{light}(\boldsymbol{n}\cdot\boldsymbol{l}_c)^+.\tag{10.3}<br>
$$</p>
<p>矢量辐照度的概念有助于理解在存在区域光源时辐照度如何表现。矢量辐照度由Gershun引入，他称之为光矢量，并由Arvo进一步扩展。使用矢量辐照度，可以将任意大小和形状的面光源精确地转换为点光源或定向光源。</p>
<p>想象一下，辐射 $L_i$ 的分布进入空间中的点p。见图10.5。我们现在假设 $L_i$ 是与波长无关的，因此可以表示为标量。对于以进入方向 $\boldsymbol{l}$ 为中心的每个无穷小立体角$d\boldsymbol{l}$ ，构造与 $\boldsymbol{l}$ 对齐并且具有等于从该方向进入的（标量）辐射的时间乘以 $d\boldsymbol{l}$ 的向量。最后，将所有这些矢量相加以产生矢量辐照度e：</p>
<p>$$<br>
\boldsymbol{e}(\boldsymbol{p})=\int_{\boldsymbol{l}\in\Theta}L_i(\boldsymbol{p,l})\boldsymbol{l}d\boldsymbol{l},\tag{10.4}<br>
$$</p>
<p>其中Θ表示在整个方向范围内执行积分。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C10/10.5.png" alt="Realtime Rendering 4th notes - chapter10"></p>
<center>图10.5 矢量辐照度的计算。 左：点p被各种形状，大小和辐射分布的光源包围。 黄色的亮度表示发出的辐射量。橙色箭头是指向所有方向的向量，其中存在任何入射辐射，并且每个长度等于来自该方向的辐射量乘以箭头所覆盖的无穷小立体角。原则上应该有无限数量的箭头。右：矢量辐照度（大橙色箭头）是所有这些矢量的总和。矢量辐照度可用于计算点p处任何平面的净辐照度。</center>
<p>矢量辐照度e可用于通过执行点积来找到通过任何方向平面的p处的净辐照度：</p>
<p>$$<br>
E(\boldsymbol{p,n})-E(\boldsymbol{p,-n})=\boldsymbol{n\cdot e(p)},\tag{10.5}<br>
$$</p>
<p>其中n是平面的法线。通过平面的净辐照度(net irradiance)是流过平面的“正侧”（由平面法线n定义）和&quot;负侧&quot;的辐照度之间的差值。就其本身而言，净辐照度对着色没有用。但是，如果没有通过&quot;负侧发射辐射&quot;（换句话说，被分析的光分布没有l和n之间的角度超过90°的部分），则 $E(\boldsymbol{p,-n})=0$ 并且</p>
<p>$$<br>
E(\boldsymbol{p,n})=\boldsymbol{n\cdot e(p)},\tag{10.6}<br>
$$</p>
<p>单个区域光源的矢量辐照度可以与公式10.6一起用于照亮Lambertian表面与任何法线n，只要n与区域光源的任何部分相距不超过90°。见图10.6。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C10/10.6.png" alt="Realtime Rendering 4th notes - chapter10"></p>
<center>图10.6 矢量辐照度的单区光源。在左侧，箭头表示用于计算矢量辐照度的矢量。在右边，大的橙色箭头是矢量辐照度e。红色虚线表示光源的范围，红色矢量（每个垂直于红色虚线之一）定义了一组表面法线的极限。该组外部的法线与区域光源的某些部分的角度大于90°。这样的法线不能使用e来正确计算它们的辐照度。</center>
<p>如果我们假设 $L_i$ 与波长无关，那么在一般情况下我们就不能再定义单个向量e。然而，彩色光通常在所有点处具有相同的相对光谱分布，这意味着我们可以将 $L_i$ 分解为颜色 $\boldsymbol{c}'$ 和与波长无关的辐射分布 $L'_i$ 。在这种情况下，我们可以计算 $\boldsymbol{e}$ 来表示 $L_i$ 并通过将 $\boldsymbol{n·e}$ 乘以 $c'$ 来扩展公式10.6。这样做会产生用于计算定向光源辐照度的相同方程式，具有以下替代：</p>
<p>$$<br>
\begin{align}<br>
\boldsymbol{l}_c&amp;=\frac{\boldsymbol{e(p)}}{\Vert\boldsymbol{e(p)}\Vert},\\<br>
\boldsymbol{c}_{light}&amp;=\boldsymbol{c}'\frac{\Vert\boldsymbol{e(p)}\Vert}{\pi}.<br>
\end{align}<br>
\tag{10.7}<br>
$$</p>
<p>我们已经有效地将任意形状和大小的面光源转换为定向光源而没有引入任何误差。</p>
<p>对于简单情况，可以解析地求解用于求出矢量辐照度的等式10.4。例如，想象一个球面光源，其中心为 $\boldsymbol{p}_l$ ，半径为 $r_l$ 。光从球体上的每个点向所有方向发射恒定的辐射亮度 $L_l$。对于这样的光源，方程10.4和10.7产生以下结果：</p>
<p>$$<br>
\begin{align}<br>
\boldsymbol{l}_c&amp;=\frac{\boldsymbol{p}_l-\boldsymbol{p}}{\Vert\boldsymbol{p}_l-\boldsymbol{p}\Vert},\\<br>
\boldsymbol{c}_{light}&amp;=\frac{r_l^2}{\Vert\boldsymbol{p}_l-\boldsymbol{p}\Vert^2}L_l.<br>
\end{align}<br>
\tag{10.8}<br>
$$</p>
<p>该等式与omni light（第5.2.2节）相同，其中 $c_{light_0} = L_l, r_0 = r_l$ ，以及标准的反平方距离衰减函数。可以调整该衰减函数以考虑球体内的点，并将光影响限制在给定的最大距离。有关此类调整的更多详细信息，请参见第5.2.2节。</p>
<p>所有这一切只有在没有“负面”辐照度的情况下才是正确的。考虑它的另一种方法是区域光源的任何部分都不能“在地平线下”，或被表面遮挡。我们可以概括这个陈述。对于朗伯曲面，区域和点光源之间的所有差异都是由遮挡差异引起的。来自点光源的辐照度遵守对于未被遮挡的所有法线的余弦定律。Snyder得出了球形光源的解析表达式，考虑了遮挡。这个表达非常复杂。然而，由于它仅依赖于两个量（$r/r_l$和 $θ_i$，$n$ 和 $l_c$ 之间的角度），因此可以将其预先计算为二维纹理。Snyder还提供了两个适用于实时渲染的函数近似值。</p>
<p>在图10.4中，我们看到区域照明的效果对粗糙表面不太明显。 这种观察结果还允许我们使用基于物理的较少但仍然有效的方法来模拟朗伯表面上的区域光的效果：包裹照明。 在这种技术中，对 $\boldsymbol{n·l}$ 的值进行了一些简单的修改，然后将其钳制为0。Forsyth给出了一种形式的包裹照明：</p>
<p>$$<br>
E=\pi\boldsymbol{c}_{light}(\frac{(\boldsymbol{n\cdot l})+k_{wrap}}{1+k_{wrap}})^+,\tag{10.9}<br>
$$</p>
<p>其中k环绕范围从0（对于点光源）到1（对于覆盖整个半球的区域光源）。Valve使用另一种模仿大面积光源效果的形式：</p>
<p>$$<br>
E=\pi\boldsymbol{c}_{light}(\frac{(\boldsymbol{n\cdot l})+1}{2})^2,\tag{10.10}<br>
$$</p>
<p>一般来说，如果我们计算区域照明，我们也应该修改我们的着色计算以考虑非点源光。如果我们不这样做，一些视觉效果可以被强烈的阴影消除。软阴影可能是区域光源最明显的效果，如第7章所述。</p>
<h4 id="1011glossymaterials">10.1.1 Glossy Materials</h4>
<p>区域光对非朗伯表面的影响更为复杂。Snyder推导出球形光源的解决方案，但它仅限于原始的反射矢量Phong材料模型，并且非常复杂。在当今的实践中，需要近似值。</p>
<p>区域光在光泽表面上的主要视觉效果是亮点。见图10.4。它的大小和形状类似于区域光，而高光的边缘根据表面的粗糙度模糊。这一观察结果导致了几种经验近似的效应。这些在实践中可能非常有说服力。例如，我们可以修改突出显示计算的结果，以包含创建大平面突出显示区域的截止阈值。这可以有效地产生球形光的镜面反射错觉，如图10.7所示。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C10/10.7.png" alt="Realtime Rendering 4th notes - chapter10"></p>
<center>图10.7</center>
<p>用于实时渲染的区域照明效果的大多数实际近似基于这样的想法：在每个着色点处找到将模仿非无限小光源的效果的等效点源照明设置。这种方法通常用于实时渲染以解决各种问题。这与我们在第9章中在表面的像素占位面积上处理BRDF积分时所见的原理相同。 它产生的近似值通常很cheap，因为所有工作都是通过改变着色方程的输入而不引入任何额外的复杂性来完成的。因为数学没有被改变，我们通常可以保证，在某些条件下，我们会回归到计算原始着色，从而保留其所有属性。 由于大多数典型系统的着色代码都基于点源光，因此区域光源使用这些代码只会引入本地的代码更改。</p>
<p>开发的第一个近似值是Mittring在虚幻引擎的“元素演示”中使用的粗糙度修改。想法是首先找到一个锥体，其中包含大部分光源辐照度到入射到表面的方向的半球上。然后我们拟合镜面周围类似的锥形，包含大多数的BRDF。见图10.8。然后，两个锥体都是半球上的函数的替身，并且它们包含这两个函数具有大于给定的任意截止阈值的方向的集合。这样做之后，我们可以通过找到一个具有不同粗糙度的新BRDF波瓣来近似光源和材料BRDF之间的卷积，该波瓣具有相应的锥体，其立体角等于光瓣角度和材质一的总和。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C10/10.8.png" alt="Realtime Rendering 4th notes - chapter10"></p>
<center>图10.8</center>
<p>Karis展示了Mittring原理在GGX / Trowbridge-Reitz BRDF（第9.8.1节）和球面光源中的应用，导致GGX粗糙度参数 $α_g$ 的简单修改：</p>
<p>$$<br>
\alpha_g'=(\alpha_g+\frac{r_l}{2\Vert\boldsymbol{p}_l-\boldsymbol{p}\Vert})^\mp<br>
$$</p>
<p>请注意在1.2节中介绍的符号 $x^\mp$ 的使用，用于在0和1之间进行clamping。这种近似效果相当好并且非常便宜，但是对于有光泽的，几乎像镜子的材质而言会失败。发生这种失败是因为镜面波瓣总是光滑的并且不能模仿由区域光源在表面上的锐利反射引起的高光。此外，大多数microfacet BRDF模型的波瓣不是“紧凑的”（局部），但表现出较宽的衰减（镜面尾部），使粗糙度重新映射效果较差。见图10.9。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C10/10.9.png" alt="Realtime Rendering 4th notes - chapter10"></p>
<center>图10.9 球形照明。从左到右：通过数值积分，粗糙度修正技术和代表点技术计算的参考解。 （图片由Epic Games Inc.的Brian Karis提供）</center>
<p>不是改变材料粗糙度，另一个想法是用光方向表示区域照明的光源，光方向根据被遮蔽的点而变化。这被称为最具代表性的点解决方案，修改光矢量，使其位于区域光表面上的点的方向上，该点朝向阴影表面产生最大的能量贡献。见图10.9。 Picott使用光线上的点来创建与反射光线最小的角度。卡里斯改进了Picott的公式，通过近似效率，最小角度点与球体上距反射光线最短距离的点。他还提出了一种廉价的配方，可以根据光线的强度进行测量，以保持整体发射的能量。见图10.10。大多数具有代表性的点解决方案都很方便，并且已针对各种光几何形状进行开发，因此了解它们的理论背景非常重要。这些方法类似于蒙特卡洛积分中的重要性采样的概念，其中我们通过对积分域上的样本求平均来数值计算定积分的值。为了更有效地这样做，我们可以尝试优先考虑对总体平均值有很大贡献的样本。</p>
<p>它们有效性的更严格证明在于定积分的中值定理（mean value theorem），它允许我们用同一函数的单个计算来代替函数的积分：</p>
<p>$$<br>
\int_Df(x)dx=f(c)\int_D1.\tag{10.11}<br>
$$</p>
<p>如果f(x)在D中是连续的，则 $\int_D1$ 是域的面积，点 $c\in D$ 位于D中函数minimum和maximum之间的线上。对于照明，我们考虑的积分是BRDF和被光线覆盖的半球区域的光辐照度。我们通常认为我们的光是均匀照射的，因此我们只需要考虑光衰减，并且大多数近似也假设域区域D从阴影点完全可见。即使有这些假设，确定点c和归一化因子 $\int_D1$ 仍然太昂贵，因此采用进一步的近似。</p>
<p>代表点解决方案也可以通过它们对突出显示形状的影响来构建。在表面的一部分上，由于反射矢量位于区域光所对应的方向锥之外，代表点不会发生变化，因此我们可以用点光进行有效照明。 然后，高光的形状仅取决于镜面凸起的底层形状。 或者，如果我们是反射矢量击中区域光的表面上的阴影点，则代表点将连续变化以指向最大贡献的方向。 这样做有效地扩展了镜面波瓣峰值，“扩大”它，这种效果类似于图10.7的硬阈值。</p>
<p>这个宽而恒定的高光峰值也是近似值中剩余的误差源之一。在较粗糙的表面上，区域光反射看起来比地面实况解决方案“更清晰”（即，通过蒙特卡洛积分获得）与粗糙度修改技术的过度模糊相反的视觉缺陷。为了解决这个问题，Iwanicki和Pesce开发了通过将BRDF波瓣，软阈值，代表点参数和比例因子（用于节能）拟合到通过数值积分计算的球面区域照明结果而获得的近似值。这些拟合函数产生一个参数表，该参数表由材料粗糙度，球半径以及光源中心与表面法线和视图矢量之间的角度索引。由于在着色器中直接使用这种多维查找表是昂贵的，因此提供了封闭形式的近似。最近，de Carpentier推出了一种改进的配方，以便在刨光角度更好地保持球形区域光源的高光形状，适用于基于微平面的BRDF。这种方法的工作原理是找到一个代表点，它使表面法线和光线半矢量之间的点积n·h最大化，而不是原始配方（源自Phong BRDF）的n·r。</p>
<h4 id="1012generallightshapes">10.1.2 General Light Shapes（常见的光源形状）</h4>
<p>到目前为止，我们已经看到了一些通过均匀发射球面区域光和任意光泽BRDF来计算阴影的方法。 这些方法中的大多数采用各种近似，以便得到快速实时计算的数学公式，并且因此与问题的地面实况解决方案相比显示出不同程度的误差。然而，即使我们有计算能力来推导出精确解，我们仍然会犯一个大错误，我们将其嵌入到我们的照明模型的假设中。真实世界的灯通常不是球体，它们几乎不是完美的均匀发射体。见图10.11。球面区域的灯在实践中仍然很有用，因为它们提供了最简单的方法来打破点源光引入的照明和表面粗糙度之间的错误关联。然而，球形光源通常是大多数真实灯具的良好近似，只要它们相对较小。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C10/10.11.png" alt="Realtime Rendering 4th notes - chapter10"></p>
<center>图10.11 常用的灯形。从左到右：球体，矩形（卡片），管（线）和具有聚焦发射的管（沿着光表面法线集中，不均匀地散布在半球中）。请注意他们产生的不同高光。</center>
<p>由于基于物理的实时渲染的目标是生成令人信服的，合理的图像，所以只有到目前为止，我们才能通过将自己局限于理想化的场景来实现这一目标。这是计算机图形学中反复出现的权衡。我们通常可以选择生成准确的解决方案，以简化问题，简化假设，或者为更一般的问题推导近似解决方案，从而更紧密地模拟现实。</p>
<p>球形灯最简单的扩展之一是“管”灯（tube light，也称为“胶囊，capsules”），它可用于表示真实世界的荧光灯管。见图10.12。对于Lambertian BRDF，Picott显示了一个封闭形式的照明积分公式，相当于评估线性光线段极值处的两个点光源的照明，并具有适当的衰减函数：</p>
<p>$$<br>
\int^{\boldsymbol{p}_1}_{\boldsymbol{p}_0}(\boldsymbol{n}\cdot\frac{x}{\Vert x\Vert})\frac{1}{\Vert x\Vert^2}dx=\frac{\frac{\boldsymbol{n\cdot p_o}}{\Vert\boldsymbol{p_0}\Vert^2}+\frac{\boldsymbol{n\cdot p_1}}{\Vert\boldsymbol{p_1}\Vert^2}}{\Vert\boldsymbol{p_0}\Vert\Vert\boldsymbol{p_1}\Vert+(\boldsymbol{p_0}\cdot\boldsymbol{p_1})},\tag{10.12}<br>
$$</p>
<p>其中 $p_0$ 和 $p_1$ 是线性光的两个端点，n是表面法线。 Picott还推导出与Phong镜面BRDF积分的代表点解决方案，将其近似为放置在光线段上的点光源的光照，当连接到所考虑的表面点时，形成与该光点的最小角度。 反射向量。 该代表点解决方案将线性光动态地转换为点1，因此我们可以使用球形光的任何近似来将灯具“加厚”成胶囊。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C10/10.12.png" alt="Realtime Rendering 4th notes - chapter10"></p>
<center>图10.12 tube light 使用representative point 方法计算。</center>
<p>与球形灯的情况一样，Karis通过使用与反射矢量的最小距离（而不是最小角度）的线上的点，在Picott的原始解决方案中呈现更有效（但稍微不那么准确）的变体，并呈现 一个缩放公式，试图恢复节能。</p>
<p>许多其他光形状的代表点近似可以相当容易地获得，例如环和Bézier段，但我们通常不希望我们的着色器分支太多。良好的光线形状可以用来代表我们场景中的许多真实世界的灯光。最具表现力的形状类别之一是平面区域光，其被定义为由给定几何形状约束的平面的一部分，例如矩形（在这种情况下它们也被称为卡片灯），盘，或更一般地，多边形。这些基元可用于广告牌和电视屏幕等发光面板，可用于常用的摄影照明（柔光箱，弹跳卡），模拟许多更复杂照明灯具的光圈，或代表从墙壁和其他场景中的大表面物体反射的照明。</p>
<p>Drobot推出了card light（以及disk）的第一个实用近似值。这又是一个代表性的点解决方案，但是由于将该方法扩展到平面的二维区域以及解决方案的整体方法的复杂性，这尤其值得注意。Drobot从均值定理开始，并且作为第一近似，确定用于光评估的良好候选点应该位于照明积分的全局最大值附近。</p>
<p>对于Lambert BRDF，这个积分是：</p>
<p>$$<br>
L_l\int_{\boldsymbol{l}\in\omega_l}(\boldsymbol{n\cdot l})^+\frac1{r^2_\boldsymbol{l}}d\boldsymbol{l},\tag{10.13}<br>
$$</p>
<p>其中 $L_l$ 是由光发射的恒定辐射亮度，$\omega_l$ 是立体角，$r_l$ 是从 $l$ 方向到表面到光平面的光线的长度，$\boldsymbol{(n·l)}^+$ 是Lambertian 中的clamping。$\boldsymbol{(n·l)}^+$的最大值是光区域的边界上的点$p_c$，其最接近点 $p'$ ，$p'$ 通过使来自表面的光线在法线方向上与光平面相交而得到。类似地，最大值 $1/r^2$ 是最靠近点 $p''$ 的边界上的点 $\boldsymbol{p}_r$，该点在光平面上最接近被着色的表面点。见图10.13。然后，被积函数的全局最大值位于连接 $p_r$ 和 $p_c$ 的段上：$\boldsymbol{p}_{max} = t_m\boldsymbol{p}_c +(1-t_m)\boldsymbol{p}_r$，$t_m\in[0,1]$。Drobot使用数值积分来找到许多不同配置的最佳代表点，然后找到一个平均效果最好的单个$t_m$。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C10/10.13.png" alt="Realtime Rendering 4th notes - chapter10"></p>
<center>图10.13 Drobot矩形区域光representative point 的近似的几何构造。</center>
<p>Drobot的最终解决方案采用了漫反射和高光照明的进一步近似，所有这些都是通过与数字上发现的地面实况解决方案进行比较来实现的。他还为纹理卡片灯的重要案例推导出一种算法，其中发射不被认为是恒定的，而是通过纹理在光的矩形区域上调制。使用三维查找表来执行该过程，该查找表包含在不同半径的圆形足迹上的发射纹理的预集成版本。Mittring采用类似的方法进行光泽反射，将反射光线与纹理矩形广告牌相交，并根据光线交叉距离索引预先计算的纹理模糊版本。这项工作先于Drobot的发展，但它是一种更经验，更少原则的方法，试图与地面实况完整解决方案相匹配。</p>
<p>对于平面多边形区域光的更一般情况，Lambert最初为完美漫射表面导出精确的闭合形式解。这种方法由Arvo改进，允许光滑材质建模为Phong镜面反射。Arvo通过将矢量辐照度的概念扩展到更高维度的辐照度张量并使用斯托克定理来解决区域积分作为沿着积分域的轮廓的更简单积分来实现这一点。他的方法所做的唯一假设是，从阴影表面点（通常可以通过用与表面相切的平面剪切光多边形来避开光）完全可见光，并且BRDF是径向对称的余弦。不幸的是，在实践中，Arvo的分析解决方案对于实时渲染而言非常昂贵，因为它需要根据区域光多边形的每个边缘评估其时间复杂度在所使用的Phong波瓣的指数中是线性的公式。最近，Lecocq通过找到轮廓积分函数的O(1)近似并将解扩展到一般的基于半矢量的BRDF，使这种方法更加实用。</p>
<p>到目前为止所描述的所有实际实时区域照明方法都采用某些简化假设以允许推导分析结构和近似来处理所得到的积分。 Heitz等人。采用线性变换余弦 linearly transformed cosines（LTCs，见https://eheitzresearch.wordpress.com/415-2/ ）的不同方法，产生实用，准确和通用的技术。他们的方法开始于在球体上设计一类具有高度表现力的功能（即，它们可以采用多种形状）并且可以容易地集成在任意球形多边形上。LTC仅使用由3×3矩阵转换的余弦波瓣，因此它们可以在半球上调整大小，拉伸和旋转以适应各种形状。一个简单的余弦叶（不像Blinn-Phong，不带指数）与球形多边形的积分已经很成熟，可追溯到兰伯特。 Heitz等人的关键观察结果。make是在叶片上用变换矩阵扩展积分不会改变其复杂性。我们可以通过矩阵的逆变换多边形域并取消积分内的矩阵，返回到简单的余弦作为被积函数。对于通用BRDF和区域光形状，剩下的唯一剩余工作是找到将球体上的BRDF函数表示为一个或多个LTC的方法（近似值），可以离线完成并在使用BRDF参数索引的查找数组中制表：粗糙度，入射角等。线性变换的基于余弦的解决方案既可用于一般纹理多边形区域光源，也可用于专门的，更便宜的计算形状，如卡片，磁盘和线光。LTC可能比代表点解决方案更昂贵，但更准确。</p>
<h3 id="102environmentlighting">10.2 Environment Lighting（环境光照）</h3>
<p>原则上，反射率（公式9.3）不区分直接来自光源的光和从天空散射的间接光或场景中的物体。 所有进入的方向都有辐射，反射方程对它们进行整合。然而，在实践中，直射光通常通过具有高辐射值的相对小的立体角来区分，并且间接光倾向于以中等到低的辐射值漫射地覆盖半球的其余部分。这种拆分提供了很好的实用理由来分别处理这两者。</p>
<p>到目前为止，区域光技术讨论了整合从光的形状发出的恒定辐射。这样做会为每个着色表面点创建一组具有恒定非零入射辐射的方向。我们现在研究的是在所有可能的传入方向上整合由变化函数定义的辐射的方法。见图10.16。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C10/10.16.png" alt="Realtime Rendering 4th notes - chapter10"></p>
<center>图10.16</center>
<p>虽然我们一般会谈到间接和“环境”照明，但我们不打算研究全局照明算法。关键的区别在于，在本章中，所有着色数学都不依赖于场景中其他表面的信息，而是依赖于一小组光基元。因此，例如，虽然我们可以使用区域光来模拟墙壁上的光反射，这是一种全局效应，但着色算法不需要知道墙的存在。 它拥有的唯一信息是光源，所有阴影都是在本地执行的。全局照明（第11章）通常与本章的概念密切相关，因为许多解决方案可以被视为计算用于每个对象或表面位置的正确局部光基元集的方法，以模拟光在场景中来回反弹的相互作用。</p>
<p>环境光是最简单的环境照明模型，其中辐射不随方向变化并且具有恒定值 $L_A$。甚至这种环境照明的基本模型也显着改善了视觉质量。不考虑间接从物体反弹的光的场景看起来非常不现实。 在这样的场景中阴影中或背离光线的物体将是完全黑色的，这与现实中发现的任何场景不同。 第376页的图10.1中的moonscape接近，但即使在这样的场景中，一些间接光也会反射到附近的物体上。</p>
<p>环境光的确切影响取决于BRDF。对于朗伯曲面，固定辐射率 $L_A$ 导致对出射辐射的恒定贡献，无论表面法线n或视图方向$\boldsymbol{v}$ 如何：</p>
<p>$$<br>
L_o(\boldsymbol{v})=\frac{\rho_{ss}}{\pi}L_A\int_{\boldsymbol{l}\in\Omega}(\boldsymbol{n\cdot l})d\boldsymbol{l}=\rho_{ss}L_A.\tag{10.14}<br>
$$</p>
<p>在着色时，这种恒定的出射辐射贡献被添加到来自直接光源的贡献。对于任意BRDF，等效方程为：</p>
<p>$$<br>
L_o(\boldsymbol{v})=L_A\int_{\boldsymbol{l}\in\Omega}f(\boldsymbol{l,v})(\boldsymbol{n\cdot l})d\boldsymbol{l}.\tag{10.15}<br>
$$</p>
<p>该等式中的积分与方向反照率 $R(\boldsymbol{v})$ （9.3节中的公式9.9）相同，因此等式等于 $L_o(\boldsymbol{v})=L_AR(\boldsymbol{v})$。较旧的实时渲染应用程序有时假定 $R(\boldsymbol{v})$ 的常数值，称为环境颜色 $c_{amb}$。这进一步简化了 $L_o(\boldsymbol{v})= \boldsymbol{c}_{amb}L_A$的等式。</p>
<p>反射率方程忽略occlusion，即，将阻止许多表面点“看到”其他物体或同一物体的其他部分的一些进入方向。这种简化通常会降低真实感，但对于环境照明尤其明显，当忽略遮挡时，环境照明看起来非常平坦。解决这个问题的方法将在第11.3节中讨论，特别是在第11.3.4节中讨论。</p>
<h3 id="103sphericalandhemisphericalfunctions">10.3 Spherical and Hemispherical Functions（球面和半球函数）</h3>
<p>为了将环境光照扩展到恒定项以外，我们需要一种方法来表示从任何方向到对象的入射辐射。首先，我们将辐射亮度视为仅是积分方向的函数，而不是表面位置。这样做是基于照明环境无限远的假设。</p>
<p>到达给定点的辐射对于每个进入的方向可以是不同的。照明可以从左边是红色，从右边是绿色，或者从顶部阻挡而不是从侧面阻挡。这些类型的量可以由球面函数（spherical functions）表示，球面函数在单位球体的表面上或在$\mathbb{R}^3$中的方向空间上定义。我们将此域称为$S$。这些函数如何工作不受它们是产生单个值还是多个值的影响。例如，用于存储标量函数的相同表示也可用于编码颜色值通，通过为每个颜色通道存储单独的标量函数。</p>
<p>假设朗伯表面，对于每个可能的表面法线方向，通过存储预先计算的辐照度函数（例如，与余弦波瓣卷积的辐射），可以使用球函数来计算环境光。更复杂的方法存储辐亮度并在运行时根据阴影表面点计算BRDF的积分。球面函数也广泛用于全局照明算法（第11章）。</p>
<p>与球形函数相关的是半球的函数，对于仅定义了一半方向的值的情况。例如，这些函数用于描述在没有来自下方的光的表面处的入射辐射。</p>
<p>我们将这些表示称为球形基础，因为它们是在球体上定义的函数的向量空间的基础。尽管环境/高光/方向形式（第10.3.3节）在技术上不是数学意义上的基础，但为简单起见，我们也将使用这些术语来引用它。将函数转换为给定表示称为投影（projection），并且从给定表示中计算函数的值称为重建（reconstruction）。</p>
<p>每个表示都有自己的权衡取舍。 我们可能在给定的基础上寻求的属性是：</p>
<ul>
<li>高效的编码（投影）和解码（查找）。</li>
<li>使球函数表示为具有少量系数和低重建误差。</li>
<li>投影的旋转不变性是旋转函数的投影的结果，与旋转函数然后投影它相同。 这种等效意味着用例如球谐函数近似的函数在旋转时不会改变。</li>
<li>易于计算编码函数的总和和乘积。</li>
<li>易于计算球形积分和卷积。</li>
</ul>
<h4 id="1031simpletabulatedforms">10.3.1 Simple Tabulated Forms（简单的表格形式）</h4>
<p>表示球形（或半球形）函数的最直接方法是选择多个方向并为每个方向存储一个值。 计算函数涉及在评估方向周围找到一些样本并用某种形式的插值重建该值。</p>
<p>这种表现形式简单而富有表现力。添加或乘以这些球形函数就像添加或乘以相应的列表条目一样简单。通过根据需要添加更多样本，我们可以编码具有任意低误差的许多不同球函数。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C10/10.17.png" alt="Realtime Rendering 4th notes - chapter10"></p>
<center>图10.17</center>
<p>以一种允许有效检索的方式在一个球体上分布样本（见图10.17）并不是微不足道的，同时相对平等地表示所有方向。最常用的技术是首先将球体展开为矩形域，然后使用点网格对该域进行采样。由于二维纹理恰好表示矩形上的点（纹素）网格，我们可以使用纹素作为样本值的底层存储。这样做可以让我们利用GPU加速的双线性纹理过滤来快速查找（重建）。在本章的后面，我们将讨论环境贴图（第10.5节），它们是这种形式的球形函数，并讨论了解展开（unwrapping）球体的不同选项。</p>
<p>列表形式有缺点。在低分辨率下，硬件过滤提供的质量通常是不可接受的。计算卷积的计算复杂度是处理照明时的常见操作，与样本数量成正比，并且可能过高。此外，投影在旋转下不是不变的，这对于某些应用可能是有问题的。例如，想象一下当它撞击物体表面时编码从一组方向发出的光的辐射。如果对象旋转，则编码结果可能会以不同方式重建。这可能导致编码的辐射能量的变化，这可以表现为场景动画的脉动伪像。通过在投影和重建期间采用与每个样本相关联的精心构造的核函数，可以减轻这些问题。但更常见的是，仅使用足够密集的采样就足以掩盖这些问题。</p>
<p>通常，当我们需要存储复杂的高频函数时需要使用表格形式，这些函数需要以低误差编码许多数据点。如果我们需要紧凑地编码球形函数，只需要几个参数，就可以使用更复杂的基础。</p>
<p>环境立方体（AC）是一种流行的基础选择，是最简单的表格形式之一，由沿主轴定向的六个平方余弦瓣构成。它被称为环境“立方体”，因为它相当于将数据存储在立方体的面中，并在我们从一个方向移动到另一个方向时进行插值。对于任何给定方向，只有三个波瓣是相关的，因此不需要从内存中获取其他三个波瓣的参数。在数学上，环境立方体可以定义为：</p>
<p>$$<br>
F_{AC}(\boldsymbol{d})=\boldsymbol{d}d\cdot sel_+(\boldsymbol{c}_+,\boldsymbol{c}_-,\boldsymbol{d}),\tag{10.16}<br>
$$</p>
<p>其中 $\boldsymbol{c}_+$ 和 $\boldsymbol{c}_-$ 包含立方体面的六个值，而 $sel_+(\boldsymbol{c}_+,\boldsymbol{c}_-,\boldsymbol{d})$ 是一个向量函数，它为每个组件假定一个来自 $\boldsymbol{c}_+$ 或 $\boldsymbol{c}_-$ 的值，基于相应的组件 $\boldsymbol{d}$ 是否是正值。</p>
<p>环境立方体类似于立方体贴图（第10.4节），每个立方体面上都有一个纹素。在某些系统中，针对此特定情况在软件中执行重建可能比在立方体贴图上使用GPU的双线性过滤更快。斯隆推导出一种简单的公式，可以在环境立方体和球谐基础之间进行转换（第10.3.2节）。</p>
<p>使用环境立方体的重建质量相当低。 通过存储和插值八个值而不是六个值（对应于立方体顶点），可以获得稍微好一些的结果。 最近，Iwanicki和Sloan提出了另一种称为环境骰子（ambient dice ，AD）的方案。 基础由沿二十面体顶点定向的平方和四次幂余弦形成。存储的十二个值中有六个用于重建，并且确定检索哪六个的逻辑比环境立方体的相应逻辑稍微复杂一些，但结果的质量要高得多。</p>
<h4 id="1032sphericalbases">10.3.2 Spherical Bases（球面基础）</h4>
<p>有许多方法可以将函数投影（编码）到使用固定数量的值（系数）的表示中。我们所需要的只是一个数学表达式，它跨越我们的球形域，我们可以改变一些参数。然后，我们可以通过拟合来近似我们想要的任何给定函数，即，找到最小化我们的表达式和给定函数之间的误差的参数的值。</p>
<p>最小的可能选择是使用常量：</p>
<p>$$<br>
F_c(\theta,\phi)=c\cdot1.<br>
$$</p>
<p>我们可以通过在单位球的表面积上对其求平均来推导给定函数f的投影：$c =\frac1{4π}\int_Ωf(θ,\phi)$。周期函数的平均值c也称为$DC$分量。这个基具有简单的优点，甚至尊重我们正在寻找的一些属性（易于重建，添加，产品，旋转不变性）。然而，它不能很好地表达大多数球函数，因为它只是用它们的平均值代替它们。我们可以使用两个系数a和b构造一个稍微复杂的近似：</p>
<p>$$<br>
F_{hemi}(\theta,\phi)=a+\frac{\cos(\theta)+1}2(b-a),<br>
$$</p>
<p>它创建了一个可以在极点处编码精确值的表示，并且可以在球体表面之间进行插值。这种选择更具表现力，但现在投影更复杂，并且对于所有旋转都不是不变的。事实上，这个基础可以看作是一个表格形式，只有两个样本，放在两极。</p>
<p>通常，当我们讨论函数空间的基础时，我们意味着我们有一组函数，其线性组合（加权和求和）可用于表示给定域上的其他函数。 这个概念的一个例子如图10.18所示。 本节的其余部分探讨了可用于近似球体上的函数的一些基础选择。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C10/10.18.png" alt="Realtime Rendering 4th notes - chapter10"></p>
<center>图10.18，基函数的基本例子。在这种情况下，对于0到5之间的输入，空间是为0到1之间的值的函数。左图显示了这样一个函数的示例。中间的图显示了一组基函数（每种颜色是不同的 右图显示了目标函数的近似值，通过将每个基函数乘以权重并将它们相加而形成。基函数按其各自的权重进行缩放。黑线表示求和的结果， 是原始函数的近似值，以灰色显示以进行比较。</center>
<h5 id="sphericalradialbasisfunctions">Spherical Radial Basis Functions</h5>
<p>使用GPU硬件过滤的表格形式的低质量重建至少在某种程度上是由用于内插样本的双线性形状函数引起的。其他功能可用于对样本进行加权以进行重建。这些功能可以产生比双线性滤波更高质量的结果，并且它们可以具有其他优点。通常用于此目的的一类功能是球形径向基函数（spherical radial basis functions，SRBF）。它们是径向对称的，这使得它们仅具有一个参数的功能，即它们所朝向的轴与评估方向之间的角度。该基础由一组称为波瓣（lobes）的这样的函数形成，这些函数遍布球体。函数的表示由每个波瓣的一组参数组成。这个集合可以包括它们的方向，但它使投影更加困难（需要非线性，全局优化）。由于这个原因，通常假定波瓣方向是固定的，在球体上均匀分布，并且使用其他参数，例如每个波瓣的大小或其扩展，即所覆盖的角度。通过计算给定方向的所有波瓣并对结果求和来执行重建。</p>
<h5 id="sphericalgaussians">Spherical Gaussians</h5>
<p>SRBF波瓣的一个特别常见的选择是球面高斯（SG），在方向统计中也称为von Mises-Fisher分布。我们应该注意到von-Mises-Fisher分布通常包括归一化常数，我们在公式中避免使用。单个lobe可以定义为：</p>
<p>$$<br>
G(\boldsymbol{v,d},\lambda)=e^{\lambda(\boldsymbol{v\cdot d}-1)},\tag{10.17}<br>
$$</p>
<p>其中v是计算方向（单位矢量），d是波瓣方向轴（分布的平均值，也是归一化的），λ≥0是波瓣锐度（控制其角宽度，也称为concentration parameter或spread）。</p>
<p>为了构造球面基，我们使用给定数量的球面高斯的线性组合：</p>
<p>$$<br>
F_G(\boldsymbol{v})=\sum_kw_kG(\boldsymbol{v,d_k},\lambda_k).\tag{10.18}<br>
$$</p>
<p>将球函数投影到该表示中需要找到参数集 ${w_k,\boldsymbol{d}_k,\lambda_k }$ 最小化重建误差。该过程通常通过数值优化来完成，通常使用非线性最小二乘法优化算法（例如Levenberg-Marquardt）。请注意，如果我们允许完整集参数在优化过程中发生变化，我们就不会使用函数的线性组合，因此公式10.18不代表基础。只有当我们选择一组固定的lobe（方向和点差）时才能获得适当的基础，以便整个域被很好地覆盖，并通过仅拟合权重 $w_k$ 来执行投影。这样做也极大地简化了优化问题，因为现在可以将其表述为普通最小二乘优化。如果我们需要在不同的数据集（投影函数）之间进行插值，这也是一个很好的解决方案。对于该用例，允许波瓣方向和锐度变化是有害的，因为这些参数是高度非线性的。</p>
<p>这种表示的优势在于 $SGs$ 上的许多操作都具有简单的解析形式。两个球面高斯的乘积是另一个球面高斯：</p>
<p>$$<br>
G_1G_2=G(\boldsymbol{v},\frac{\boldsymbol{d}'}{\Vert\boldsymbol{d}'\Vert},\lambda'),<br>
$$</p>
<p>where:</p>
<p>$$<br>
\boldsymbol{d}'=\frac{\lambda_1\boldsymbol{d}_1+\lambda_2\boldsymbol{d}_2}{\lambda_1+\lambda_2},;;\lambda'=(\lambda_1+\lambda_2)\Vert\boldsymbol{d}'\Vert.<br>
$$</p>
<p>球体高斯在球体上的积分也可以通过分析计算：</p>
<p>$$<br>
\int_\Omega G(\boldsymbol{v})d\boldsymbol{v}=2\pi\frac{1-e^{2\lambda}}{\lambda},<br>
$$</p>
<p>这意味着两个球面高斯相乘的积分也有一个简单形式的公式。</p>
<p>如果我们可以将光辐射表示为球面高斯，那么我们可以将其乘积与以相同表示编码的BRDF集成以执行照明计算。 由于这些原因，SGs已被用于许多研究项目以及工业应用。</p>
<p>至于平面上的高斯分布，可以推广von Mises-Fisher分布以允许各向异性。徐等人。引入了各向异性球面高斯（ASGs, 见图10.19），它是通过用两个补充轴t和b增加单向d来定义的，它们共同形成一个正交切线框架：</p>
<p>$$<br>
G(\boldsymbol{v},[\boldsymbol{d,t,b}],[\lambda,\mu])=S(\boldsymbol{v,d})e^{-\lambda(\boldsymbol{v\cdot t})^2-\mu(\boldsymbol{v\cdot b})^2},\tag{10.19}<br>
$$</p>
<p>其中λ, μ≥0控制沿切线框的两个轴的波瓣扩散，$S(v,d)=(v·d)^+$是平滑项。 这个项是定向统计中使用的Fisher-Bingham分布与我们用于计算机图形的ASG之间的主要区别。 徐等人。 还为积分，乘积和卷积运算符提供分析近似。</p>
<p>虽然SG具有许多理想的属性，但它们的一个缺点是，与表格形式不同，并且在一般内核（带宽）有限的情况下，它们具有全局支持。每个波瓣对于整个球体都是非零的，即使其衰减相当快。 这个全局范围意味着如果我们使用N个lobe来表示一个函数，我们将需要它们中的所有N个用于任何方向的重建。</p>
<p>Spherical Harmonics（球谐波）</p>
<p>球谐函数（SH）是球面上的一组正交基函数。正交基函数集是一组，使得来自该集的任何两个不同函数的内积为零。内积是点乘的更一般但相似的概念。两个向量的内积是它们的点积：分量对之间的乘积之和。通过考虑这些函数的积分，我们可以类似地推导出两个函数的内积的定义：</p>
<p>$$<br>
\langle f_i(x),f_j(x) \rangle \equiv \int f_i(x)f_j(x)dx,\tag{10.20}<br>
$$</p>
<p>积分在相关域上执行的。对于图10.18中所示的函数，相关域在x轴上介于0和5之间（请注意，此特定函数集不是正交的）。对于球形函数，形式略有不同，但基本概念是相同的：</p>
<p>$$<br>
\langle f_i(\boldsymbol{n}),f_j(\boldsymbol{n}) \rangle \equiv \int_{\boldsymbol{n}\in\Theta} f_i(\boldsymbol{n})f_j(\boldsymbol{n})d\boldsymbol{n},\tag{10.21}<br>
$$</p>
<p>其中 $\boldsymbol{n}\in\Theta$ 表示在单位球上执行积分。</p>
<p>标准正交集(orthonormal set)是一个正交集（orthogonal set），即集合中任何函数和它自身的内积等于1。更正式地说，函数集 $f_j()$是otrhonormal的条件是：</p>
<p>$$<br>
\langle f_i(),f_j() \rangle =<br>
\begin{cases}<br>
0, ;where ;i\ne j,\\<br>
1, ;where ;i= j.<br>
\end{cases}<br>
\tag{10.22}<br>
$$</p>
<p>图10.20显示了一个类似于图10.18的示例，其中基函数是标准正交的。 注意，图10.20中所示的标准正交基函数不重叠。 这种情况对于非负函数的标准正交集是必要的，因为任何重叠都意味着非零内积。 在其范围的一部分上具有负值的函数可以重叠并且仍然形成正交集。 这种重叠通常导致更好的近似，因为它允许碱基是平滑的。 具有不相交域的基础往往会导致不连续。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C10/10.20.png" alt="Realtime Rendering 4th notes - chapter10"></p>
<center>图10.20，正交基函数。此示例使用与图10.18相同的空间和目标函数，但基函数已被修改为标准正交。 左图显示目标函数，中间显示基本函数的正交集，右图显示缩放基函数。对目标函数的最终近似显示为黑色虚线，并且原始函数以灰色显示以进行比较。</center>
<p>标准正交基的优点是找到与目标函数最接近的近似的过程是直截了当的。为了执行投影，每个基函数的系数是目标函数 $f_{target}()$ (具有适当的基函数)的内积：</p>
<p>$$<br>
k_j=\langle f_{target}(x),f_j(x) \rangle,\\<br>
f_{target}(x)\approx\sum_{j=1}^nk_jf_j().\tag{10.23}<br>
$$</p>
<p>在实践中，该积分必须通过数值计算，通常通过蒙特卡罗采样，平均在球体上均匀分布的n个方向。</p>
<p>正交基础在概念上类似于对于三维向量的“标准基”。标准基的目标不是函数，而是一个点的位置。标准基由三个向量组成（每个向量一个） 通过公式10.22中使用的相同定义的标准基。标准基上的点投影的方法也是相同的，因为系数是位置之间的点积的结果。维度）而不是一组函数。 矢量和基矢量。一个重要的区别是标准基准精确地再现每个点，而有限的基函数集只能近似其目标函数。结果可以是精确的，因为标准基础使用三个基矢量来表示三个  - 维空间。函数空间具有无限多个维度，因此有限数量的基函数永远无法完美地表示它。</p>
<p>球谐波是正交的和标准正交的，并且它们具有其他几个优点。 它们是旋转不变的，并且SH基函数的评估成本低廉。它们是单位长度矢量的x，y和z坐标中的简单多项式。然而，像球形高斯一样，它们具有全局支持，因此在重建期间需要计算所有基函数。基函数的表达式可以在几个参考文献中找到，包括斯隆的演示文稿。他的演讲值得注意，因为它讨论了许多处理球谐函数的实用技巧，包括公式和某些情况下的着色器代码。最近，斯隆还推出了有效的SH重建方法。</p>
<p>SH基函数排列在频带中。第一个基函数是常数，接下来的三个是在球体上缓慢变化的线性函数，接下来的五个函数表示稍微更快变化的二次函数。见图10.21。低频率（即在球体上缓慢变化）的函数，例如辐照度值，用相对较少数量的SH系数精确表示（我们将在第10.6.1节中看到）。</p>
<p>当投射到球谐函数时，得到的系数表示投影函数的各种频率的幅度，即其频谱。在该谱域中，基本性质成立：两个函数的乘积的积分等于函数投影的系数的点积。此属性允许我们有效地计算光照积分。</p>
<p>关于球谐函数的许多操作在概念上是简单的，沸腾到系数向量上的矩阵变换。在这些操作中，重要的是计算两个函数的乘积投射到球谐函数，旋转投影函数，计算卷积。在实践中SH中的矩阵变换意味着这些操作的复杂性在所使用的系数的数量上是二次的，这可能是相当大的成本。幸运的是，这些矩阵通常具有可以被利用来设计更快算法的特殊结构。Kautz等人提出了一种通过将旋转计算分解为围绕x轴和z轴的旋转来优化旋转计算的方法。 Hable给出了一种用于快速旋转低阶SH投影的流行方法。Green的调查讨论了如何利用旋转矩阵的块结构来加快计算速度。目前，现有技术的特点是分解为纬向谐波，如Nowrouzezahrai等人所提出的。</p>
<p>如下所述的光谱变换（例如球谐波和H-basis）的常见问题是它们可以表现出称为振铃的视觉伪像（也称为吉布斯现象）。如果原始信号包含无法用带限近似表示的快速变化，则重建将显示振荡。在极端情况下，这种重建功能甚至可以产生负值。可以使用各种预过滤方法来解决该问题。<br>
$$<br>
<br>
$$</p>
<h5 id="othersphericalrepresentations">Other Spherical Representations</h5>
<p>许多其他表示可以使用有限数量的系数来编码球面函数。线性变换后的余弦（第10.1.2节）是能够有效逼近BRDF函数的表示的一个例子，同时具有易于在球体的多边形截面上可积的特性。</p>
<p>球面小波是平衡空间中的局部性（具有紧凑支撑）和频率（平滑性）的基础，允许压缩表示高频功能。球形分段常数基函数将球体划分为具有恒定值的区域，并且依赖于矩阵因子分解的双聚类近似也已用于环境照明。</p>
<h4 id="1033hemisphericalbases">10.3.3 Hemispherical Bases</h4>
<p>尽管上述基础可用于表示半球函数，但它们是浪费的。 信号的一半总是等于零。在这些情况下，通常优选使用直接在半球形域上构建的表示。这与在曲面上定义的函数尤其相关：BRDF，入射辐射和到达对象的给定点的辐照度都是常见的例子。这些函数自然地受限于以给定表面点为中心并与表面法线对齐的半球; 它们没有指向对象内部的方向的值。</p>
<h5 id="ambienthighlightdirection">Ambient/Highlight/Direction</h5>
<p>沿着这些线的最简单的表示之一是一个恒定函数和一个方向的组合，其中信号在半球上最强。它通常被称为环境/高光/方向（Ambient/Highlight/Direction，AHD）基础，其最常见的用途是存储辐照度。名称AHD表示各个组件代表的含义：恒定的环境光，加上单个方向光，近似于“高亮”方向的辐照度，以及大部分入射光聚集的方向。AHD基础通常需要存储8个参数。方向矢量使用两个角度，环境和定向光强度使用两种RGB颜色。第一个值得注意的用途是在Quake III游戏中，动态物体的体积照明以这种方式存储。从那时起用于几个称号，例如“使命召唤”franchise。</p>
<p>以这种表示的投射有点棘手。 因为它是非线性的，所以找到接近给定输入的最佳参数在计算上是昂贵的。在实践中，使用启发式方法代替。信号首先投射到球谐波，最佳线性方向用于定向余弦波瓣。给定方向，可以使用minimalsquares最小化来计算环境和高光值。 Iwanicki和Sloan展示了如何在执行非负性的同时执行此投影。</p>
<h5 id="radiositynormalmappinghalflife2basis">Radiosity Normal Mapping/Half-Life 2 Basis</h5>
<h5 id="hemisphericalharmonicshbasis">Hemispherical Harmonics/H-Basis</h5>
<p>hemispherical harmonics (HSHs) ...暂略</p>
<h3 id="104environmentmapping">10.4 Environment Mapping</h3>
<p>在一个或多个图像中记录球形函数称为环境映射，因为我们通常使用纹理映射来实现表中的查找。这种表现形式是最强大和最受欢迎的环境照明形式之一。与其他球形表示相比，它消耗更多内存，但实时解码简单快速。此外，它可以表示任意高频率的球形信号（通过增加纹理的分辨率）并准确地捕获任何环境辐射范围（通过增加每个通道的位数）。这样的准确性是有代价的。 与存储在其他常用纹理中的颜色和着色器属性不同，存储在环境贴图中的辐射值通常具有高动态范围。每个纹素的位数越多意味着环境贴图比其他纹理占用更多的空间，并且访问速度可能更慢。</p>
<p>我们对任何全局球函数（即，用于场景中的所有对象的函数）具有基本假设，即入射辐射 $L_i$ 仅取决于方向。这种假设要求被反射的物体和光线很远，反射器不会反射自身。</p>
<p>依赖于环境映射的着色技术通常不具有表示环境照明的能力，而在于我们如何将它们与给定材料集成。也就是说，为了进行整合，我们必须在BRDF上使用哪种近似和假设？反射映射是环境映射的最基本情况，我们假设BRDF是一个完美的镜像。光学平坦的表面或镜子将入射光线反射到光的反射方向 $r_i$（第9.5节）。类似地，输出辐射亮度包括来自仅一个方向的入射辐射，即反射视图矢量 r。该向量的计算方式与 $r_i$ 相同（公式9.15）：</p>
<p>$$<br>
\boldsymbol{r}=2(\boldsymbol{n\cdot v})\boldsymbol{n}-\boldsymbol{v}.\tag{10.28}<br>
$$</p>
<p>对于镜像的反射方程大大简化：</p>
<p>$$<br>
L_o(\boldsymbol{v})=F(\boldsymbol{n,r})L_i(\boldsymbol{r}),\tag{10.29}<br>
$$</p>
<p>其中F是菲涅耳项（第9.5节）。请注意，与基于半矢量的BRDF（使用半矢量h和l或v之间的角度）中的菲涅耳项不同，公式10.29中的菲涅耳项使用表面法线n和反射矢量r之间的角度（这与n和v之间的角度相同）。</p>
<p>由于入射辐射 $L_i$ 仅取决于方向，因此可以将其存储在二维表中。这种表示使我们能够有效地照亮具有任意入射辐射分布的任何形状的镜面状表面。我们通过为每个点计算r并在表中查找辐射来实现。该表称为environment map，由Blinn和Newell介绍。见图10.23。</p>
<p>reflection mapping 算法的步骤如下：</p>
<ul>
<li>生成/加载表示环境的纹理。</li>
<li>对于每个包含反射对象的像素，计算该对象表面位置的法线。</li>
<li>从v和n计算reflected view vector 。</li>
<li>使用reflected view vector 计算环境贴图中的索引，该索引表示反射视图方向上的入射辐射。</li>
<li>使用环境贴图中的纹素数据作为公式10.29中的入射辐射。</li>
</ul>
<p>值得一提的环境映射的潜在问题是。使用环境映射时，比较平的面通常不能很好地工作。平坦表面的问题是从其反射的光线通常不会变化超过几度。这种紧密的聚类导致环境表的一小部分被映射到相对较大的表面上。在第11.6.1节中讨论的也使用发射辐射的位置信息的技术可以提供更好的结果。此外，如果我们假设完全平坦的表面，例如地板，则可以使用平面反射的实时技术（第11.6.2节）。</p>
<p>用纹理数据照亮场景的想法也称为基于图像的照明（image-based lighting，IBL），通常当使用捕获360度全景时，HDR图像的相机从真实世界场景获得环境地图。</p>
<p>有多种投影函数可将反射的视图矢量映射到一个或多个纹理中。我们在这里讨论比较流行的映射，并注意每个映射的优势。</p>
<h4 id="1041latitudelongitudemapping">10.4.1  Latitude-Longitude Mapping</h4>
<p>1976年，Blinn和Newell开发了第一个环境映射算法。他们使用的映射是在地球上使用的熟悉的纬度/经度系统，这就是为什么这种技术通常被称为纬度 - 经度映射或纬度 - 长度映射的原因。他们的计划不像是从外面看的地球，而是像夜空中的星座图。 就像地球上的信息可以展平为墨卡托或其他投影地图一样，可以将围绕空间中的点的环境映射到纹理。 当针对特定表面位置计算反射视图矢量时，矢量被转换为球面坐标 $(ρ,\phi)$ 。这里的φ，相当于经度，从0到2π弧度变化，ρ，纬度，从0到π弧度变化。 对 $(ρ,\phi)$ 由公式10.30计算，其中 $\boldsymbol{r} =(\boldsymbol{r}_x, \boldsymbol{r}_y, \boldsymbol{r}_z)$ 是归一化的反射视图向量，其中+ z为上方向：</p>
<p>$$<br>
\rho=arccos(r_z);and;\phi=atan2(r_y,r_x).\tag{10.30}<br>
$$</p>
<p>有关atan2的说明，请参见第一章。然后，这些值用于访问环境贴图并检索在反射视图方向上看到的颜色。请注意，纬度 - 经度映射与Mercator projection不同。它使纬度线之间的距离保持恒定，而Mercator在极点处变为无穷大。</p>
<p>将球体展开到平面中总是需要一些变形，特别是如果我们不允许多次切割，并且每个投影在保留区域，距离和局部角度之间都有自己的权衡。 这种映射的一个问题是信息密度远不一致。 从图10.25的顶部和底部的极端拉伸可以看出，极点附近的区域比赤道附近的区域接收更多的纹素。 这种失真是有问题的，不仅因为它不会导致最有效的编码，而且在采用硬件纹理滤波时也会导致伪像，特别是在两极奇点处可见。 滤波内核不遵循纹理的拉伸，因此在具有较高纹理像素密度的区域中有效地收缩。 还要注意，尽管投影数学很简单，但它可能效率不高，因为诸如反余弦的超越函数在GPU上是昂贵的。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C10/10.25.png" alt="Realtime Rendering 4th notes - chapter10"></p>
<center>图10.25</center>
<h4 id="1042spheremapping">10.4.2 Sphere Mapping</h4>
<p>最初由威廉姆斯提及，由米勒和霍夫曼独立开发，球体映射是一般商业图形硬件支持的第一种环境贴图技术。纹理图像是从完全反射的球体中正交观察的环境外观得出的，因此这种纹理称为球体图。制作真实环境的球体地图的一种方法是拍摄闪亮球体的照片，例如圣诞树装饰物。 见图10.26。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C10/10.26.png" alt="Realtime Rendering 4th notes - chapter10"></p>
<center>图10.26</center>
<p>得到的圆形图像也称为光探测器，因为它捕获球体位置处的照明情况。即使我们在运行时使用其他编码，拍摄球形探针也是捕获基于图像的照明的有效方法。如果捕获具有足够的分辨率来克服方法之间的失真差异，我们总是可以在球形投影和另一种形式之间进行转换，例如稍后讨论的立方体映射（第10.4.3节）。</p>
<p>反射球体在整个球体的正面显示整个环境。它将每个反射的视图方向映射到该球体的二维图像上的点。假设我们想要转向另一个方向，即在球体地图上给出一个点，我们会想要反射的视图方向。为此，我们将在该点处取球面上的曲面法线，然后生成反射的视图方向。因此，为了反转过程并从反射的视图矢量获取球体上的位置，我们需要在球体上导出曲面法线，然后生成访问球体贴图所需的$(u, v)$参数。</p>
<p>球体的法线是反射视图矢量r和原始视图矢量v之间的半角矢量，它是球体图的空间中的(0,0,1)。见图10.27。该法向量n是原始和反射视图矢量的总和，即 $(r_x, r_y, r_z + 1)​$。归一化此向量会使单位正常：</p>
<p>$$<br>
\boldsymbol{n}=(\frac{r_x}m,\frac{r_y}m,\frac{r_z+1}m),;where;m=\sqrt{r_x^2+r_y^2+(r_z + 1)^2}.\tag{10.31}<br>
$$</p>
<p>如果球体位于原点并且其半径为1，则单位法线的坐标也是球体上法线的位置h。我们不需要 $h_z$，因为（hx，hy）描述了球体图像上的一个点，每个值都在[-1,1]范围内。要将此坐标映射到范围[0,1）以访问球体贴图，请将每个坐标除以2并添加一半：</p>
<p>$$<br>
m=\sqrt{r_x^2+r_y^2+(r_z + 1)^2},;u=\frac{r_x}{2m}+0.5,;and;v=\frac{r_y}{2m}+0.5.\tag{10.32}<br>
$$</p>
<p>与纬度 - 经度映射相比，球体映射计算起来更简单，并且显示了位于图像圆边缘周围的一个奇点。缺点是球体贴图纹理捕获仅对单个视图方向有效的环境视图。此纹理确实捕获整个环境，因此可以计算新查看方向的纹理坐标。然而，这样做会导致视觉伪影，因为球形图的小部分由于新视图而变得放大，并且边缘周围的奇点变得明显。在实践中，球形图通常被假定为跟随相机，在视图空间中操作。</p>
<p>由于球面图是针对固定视图方向定义的，因此原则上球面图上的每个点不仅定义反射方向，还定义表面法线。见图10.27。对于任意各向同性BRDF，可以求解反射方程，并且其结果可以存储在球形图中。该BRDF可包括漫反射，镜面反射，逆反射和其他项。只要照明和视图方向固定，球形图就是正确的。甚至可以使用在实际照射下的真实球体的摄影图像，只要球体的BRDF是均匀的和各向同性的。</p>
<p>也可以索引两个球面图，一个具有反射矢量，另一个具有曲面法线，以模拟镜面反射和漫反射环境效果。 如果我们调整存储在球体贴图中的值以考虑表面材料的颜色和粗糙度，我们有一种廉价的技术可以产生令人信服的（尽管与视图无关的）材料效果。 这种方法由雕刻软件Pixologic ZBrush推广为“MatCap”着色。见图10.28。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C10/10.28.png" alt="Realtime Rendering 4th notes - chapter10"></p>
<center>图10.28</center>
<h4 id="1043cubemapping">10.4.3 Cube Mapping</h4>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C10/10.29.png" alt="Realtime Rendering 4th notes - chapter10"></p>
<p>1986年，Greene推出了立方体环境地图，通常称为立方体地图。 这种方法是当今最流行的方法，其投影直接在现代GPU上的硬件中实现。 通过将环境投影到立方体的侧面来创建立方体贴图，该立方体的中心位于相机的位置。 然后将立方体面上的图像用作环境贴图。 立方体贴图通常在“交叉”图中可视化，即打开立方体并将其展平到平面上。但是，在硬件立方体贴图上存储为六个方形纹理，而不是单个矩形纹理，因此不会浪费空间。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C10/10.31.png" alt="Realtime Rendering 4th notes - chapter10"></p>
<center>图10.31</center>
<p>通过在立方体中心使用相机渲染场景六次，以90°视角观察每个立方体面，可以合成地创建立方体贴图。见图10.31。为了从真实环境生成立方体贴图，通常通过拼接或专用相机获取的球形全景图被投影到立方体贴图坐标系中。</p>
<p>与球体映射不同，立方体环境映射与视图无关（view-independent）。它还具有比纬度 - 经度映射更加均匀的采样特性，纬度 - 经度映射与赤道相比对极点进行过采样。Wan等人提出了一种称为 $isocube$ 的映射，它具有比立方体映射更低的采样率差异，同时仍然利用立方体映射纹理硬件来提高性能。</p>
<p>访问立方体贴图非常简单。任何矢量都可以直接用作三分量纹理坐标，以便在指向的方向上获取数据。因此，对于反射，我们可以将反射的视图矢量r传递给GPU，甚至不需要对其进行标准化。在较旧的GPU上，双线性过滤可以显示沿立方体边缘的接缝，因为纹理硬件无法在不同的立方体面上正确过滤（执行起来有些昂贵的操作）。开发了回避这个问题的技术，例如使视图投影更宽一些，以便单个面也包含这些相邻的纹素。所有现代GPU现在都可以跨边缘正确执行此过滤，因此不再需要这些方法。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C10/10.32.png" alt="Realtime Rendering 4th notes - chapter10"></p>
<center>图10.32 Cube map unwrapping of a sphere (left) compared to octahedral unwrapping (right).</center>
<h4 id="1044otherprojections">10.4.4 Other Projections</h4>
<p>今天，立方体贴图是环境照明最受欢迎的表格表示，因为它具有多功能性，再现高频细节的准确性以及GPU上的执行速度。 但是，已经开发了一些值得一提的其他投影。</p>
<p>Heidrich和Seidel建议使用两个纹理来执行双抛物面环境映射（dual paraboloid environment mapping）。这个想法就像是球体映射的想法，但不是通过记录球体环境的反射来生成纹理，而是使用两个抛物线投影。每个抛物面创建一个类似于球体地图的圆形纹理，每个抛物面覆盖一个环境半球。</p>
<p>与球体映射一样，反射的视图光线在地图的基础上，即在其参照系中计算。反射视图矢量的z分量的符号用于决定要访问的两个纹理中的哪一个。访问函数是</p>
<p>$$<br>
u=\frac{r_x}{2(1+r_z)}+0.5,;v=\frac{r_y}{2(1+r_z)}+0.5\tag{10.33}<br>
$$</p>
<p>对于正面图像，对于背面图像，对于 $r_z$ 具有符号反转。</p>
<p>与球形图相比，抛物线图具有更均匀的纹理像素采样，甚至是立方体图。但是，必须注意在两个投影之间的接缝处进行适当的采样和插值，这使得访问双抛物面图更加昂贵。</p>
<p>八面体映射（Octahedral mapping）是另一个值得注意的投影。它不是将周围的球体映射到立方体，而是映射到八面体（见图10.32）。为了将这种几何形状展平成纹理，将其八个三角形面切割并布置在平面上。可以是正方形或矩形配置。如果我们使用方形配置，访问八面体映射的数学非常有效。 给定反射方向r，我们使用绝对值 $L_1$ 计算标准化版本：</p>
<p>$$<br>
\boldsymbol{r'}=\frac{\boldsymbol{r}}{\vert r_x\vert+\vert r_y\vert+\vert r_z\vert}.<br>
$$</p>
<p>对于 $r'_y​$ 为正的情况，我们可以将方形纹理索引为：</p>
<p>$$<br>
u=r_x'\cdot0.5+0.5,v=r_y'\cdot0.5+0.5.\tag{10.34}<br>
$$</p>
<p>在 $r'_y$ 为负的情况下，我们需要用变换将“八面体的后半部分”向外折叠</p>
<p>$$<br>
u=(1-\vert r'_z\vert)\cdot sign(r_x')\cdot0.5+0.5,;v=(1-\vert r_x'\vert)\cdot sign(r'_z)\cdot0.5+0.5.\tag{10.35}<br>
$$</p>
<p>八面体映射不会受到双抛物面映射的滤波问题的影响，因为参数化的接缝对应于所使用的纹理的边缘。纹理“环绕”采样模式可以自动从另一侧访问纹素并执行正确的插值。虽然投影的数学稍微涉及，但实际上性能更好。引入的失真量类似于立方体的数量当不存在立方体贴图纹理硬件时，八面体贴图可能是一个很好的选择。另一个值得注意的用途是仅使用两个坐标表示三维方向（标准化向量），作为压缩（第16.6节）。</p>
<p>对于围绕轴径向对称的环境地图的特殊情况，Stone提出了使用单个一维纹理的简单因子分解，该纹理沿着来自对称轴的任何子午线存储辐射值。 他将这个方案扩展到二维纹理，在每一行中存储一个预先与不同的Phong波瓣卷积的环境地图。 这种编码可以模拟各种材料，并用于编码晴朗天空发出的辐射。</p>
<h3 id="105specularimagebasedlighting">10.5 Specular Image-Based Lighting</h3>
<p>虽然环境贴图最初是作为渲染镜像表面的技术而开发的，但它也可以扩展为光泽反射。当用于模拟无限远光源的一般镜面反射效果时，环境贴图也称为镜面光探测器。使用该术语是因为它们捕获场景中给定点处的所有方向的辐射（因此探测），并且使用该信息来评估一般BRDF，不仅是纯镜子或朗伯曲面的受限情况。名称specular cube maps 也用于将环境光照存储在立方体贴图中的常见情况，这些立方体贴图已被操纵以模拟光泽材质上的反射。</p>
<p>为了模拟表面粗糙度，可以对纹理中的环境表示进行预滤波。通过模糊环境贴图纹理，我们可以呈现镜面反射，看起来比完全镜像反射更粗糙。这种模糊应该以非线性方式进行，即纹理的不同部分应该模糊不同。需要这种调整，因为环境贴图纹理表示具有到理想的方向球面空间的非线性映射。两个相邻纹素的中心之间的角距离不是恒定的，立体角也不是由单个纹理像素覆盖。预处理立方体贴图的专用工具，例如AMD的CubeMapGen（现在是开源），在过滤时会考虑这些因素。来自其他面的相邻样本用于创建mipmap链，并且每个纹素的角度范围都被考虑在内。图10.33显示了一个示例。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C10/10.33.png" alt="Realtime Rendering 4th notes - chapter10"></p>
<center>图10.33</center>
<p>模糊环境贴图，同时凭经验接近粗糙表面的外观，与实际的BRDF无关。更有原则的方法是在考虑给定的表面法线和视图方向时考虑BRDF函数在球体上采用的形状。然后，我们使用此分布过滤环境贴图。见图10.34。过滤带有镜面波瓣的环境贴图并非易事，因为BRDF可以采用任何形状，具体取决于其粗糙度参数以及视图和法线向量。输入值的至少五个维度（粗糙度和视图和法线方向各自的两个极角）控制得到的波瓣形状。为每个选择存储几个环境贴图是不可行的。</p>
<h4 id="1051prefilteredenvironmentmapping">10.5.1 Prefiltered Environment Mapping</h4>
<p>应用于光泽材料的环境照明预过滤的实际实施需要使用BRDF的近似值，以便得到的纹理是独立于视图和法向量的。如果我们将BRDF的形状变化仅限制为材质的光泽度，我们可以计算并存储与粗糙度参数的不同选择相对应的一些环境贴图，并选择在运行时使用的适当的环境贴图。在实践中，这意味着限制我们使用的模糊核，并因此限制波瓣形状，以围绕反射矢量径向对称。</p>
<p>想象一下，一些光线从给定的反射视图方向附近进入。直接来自反射视图方向的光将产生最大贡献，随着入射光的方向与反射视图方向的不同而下降。环境贴图纹理区域的面积乘以纹素的BRDF贡献得出该纹理像素的相对效果。该加权贡献乘以环境地图纹理元素的颜色，并将结果相加以计算 $\boldsymbol{q}$。还计算加权贡献的总和s。最终结果 $\boldsymbol{q}/s$是在反射视图方向的波瓣上积分的整体颜色，并存储在生成的反射图中。</p>
<p>如果我们使用Phong模型，径向对称假设自然成立，我们几乎可以精确地计算环境光。Phong凭经验得出了他的模型，与我们在第9.8节中看到的BRDF相比，没有物理动机。Phong的模型和我们在第9.8.1节中讨论的Blinn-Phong BRDF都是余弦波提升到幂，但在Phong着色的情况下，余弦是由反射和视图矢量的点积形成的（公式9.15），而不是半矢量（见公式9.33）和法线。这使得反射波瓣旋转对称。</p>
<p>对于径向对称的镜面波瓣，我们仍然无法容纳的唯一效果是，因为它使得波瓣形状取决于视图方向，是地平线削波。想想看一个闪亮的（不是镜子）球体。例如，在球体表面中心附近看一个对称的Phong叶。查看附近的表面球体的轮廓实际上必须切掉一片叶片，因为地平线下方没有光线可以到达眼睛。见图10.35。这与我们之前在讨论区域照明近似时看到的问题相同（第10.1节），实际上它经常被实时方法忽略。这样做会在掠射角度时产生过亮的阴影。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C10/10.35.png" alt="Realtime Rendering 4th notes - chapter10"></p>
<center>图10.35</center>
<p>Heidrich和Seidel 以这种方式使用单个反射贴图来模拟表面的模糊度。为了适应不同的粗糙度级别，通常使用环境立方体贴图的mipmap（第6.2.2节）。每个级别用于存储传入辐射的模糊版本，较高的mip级别存储较粗糙的表面，即较宽的Phong波瓣。在运行期间，我们可以通过使用反射向量来解决立方体贴图，并根据所需的Phong指数（材料粗糙度）强制选择给定的mip级别。见图10.36。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C10/10.36.png" alt="Realtime Rendering 4th notes - chapter10"></p>
<center>图10.36</center>
<p>较粗糙的材料使用较宽的过滤区域可以消除高频，因此需要较低的分辨率才能获得足够的结果，从而完美映射到mipmap结构。此外，通过采用GPU硬件的三线性滤波，可以在预滤波的mip级别之间进行采样，模拟我们没有精确表示的粗糙度值。当与菲涅耳术语结合使用时，这种反射贴图对于光泽表面来说效果很好。</p>
<p>出于性能和混叠的原因，要使用的mipmap级别的选择不仅应考虑阴影点处的材质粗糙度，还应考虑由阴影的屏幕像素覆盖区域覆盖的表面区域上的法线和粗糙度的变化。Ashikhmin和Ghosh指出，为了获得最佳结果，应比较两个候选mipmap级别的指数（由纹理硬件计算的缩小级别和对应于当前滤波器宽度的级别），以及应使用较低分辨率的mipmap级别。为了更准确，应该考虑表面方差的扩大效应，并且应该采用对应于最佳拟合像素覆盖区中的平均波瓣的BRDF波瓣的新粗糙度水平。此问题与BRDF抗锯齿（第9.13.1节）完全相同，并且适用相同的解决方案。</p>
<p>前面提出的滤波方案假定给定反射视图方向的所有波瓣具有相同的形状和高度。该假设还意味着叶片必须径向对称。除了地平线上的问题之外，大多数BRDF在所有角度都没有均匀的，径向对称的波瓣。例如，在掠射角处，叶片通常变得更尖锐和更薄。而且，叶片的长度通常随仰角而变化。</p>
<p>对于弯曲表面通常不会感觉到这种效果。然而，对于诸如地板的平坦表面，径向对称的滤波器会引入明显的误差。</p>
<h5 id="convolvingtheenvironmentmap">Convolving the Environment Map（卷积环境映射）</h5>
<p>生成预滤波环境贴图意味着计算每个纹素，对应于方向v，环境辐射与反射lobe D的积分：</p>
<p>$$<br>
\int_\Omega D(\boldsymbol{l,v})L_i(\boldsymbol{l})d\boldsymbol{l}.<br>
$$</p>
<p>该积分是球形卷积（spherical convolution），并且通常不能以分析方式执行，因为对于环境地图而言，$L_i​$ 仅以表格形式已知。一种流行的数值解决方案是采用蒙特卡罗方法：</p>
<p>$$<br>
\int_\Omega D(\boldsymbol{l,v})L_i(\boldsymbol{l})d\boldsymbol{l}\approx\lim_{N\to\infin}\frac1N\sum^N_{k=1}\frac{D(\boldsymbol{l_k,v})L_i(\boldsymbol{l}_k)}{p(\boldsymbol{l}_k,\boldsymbol{v})},\tag{10.36}<br>
$$</p>
<p>其中 $\boldsymbol{l_k}, k = 1,2,...,N$ 代表的是单位球面（方向）上的离散样本，并且 $p(l_k,v)$ 是与在方向 $l_k$ 上生成样本相关联的概率函数。如果我们均匀地对球体进行采样，则 $p(l_k,v)= 1$。虽然这个求和对于我们想要整合的每个方向v是正确的，但是当将结果存储在环境贴图中时，我们还必须考虑投影所带来的失真，通过对每个计算的纹理像素加权它所对应的立体角（参见Driscoll ）。</p>
<p>虽然蒙特卡罗方法简单而正确，但它们可能会将大量样本收敛到积分的数值，即使对于离线过程也可能很慢。这种情况对于mipmap的第一级尤其如此，其中我们编码浅的镜面反射（在Blinn-Phong的情况下为高指数，对于Cook-Torrance为低粗糙度）。我们不仅需要更多的纹素来计算（因为我们需要分辨率来存储高频细节），而且对于不接近完美反射的方向，波瓣可能几乎为零。大多数样品都被“浪费”，因为$D(l_k,v)≈0$。</p>
<p>为了避免这种现象，我们可以使用重要性采样，我们生成方向，其概率分布试图匹配镜面波瓣的形状。这样做是蒙特卡洛积分的常见方差减少技术，并且对于大多数常用的波瓣存在重要性采样策略。对于更有效的采样方案，还可以考虑环境图中辐射的分布以及镜面波瓣的形状。然而，所有依赖点采样的技术通常仅用于离线渲染和地面实例模拟，因为通常需要数百个样本。</p>
<p>为了进一步减少采样方差（noise），我们还可以估计采样之间的距离，并使用锥的总和而不是单个方向进行积分。使用锥体对环境贴图进行采样可以通过对其一个mip级别进行点采样来近似，选择其纹理像素大小跨越类似于锥体的立体角的水平。这样做会引入偏差，但它允许我们大大减少实现无噪声结果所需的样本数量。可以借助于GPU以交互速率执行这种类型的采样。</p>
<p>同样利用面积样本，McGuire等开发了一种技术，旨在实时地近似用镜面反射的卷积结果，而不需要预先计算。该过程通过明智地混合非预先过滤的环境立方体图的多个mipmap级别来完成，以便重建Phong lobe的形状。以类似的方式，Hensley等人使用求和区域表（第6.2.2节）快速执行近似。 McGuire等人和Hensley等人的技术没有任何预先计算，因为在渲染环境地图后，他们仍然需要我们分别生成mip级别或前缀和。对于这两种情况，存在有效算法，因此所需的预计算比执行完全镜面波瓣卷积快得多。这两种技术都足够快，甚至可以实时用于环境照明的表面着色，但它们不如依赖于特殊预过滤的其他方法那么准确。</p>
<p>Kautz等人呈现另一种变型，用于快速生成滤波抛物面反射映射的分层技术。最近，Manson和Sloan使用有效的二次B样条滤波方案显着改进了现有技术，以生成环境地图的mip级别。这些特殊计算的B样条滤波mips然后通过组合几个样本来使用，其方式与McGuire等人和Kautz等人的技术类似，以产生快速和准确的近似。这样做可以实时生成与通过重要性采样蒙特卡罗技术计算的地面实况无法区分的结果。</p>
<p>快速卷积技术允许实时更新预过滤的立方体贴图，这在我们想要过滤的环境贴图动态呈现时是必需的。使用环境贴图经常使得对象难以在不同的照明情况之间移动，例如，从一个房间到另一个房间。可以在帧与帧之间（或每几帧一次）重新生成立方环境贴图，因此如果采用有效的滤波方案，则在新的镜面反射贴图中交换相对便宜。</p>
<p>重新生成完整环境贴图的替代方法是将动态光源的镜面反射高光添加到静态基础环境贴图中。添加的高光可以是预过滤的“斑点”，它们被添加到预过滤的基础环境贴图中。这样做可以避免在运行时进行任何过滤。这些限制是由于环境贴图的假设，灯光和反射的物体是遥远的。所以不要随观察物体的位置而改变。这些要求意味着不能轻易使用局部光源。</p>
<p>如果几何是静态的，但是一些光源（例如太阳）移动，用于更新探针的廉价技术不需要在立方体贴图中动态渲染场景，即将表面属性（位置，法线，材料）存储在G缓冲区环境地图。第20.1节详细讨论了G缓冲区。然后，我们使用这些属性计算表面在环境贴图中的出射辐射。该技术用于使命召唤：无限战争，巫师3 和孤岛惊魂4等。</p>
<h4 id="1052splitintegralapproximationformicrofacetbrdfsbrdf">10.5.2 Split-Integral Approximation for Microfacet BRDFs （微面BRDF的分步积分近似）</h4>
<p>环境照明的有用性如此之大，以至于已经开发了许多技术来减少立方体图预滤波中固有的BRDF近似问题。</p>
<p>到目前为止，我们已经描述了通过假设一个Phong lobe然后乘以完美镜像Fresnel术语后的近似值：</p>
<p>$$<br>
\int_{\boldsymbol{l}\in\Omega}f(\boldsymbol{l,v})L_i(\boldsymbol{l})(\boldsymbol{n\cdot l})d\boldsymbol{l}\approx F(\boldsymbol{n,v})\int_\boldsymbol{l\in\Omega}D_{Phong}(\boldsymbol{r})L_i(\boldsymbol{l})(\boldsymbol{n\cdot l})d\boldsymbol{l},\tag{10.37}<br>
$$</p>
<p>其中 $\int_ΩD_{Phong}(\boldsymbol{r})$ 为每个r预先计算到环境立方体图中。如果我们使用公式9.34考虑镜面微平面BRDF $f_{smf}​$，则为了方便重复此过程：</p>
<p>$$<br>
f_{smf}(\boldsymbol{l,v})=\frac{F(\boldsymbol{h,l})G_2(\boldsymbol{l,v,h})D(\boldsymbol{h})}{4\vert\boldsymbol{n\cdot l}\vert\vert\boldsymbol{n\cdot v}\vert}\tag{10.38}<br>
$$</p>
<p>我们注意到，即使假设 $D(h)≈D_{Phong}(r)$ 有效，我们也从照明积分中去除了BRDF的重要部分。也就是阴影项$G_2(1,v,h)$ 和半矢量菲涅耳项 $F(h,l)$，其在积分外的应用没有理论基础。 Lazarov 表明，使用依赖于n·v的完美镜像菲涅耳，而不是像微面BRDF那样的n·h，会产生比完全不使用菲涅耳项更大的误差。Gotanda，Lazarov和Karis独立地推导出类似的split-integral approximation：</p>
<p>$$<br>
\int_\boldsymbol{l\in\Omega}f_{smf}(\boldsymbol{l,v})L_i(\boldsymbol{l})(\boldsymbol{n\cdot l})d\boldsymbol{l}\approx\int_{\boldsymbol{l\in\Omega}}D(\boldsymbol{r})L_i(\boldsymbol{l})(\boldsymbol{n\cdot l})d\boldsymbol{l}\int_{\boldsymbol{l\in\Omega}}f_{smf}(\boldsymbol{l,v})(\boldsymbol{n\cdot l})d\boldsymbol{l}.\tag{10.39}<br>
$$</p>
<p>请注意，即使这个解决方案通常被称为split integral，“我们也不会将积分分解为两个不相交的项，因为这不是一个很好的近似值。记住fsmf包括镜面波瓣D，我们注意到后者以及相反，n·l项在两侧都被复制。在分裂积分近似中，我们在两个积分中包括环境映射中围绕反射向量对称的所有项。Karis称其为split-sum因为它是在他在预计算中使用的重要性采样数值积分器（公式10.36），但实际上它是相同的解决方案。</p>
<p>得到的两个积分都可以有效地预先计算。第一个仅取决于表面粗糙度和反射矢量，假设是径向对称的D波瓣。在实践中，我们可以使用任何波瓣，施加 $\boldsymbol{n = v = r}​$。像往常一样，这个积分可以预先计算并存储在立方体贴图的mip级别中。为了在将半矢量BRDF转换为反射矢量周围的波瓣时在环境光和分析光之间获得类似的亮点，radial-symmetric lobe应使用修改的粗糙度。例如，要使用半角从纯Phong反射向量镜面项转换为Blinn-Phong BRDF，通过将指数除以4得到良好的拟合。</p>
<p>第二个积分是镜面项 $R_{spec}(v)$的半球方向反射（第9.3节）。$R_{spec}$ 函数取决于仰角 $θ$，粗糙度 $α$ 和菲涅耳项 $F$ 。通常使用Schlick近似（公式9.16）来实现F，其仅在单个值 $F_0$ 上参数化，从而使 $R_{spec}$ 成为三个参数的函数。Gotanda以数字方式预先计算$R_{spec}$，将结果存储在三维查找表中。卡里斯和拉扎罗夫指出，$F_0$ 的值可以从$R_{spec}$ 中计算出来，导致两个因子，每个因素取决于两个参数：仰角和粗糙度。Karis将$R_{spec}$的预计算查找减少到可以存储在双通道纹理中的二维表，而Lazarov通过函数拟合推导出对两个因子中的每一个的分析近似。更准确，更简单的分析近似后来由Iwanicki和Pesce推导出来。请注意，$R_{spec}$也可用于提高漫反射BRDF模型的精度（参见第352页的公式9.65）。如果两种技术都在同一个应用程序中实现，那么 $R_{spec}$ 的实现可以用于两者，从而提高效率。</p>
<p>分裂积分解决方案对于恒定的环境贴图是精确的。立方体贴图部件提供缩放镜面反射率的光照强度，这是在均匀照明下正确的BRDF积分。根据经验，卡里斯和拉扎罗夫都观察到近似也适用于一般环境地图，特别是如果频率内容相对较低，这在室外场景中并不罕见。见图10.37。与基本事实相比，这种技术中最大的误差来源是对预滤波环境立方体图的径向对称，非限幅镜面波瓣的限制（图10.35）。 Lagarde建议基于表面粗糙度使用于从反射方向向法线取预滤波环境图的矢量偏斜，因为根据经验，与地面实况相比，这减少了误差。这样做是合理的，因为它部分地补偿了没有用表面进入的辐射半球削波波瓣。</p>
<h4 id="1053asymmetricandanisotropiclobeslobes">10.5.3 Asymmetric and Anisotropic Lobes（不对称和各向异性的Lobes）</h4>
<p>到目前为止我们看到的解决方案都局限于各向同性的镜面波瓣，这意味着当入射和出射方向绕表面法线旋转时（见第9.3节）它们不会发生变化，并且围绕反射向量径向对称。微面BRDF Lobe围绕半矢量 $\boldsymbol{h}$（公式9.33）定义，因此即使在各向同性情况下也不会具有我们需要的对称性。半矢量取决于光方向l，其对于环境照明没有唯一定义。因此，在Karis 之后，对于这些BRDF，我们施加$\boldsymbol{n = v = r}$并得出恒定的粗糙度校正因子，以使镜面高光的大小与原始的半矢量公式相匹配。这些假设都是相当大的误差来源（见图10.38）。</p>
<p>我们在第10.5.1节中提到的一些方法可用于以交互速率计算具有任意BRDF的环境照明，例如来自Luksch等人和Colbert和Kriv'anek的一些方法。然而，由于这些方法需要数十个样本，因此它们很少用于表面的实时着色。相反，它们可被视为蒙特卡洛积分的快速重要性采样技术。</p>
<p>通过在镜面波瓣上施加径向对称来创建预滤波环境贴图，并且通过访问对应于当前表面镜面粗糙度的预滤波瓣的简单直接逻辑，我们的结果仅在直接观察表面时保证是正确的（n = v）。在所有其他情况下，没有这样的保证，并且在掠射角度下，无论BRDF波瓣的形状如何，我们都会产生误差，因为我们忽略了真实的波瓣不能在着色表面点的地平线下方倾斜。通常，镜面反射的确切方向上的数据很可能不是现实的最佳匹配。</p>
<p>Kautz和McCool通过使用存储在预滤波环境图中的径向对称叶片的更好的采样方案来改进之前的预积分。他们提出了两种方法。第一个使用单个样本，但试图找到最好的波瓣在当前视图方向上近似BRDF，而不是依赖于恒定的校正因子。第二种方法平均来自不同叶片的几个样品。第一种方法更好地模拟掠射角度的表面。它们还导出校正因子以解释使用径向对称波瓣近似与原始BRDF相比反射的总能量的差异。第二种解决方案将结果扩展到包括半矢量模型典型的拉伸高光。在这两种情况下，优化技术用于计算驱动预滤波叶片采样的参数表。Kautz和McCool的技术使用贪婪拟合算法和抛物线环境贴图。</p>
<p>最近，Iwanicki和Pesce [807]使用一种称为Nelder-Mead最小化的方法，对GGX BRDF和环境立方体图进行了类似的近似。他们还分析了利用现代GPU的硬件各向异性过滤能力来加速采样的想法。</p>
<p>使用来自预过滤立方体图的单个样本，但使其位置适应更复杂的镜面BRDF峰值的想法，Revie [1489]也探讨了毛发渲染与延迟着色相结合（第20.1节）。在这种情况下，限制并不直接源于环境映射，而是源于在G缓冲区中编码尽可能少的参数的需要。 McAuley [1154]扩展了这个想法，使用这种技术对延迟渲染系统中的所有表面。</p>
<p>McAllister等开发了一种技术，通过利用Lafortune BRDF的特性，能够产生各种效果，包括各向异性和逆向反射。该BRDF 本身是基于物理的渲染的近似。它由多个Phong叶片组成，围绕反射方向扰动。 Lafortune证明了这种BRDF能够通过将这些叶片拟合到He-Torrance模型以及从gonioreflectometer测量真实材料来表示复杂材料。McAllister的技术依赖于注意到，由于Lafortune叶片是广义Phong叶片，因此可以使用传统的预滤波环境图，其mips编码不同的Phong指数。格林等人提出了一种类似的方法，其使用高斯波瓣而不是Phong波瓣。此外，他们的方法可以扩展到支持环境地图的方向阴影（第11.4节）。</p>
<h3 id="106irradianceenvironmentmapping">10.6 Irradiance Environment Mapping（辐照度环境映射）</h3>
<p>上一节讨论了使用过滤环境贴图进行光泽镜面反射。这些图也可用于漫反射。镜面反射的环境贴图具有一些共同的属性，无论它们是未过滤的还是用于镜面反射，或者它们被过滤并用于光泽反射。在这两种情况下，镜面反射环境贴图都使用反射的视图矢量进行索引，并且它们包含辐射值。未过滤的环境贴图包含传入的辐射亮度值，过滤的环境贴图包含传出的辐射亮度值。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C10/10.39.png" alt="Realtime Rendering 4th notes - chapter10"></p>
<center>图10.39 计算辐照度环境映射。表面法线周围的余弦加权半球从环境纹理（在这种情况下为立方体图）中采样并求和以获得与视图无关的辐照度。绿色方块表示立方体贴图的横截面，红色刻度线表示纹理像素之间的边界。尽管显示了立方体贴图表示，但是可以使用任何环境表示。</center>
<p>相比之下，漫反射的环境贴图仅使用曲面法线n进行索引，并且它们包含辐照度值。出于这个原因，他们被称为辐照度环境贴图。图10.35显示环境贴图的光泽反射在某些条件下由于其固有的模糊性而存在误差。相同的反射视图矢量可以对应于不同的反射情况。辐照度环境贴图不会发生此问题。曲面法线包含漫反射的所有相关信息。由于与原始照明相比，辐照度环境图极其模糊，因此它们可以以显着较低的分辨率存储。通常使用预滤波镜面环境图的最低mip级别之一来存储辐照度数据。此外，与我们之前研究过的光泽反射不同，我们没有整合到需要被修剪到表面法线周围的半球的BRDF波瓣。环境照明与钳位余弦波瓣的卷积是精确的，而不是近似值。</p>
<p>对于地图中的每个纹素，我们需要总结影响面向给定法线方向的表面的所有照明的余弦加权贡献。通过将覆盖整个可见半球的深远过滤器应用于原始环境映射来创建辐照度环境映射。滤波器包括余弦因子。见图10.39。图10.26中的球体图具有相应的辐照度图，如图10.40所示。使用的辐照度图的一个例子如图10.41所示。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C10/10.40.png" alt="Realtime Rendering 4th notes - chapter10"></p>
<center>图10.40</center>
<p>辐照度环境贴图与镜面反射环境或反射贴图分开存储和访问，通常在与视图无关的表示中，例如立方体贴图。见图10.42。而不是反射的视图矢量，表面法线用于访问立方体贴图以检索辐照度。从辐照度环境图中检索的值乘以漫反射率，从镜面反射环境图中检索的值乘以镜面反射率。菲涅耳效应也可以建模，在掠射角增加镜面反射（并可能降低漫反射）。</p>
<p>由于辐照度环境图使用极宽的滤波器，因此难以通过采样在运行中有效地创建它们。 King 讨论了如何在GPU上执行卷积以创建辐照度图。通过将环境地图转换为频域，他能够在2004年代硬件上以超过300 FPS的速率生成辐照度图。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C10/10.42.png" alt="Realtime Rendering 4th notes - chapter10"></p>
<center>图10.42 立方体贴图（左）及其对应的滤光辐照度图（右）。 </center>
<p>用于漫射或粗糙表面的滤波环境地图可以以低分辨率存储，但是有时也可以从场景的相对小的反射贴图生成，例如64×64纹素的立方体贴图面。这种方法的一个问题是渲染成这种小纹理的区域光源可能会落在纹理像素之间，“导致光线完全闪烁或褪色。为了避免这个问题，Wiley和Scheuermann建议代表这种光渲染动态环境贴图时，通过巨大的“卡片”（纹理矩形）来源。</p>
<p>与光泽反射的情况一样，动态光源也可以添加到预滤光辐照度环境图中。 Brennan给出了一种廉价的方法。想象一下单个光源的辐照度图。在光的方向上，当光线直接照射到表面上时，辐射是最大的。给定表面法线方向（即给定纹理像素）的辐射度随着与光的角度的余弦而下降，并且在表面背离光的情况下为零。 GPU可用于通过渲染半球（表示以观察者为中心的余弦波）以及沿着光线方向的半球极点，将该贡献直接快速添加到现有的辐照度图。</p>
<h4 id="1061sphericalharmonicsirradiance">10.6.1 Spherical Harmonics Irradiance （球谐函数辐照度）</h4>
<p>虽然我们已经讨论了仅使用诸如立方体贴图之类的纹理来表示辐照度环境贴图，但是其他表示也是可能的，如第10.3节中所示。特别是球面谐波作为辐照度环境贴图表示非常流行，因为来自环境照明的辐照度是平滑的。利用余弦波瓣进行辐射会导致从环境映射中去除所有高频分量。</p>
<p>Ramamoorthi和Hanrahan表明，只用前九个SH系数（每个系数是RGB矢量，因此我们需要存储27个浮点数），辐照度环境图可以表示为误差在1％的精度。然后可以将任何辐照度环境图解释为球函数 $E(\boldsymbol{n})$ 并使用等式10.21和10.23投影到九个RGB系数上。这种形式比立方体或抛物线图更紧凑，在渲染过程中，可以通过计算一些简单的多项式来重建辐照度，而不是访问纹理。通常，如果辐照度环境图表示间接照明（交互式应用中的常见情况），则需要较低的精度。在这种情况下，对于恒定基函数和三个线性基函数，四个系数通常可以产生良好的结果，因为间接照明倾向于低频，即随角度缓慢变化。</p>
<p>Ramamoorthi和Hanrahan 还表明，通过将每个系数乘以常数，可以将入射辐射函数 $L(\boldsymbol{l})​$ 的SH系数转换成辐照度函数 $E(\boldsymbol{n})​$ 的系数。这样做可以快速地将环境贴图过滤到辐照度环境贴图中，即将它们投影到SH基础中，然后将每个系数乘以常数。例如，这是King的快速辐照度滤波实现的工作原理。该想法是从辐射度计算辐照度等效于在进入辐射函数 $L(\boldsymbol{l})​$ 和钳位余弦函数 $cos(θ_i)^+​$之间执行球形卷积。由于钳位余弦函数关于球体的z轴旋转对称，因此它在SH中呈现特殊形式：其投影在每个频带中仅具有一个非零系数。非零系数对应于图10.21（第401页）中间列中的基函数，也称为区域谐波（zonal harmonics）。</p>
<p>在一般球函数和旋转对称函数（例如钳位余弦函数）之间执行球面卷积的结果是球体上的另一个函数。可以在函数的SH系数上有效地执行该卷积。卷积结果的SH系数等于两个函数的系数的乘积（乘法），由 $\sqrt{4π/(2l + 1)}$缩放，其中 $\boldsymbol{l}$ 是频带索引。然后，辐照度函数$E(\boldsymbol{n})$的SH系数等于辐射函数 $L(\boldsymbol{l})$ 的系数乘以钳位余弦函数 $cos(θ_i)^+$ 的系数，由频带常数缩放。超过前九个的 $cos(θ_i)^+$ 系数具有小值，这解释了为什么九个系数足以表示辐照度函数 $E(\boldsymbol{n})$ 。可以以这种方式快速评估SH辐照度环境图。 Sloan描述了一种有效的GPU实现。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C10/10.43.png" alt="Realtime Rendering 4th notes - chapter10"></p>
<center>图10.43 钳位余弦函数（红色）与其九系数球谐函数近似（蓝色）。近似值非常接近。注意π/2和π之间的轻微下降和零上升。</center>
<p>这里有一个固有的近似，因为虽然 $E(\boldsymbol{n})​$ 的高阶系数很小，但它们不是零。见图10.43。近似值非常接近，尽管π= 2和π之间的曲线的“摆动”应该为零，在信号处理中称为振铃。通常在高频函数近似少量的情况下发生。基本函数，如第10.3.2节所示。在π= 2时钳位为零是一个急剧变化，这意味着我们的钳位余弦函数具有无限频率信号。在大多数情况下振铃不明显，但可以看出极端的光照条件，如物体阴影侧的颜色变化或明亮\斑点。如果辐照度环境贴图用于仅存储间接照明（经常发生），则振铃不太可能成为问题。有一些预过滤方法可以最大限度地减少问题。见图10.44。</p>
<p>图10.40显示了直接导出的辐照度图如何与由九项函数合成的辐照度图进行比较。该SH表示可以在渲染期间使用当前曲面法线 n 进行评估，或者可以用于快速创建立方体或抛物线图以供以后使用。这种照明便宜并且为漫射情况提供了良好的视觉效果。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C10/10.44.png" alt="Realtime Rendering 4th notes - chapter10"></p>
<center>图10.44</center>
<p>动态渲染的三次环境地图可以投影到SH基础上。由于三次环境贴图是入射辐射函数的离散表示，因此公式10.21中球面上的积分成为立方体贴图纹素的总和：</p>
<p>$$<br>
k_{Lj}=\sum_tf_j(\boldsymbol{r}[t])L[t]d\omega[t],\tag{10.40}<br>
$$</p>
<p>其中t是当前立方体贴图纹理元素的索引，$\boldsymbol{r}[t]$ 是指向当前纹素的方向向量，$f_j(\boldsymbol{r}[t]) $是在 $\boldsymbol{r}[t]$ 处计算的第j个SH基函数，$L[t]$ 是纹素中存储的辐射，$d\omega[t]$ 是纹素对应的立体角。Kautz，King和Sloan描述了如何计算 $d\omega[t]$。</p>
<p>$$<br>
k_{Ej}=k'_{\cos+j}k_{Lj}=k'_{\cos+j}\sum_tf_j(\boldsymbol{r}[t])L[t]d\omega[t],\tag{10.41}<br>
$$</p>
<p>其中 $k_{Ej}​$ 是辐照度函数 $E(\boldsymbol{n})​$ 的第 $j​$ 个系数，$k_{Lj}​$ 是入射辐射函数 $L(\boldsymbol{l})​$ 的第j个系数，$k_{\cos+j}​$ 是钳位余弦函数 $cos(θ_i)^+​$ 的第 $j​$ 个系数通过 $\sqrt{4π/(2l + 1)}​$（$l​$ 是频带指数）。</p>
<p>给定t和立方体图分辨率，因子 $k'_{\cos+j}f_j(\boldsymbol{r}[t])d\omega[t]$ 对于每个基函数 $f_j()$ 是恒定的。这些基本因子可以离线预先计算并存储在立方体贴图中，立方体贴图的分辨率应与将要渲染的动态环境贴图的分辨率相同。通过在每个颜色通道中打包单独的基础因子，可以减少使用的纹理数量。为了计算动态立方体贴图的辐照度系数，将适当的基本因子图的纹素与动态立方体贴图的纹素相乘，并将结果相加。除了关于动态辐照度立方体贴图的信息之外，King还提供了关于GPU SH投影的实现细节。</p>
<p>可以将动态光源添加到现有的SH辐照度环境地图中。这种合并是通过计算灯的辐照度贡献的SH系数并将它们加到现有系数来完成的。这样做可以避免重新计算整个辐照度环境贴图的需要。这是一个简单的过程，因为存在点，圆盘和球面光系数的简单解析表达式。对系数求和与对辐照度求和具有相同的效果。通常，这些表示以带状谐波给出，对于与z轴对准的光，然后可以应用旋转以将光定向到任意方向。带状谐波旋转是SH旋转的一个特例（第10.3.2节），效率更高，只需要一个点积而不是全矩阵变换。具有更复杂形状的光源的系数可以通过将它们绘制成图像然后以数字方式投影到SH基础上来计算。对于物理天空模型的特殊情况，Habel展示了Preetham天窗在球谐函数中的直接扩展。</p>
<p>将普通分析光源投射到SH中的容易性是重要的，因为通常环境照明用于代替远距离或不太强烈的光源。补光灯是一个重要的案例。在渲染中，放置这些光源以模拟场景中的间接光，即从表面反射的光。通常不计算填充光的镜面贡献，特别是因为这些光相对于阴影对象可以在物理上较大并且与场景中的其他照明源相比相对暗淡。这些因素使他们的镜面高光更加分散，不那么引人注目。这种类型的灯在电影和视频的照明中具有真实世界的类比，其中物理补光（fill lights）通常用于在阴影中添加照明。</p>
<p>在球谐波空间中，进行相反的推导也很简单，即从SH中投射的辐射中提取分析光源。在他对SH技术的调查中，Sloan 展示了如果给定具有已知轴的定向光源，很容易从SH辐照度表示计算光应该具有的强度以最小化其自身与编码辐照度之间的误差。</p>
<p>在他之前的工作中斯隆展示了如何通过仅使用第一（线性）波段中的系数来选择近似最佳方向。调查还包括一种提取多个方向灯的方法。这项工作表明球谐函数是光总和的实用基础。我们可以将多个灯投射到SH中并提取较少数量的方向灯，这些灯可以近似于投影组。 lightcuts框架提供了聚合不太重要的灯光的原则方法。</p>
<p>虽然最常用于辐照度，但SH投影可用于模拟光滑的，视图相关的BRDF照明。 Ramamoorthi和Hanrahan描述了一种这样的技术。它们不是单一颜色，而是在立方体图中存储编码环境地图的视图依赖性的球面谐波投影的系数。但是，在实践中，这种技术比我们之前看到的预过滤环境贴图方法需要更多的空间。Kautz等人使用SH系数的二维表导出更经济的解决方案，但是该方法限于相当低频的照明。</p>
<h4 id="1062otherrepresentation">10.6.2 Other Representation（其他）</h4>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C10/10.45.png" alt="Realtime Rendering 4th notes - chapter10"></p>
<center>图10.45</center>
<p>尽管立方体贴图和球面谐波是辐照度环境贴图的最常用表示，但其他表示也是可能的。见图10.45。许多辐照度环境地图有两种主要颜色：顶部的天空颜色和底部的底色。受此观察的启发，Parker等人呈现仅使用两种颜色的半球照明模型。假设上半球发出均匀的辐射亮度 $L_{sky}$，并假设下半球发出均匀的辐射 $L_{ground}$。这种情况下的辐照度积分是</p>
<p>$$<br>
E=<br>
\begin{cases}<br>
\pi((1-\frac12\sin\theta)L_{sky}+\frac12\sin\theta L_{ground}),;where;\theta\lt90^\circ,\\<br>
\pi(\frac12\sin\theta L_{sky}+(1-\frac12\sin\theta) L_{ground}),;where;\theta\ge90^\circ,<br>
\end{cases}\tag{10.42}<br>
$$</p>
<p>其中θ是表面法线和天空半球轴之间的角度。 Baker和Boyd提出了更快的近似（由Taylor描述）：</p>
<p>$$<br>
E=\pi(\frac{1+\cos\theta}2L_{sky}+\frac{1-\cos\theta}2L_{ground}),\tag{10.43}<br>
$$</p>
<p>这是天空和地面之间的线性插值，使用 $(cosθ+ 1)/2$ 作为插值因子。术语cosθ通常快速计算为点积，并且在天空半球轴是基本轴之一（例如，y轴或z轴）的常见情况下，不需要计算全部，因为它等于n的世界空间坐标之一。近似值相当接近并且明显更快，因此对于大多数应用来说，它更适合于完整表达式。</p>
<p>Forsyth提出了一种廉价而灵活的照明模型，称为trilight，它包括定向，双向，半球形和环绕照明作为特殊情况。</p>
<p>Valve最初引入了辐照度的环境立方体表示（第10.3.1节）。通常，我们在第10.3节中看到的所有球面函数表示都可用于预先计算的辐照度。对于辐照度函数所代表的低频信号，我们知道SH是一个很好的近似。我们倾向于创建特殊方法来简化或使用比球谐波更少的存储。</p>
<p>如果我们想要计算遮挡和其他全局照明效果，或者如果我们想要合并光泽反射（第10.1.1节），则需要更复杂的高频表示。预计算照明以考虑所有相互作用的一般概念称为预计算辐射传输（PRT），将在第11.5.3节中讨论。捕获高频率的光泽照明也称为全频照明。小波表示通常在此上下文中使用作为压缩环境贴图的方法，并且以类似于球谐函数的方式设计有效的算子。Ng等人展示了使用 Haar小波（haar wavelets） 来推广辐照度环境映射以模拟自阴影。它们以小波为基础存储环境贴图和阴影函数，它们在对象表面上变化。这种表示是值得注意的，因为它相当于环境立方体图的变换，执行每个立方体面的二维小波投影。因此，它可以被视为立方体贴图的压缩技术。</p>
<h3 id="107sourcesoferror">10.7 Sources of Error （误差来源）</h3>
<p>为了正确执行着色，我们必须计算非点源光上的积分。在实践中，这一要求意味着我们可以根据所考虑的灯的特性采用许多不同的技术。通常，实时引擎可以分析地模拟一些重要的光，在光区域上近似积分并通过阴影图计算遮挡。所有其他光源，如远距离照明，天空，补光和光在表面上反射，通常由镜面反射分量的环境立方体贴图和漫反射辐照度的球面基础表示。</p>
<p>采用混合技术进行照明意味着我们永远不会直接使用给定的BRDF模型，但具有不同程度的误差的近似值。有时BRDF近似是明确的，因为我们拟合中间模型以计算光照积分——LTC就是一个例子。其他时候，我们在某些（通常是罕见的）条件下构建对于给定BRDF精确的近似值，但一般会出现误差，预滤波的立方体贴图属于此类别。</p>
<p>在开发实时着色模型时要考虑的一个重要方面是确保不同形式的照明之间的差异不明显。具有来自不同表示的相干光可能在视觉上比每个所呈现的绝对近似误差更重要。</p>
<p>遮挡对于逼真的渲染也是至关重要的，因为光线“泄漏”应该没有的地方往往比不应该有光的地方更明显。大多数区域的光线表示对于阴影来说并不是微不足道的。今天没有任何实时遮蔽技术，即使在考虑“软化”效果（第7.6节）时，也能准确地考虑光的形状。我们计算一个标量因子，当一个物体投射阴影时，我们将它们相乘以减少给定光线的贡献，这是不正确的；我们应该在与BRDF进行整合时考虑这种遮挡。环境照明的情况特别困难，因为我们没有明确的主导光方向，因此不能使用点源光的遮蔽技术。</p>
<p>即使我们已经看到一些相当先进的照明模型，重要的是要记住这些并不是真实世界照明源的精确表示。例如，在环境照明的情况下，我们假设辐射源无限远，这是永远不可能的。我们所看到的所有分析灯都在一个更强大的假设上工作，即灯在其表面上的每个点上均匀地在出射半球上发出辐射。在实践中，这种假设可能是错误的来源，因为真正的灯光通常具有很强的方向性。在摄影和电影照明中，经常使用特制的面具和过滤器（称为图案片，黄瓜或饼干）来产生艺术效果。例如，摄影师格雷戈里·克鲁兹森（Gregory Crewdson）参见图10.46中的复杂电影摄影。为了在保持大面积发射的同时限制照明角度，可以在大型发光面板（所谓的柔光箱）的前面添加称为蜂窝的屏蔽黑色材料的网格。镜子和反射器的复杂配置也可用于灯具的外壳，例如室内照明，汽车前灯和手电筒。见图10.47。这些光学系统产生远离物理中心辐射光的一个或多个虚拟发射器，并且在执行衰减计算时应考虑该偏移。</p>
<p>请注意，应始终在感知的，面向结果的框架中评估这些错误（除非我们的目标是进行预测渲染，即可靠地模拟曲面的真实世界外观）。在艺术家的手中，某些简化，即使不现实，仍然可以产生有用和富有表现力的原语。物理模型在使艺术家更容易创建视觉上合理的图像时非常有用，但它们并不是他们自己的目标。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C10/10.47.png" alt="Realtime Rendering 4th notes - chapter10"></p>
<center>图10.47</center>
<h3 id="furtherreadingandresources">Further Reading and Resources</h3>
<p>“Light Science and Magic: An Introduction to Photographic Lighting ”一书是理解真实世界摄影灯的绝佳参考。对于电影照明Set Lighting Technician’s Handbook: Film Lighting Equipment, Practice, and Electrical Distribution 是一个很好的介绍。</p>
<p>Debevec在图像照明领域开创的工作对于需要从真实世界场景捕捉环境地图的人来说非常感兴趣。<br>
SIGGRAPH 2003课程以及Reinhard等人的High Dynamic Range Imaging: Acquisition, Display, and Image-Based Lighting 一书涵盖了大部分这项工作。</p>
<p>可以帮助模拟的一种资源是光配置文件。照明工程学会（IES）发布了照明测量的手册和文件格式标准。这种格式的数据通常可从许多制造商处获得。 IES标准仅限于通过其角度发射轮廓描述光。它没有完全模拟光学系统对衰减的影响，也没有模拟光表面区域的发射。</p>
<p>Szirmay-Kalos 关于镜面反射效应的最新报告包括许多对环境绘图技术的参考。</p>
<p>（Chapter 10 End.）</p>
]]></content:encoded></item><item><title><![CDATA[Realtime Rendering 4th notes - chapter9]]></title><description><![CDATA[<blockquote>
<p>&quot;Let the form of an object be what it may,|light, shade, and<br>
perspective will always make it beautiful. &quot; — John Constable</p>
</blockquote>
<p>在本章中，我们将介绍基于物理的着色的各个方面。我们首先在9.1节中介绍光物质相互作用的物理特性，然后在9.2到9.4节中，我们将展示这些物理如何关联到着色过程。第9.5至9.7节专门用于构建基于物理的着色模型的构建块，模型本身——涵盖各种材质类型——在第9.8至9.12节中讨论。最后，在9.13节中，我们描述了材料如何混合在一起，并且我们介绍了避免混叠和保持表面外观的过滤方法。</p>
<h3 id="91physicsoflight">9.1 Physics of Light（光物理）</h3>]]></description><link>http://xx-ma.com/rtr4-9/</link><guid isPermaLink="false">5c21a2d974cbc057b22c32cb</guid><category><![CDATA[notes]]></category><category><![CDATA[graphics]]></category><category><![CDATA[realtime-rendering]]></category><category><![CDATA[code]]></category><dc:creator><![CDATA[Marianax]]></dc:creator><pubDate>Tue, 25 Dec 2018 06:37:47 GMT</pubDate><media:content url="http://xx-ma.com/content/images/2018/12/gaetano-cessati-122235-unsplash-1.jpg" medium="image"/><content:encoded><![CDATA[<blockquote>
<img src="http://xx-ma.com/content/images/2018/12/gaetano-cessati-122235-unsplash-1.jpg" alt="Realtime Rendering 4th notes - chapter9"><p>&quot;Let the form of an object be what it may,|light, shade, and<br>
perspective will always make it beautiful. &quot; — John Constable</p>
</blockquote>
<p>在本章中，我们将介绍基于物理的着色的各个方面。我们首先在9.1节中介绍光物质相互作用的物理特性，然后在9.2到9.4节中，我们将展示这些物理如何关联到着色过程。第9.5至9.7节专门用于构建基于物理的着色模型的构建块，模型本身——涵盖各种材质类型——在第9.8至9.12节中讨论。最后，在9.13节中，我们描述了材料如何混合在一起，并且我们介绍了避免混叠和保持表面外观的过滤方法。</p>
<h3 id="91physicsoflight">9.1 Physics of Light（光物理）</h3>
<p>光与物质的相互作用构成了基于物理的着色的基础。 要理解这些相互作用，有助于对光的本质有一个基本的了解。<br>
在物理光学中，光被建模为电磁横波（transverse wave），即垂直于其传播方向振荡电场和磁场的波。两个场的振荡是成对的。磁场矢量和电场矢量彼此垂直，并且它们的长度比是固定的。 该比率等于相速度，我们将在后面讨论。</p>
<p>在图9.1中，我们看到一个简单的光波。事实上，这是最简单的正弦函数。 该波具有单个波长，用希腊字母$λ$表示。正如我们在8.1节中看到的那样，光的感知颜色与其波长密切相关。因此，单波长光被称为单色光。 然而，在实践中遇到的大多数光波是多色的，包含许多不同的波长。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.1.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.1</center>
<p>图9.1中的光波是线性偏振的（linearly polarized）。这意味着对于空间中的固定点，电场和磁场均沿着线来回移动。 相比之下，在本书中，我们关注的是非偏振光，这种光非常普遍。 在非偏振光中，场振荡在垂直于传播轴的所有方向上均匀地扩展。 尽管它们简单，但是理解单色线性偏振波的行为是有用的，因为任何光波都可以被分解成这种波的组合。</p>
<p>如果我们随着时间跟踪具有给定相位（例如，振幅峰值）的波上的点，我们将看到它以恒定速度在空间中移动，这是波的相速度（phase velocity）。对于穿过真空的光波，相速度为c，通常称为光速，大约每秒300,000千米。</p>
<p>在8.1.1节中，我们讨论了这样一个事实，即对于可见光，单个波长的大小在大约400 - 700纳米的范围内。为了给这个长度一些概念，它大约是单根蜘蛛丝线宽度的一半到三分之一，它本身不到人类头发宽度的五十分之一。见图9.2。在光学中，谈论相对于光波长的特征的尺寸通常是有用的。在这种情况下，我们可以说蜘蛛丝线的宽度约为2λ-3λ（2-3倍光波长），并且头发的宽度约为100λ-200λ。</p>
<p>光波携带能量。能量流的密度等于电场和磁场的大小的乘积——因为幅度彼此成比例——与电场的平方幅度成比例。我们专注于电场，因为它比磁场更强烈地影响物质。在渲染中，我们关注的是随时间的平均能量流量，其与平方波幅度成比例。该平均能量流密度是辐照度，用字母E表示。辐射照度及其与其他光量的关系在8.1.1节中讨论。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.2.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.2</center>
<p>光波线性的组合在一起。最终的波是分量波的总和。然而，由于辐射照度与振幅的平方成正比，这似乎会导致矛盾。例如，将两个相等的波相加不会导致辐射照度“1 + 1 = 4”情况？由于辐射照度测量能量流量，这是否会违反能量守恒？这两个问题的答案分别是“有时”和“不”。</p>
<p>为了说明，我们将看一个简单的例子：增加n个单色波，相位除外。n个波中的每一个的振幅是a。 如前所述，每个波的辐照度$E_1$与$a_2$成比例，换句话说，对于某个常数k，$E_1 = ka^2$。</p>
<p>图9.3显示了此案例的三个示例场景。在左边，波浪都以相同的相位排列并相互加强。 组合波辐照度是单波的$n^2$倍，其是各个波的辐照度值之和的n倍。这种情况称为建设性干扰（constructive interference）。在图的中心，每对波都处于反相，相互抵消。 组合波具有零振幅和零辐照度。 这种情况是破坏性干扰（destructive interference）。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.3.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.3</center>
<p>建设性和破坏性干扰是相干相加的两种特殊情况，其中波的波峰和波谷以一致的方式排列。 取决于相对相位关系，n个相同波的相干相加可以产生相对于单个波在0到 $n^2$倍的辐射。</p>
<p>然而，大多数情况下，当波浪加起来时，它们是相互不相干的，这意味着它们的相位相对随机。这在图9.3的右侧说明。 在这种情况下，组合波的幅度是$\sqrt{n}a$，并且各个波的辐照度线性地加到一个波的辐照度的n倍，正如人们所期望的那样。</p>
<p>似乎破坏性和建设性的干扰违反了能量守恒。但是图9.3没有显示完整的图片，它只显示了一个位置的波浪交互。 当波在空间中传播时，它们之间的相位关系从一个位置变为另一个位置，如图9.4所示。在一些位置，波构造性地干涉，并且组合波的辐照度大于各个波的辐照度值之和。 在其他位置，它们破坏性地干涉，导致组合辐照度小于各个波辐照度值的总和。 这并不违反能量守恒定律，因为通过建设性干涉获得的能量和通过破坏性干扰损失的能量总是被抵消。</p>
<p>当物体中的电荷振荡时发射光波。引起振动，电能，化学能的部分能量被转换为从物体辐射出的光能。在渲染中，这些对象被视为光源。我们在第5.2节中首先讨论了光源，第10章将从更加基于物理的角度对它们进行描述。</p>
<p>在发射光波之后，它们穿过太空直到它们遇到一些与之相互作用的物质。 大多数光物质相互作用的核心现象很简单，与上面讨论的发射情况非常相似。振荡电场推动和拉动物质中的电荷，使它们依次振荡。振荡电荷发射新的光波，其将入射光波的一些能量重定向到新的方向。 这种称为散射（scattering）的反应是各种光学现象的基础。</p>
<p>散射光波具有与原始波相同的频率。当通常情况下，原始波包含多个光频率时，每个光分别与物质相互作用。一个频率的入射光能量对不同频率的发光能量没有贡献，除了荧光和磷光等特殊情况，我们在本书中将不再描述。</p>
<p>分离的分子在所有方向上散射光，强度有一些方向性变化。更多的光在靠近原始传播轴的方向上向前和向后散射。 分子作为散射体的有效性——其附近的光波全部散射的可能性——随波长而变化很大。与长波长光相比，短波长光更有效地散射。</p>
<p>在渲染中，我们关注许多分子的集合。与这种聚集体的光相互作用不一定类似于与分离的分子的相互作用。从附近分子散射的波通常是相互相干的，因此表现出干扰，因为它们来自相同的入射波。本节的其余部分专门讨论了几种重要的多分子光散射特殊情况。</p>
<h4 id="911particles">9.1.1 Particles（粒子）</h4>
<p>在理想气体中，分子不会相互影响，因此它们的相对位置是完全随机且不相关的。 虽然这是一种抽象，但对于正常大气压下的空气来说，它是一个相当不错的模型。在这种情况下，从不同分子散射的波之间的相位差是随机的并且不断变化的。结果，散射波是不相关的，它们的能量线性增加，如图9.3的右边部分所示。 换句话说，从n个分子散射的聚合光能是从单个分子散射的光的n倍。</p>
<p>相反，如果分子紧密地堆积成远小于光波长的簇，则每个簇中的散射光波同相并且建设性干预。这会导致散射波能量以二次方式相加，如图9.3左侧所示。因此，从一小群n个分子散射的光的强度是从单个分子散射的光的$n^2$ 倍，这是相同数量的分子在理想气体中散射的光的n倍。这种关系意味着对于每立方米固定密度的分子，将分子聚集成簇将显着增加散射光的强度。使簇更大，同时仍保持总分子密度恒定，将进一步增加散射光强度，直到簇直径变得接近光波长。除此之外，簇尺寸的额外增加不会进一步增加散射光强度。</p>
<p>这个过程解释了为什么云和雾如此强烈地散射光。它们都是由凝结产生的，凝结是空气中水分子聚集成越来越大的团簇的过程。 即使水分子的总密度不变，这也显着增加了光散射。第14.4.2节讨论了云的渲染。</p>
<p>随着粒子的尺寸增加超过波长，散射波在整个粒子上不再同相的事实改变了散射的特性。 散射越来越趋于向前方向，并且波长依赖性降低，直到所有可见波长的光均匀地散射。这种类型的散射称为米氏散射（Mie scattering）。 Rayleigh and Mie scattering 在第14.1节中有更详细的介绍。</p>
<h4 id="912media">9.1.2 Media（介质）</h4>
<p>另一个重要的情况是光通过均匀介质传播，该介质是填充有均匀间隔的相同分子的体积。 分子间距不必像晶体那样完全规则。 如果液体和非结晶固体的组成是纯的（所有分子都相同）并且它们没有间隙或气泡，则它们可以是光学均匀的。</p>
<p>在均匀介质中，散射波在除原始传播方向之外的所有方向上破坏性地干涉。在原始波与从各个分子散射的所有波组合之后，最终结果与原始波相同，除了其相速度和（在某些情况下的）振幅。最终波没有表现出任何散射，它已经被破坏性干涉有效地抑制了。</p>
<p>原始波和新波的相速度的比率定义了介质的光学性质，称为折射率（index of refraction，IOR / refractive index ），由字母n表示。有些介质是吸收性的。它们将部分光能转换为热能，导致波幅随距离呈指数减小。降低速率由衰减指数(attenuation index)定义，由希腊字母$\kappa$（kappa）表示。 n和κ通常都随波长而变化。 总之，这两个数字完全定义了介质如何影响给定波长的光，它们通常组合成单个复数$n +iκ$，称为复折射率。折射率抽象出分子级细节。 光相互作用并使介质能够作为连续体积处理，这样更简单。</p>
<p>虽然光的相速不直接影响外观，但速度的变化确实如此，我们将在后面解释。另一方面，光吸收对视觉效果有直接影响，因为它会降低光的强度，并且（如果波长变化）也会改变其颜色。 图9.5显示了一些光吸收的例子。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.5.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.5</center>
<p>非均匀介质通常可以被建模为具有嵌入散射粒子的均匀介质。抑制均匀介质中散射的破坏性干涉是由分子的均匀排列引起的，因此也是由它们产生的散射波引起的。分子分布的任何局部变化都将破坏这种破坏性干涉模式，允许散射光波传播。这种局部变化可以是不同分子类型的集群，气隙，气泡或密度变化。在任何情况下，它都会像前面讨论的粒子那样散射光，散射属性同样取决于簇的大小。甚至气体也可以这种方式建模。对于这些，“散射粒子”是由分子的恒定运动引起的瞬态密度波动。该模型能够为气体建立有意义的n值，这有助于理解它们的光学性质。图9.6显示了一些光散射的例子。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.6.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.6 从左到右：水，含几滴牛奶的水，含约10％牛奶的水，全脂牛奶和乳白玻璃。 大多数牛奶的散射粒子大于可见光波长，因此其散射主要是无色的，中间图像中有明显的蓝色色调。 乳白玻璃中的散射颗粒均小于可见光波长，因此散射蓝光更强烈，而不是红光。由于分开的浅色和深色背景，透射光在左侧更明显，而散射光在右侧更明显。</center>
<p>散射和吸收都与尺度有关。 在小场景中不产生任何明显散射的介质在较大尺度上可能具有非常明显的散射。 例如，当观察房间中的一杯水时，在空气中的光的散射和在水中的吸收是不可见的。但是，在更大的环境中，两种效果都很重要，如图9.7所示。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.7.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.7</center>
<p>在一般情况下，介质的外观是由散射和吸收的某种组合引起的，如图9.8所示。散射程度决定了浑浊，高散射会产生不透明的外观。除了极少数例外，例如图9.6中的乳白玻璃，固体和液体介质中的颗粒往往比光波长大，并且倾向于均匀地散射所有可见波长的光。因此，任何颜色色调通常由吸收的波长依赖性引起。介质的亮度是这两种现象的结果。特别是白色是高散射和低吸收组合的结果。这将在第14.1节中详细讨论。</p>
<h4 id="913surfaces">9.1.3 Surfaces（表面）</h4>
<p>从光学角度来看，物体表面是分离具有不同折射率值的体积的二维界面。在典型的渲染情况下，外部体积包含空气，折射率约为1.003，为简单起见，通常假设为1。内部体积的折射率取决于制造物体的物质。</p>
<p>当光波照射到表面时，该表面的两个方面对结果具有重要影响：两侧的物质和表面几何形状。 我们将首先关注物质方面，假设最简单的表面几何形状，一个完美的平面。我们将“外部”（入射或入射波发生的一侧）的折射率表示为$n_1$，并将“内部的折射率”表示为“（通过表面后波将被传输的地方）$n_2$。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.8.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.8</center>
<p>我们在前面的部分中已经看到，当光波遇到材料成分或密度的不连续性时，即在折射率方面，光波会散射。分离不同折射率的平面是一种特殊类型的不连续性，它以特定方式散射光。边界条件要求平行于表面的电场分量是连续的。换句话说，电场矢量到表面平面的投影必须在表面的任一侧上匹配。这有几个含义：</p>
<ol>
<li>在表面，任何散射波必须与入射波同相或180°异相。因此，在表面处，散射波的峰值必须与入射波的峰值或波谷对齐。这限制了散射波仅沿两个可能的方向进入，一个向前延伸到表面，一个向远离它的方向。第一个是透射波（transmitted wave），第二个是反射波（reflected wave）。</li>
<li>散射波必须与入射波具有相同的频率。我们在这里假设单色波，但我们讨论的原理可以通过首先将其分解为单色分量来应用于任何一般波。</li>
<li>当光波从一种介质移动到另一种介质时，波通过介质的相速度与相对折射率$(n_1/n_2)$成比例地变化。 由于频率是固定的，因此波长也与$(n_1/n_2 )$成比例地变化。</li>
</ol>
<p>最终结果如图9.9所示。反射波和入射波方向与表面法线具有相同的角度$θ_i$。透射波方向以角度$θ_t$弯曲（折射），其与$θ_i$具有以下关系：</p>
<p>$$<br>
\sin(\theta_t)=\frac{n_1}{n_2}\sin(\theta_i).\tag{9.1}<br>
$$</p>
<p>这种折射方程称为斯涅尔定律（Snell's law）。它用于全局折射效应，将在第14.5.2节中进一步讨论。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.9.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.9 撞击平面的光波分离折射率n1和n2。图的左侧示出了入射波从右上方进入的侧视图。红色波段的强度表示波相。表面下方的波的间隔与比率（n1/n2）成比例地变化，在这种情况下为0.5。相位沿表面排列，因此间距的变化会弯曲（折射）透射波方向。三角形结构显示了Snell定律的推导。为清楚起见，图的右上方分别显示了反射波。它具有与入射波相同的波间距，因此其方向与表面法线具有相同的角度。该图的右下方显示了波方向矢量。</center>
<p>虽然折射通常与透明材料（如玻璃和水晶）有关，但它也会发生在不透明物体的表面。 当不透明物体发生折射时，光在物体内部经历散射和吸收。 光与物体的介质相互作用，就像图9.8中的各种液体杯一样。 在金属的情况下，内部包含许多自由电子（未与分子结合的电子），它可以“吸收”折射的光能并将其重定向到反射波中。这就是金属具有高吸收性和高反射率的原因。</p>
<p>我们讨论过的表面折射现象——反射和折射——需要在小于单一波长的距离内发生折射率的突然变化。折射率的更渐进的变化不会使光分裂，而是使其路径弯曲，以折射中发生的不连续弯曲的连续模拟。当空气密度因温度而变化时，通常可以看到这种效果，例如海市蜃楼和热变形。见图9.10。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.10.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.10</center>
<p>即使是具有明确边界的物体，如果它浸没在具有相同折射率的物质中，也将没有可见表面。在没有折射率变化的情况下，不会发生反射和折射。 图9.11就是一个例子。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.11.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.11</center>
<p>到目前为止，我们一直关注物质在表面两侧的影响。我们现在将讨论影响表面外观的另一个重要因素：几何。严格来说，完全平坦的平面是不可能的。每个表面都有某种不规则性，即使只有构成表面的单个原子。然而，远小于波长的表面不规则性对光没有影响，并且比波长大得多的表面不规则性有效地使表面倾斜而不影响其局部平坦度。 只有尺寸在1-100波长范围内的不规则性会导致表面的表现与平面不同，这种现象称为衍射，将在第9.11节中进一步讨论。</p>
<p>在渲染中，我们通常使用几何光学，忽略诸如干涉和衍射之类的波效应。 这相当于假设所有表面不规则性都小于光波长或更大。 在几何光学中，光被建模为光线而不是波。 在光线与表面相交的点处，表面被局部地处理为平面。 图9.9右下角的图表可以看作是反射和折射的几何光学图片，与该图其他部分中呈现的波浪图片形成对比。 从这一点开始，我们将一直保持几何光学领域，直到第9.11节，该部分致力于基于波动光学的着色模型。</p>
<p>如前所述，比波长大得多的表面不规则性改变了表面的局部取向。当这些不规则表面太小以至于不能单独渲染时，也就是小于一个像素时，我们将它们称为微观几何。反射和折射的方向取决于表面法线。 微观几何学的效果是在表面上的不同点处改变该法线，从而改变反射和折射的光方向。</p>
<p>即使表面上的每个特定点仅在单个方向上反射光，每个像素也覆盖许多反射各个方向的光的表面点。外观由所有不同反射方向的聚合结果驱动。图9.12显示了两个表面的示例，这两个表面在宏观尺度上具有相似的形状，但显着不同的微观几何。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.12.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.12</center>
<p>对于渲染，我们不是明确地对微观几何进行建模，而是对其进行统计处理并将表面视为具有微观结构法线的随机分布。因此，我们将表面建模为在连续的方向扩散中反射（和折射）光。 这种扩散的宽度，以及因此反射和折射细节的模糊性，取决于微观几何法向量的统计方差，换句话说，表面微尺度粗糙度。见图9.13。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.13.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.13</center>
<h4 id="914subsurfacescattering">9.1.4 Subsurface Scattering（次表面散射）</h4>
<p>折射光继续与物体的内部体积相互作用。 如前所述，金属反射大部分入射光并迅速吸收其余部分。相反，非金属表现出各种各样的散射和吸收行为，这与图9.8中的液体杯中所见的类似。具有低散射和吸收的材料是透明的，将任何折射光透射通过整个物体。 第5.5节讨论了在没有折射的情况下渲染这种材料的简单方法，第14.5.2节将详细介绍折射。 在本章中，我们将重点关注不透明物体，其中透射光经历多次散射和吸收事件，直到最后一些物体从表面重新发射回来。 见图9.14。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.14.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<p>这种次表面散射光以离入射点不同的距离离开表面。入口 - 出口距离的分布取决于材料中散射粒子的密度和性质。 这些距离与阴影比例（像素的大小或阴影样本之间的距离）之间的关系很重要。如果入口 - 出口距离与阴影比例相比较小，则可以假设它们实际上为零以用于着色目的。这允许次表面散射与表面反射结合到局部着色模型中，在一点处的出射光仅取决于同一点处的入射光。 然而，由于次表面散射光具有与表面反射光明显不同的外观，因此将它们分成单独的阴影术是方便的。 镜面反射（specular）表示表面反射，漫反射（diffuse）表示局部次表面散射。</p>
<p>值得注意的是，局部和全局次表面散射技术可以模拟完全相同的物理现象。每种情况的最佳选择不仅取决于材料特性，还取决于观察规模。例如，当渲染儿童玩塑料玩具的场景时，可能需要全局技术来准确渲染儿童的皮肤，并且局部漫射阴影模型足以满足玩具的需要。这是因为皮肤中的散射距离比塑料中的散射距离要大得多。然而，如果相机距离足够远，则皮肤散射距离将小于像素，并且局部阴影模型对于儿童和玩具都是准确的。相反，在极端特写镜头中，塑料会表现出明显的非局部次表面散射，并且需要全局技术来准确地渲染玩具。</p>
<h3 id="92thecamera">9.2 The Camera（相机）</h3>
<p>如8.1.1节所述，在渲染中我们计算从着色表面点到摄像机位置的辐射亮度。这模拟了成像系统的简化模型，例如胶片相机，数码相机或人眼。</p>
<p>这种系统包含一个由许多离散小传感器组成的传感器表面。比如眼睛中的视杆细胞和视锥细胞，数码相机中的光电二极管或膜中的染料颗粒。这些传感器中的每一个都检测其表面上的辐照度值并产生颜色信号。辐照度传感器本身不能产生图像，因为它们平均来自所有进入方向的光线。出于这个原因，完整的成像系统包括具有单个小孔（开口）的防光外壳，该小孔限制光可以进入和撞击传感器的方向。放置在光圈处的透镜聚焦光，使得每个传感器仅从一小组进入方向接收光。外壳，光圈和镜头具有使传感器具有方向特定性的综合效果。它们在一个小区域和一小组进入方向上平均光线。而不是像我们在8.1.1节中看到的那样测量平均辐射照度来量化来自所有方向的光流的表面密度，这些传感器测量的平均辐射度量化单个光线的亮度和颜色。</p>
<p>从历史上看，渲染模拟了一种特别简单的成像传感器，称为针孔相机，如图9.16顶部所示。 针孔照相机在理想情况下具有极小的光圈，零尺寸的数学点和无透镜。 点孔径限制传感器表面上的每个点以收集单个光线，其中离散传感器收集窄锥光线，其基部覆盖传感器表面并且其顶点位于孔径处。 渲染系统模拟针孔摄像机的方式略有不同（但相当），如图9.16中间部分所示。 针孔孔的位置由点c表示，通常称为“相机位置”或“眼睛位置”。 这一点也是透视变换的投影中心（见第4.7.2节）。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.16.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.16</center>
<p>渲染时，每个着色样本对应于单个光线，因此对应于传感器表面上的采样点。抗锯齿过程（第5.4节）可以解释为重建在每个离散传感器表面上收集的信号。然而，由于渲染不受物理传感器的限制，我们可以更一般地处理该过程，因为从离散样本重建连续图像信号。</p>
<p>虽然已经构建了实际的针孔摄像机，但它们对于实际使用的大多数摄像机以及人眼而言都是不良模型。使用镜头的成像系统的模型显示在图9.16的底部。包括允许使用更大孔径的透镜，这极大地增加了成像系统收集的光量。但是，它还会导致相机具有有限的景深（第12.4节），模糊太近或太远的物体。</p>
<p>除了限制景深外，镜头还具有额外的效果。每个传感器位置都会接收一束光线，即使对于完美聚焦的点也是如此。 每个着色样本代表单个观察光线的理想化模型有时会引入数学奇点，数值不稳定性或视觉混叠。在渲染图像时牢记物理模型可以帮助我们识别和解决这些问题。</p>
<h3 id="93thebrdfbrdf">9.3 The BRDF（BRDF）</h3>
<p>最终，基于物理的渲染归结为计算沿着一组视线进入相机的辐射。使用第8.1.1节中介绍的入射辐射的符号，对于给定的视图射线，我们需要计算的量是$L_i(\boldsymbol{c},\boldsymbol{-v})$，其中c是摄像机位置，-v是沿着视线的方向。由于两种符号约定，我们使用-v。首先，$L_i()$中的方向向量总是指向远离给定点，在这种情况下摄像机位置是给定点。其次，视图矢量v始终指向相机。</p>
<p>在渲染中，场景通常被建模为在它们之间具有介质的对象的集合（单词“介质media”实际上来自拉丁词，用于“在中间”或“在中间”）。通常所讨论的介质是适量的相对清洁的空气，其不会显着影响光线的辐射，因此可以忽略，用于渲染。有时光线可能穿过介质，通过吸收或散射明显影响其辐射。这种介质被称为参与介质（participating media），因为它们参与了灯光在场景中的传输。 第14章将详细介绍参与介质。在本章中，我们假设没有参与介质，因此进入摄像机的辐射率等于离开最近物体表面朝向摄像机方向的辐射率：</p>
<p>$$<br>
L_i(\boldsymbol{c},\boldsymbol{-v})=L_o(\boldsymbol{p},\boldsymbol{v})\tag{9.2}<br>
$$</p>
<p>其中p是视线与最近物体表面的交点。</p>
<p>按照公式9.2，我们的新目标是计算$L_o(\boldsymbol{p},\boldsymbol{v})$。此计算是第5.1节中讨论的着色模型计算的基于物理的版本。有时辐射是由表面直接发出的。更常见的是，离开表面的辐射源自其他地方，并通过第9.1节中描述的物理相互作用由表面反射到视线中。在本章中，我们不考虑透明度（第5.5节和第14.5.2节）和全局次表面散射（第14.6节）。换句话说，我们专注于局部反射现象，它将光线重新导向当前着色点之外。这些现象包括表面反射以及局部次表面散射，并且仅取决于入射光方向l和出射视图方向v。局部反射率由双向反射分布函数（bidirectional reflectance distribution function ，BRDF）量化，表示为$f(\boldsymbol{l},\boldsymbol{v})$。</p>
<p>在其原始推导中，BRDF被定义为均匀表面。也就是说，假设BRDF在表面上是相同的。但是，现实世界（以及渲染场景中）的物体很少在其表面上具有均匀的材料属性。甚至由单一材质（例如，由银制成的雕像）组成的物体也将具有划痕，失去光泽的斑点，污点和其他变化，这些变化导致其视觉特性从一个表面点改变到下一个表面点。从技术上讲，基于空间位置捕获BRDF变化的函数称为空间变化的BRDF（spatially varying，SVBRDF）或空间BRDF（spatial，SBRDF）。然而，这种情况在实践中如此普遍，以至于经常使用简称BRDF并且隐含地假设它取决于表面位置。</p>
<p>传入和传出方向均具有两个自由度。 经常使用的参数化涉及两个角度：相对于表面法线n的高度θ和关于n的方位角（水平旋转）。在一般情况下，BRDF是四个标量变量的函数。各向同性BRDF是一个重要的特殊情况。当输入和输出方向围绕表面法线旋转时，这种BRDF保持不变，保持它们之间的相对角度。图9.17显示了两种情况下使用的变量。各向同性BRDF是三个标量变量的函数，因为只需要光和摄像机旋转之间的单个角度$\phi$。 这意味着如果将均匀的各向同性材料放置在转盘上并旋转，则在给定固定光和相机的情况下，对于所有旋转角度看起来都是相同的。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.17.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.17</center>
<p>由于我们忽略了荧光和磷光等现象，我们可以假设给定波长的入射光在相同波长下反射。反射的光量可以基于波长而变化，其可以以两种方式之一建模。要么将波长视为BRDF的附加输入变量，要么将BRDF视为返回光谱分布值。 虽然第一种方法有时用于离线渲染，但在实时渲染中总是使用第二种方法。由于实时渲染器将光谱分布表示为RGB三元组，因此这仅表示BRDF返回RGB值。</p>
<p>为了计算$L_o(\boldsymbol{p},\boldsymbol{v})$，我们将BRDF合并到反射率方程（reflectance equation）中：</p>
<p>$$<br>
L_o(\boldsymbol{p},\boldsymbol{v})=\int_{\boldsymbol{l}\in\Omega}f(\boldsymbol{l},\boldsymbol{v})L_i(\boldsymbol{p},\boldsymbol{l})(\boldsymbol{n}\cdot\boldsymbol{l})d\boldsymbol{l}.\tag{9.3}<br>
$$</p>
<p>积分符号上的$\boldsymbol{l}\in\Omega$下标表示对位于表面上方的单位半球中的l个矢量执行积分（以表面法线n为中心）。请注意，l在进入方向的半球上连续扫过，这不是一个特定的“光源方向”。这个想法是任何进入的方向都可以（并且通常会）有一些与之相关的辐射。我们使用dl来表示l周围的微分立体角（立体角在8.1.1节中讨论过）。</p>
<p>总之，反射率方程表明，出射辐射率等于入射辐射率的积分（对所有在$\Omega$中的$\boldsymbol{l}$）乘以BRDF乘以n和l之间的点积。</p>
<p>为简洁起见，对于本章的其余部分，我们将省略$L_i(),L_o()$中的表面点p，则反射方程为：</p>
<p>$$<br>
L_o(\boldsymbol{v})=\int_{\boldsymbol{l}\in\Omega}f(\boldsymbol{l},\boldsymbol{v})L_i(\boldsymbol{l})(\boldsymbol{n}\cdot\boldsymbol{l})d\boldsymbol{l}.\tag{9.4}<br>
$$</p>
<p>当计算反射率方程时，通常使用球面坐标φ和θ来参数化半球。对于该参数化，微分立体角$d\boldsymbol{l}$等于$\sinθ_idθ_id\phi_i$。使用此参数化，可以导出公式9.4的双积分形式，它使用球面坐标（回想起$(\boldsymbol{n}\cdot\boldsymbol{l})=\cos\theta_i$）：</p>
<p>$$<br>
L_o(\theta_o,\phi_o)=\int_{\phi_i=o}^{2\pi}\int_{\theta_i=0}^{\pi/2}f(\theta_i,\phi_i,\theta_o,\phi_o)L_i(\theta_i,\phi_i)\cos\theta_i\sin\theta_id\theta_id\phi_i.\tag{9.5}<br>
$$</p>
<p>角度$\theta_i$，$\phi_i$，$\theta_o$和$\phi_o$如图9.17所示。</p>
<p>在某些情况下，使用略微不同的参数化可以更方便，使用仰角的余弦值$\mu_i=\cos\theta_i$和$\mu_o=cosθ_o$作为变量而不是角度$\theta_i$和$\theta_o$本身。对于该参数化，微分立体角$d\boldsymbol{l}$ 等于$d\mu_id\phi_i$。使用$(\mu,\phi)$参数化产生以下积分形式：</p>
<p>$$<br>
L_o(\mu_o,\phi_o)=\int_{\phi_i=o}^{2\pi}\int_{\mu_i=0}^1f(\mu_i,\phi_i,\mu_o,\phi_o)L_i(\mu_i,\phi_i)\mu_id\mu_id\phi_i.\tag{9.6}<br>
$$</p>
<p>仅在光和视图方向都在表面上方的情况下才定义BRDF。通过将BRDF乘以零或者不首先评估BRDF的这些方向，可以避免光方向在表面下的情况。但是，表面下的视图方向呢，换句话说，点积n·v是负的？从理论上讲，这种情况永远不会发生。表面将背离相机，因此是不可见的。然而，在实时应用中常见的插值顶点法线和法线贴图可以在实践中产生这样的情况。通过将n·v钳位到0或使用其绝对值，可以避免对表面下的视图方向评估BRDF，但是这两种方法都可能导致伪影。 Frostbite引擎使用$(\boldsymbol{n}\cdot\boldsymbol{v})$的绝对值加上一个小数字（0.00001）来避免除以零。另一种可能的方法是“soft clamp”，随着n和v之间的角度增加超过90°，它逐渐变为零。</p>
<p>物理定律对任何BRDF都施加了两个约束。 第一个约束是亥姆霍兹互易性（Helmholtz reciprocity），这意味着可以切换输入和输出角度，并且函数值将是相同的：</p>
<p>$$<br>
f(\boldsymbol{l},\boldsymbol{v})=f(\boldsymbol{v},\boldsymbol{l})\tag{9.7}<br>
$$</p>
<p>在实践中，渲染中使用的BRDF经常违反亥姆霍兹互易性而并没有明显的失真，除了特别需要互易性的离线渲染算法，例如双向路径追踪。 但是，在确定BRDF是否物理合理时，它是一种有用的工具。</p>
<p>第二个限制是能量守恒，输出能量不能大于输入能量（不包括发光表面，这是作为特殊情况处理的）。离线渲染算法（如路径跟踪）需要节能以确保收敛。对于实时渲染，不需要精确的节能，但近似的节能非常重要。使用明显违反能量守恒的BRDF渲染的表面太亮，因此可能看起来不切实际。</p>
<p>定向半球反射率（directional-hemispherical reflectance）$R(\boldsymbol{l})$是与BRDF有关的函数。 它可以用来衡量BRDF保存能量的程度。尽管名称有些令人生畏，但定向半球反射是一个简单的概念。它测量来自给定方向的光量，该光线完全反射到半球表面法线周围的任何向外方向。基本上，它测量给定进入方向的能量损失。此函数的输入是传入方向向量$\boldsymbol{l}$，其定义如下所示：</p>
<p>$$<br>
R(\boldsymbol{l})=\int_{\boldsymbol{v}\in\Omega}f(\boldsymbol{l},\boldsymbol{v})(\boldsymbol{n}\cdot\boldsymbol{v})d\boldsymbol{v}\tag{9.8}<br>
$$</p>
<p>注意，这里v，与反射率方程中的l一样，扫过整个半球并且不代表单一的观察方向。</p>
<p>类似但在某种意义上相反的功能，半球方向反射率（hemispherical-directional reflectance）$R(\boldsymbol{v})$可以类似地定义：</p>
<p>$$<br>
R(\boldsymbol{v})=\int_{\boldsymbol{l}\in\Omega}f(\boldsymbol{l},\boldsymbol{v})(\boldsymbol{n}\cdot\boldsymbol{l})d\boldsymbol{l}\tag{9.9}<br>
$$</p>
<p>如果BRDF的参数是可以互换的，则半球方向反射率和方向半球反射率相等，并且可以使用相同的函数来计算任一个。在可互换使用的情况下，定向反照率(Directional albedo)可用作两种反射率的总称。</p>
<p>由于能量守恒，方向半球反射率$R(\boldsymbol{l})$的值必须始终在[0, 1]。 反射率值0表示所有入射光被吸收或以其他方式丢失的情况。如果所有光都被反射，则反射率将为1，大多数情况下，它将介于这两个值之间。与BRDF一样，$R(l)$的值随波长而变化，因此它表示为RGB矢量用于渲染目的。 由于每个组件（红色，绿色和蓝色）被限制在区间[0, 1]之间，可以简单的认为$R(\boldsymbol{l})$的值为一个颜色。请注意，此限制不适用于BRDF的值。作为分布函数，如果BRDF描述的分布非常不均匀，则BRDF可以在某些方向（例如突出显示的中心）具有任意高的值。 BRDF能量守恒的必要条件是对于l的所有可能值，$R(\boldsymbol{l})$不大于1。</p>
<p>最简单的BRDF是Lambertian，它对应于第5.2节中简要讨论的Lambertian着色模型。 Lambertian BRDF具有恒定值。 区分Lambertian着色的众所周知的（n·l）因子不是BRDF的一部分，而是公式9.4的一部分。尽管简单，Lambertian BRDF通常用于实时渲染以表示局部次表面散射（尽管它被更准确的模型所取代，如第9.9节所述）。 朗伯表面的方向半球反射率也是常数。 对于$f(\boldsymbol{l},\boldsymbol{v})$的常数值，计算公式9.8，得到以下作为BRDF函数的方向半球反射率值：</p>
<p>$$<br>
R(\boldsymbol{l})=\pi f(\boldsymbol{l},\boldsymbol{v})\tag{9.10}<br>
$$</p>
<p>Lambertian BRDF的恒定反射率值通常被称为漫反射色彩$c_{\text{diff}}$或反照率$\rho$。在本章中，为了强调与次表面散射的联系，我们将这个量称为次表面反照率（subsurface albedo）$\rho_{ss}$。 第9.9.1节详细讨论了次表面反照率。公式9.10中的BRDF给出以下结果：</p>
<p>$$<br>
f(\boldsymbol{l},\boldsymbol{v})=\frac{\rho_{ss}}{\pi}.\tag{9.11}<br>
$$</p>
<p>1/π是由于在半球上积分余弦产生π值引起的，这些因素常见于BRDF。</p>
<p>理解BRDF的一种方法是在输入方向保持不变的情况下将其可视化，见图9.18。对于给定的入射光方向，将显示所有传出方向的BRDF值。交叉点周围的球形部分是漫射分量，因为输出辐射在任何方向上反射具有相同的概率。椭圆形片是镜面波瓣（specular lobe）。当然，这些波瓣在入射光的反射方向上，波瓣的厚度对应于反射的模糊性。 通过互易原理，这些相同的可视化也可以被认为是每个不同的入射光方向对单个输出方向有多大贡献。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.18.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.18示例BRDF。来自每个图右侧的绿色实线是入射光方向，绿色和白色虚线是理想的反射方向。在第一行中，左图显示了Lambertian BRDF（一个简单的半球）。中图显示Blinn-Phong突出显示增加了Lambertian术语。 右图显示Cook-Torrance BRDF。 请注意反射高光在反射方向上的强度不是很强。在底行，左图显示了Ward的各向异性模型的特写。在这种情况下，效果是使镜面反射倾斜。中图显示了Hapke/Lommel-Seeliger“月球表面（lunar surface）”BRDF，它具有强烈的逆向反射。右图显示了Lommel-Seeliger散射，其中多尘的表面将光散射到掠射角。 （图片由Szymon Rusinkiewicz提供，来自他的“bv”BRDF浏览器。）</center>
<h3 id="94illumination">9.4 Illumination（照明）</h3>
<p>反射率方程（方程式9.4）中的$L_i(\boldsymbol{l})$（入射辐射）项表示从场景的其他部分照射到阴影表面点上的光。全局照明算法通过模拟光如何在整个场景中传播和反射来计算$L_i(\boldsymbol{l})$。这些算法使用渲染方程，其反射方程是一种特殊情况。全局照明将在第11章中讨论。在本章和下一章中，我们将重点放在局部照明上，它使用反射方程在每个表面点局部计算着色。在局部照明算法中，$L_i(\boldsymbol{l})$是给出的并且不需要计算。</p>
<p>在写实的场景中，$L_i(\boldsymbol{l})$包括来自所有方向的非零辐射，无论是直接从光源发射还是从其他表面反射。与5.2节中讨论的平行光和点源光不同，真实世界的光源是覆盖非零立体角的区域光源。在本章中，我们使用限制形式的$L_i(\boldsymbol{l})$，仅由平行光和点源光组成，为第10章留下更多的一般照明环境。这种限制允许更集中的讨论。</p>
<p>虽然平行光和点源光是非物理抽象，但它们可以作为物理光源的近似来导出。 这样的推导很重要，因为它使我们能够将这些灯合并到基于物理的渲染框架中，并确保我们理解所涉及的错误。</p>
<p>我们采用一个小的远距离区域光并将$\boldsymbol{l}_c$定义为指向其中心的向量。我们还将光的颜色定义为从白色朗伯表面朝向光$\boldsymbol{n=l_c}$的反射辐射。这是为了创作的直观定义，因为光的颜色直接对应于其视觉效果。</p>
<p>根据这些定义，可以导出定向光作为将区域光的大小缩小到零同时保持$c_{light}$值的极限情况。在这种情况下，反射系数公式（公式9.4）中的积分简化为单个BRDF，计算成本显着降低：</p>
<p>$$<br>
L_o(\boldsymbol{v})=\pi f(\boldsymbol{l}_c,\boldsymbol{v})\boldsymbol{c}_{light}\boldsymbol{n}\cdot\boldsymbol{l}_c)\tag{9.12}<br>
$$</p>
<p>点积$(\boldsymbol{n}\cdot\boldsymbol{l})$通常clamping到0，作为从表面下的灯光中跳过贡献的便捷方法：</p>
<p>$$<br>
L_o(\boldsymbol{v})=\pi f(\boldsymbol{l}_c,\boldsymbol{v})\boldsymbol{c}_{light}(\boldsymbol{n}\cdot\boldsymbol{l}_c)^+.\tag{9.13}<br>
$$</p>
<p>请注意第1.2节中介绍的+的表示法，它表示负值被clamping到0。</p>
<p>点光源可以类似地对待。 唯一的区别是区域光不需要远离，并且光线作为到光的距离的倒数平方而下降，如公式5.11中所示。 在多个光源的情况下，多次计算公式9.12并将结果相加：</p>
<p>$$<br>
L_o(\boldsymbol{v})=\pi\sum^n_{i=1}f(\boldsymbol{l}_{c_i},\boldsymbol{v})\boldsymbol{c}_{light_{i}}(\boldsymbol{n}\cdot\boldsymbol{l}_{c_i})^+,\tag{9.14}<br>
$$</p>
<p>其中$\boldsymbol{l}_{c_i}$和$\boldsymbol{c}_{light_{i}}$分别是第i个光的方向和颜色。请注意与公式5.6的相似之处。</p>
<p>公式9.14中的π因子抵消了经常出现在BRDF中的1/π因子（例如，公式9.11）。此抵消将除法运算移出着色器，并使着色方程更易于阅读。但是，在从学术论文中调整BRDF以用于实时着色方程时必须小心。通常，BRDF在使用前需要乘以π。</p>
<h3 id="95fresnelreflectance">9.5 Fresnel Reflectance（菲涅尔反射）</h3>
<p>在9.1节中，我们讨论了高层次的光物质相互作用。 在9.3节中，我们介绍了用数学表达这些相互作用的基本机制：BRDF和反射方程。 现在我们已经准备好开始深入研究特定现象，量化它们，以便它们可以用于着色模型。 我们将从在9.1.3节中初次讨论过的平面反射开始。</p>
<p>物体的表面是周围介质（通常是空气）和物体物质之间的界面。 光与两种物质之间的平面界面的相互作用遵循由Augustin-Jean Fresnel（1788-1827）开发的菲涅耳方程。菲涅耳方程需要遵循几何光学假设的平面界面。换句话说，假设表面在1个光波长和100个波长之间没有任何不规则性。小于该范围的不规则性对光没有影响，并且较大的不规则性有效地使表面倾斜但不影响其局部平坦度。</p>
<p>入射在平坦表面上的光分成反射部分和折射部分。反射光的方向（由矢量$\boldsymbol{r}_i$表示）与表面法线n形成相同的角度（$\theta_i$）作为进入方向$\boldsymbol{l}$。反射向量 $\boldsymbol{r}_i$可以从n和l计算：</p>
<p>$$<br>
\boldsymbol{r}_i=2(\boldsymbol{n}\cdot\boldsymbol{l})\boldsymbol{n}-\boldsymbol{l}.\tag{9.15}<br>
$$</p>
<p>见图9.19。反射的光量（作为入射光的一部分）由菲涅耳反射率（Fresnel reflectance）F描述，其取决于入射角$θ_i$。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.19.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.19</center>
<p>如第9.1.3节所述，反射和折射受平面两侧两种物质折射率的影响。我们将继续使用该讨论中的符号。值$n_1$ 是交界面上面物质的折射率，即入射和反射光传播的地方，$n_2$ 是交界面下物质的折射率，即折射光传播的地方。</p>
<p>菲涅耳方程描述了F对$\theta_i$，$n_1$和 $n_2$ 的依赖性。 我们将描述它们的重要特征，而不是呈现有些复杂的方程本身。</p>
<h4 id="951externalreflection">9.5.1 External Reflection（外反射）</h4>
<p>External Reflection是n1 &lt;n2的情况。 换句话说，光源自折射率较低的表面一侧。 大多数情况下，这一面含有空气，折射率约为1.003。 为简单起见，我们假设n1 = 1。 从物体到空气的相反过渡称为内反射（internal reflection），稍后将在9.5.3节中讨论。</p>
<p>对于给定的物质，菲涅耳方程可以解释为定义反射率函数$F(\theta_i)$，仅取决于入射光角度。原则上，$F(\theta_i)$ 的值在可见光谱上连续变化。出于渲染目的，其值被视为RGB向量。函数$F(\theta_i)$具有以下特征：</p>
<ul>
<li>当$\theta_i=0°$ 时，光垂直于表面$(l=n)$，$F(\theta_i) $具有作为物质属性的值。该值$F_0$可以被认为是物质的特征镜面颜色。$\theta_i= 0°$时的情况称为法向入射（normal incidence）。</li>
<li>当$\theta_i$ 增加并且光以越来越多的掠射角照射到表面时，$F(\theta_i) $的值将趋于增加，在$\theta_i= 90°$时所有频率值达到值1（白色）。</li>
</ul>
<p>图9.20显示了几种物质的$F(\theta_i)$ 函数，它以几种不同的方式显示。曲线是高度非线性的，它们几乎没有变化，直到$\theta_i = 75°$左右，然后很快变为1。从$F_0$ 到1的增加大多是无变化的，尽管有些物质在达到白色之前（例如图9.20中的铝）略有下降 。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.20.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.20</center>
<p>在镜面反射的情况下，出射角或视角与入射角相同。这意味着与$\theta_i$ 接近90°的入射光对眼镜来说也同样处于掠射角。因此，反射率的增加主要见于物体的边缘。此外，从相机的角度来看，表面上反射率增加最强的部分会缩短，因此它们占据的像素数量相对较少。为了显示菲涅耳曲线与其视觉突出度成比例的不同部分，图9.22中的菲涅耳反射率图和彩条以及图9.20的下半部分相对于$sin(\theta_i)$而不是直接针对$\theta_i$ 绘制。 图9.21说明了为什么$sin(\theta_i)$是一个适当的轴选择。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.21.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.21</center>
<p>从这一点来看，我们通常会使用符号$F(\boldsymbol{n},\boldsymbol{l})$代替$F(\theta_i)$作为菲涅耳函数，以强调所涉及的向量。回想一下$\theta_i$ 是矢量n和l之间的角度。当菲涅耳函数作为BRDF的一部分并入时，通常用不同的矢量代替表面法线n。详细信息请参见第9.8节。</p>
<p>掠射角度下反射率的增加通常被称为呈现出菲涅耳效应（在其他领域，该术语具有与无线电波传输有关的不同含义）。 您可以通过简短的实验看到菲涅耳效应。 拿一部智能手机，坐在明亮的区域，如电脑显示器。 不打开它，首先将手机靠近胸部，向下看，然后稍微倾斜，使其屏幕反射显示器。 手机屏幕上的显示器应该反射较弱。 这是因为玻璃的垂直入射反射率非常低。 现在将智能手机抬起，使其大致位于您的眼睛和显示器之间，并再次调整屏幕角度以反映显示器。 现在，手机屏幕上显示器的反射几乎与显示器本身一样明亮。</p>
<p>除了它们的复杂性之外，菲涅耳方程还具有其他属性，使得它们在渲染中的直接使用变得困难。 它们需要在可见光谱上采样的折射率值，并且这些值可以是复数。图9.20中的曲线表明基于特征镜面反射颜色$F_0$ 的更简单的方法。Schlick给出了菲涅耳反射率的近似值：</p>
<p>$$<br>
F(\boldsymbol{n},\boldsymbol{l})\approx F_0+(1-F_0)(1-(\boldsymbol{n\cdot l})^+)^5.\tag{9.16}<br>
$$</p>
<p>此功能是白色和$F_0$ 之间的RGB插值。尽管如此简单，但近似值是相当准确的。</p>
<p>图9.22包含了几种与Schlick曲线不同的物质，在“变为白色”之前显示出明显的“凹陷”。实际上，选择底行中的物质是因为它们在很大程度上偏离了Schlick近似。即使对于这些物质也是如此。物质产生的误差是非常微妙的，如图中每个图底部的彩条所示。在极少数情况下，精确捕捉这些材料的行为很重要，可以使用Gulbrandsen给出的替代近似值这种近似可以实现与金属的完整菲涅耳方程的紧密匹配，尽管它比Schlick的计算成本更高。更简单的选择是修改Schlick的近似值以允许将最终项提高到5以外的幂（如公式9.18所示）这将改变90°过渡到白色的“锐度”，这可能导致更接近的匹配。拉加德总结了菲涅耳方程和它们的几个近似值。</p>
<p>使用Schlick近似时，$F_0$ 是控制菲涅耳反射率的唯一参数。这很方便，因为F0在[0,1]中有一个明确定义的有效值范围。易于使用标准颜色选择界面进行设置，并且可以使用为颜色设计的纹理格式进行纹理化。此外，F0的参考值可用于许多真实材料。 折射率也可用于计算$F_0$。通常假设$n_1 = 1$，近似于空气的折射率，并使用n而不是$n_2$来表示物体的折射率。 这种简化给出了以下等式：</p>
<p>$$<br>
F_0=(\frac{n-1}{n+1})^2\tag{9.17}<br>
$$</p>
<p>如果使用（复杂）结果的大小，该等式甚至对于复值折射率（例如金属的折射率）也起作用。 在折射率在可见光谱上显着变化的情况下，计算$F_0$的精确RGB值需要首先在密集的波长采样下计算$F_0$ ，然后使用第8.1.3节中描述的方法将得到的光谱矢量转换为RGB值。</p>
<p>在一些应用中，使用了更一般的Schlick近似形式：</p>
<p>$$<br>
F(\boldsymbol{n},\boldsymbol{l})\approx F_0+(F_{90}-F_0)(1-(\boldsymbol{n\cdot l})^+)^{\frac1p}\tag{9.18}<br>
$$</p>
<p>这样可以控制菲涅耳曲线在90°处的颜色，以及“清晰度”。使用这种更通用的形式通常是为了增加艺术控制，但它也可以在某些情况下帮助匹配实现物理效果。如上所述，修改幂可以使某些材质更合适。另外，将$F_{90}$设置为白色以外的颜色可以帮助匹配Fresnel方程未能很好描述的材质，例如表面由小于波长的细小灰尘覆盖。</p>
<h4 id="952typicalfresnelreflectancevalues">9.5.2 Typical Fresnel Reflectance Values（典型的菲涅尔反射值）</h4>
<p>就其光学性质而言，物质分为三个主要类别。有电介质（dielectrics，绝缘体）；金属（导体）；和半导体（介于电介质和金属之间）。</p>
<h5 id="fresnelreflectancevaluesfordielectrics">Fresnel Reflectance Values for Dielectrics  （电介质的菲涅尔反射率）</h5>
<p>日常生活中遇到的大多数材料是电介质，玻璃，皮肤，木材，头发，皮革，塑料，石材和混凝土等等。水也是电介质。最后可能是令人惊讶的，因为在日常生活中已知水导电，但这种导电性是由于各种杂质造成的。电介质具有相当低的$F_0$值，通常为0.06或更低。在垂直入射时的这种低反射率使得菲涅耳效应对于电介质尤其可见。电介质的光学性质在可见光谱上很少变化很大，导致无色反射率值。几种常见电介质的$F_0$值如表9.1所示。这些值是标量而不是RGB，因为RGB通道对于这些材料没有显着差异。为方便起见，表9.1包括线性值以及使用sRGB传递函数编码的8位值（通常应用程序在纹理绘制中使用的形式）。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/t9.1.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>表9.1</center>
<p>其他电介质的$F_0$ 值可以通过查看表中的类似物质来推断。 对于未知的电介质，0.04是合理的默认值，与大多数常见材料相差不远。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/t9.2.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>表9.2</center>
<p>一旦光被传输到电介质中，它可以进一步散射或吸收。 第9.9节将更详细地讨论此过程的模型。 如果材料是透明的，那么光将持续直到它从内部离开物体表面，这将在第9.5.3节中详述。</p>
<h5 id="fresnelreflectancevaluesformetals">Fresnel Reflectance Values for Metals 金属的菲涅耳反射值</h5>
<p>金属的$F_0$ 几乎总是0.5或更高。一些金属具有在可见光谱范围内变化的光学性质，导致有色反射率值。 几种金属的F0值如表9.2所示。</p>
<p>与表9.1类似，表9.2具有线性值以及用于纹理化的8位sRGB编码值。 然而，这里我们给出RGB值，因为许多金属都有菲涅耳反射率的颜色。这些RGB值使用sRGB（和Rec.709）的原色和白点定义。 黄金有一个不寻常的$F_0$ 值。它是着色中最强烈的，红色通道值略高于1（它比sRGB / Rec.709色域超出了一点店）和特别低的蓝色通道值（表9.2中唯一的值明显低于0.5）。它也是最亮的金属之一，从表中的位置可以看出，它按照增加亮度的顺序排序。黄金的明亮和强烈的颜色反射可能有助于其历史上独特的文化和经济意义。</p>
<p>回想一下，金属会立即吸收任何透射光，因此它们不会出现任何次表面散射或透明度。金属的所有可见颜色都来自$F_0$ 。</p>
<h5 id="fresnelreflectancevaluesforsemiconductors">Fresnel Reflectance Values for Semiconductors 半导体的菲涅尔反射值</h5>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/t9.3.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>表9.3</center>
<p>正如人们所预料的那样，半导体在最亮的电介质和最暗的金属之间具有$F_0$值，如表9.3所示。在实践中很少需要渲染这样的物质，因为大多数渲染的场景都没有充满晶体硅块。出于实际目的，应避免$F_0$ 值在0.2和0.45之间的范围，除非您故意尝试模拟异国情调或不切实际的材质。</p>
<h5 id="fresnelreflectancevaluesinwater">Fresnel Reflectance Values in Water</h5>
<p>在我们对外部反射的讨论中，我们假设渲染表面被空气包围。 如果不是，则反射率将改变，因为它取决于界面两侧的折射率之间的比率。 如果我们不能再假设$n_1 = 1$，那么我们需要将公式9.17中的n替换为相对折射率，$n_1/n_2$。 这产生以下更通用的等式：</p>
<p>$$<br>
F_0=(\frac{n_1-n_2}{n_1+n_2})^2.\tag{9.19}<br>
$$</p>
<p>可能最常遇到的$n_1\neq1$的情况，是渲染水下场景时。由于水的折射率比空气的折射率高约1.33倍，因此$F_0$ 的值在水下是不同的。对于电介质而言，这种效应比对金属更强，如表9.4所示。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/t9.4.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>表9.4</center>
<h5 id="parameterizingfresnelvalues">Parameterizing Fresnel Values （参数化菲涅尔值）</h5>
<p>经常使用的参数化结合了镜面反射颜色$F_0$ 和漫反射颜色$ρ_{ss}$（漫反射颜色将在第9.9节中进一步讨论）。这种参数化利用了观察到金属没有漫反射颜色，并且电介质具有一组有限的$F_0$ 可能值，它包括RGB表面颜色$c_{surf}$和标量参数m，称为&quot;metallic“或&quot;metalness&quot;。如果m = 1，则将$F_0$设置为$c_{surf}$并将$ρ_{ss}$设置为黑色。如果$m=0$，则将$F_0$ 设置为介电值（常数或由附加参数控制），并将$ρ_{ss}$设置为$c_{surf}$。</p>
<p>由布朗大学首先使&quot;metalness&quot;作为参数使用的早期着色模型的一部分出现，其当前形式的参数化首先由皮克斯用于电影Wall-E。对于迪士尼使用的着色模型 ，用于Wreck-It Ralph的迪士尼动画电影，Burley添加了一个额外的标量“镜面”参数来控制介质$F_0$ 在有限的范围内。这种形式的参数化用于虚幻引擎，Frostbite引擎使用稍微不同的形式，电介质的$F_0$ 值可能范围更大。游戏“使命召唤：无限战争”使用一种变体，将这些金属度和镜面反射参数打包成单个值，以节省内存。</p>
<p>对于那些使用此金属度参数化而不是直接使用$F_0$ 和$ρ_{ss}$ 的渲染应用程序，动机包括用户便利性和保存纹理或G缓冲区存储。在游戏“使命召唤：无限战争”中，此参数化以不寻常的方式使用。 艺术家为$F_0$ 和$ρ_{ss}$ 绘制纹理，它们作为压缩方法自动转换为金属度参数化。</p>
<p>一些实时应用使用的另一种参数化技巧利用了这样的事实：除了特殊抗反射涂层之外，没有材质具有低于0.02的$F_0 $值。该技巧用于抑制表示空腔或空隙的表面区域中的镜面高光。不使用单独的镜面遮挡纹理，使用低于0.02的$F_0$ 值来“关闭”菲涅耳边缘增亮。这种技术最初由Schüler提出，用于Unreal和Frostbite引擎。</p>
<h4 id="953internalreflection">9.5.3 Internal Reflection（内反射）</h4>
<p>尽管在渲染中更经常遇到外部反射，但内部反射有时也很重要。 当n1&gt; n2时发生内部反射。换句话说，当光在透明物体内部行进并“从内部”遇到物体表面时，会发生内部反射。见图9.23。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.23.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.23</center>
<p>斯内尔定律（Snell's law）表明，对于内反射，$sinθ_t&gt;sinθ_i$。 由于这些值都在0°和90°之间，因此这种关系也意味着$θ_t&gt;θ_i$，如图9.23所示。在外部反射的情况下，情况恰恰相反，将此行为与第302页的图9.9进行比较。这种差异是理解内部和外部反射如何不同的关键。 在外部反射中，sinθ的有效（较小）值存在于0和1之间的sinθi的每个可能值。对于内部反射也是如此。 对于$θ_i$大于临界角（critical angle）θc的值，Snell定律意味着$sinθ_t&gt; 1$，这是不可能的。现实中发生的事情是没有$θ_t$。 当$θ_i&gt;θ_c$时，不发生透射，并且所有入射光都被反射。 这种现象称为全内反射(total internal reflection)。</p>
<p>在可以切换输入和传输矢量并且反射率保持相同的意义上，菲涅耳方程是对称的。结合Snell定律，这种对称性意味着内反射的$F(θ_i)$曲线将类似于外部反射曲线的&quot;压缩&quot;版本。两种情况下$F_0$的值相同，在θc而不是在90°时的内部反射曲线达到完美反射率。如图9.24所示，这也表明，在内部反射的情况下，平均反射率更高。例如，这就是为什么在水下看到的气泡具有高反射性，银色的外观。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.24.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.24</center>
<p>内部反射仅发生在电介质中，因为金属和半导体可以快速吸收在其内部传播的任何光。由于电介质具有实数折射率，因此从折射率或$F_0$ 计算临界角非常简单：</p>
<p>$$<br>
\sin\theta_c=\frac{n_2}{n_1}=\frac{1-\sqrt{F_o}}{1+\sqrt{F_0}}.\tag{9.20}<br>
$$</p>
<p>公式9.16中所示的Schlick近似对于外部反射是正确的。通过将透射角$θ_t$代入$θ_i$ ，它可以用于内反射。如果已经计算了传输方向向量t（例如，用于渲染折射，参见第14.5.2节），则可以使用它来找到$θ_t$ 。 否则Snell定律可用于从$θ_i$计算$θ_t$ ，但这是昂贵的并且需要折射率，可能是不可用的。</p>
<h3 id="96microgeometry">9.6 Microgeometry（微观几何学）</h3>
<p>正如我们之前在第9.1.3节中所讨论的那样，远小于像素的表面不规则性无法明确地建模，因此BRDF会在统计上模拟其聚合效应。现在我们继续研究几何光学领域，它假设这些不规则性要么小于光波长（因此对光的行为没有影响），要么大得多。“波光学领域”中的不规则性（大小约为1-100波长）的影响将在第9.11节中讨论。</p>
<p>每个可见表面点包含许多微表面法线，它们反射不同方向的反射光。由于单个微平面的方向有些随机，因此将它们建模为统计分布是有意义的。对于大多数表面，微观几何表面法线的分布是连续的，在宏观表面法线处具有强峰。 这种分布的“紧密度”由表面粗糙度决定。表面越粗糙，“微观几何法线”将越多。</p>
<p>增加微尺度粗糙度的可见效果是反射的环境细节的更大模糊。在小而明亮的光源的情况下，这种模糊导致更宽和更暗的镜面高光。来自较粗糙表面的那些较暗，因为光能被扩散到更宽的方向锥。这种现象可以在图9.12中的照片中看到。</p>
<p>图9.25显示了可见的反射率是如何从各个微尺度表面细节的聚合反射得到的。该系列图像显示由单个光照射的曲面，其中凸起的比例稳定地减小，直到在最后一个图像中凸起比单个像素小得多。许多小亮点中的统计模式最终成为所得聚合亮点形状的细节。例如，周边的单个凹凸高光的相对稀疏性成为远离其中心的聚合高光的相对暗度。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.25.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<p>对于大多数表面，微尺度表面法线的分布是各向同性的，这意味着它是旋转对称的，缺乏任何固有的方向性。其他表面具有各向异性的微米级结构。这样的表面具有各向异性的表面法线分布，导致反射和高光的方向模糊。见图9.26。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.26.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.26</center>
<p>一些表面具有高度结构化的微观几何学，导致各种微尺度正态分布和表面外观。 面料是一个常见的例子，天鹅绒和绸缎的独特外观是由于它们的微观几何结构。织物模型将在第9.10节中讨论。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.27.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.27</center>
<p>虽然多个表面法线是微观几何学对反射率的主要影响，但其他影响也很重要。阴影是指通过微尺度表面细节遮挡光源，如图9.27左侧所示。在图的中心处显示掩蔽，其中一些面相对于摄像机被隐藏。</p>
<p>如果微观几何高度与表面法线之间存在相关性，则阴影和遮蔽可以有效地改变正态分布。例如，想象一下通过风化或其他过程使凸起部分平滑的表面，并且下部保持粗糙。在掠射角度时，表面的下部将趋向于被遮蔽或遮盖，从而产生有效的更光滑的表面。见图9.28。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.28.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.28</center>
<p>对于所有表面类型，表面不规则的可见尺寸随着与法线的入射角$θ_i$ 的增加而减小。在非常掠射的角度，这种效果可以减少观察到的不规则尺寸比光的波长短，使它们“消失”。这两种效果与菲涅耳效应相结合，当观察和照明角度接近90°时，使表面看起来很高度反光。</p>
<p>微尺度表面细节遮挡的光线不会消失。它可能反映在其他微观几何学上。在它到达眼睛之前，光可以以这种方式经历多次反弹。这种互反射显示在图9.27的右侧。由于在每次反弹时光被菲涅耳反射率衰减，因此在电介质中互相反射趋于微妙。在金属中，多次反射是任何可见漫反射的来源，因为金属缺乏次表面散射。彩色金属的多次反射比初级反射更深，因为它们是光与表面多次相互作用的结果。</p>
<p>到目前为止，我们已经讨论了微观几何学对镜面反射率的影响，即表面反射率。 在某些情况下，微尺度表面细节也会影响次表面反射率。如果微观几何不规则性大于次表面散射距离，则阴影和遮蔽可以引起逆向反射效应，其中光优先被反射回入射方向。出现这种效果是因为当观看和照明方向差别很大时，阴影和遮蔽会遮挡被照亮的区域。见图9.29。逆向反射倾向于使粗糙表面具有平坦的外观。 见图9.30。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.29.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.29</center>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.30.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.30</center>
<h3 id="97microfacettheory">9.7 Microfacet Theory（微面理论）</h3>
<p>许多BRDF模型基于微观几何学对反射率的影响的数学分析，称为微平面理论。 该工具最初由光学界的研究人员开发。它于1977年由Blinn引入计算机图形学，1981年由Cook和Torrance引入。该理论基于微观几何的建模，作为微平面（microfacets）的集合。</p>
<p>这些微小面中的每一个都是平坦的，具有单个微平面法线m。微平面根据micro-BRDF $f_μ(\boldsymbol{1}, \boldsymbol{v}, \boldsymbol{m})$单独反射光，所有微平面的组合反射率相加得到整个表面的BRDF。通常的选择是每个microfacet都是一个完美的菲涅耳镜，产生一个镜面微平面BRDF，用于建模表面反射。但是，其他选择也是可能的。漫射微BRDF已被用于创建若干局部次表面散射模型。衍射微BRDF用于创建结合几何和波光学效应的着色模型。</p>
<p>microfacet模型的一个重要特性是microfacet法线m的统计分布。此分布由曲面的正态分布函数或（normal distribution function）NDF定义。一些参考文献使用术语“（distribution of normals）法线分布”来避免与正态分布混淆。我们将使用$D(\boldsymbol{m})$来指代方程中的NDF。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.31.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<p>NDF $D(\boldsymbol{m})$是微观几何表面面积上的微平面表面法线的统计分布。将$D(\boldsymbol{m})$积分在整个微平面法线上，得到微表面的面积。更有用的是，积分$D(\boldsymbol{m})(\boldsymbol{n·m})$，$D(\boldsymbol{m})$投影到宏观表面平面上，按照惯例给出宏观曲面贴片的面积等于1，如图9.31左侧所示。换句话说，投影$D(\boldsymbol{m})(\boldsymbol{n·m})$被归一化：</p>
<p>$$<br>
\int_{\boldsymbol{m}\in\Theta}D(\boldsymbol{m})(\boldsymbol{n·m})d\boldsymbol{m}=1\tag{9.21}<br>
$$</p>
<p>积分在整个球体上，在这里用Θ表示，不像本章中的先前球形积分，它仅集成在以n为中心的半球上，用Ω表示。这种表示法在大多数图形出版物中使用，但有些参考文献使用Ω来表示完整的球体。实际上，图形中使用的大多数微结构模型都是高度场，这意味着对于Ω外的所有方向，$D(\boldsymbol{m})=0$。但是，公式9.21也适用于非高度场微观结构。</p>
<p>更一般地，微表面的投影和宏观表面在垂直于视图方向v的平面上的投影是相等的：</p>
<p>$$<br>
\int_{\boldsymbol{m}\in\Theta}D(\boldsymbol{m})(\boldsymbol{n·m})d\boldsymbol{m}=\boldsymbol{v\cdot n}.\tag{9.22}<br>
$$</p>
<p>公式9.21和9.22中的点积不会被钳制为0。图9.31的右侧显示了原因。公式9.21和9.22强加了函数$D(\boldsymbol{m})$必须服从的约束是有效的NDF。</p>
<p>直觉上，NDF就像是微平面法线的直方图。它使用微平面法线更可能指向的方向上作为高度值。大多数表面都具有在宏观表面法线n处显示出强峰值的NDF。第9.8.1节将介绍几种用于渲染的NDF模型。</p>
<p>再看一下图9.31的右侧。尽管存在许多具有重叠投影的微平面，但最终用于渲染我们仅关注可见微平面，即在每个重叠集中最接近相机的微平面。这一事实表明了将投影的微平面区域与投影的宏观几何区域相关联的另一种方法：可见微平面的投影面积之和等于宏观表面的投影面积。我们可以通过定义掩蔽函数（masking function）$G_1(\boldsymbol{m},\boldsymbol{v})$来数学表达，它给出了沿着视图向量v可见的具有正常m的微平面的分数。$G_1(\boldsymbol{m,v})D(\boldsymbol{m})(\boldsymbol{v\cdot m})^+dm$的积分在球体上方然后给出投影到垂直于v的平面上的宏观表面区域：</p>
<p>$$<br>
\int_{\boldsymbol{m}\in\Theta}G_1(\boldsymbol{m,v})D(\boldsymbol{m})(\boldsymbol{v·m})^+d\boldsymbol{m}=\boldsymbol{v\cdot n}.\tag{9.23}<br>
$$</p>
<p>如图9.32所示。与公式9.22不同，公式9.23中的点积被钳制为零。背面微平面不可见，因此在这种情况下不计算它们。乘积$G_1(\boldsymbol{n·m})D(\boldsymbol{m})$ 是可见法线的分布（distribution of visible normals）。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.32.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.32</center>
<p>公式9.23对$G_1(m, v)$施加约束，但不能唯一地决定它。存在满足给定微平面正态分布$D(m)$的约束的无限函数。 这是因为$D(m)$ 没有完全指定微表面。 它告诉我们有多少微平面具有指向某些方向的法线，而不是它们如何排列。</p>
<p>尽管多年来已经提出了各种$G_1$函数，但是在Heitz的优秀论文中，至少在现在已经解决了使用哪种函数的困境。 Heitz讨论了Smith掩蔽函数，该函数最初是为高斯正态分布导出的，后来推广到任意NDF。Heitz表明，出于文献中提出的掩蔽函数，只有两个，Smith函数和Torrance-Sparrow“V腔”函数服从公式9.23，且在数学上是有效的。他进一步表明，Smith函数与随机微表面的行为比TorranceSparrow函数更接近。Heitz还证明了Smith掩蔽函数是唯一可能的函数，它既遵循公式9.23又具有正常掩蔽独立性的方便性。这意味着只要m不是背面，即只要$m·v≥0$，$G_1(m,v)$的值就不依赖于m的方向。Smith $G_1$ 函数具有以下形式：</p>
<p>$$<br>
G_1(\boldsymbol{m},\boldsymbol{v})=\frac{\chi^+(\boldsymbol{m\cdot v})}{1+\Lambda(\boldsymbol{v})},\tag{9.24}<br>
$$</p>
<p>其中$\chi^+(x)$是正特征函数（characteristic function）:</p>
<p>$$<br>
\chi^+(x)=<br>
\begin{cases}<br>
1,;where;x\gt0,\\<br>
0,;where;x\le0.<br>
\end{cases}\tag{9.25}<br>
$$</p>
<p>每个NDF的Λ(lambda)函数不同。在Walter和Heitz等人的出版物中描述了导出给定NDF的Λ的过程。</p>
<p>Smith masking function确实有一些缺点。从理论的角度来看，它的要求与实际表面的结构不一致，甚至可能在物理上不可能实现。从实际的角度来看，虽然它对于随机表面非常准确，但是对于在法线方向和遮蔽之间具有更强依赖性的表面，例如图9.28中所示的表面，其预期精度会降低，特别是如果表面具有一些重复结构（和大多数面料一样）。 然而，在找到更好的替代方案之前，它是大多数渲染应用程序的最佳选择。</p>
<p>给定微观几何描述，包括微BRDF$f_μ(l, v, m)$，正态分布函数$D(m)$ 和掩蔽函数$G_1(m,v)$ ，可以导出整个宏观表面BRDF：</p>
<p>$$<br>
f(\boldsymbol{l},\boldsymbol{v})=\int_{\boldsymbol{m}\in\Omega}f_μ(\boldsymbol{l, v, m})G_2(\boldsymbol{l,v,m})D(\boldsymbol{m})\boldsymbol{\frac{(m\cdot l)^+}{\vert n\cdot l \vert}\frac{(m\cdot v)^+}{\vert n\cdot v \vert}}d\boldsymbol{m}.\tag{9.26}<br>
$$</p>
<p>该积分位于以n为中心的半球Ω上，以避免从表面下收集光贡献。 公式9.26使用联合屏蔽阴影函数（joint masking-shadowing function）$G_2(l, v, m)$代替屏蔽函数 $G_1(m, v)$ 。该函数从$G_1$ 导出，给出了从两个方向可见的具有正常m的微平面的分数：视图向量v和光向量l。通过包含$G_2$函数，公式9.26使BRDF能够考虑屏蔽和阴影，但不考虑微平面之间的互反射（参见第329页的图9.27）。缺少微平面互反射是由公式9.26得出的所有BRDF共有的限制。这样的BRDF有点太暗了。在第9.8.2和9.9节中，我们将讨论为解决此限制而提出的一些方法。</p>
<p>Heitz讨论了$G_2$ 函数的几个版本。最简单的是可分离的形式，其中使用$G_1$ 分别评估掩蔽和阴影并相乘：</p>
<p>$$<br>
G_2(\boldsymbol{l,v,m})=G_1(\boldsymbol{v,m})G_1(\boldsymbol{l,m})\tag{9.27}<br>
$$</p>
<p>这种形式相当于假设遮蔽和阴影是不相关的事件。实际上是不对的，并且假设使用这种形式的$G_2$ 导致BRDF过度变暗。</p>
<p>作为一个极端的例子，考虑视图和光线方向相同的情况。在这种情况下，$G_2$ 应该等于$G_1$ ，因为没有任何可见小平面被遮蔽，但是公式9.27中的$G_2$ 将等于 $G^2_1$。</p>
<p>如果微表面是高度场，这通常是渲染中使用的微表面模型的情况，那么每当v和l之间的相对方位角φ等于0°时，$G_2(l,v, m)$应该等于$min(G_1(v, m),G_1(l, m))$。有关φ的说明，请参见图9.17。这种关系提出了一种通用的方法来解释可以与任何$G_1$ 函数一起使用的屏蔽和阴影之间的相关性：</p>
<p>$$<br>
G_2(\boldsymbol{l,v,m})=\lambda(\phi)G_1(\boldsymbol{v,m})G_1(\boldsymbol{l,m})+(1-\lambda(\phi))min(G_1(\boldsymbol{v,m}),G_1(\boldsymbol{l,m})),\tag{9.28}<br>
$$</p>
<p>其中$λ(\phi)$是随角度φ增加而从0增加到1的某个函数。Ashikhmin等人，建议高斯标准偏差为15°（~0.26弧度）：</p>
<p>$$<br>
\lambda{(\phi)}=1-e^{-7.3\phi^2}.\tag{9.29}<br>
$$</p>
<p>van Ginneken等人提出了不同的λ函数：</p>
<p>$$<br>
\lambda(\phi)=\frac{4.41\phi}{4.41\phi+1}.\tag{9.30}<br>
$$</p>
<p>无论光和视图方向的相对对准，还有另一个原因是在给定表面点处的掩蔽和阴影是相关的。两者都与点相对于表面其余部分的高度有关。对于较低点，掩蔽的概率增加，并且遮蔽的概率也增加。如果使用Smith掩蔽函数，则可以通过Smith height-correlated masking-shadowing function精确地解释此相关性：</p>
<p>$$<br>
G_2(\boldsymbol{l,v,m})=\frac{\chi^+(\boldsymbol{m\cdot v})\chi^+(\boldsymbol{m\cdot l})}{1+\Lambda(\boldsymbol{v})+\Lambda(\boldsymbol{l})}.\tag{9.31}<br>
$$</p>
<p>Heitz还描述了一种结合方向和高度相关性的Smith $G_2$ 形式：</p>
<p>$$<br>
G_2(\boldsymbol{l,v,m})=\frac{\chi^+(\boldsymbol{m\cdot v})\chi^+(\boldsymbol{m\cdot l})}{1+max(\Lambda(\boldsymbol{v}),\Lambda(\boldsymbol{l}))+\lambda(\boldsymbol{v,l})min(\Lambda(\boldsymbol{v}),\Lambda(\boldsymbol{l}))},\tag{9.32}<br>
$$</p>
<p>其中函数$λ(\boldsymbol{v},\boldsymbol{l})$可以是经验函数，例如公式9.29和9.30中的函数，或者特定于给定NDF得到的函数。</p>
<p>在这些替代方案中，Heitz推荐了Smith函数的高度相关形式（公式9.31），因为它与不相关的形式具有相似的成本和更高的精度。这种形式在实践中使用最广泛，尽管一些从业者使用可分离的形式（公式9.27）。</p>
<p>一般的微平面BRDF（公式9.26）不直接用于渲染。在给定微BRDF $f_μ$的特定选择的情况下，它用于导出闭合形式的解（精确或近似）。此类派生的第一个示例将在下一节中显示。</p>
<h3 id="98brdfmodelsforsurfacereflectionbrdf">9.8 BRDF Models for Surface Reflection（表面反射的BRDF模型）</h3>
<p>除了极少数例外，基于物理渲染的镜面BRDF公式都来自于微观理论。在镜面反射的情况下，每个微平面都是非常光滑的菲涅耳镜。回想一下，这样的镜子在单个反射方向上反射每个入射光线。这意味着每个小平面的微BRDF $f_μ(l,v,m)$等于零，除非v平行于l的反射。对于给定的l和v向量，这种配置等同于微平面法线m与指向l和v之间正好中间的向量对齐的情况。该向量是半向量h。见图9.33。它是通过添加v和l并将结果标准化来计算的：</p>
<p>$$<br>
\boldsymbol{h}=\frac{\boldsymbol{l+v}}{\Vert\boldsymbol{l+v}\Vert}.\tag{9.33}<br>
$$</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.33.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.33</center>
<p>当从公式9.26导出镜面微平面模型时，菲涅耳镜微BRDF $f_μ(l,v,m)=0$对于所有$m\neq h$都很方便，因为它将积分简化成在$m=h$时的一个整合函数。这样做会产生镜面BRDF公式：</p>
<p>$$<br>
f_{spec}(\boldsymbol{l,v})=\frac{F(\boldsymbol{h,l})G_2(\boldsymbol{l,v,h})D(\boldsymbol{h})}{4\vert\boldsymbol{n\cdot l}\vert\vert\boldsymbol{n\cdot v}\vert}\tag{9.34}<br>
$$</p>
<p>关于推导的细节可以在Walter等人，Heitz和Hammon的出版物中找到。 Hammon还展示了一种通过计算n·h和l·h来优化BRDF实现的方法，而无需计算向量h本身。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.34.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.34</center>
<p>我们在公式9.34中使用符号$f_{spec}$作为BRDF项，表示它只模拟表面（镜面）反射。 在完整的BRDF中，它可能会与模拟次表面（漫反射）着色的附加术语配对。 为了对公式9.34提供一些直观感受，考虑只有那些碰巧使其法线与半矢量（m = h）对齐的微平面才能正确定向，以将来自l的光反射到v中。见图9.34。因此，反射光的量取决于法线等于h的微平面的浓度。该值由$D(h)$给出，这是从光和视图方向可见的那些微平面的分数，其等于$G_2(l,v,h)$，并且由这些微平面中的每一个反射的光的部分 ，由$F(h, l)$指定。在菲涅耳函数的计算中，矢量h代替表面法线，例如，当计算公式9.16中的Schlick近似时。</p>
<p>在掩蔽阴影函数中使用半矢量允许较小的简化。由于所涉及的角度永远不会大于90°，因此可以去除公式9.24，9.31和9.32中的$χ^+$项。</p>
<h4 id="981normaldistributionfunctions">9.8.1 Normal Distribution Functions（法线分布函数）</h4>
<p>法线分布函数对渲染表面的外观有显着影响。在微平面法线球体上绘制的NDF的形状决定了反射光锥（镜面波瓣）的宽度和形状，反过来又决定了镜面高光的大小和形状。NDF影响表面粗糙度的整体感知，以及更微妙的视觉方面，例如高光是否具有明显的边缘或被雾霾包围。</p>
<p>然而，镜面波瓣不是NDF形状的简单副本。 根据表面曲率和视角，它会因高光形状被或多或少地扭曲。对于在掠射角处观察的平坦表面，这种扭曲特别强烈，如图9.35所示。Ngan等人，分析了这种扭曲背后的原因。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.35.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.35，左侧的图像使用非物理Phong反射模型渲染。 该模型的镜面波瓣围绕反射矢量旋转对称。这种BRDF经常在计算机图形学的早期使用。中心的图像使用基于物理的microfacet BRDF进行渲染。左上角和中间显示以掠射角点亮的平面。左上角显示不正确的圆形突出显示，而中心显示微平面BRDF上的特征突出显示伸长。该中心视图与现实相符，如右图所示。高亮度形状的差异在较低的两个渲染图像中显示的球体上更加微妙，因为在这种情况下，表面曲率是高光形状的主要因素。（照片由Elan Ruskin提供。）</center>
<h5 id="isotropicnormaldistributionfunctions">Isotropic Normal Distribution Functions 各向同性的法线分布函数</h5>
<p>渲染中使用的大多数NDF关于宏观表面法线n是各向同性的。在这种情况下，NDF只是一个单变量函数，n与微平均法线m之间的角度$θ_m$。理想情况下，NDF可以写成$cosθ_m$的表达式，可以有效地计算为n和m的点积。</p>
<p>Beckmann NDF是由光学界开发的第一批微面模型中使用的正态分布。它至今仍在该社区中广泛使用。它也是Cook-Torrance BRDF选择的NDF。归一化的Beckmann分布具有以下形式：</p>
<p>$$<br>
D(\boldsymbol{m})=\frac{\chi^+(\boldsymbol{n\cdot m})}{\pi\alpha^2_b(\boldsymbol{n\cdot m})^4}\exp(\frac{(\boldsymbol{n\cdot m})^2-1}{\alpha^2_b(\boldsymbol{n\cdot m})^2}).\tag{9.35}<br>
$$</p>
<p>对于指向宏观表面下的所有微平面法线，$\chi^+(\boldsymbol{n\cdot m})$确保NDF的值为0。这个属性告诉我们，这个NDF，就像我们将在本节讨论的所有其他NDF一样，描述了一个高度显微表面。$α_b$参数控制表面粗糙度。它与微观几何表面的均方根（RMS）斜率成比例，因此$α_b= 0$表示完全光滑的表面。</p>
<p>为了得到Beckmann NDF的Smith $G_2$函数，我们需要相应的$Λ$函数，插入公式9.24（如果使用$G_2$的可分离形式），9.31（对于高度相关形式），或9.32（对于方向和高度） 。</p>
<p>Beckmann NDF是形状不变的（shape-invariant），它简化了$Λ$的推导。如Heitz所定义的，如果其粗糙度参数的效果等于缩放（拉伸）微表面，则各向同性NDF是形状不变的。形状不变的NDF可以用以下形式编写：</p>
<p>$$<br>
D(\boldsymbol{m})=\frac{\chi^+(\boldsymbol{n\cdot m})}{\pi\alpha^2_b(\boldsymbol{n\cdot m})^4}g(\frac{\sqrt{1-(\boldsymbol{n\cdot m})^2}}{\alpha(\boldsymbol{n\cdot m})}),\tag{9.36}<br>
$$</p>
<p>其中g代表任意的单变量函数。 对于任意各向同性NDF，$Λ$函数取决于两个变量。第一个是粗糙度α，第二个是计算$Λ$的入射角矢量（v或l）。然而，对于形状不变的NDF，$Λ$函数仅取决于变量a：</p>
<p>$$<br>
a=\frac{\boldsymbol{n\cdot s}}{\alpha\sqrt{1-(\boldsymbol{n\cdot s})^2}},\tag{9.37}<br>
$$</p>
<p>其中s是表示v或l的向量。在这种情况下$Λ$仅依赖于一个变量的事实便于实现。单变量函数可以更容易地拟合近似曲线，并且可以在一维数组中制表。</p>
<p>Beckmann NDF的$Λ$函数是：</p>
<p>$$<br>
\Lambda(a)=\frac{erf(a)-1}{2}+\frac1{2a\sqrt{\pi}}\exp(-a^2).\tag{9.38}<br>
$$</p>
<p>公式9.38的计算成本很高，因为它包括误差函数erf。因此，通常使用近似值：</p>
<p>$$<br>
\Lambda(a)\approx<br>
\begin{cases}<br>
\frac{1-1.259a+0.396a^2}{3.535a+2.181a^2},&amp;where;a\lt1.6,\\<br>
0,&amp;where;a\ge1.6.<br>
\end{cases}\tag{9.39}<br>
$$</p>
<p>我们将讨论的下一个NDF是Blinn-Phong NDF。它在过去被广泛用于计算机图形学，但最近它已很大程度上被取代了。Blinn-Phong NDF仍然用于计算非常宝贵的情况（例如，在移动硬件上），因为计算成本比本节讨论的其他NDF便宜。</p>
<p>Blinn-Phong NDF由Blinn推导出来，作为（基于非物理的）Phong着色模型的修改：</p>
<p>$$<br>
D(\boldsymbol{m})=\chi^+(\boldsymbol{n\cdot m})\frac{\alpha_p+2}{2\pi}(\boldsymbol{n\cdot m})^{\alpha_p}.\tag{9.40}<br>
$$</p>
<p>幂$α_p$是Phong NDF的粗糙度参数。高值表示光滑表面，低值表示粗糙表面。对于非常光滑的表面，$α_p$ 的值可以任意高，完美的镜面需要$α_p=\infty $。通过将$α_p$ 设置为0，可以实现最大随机表面（均匀NDF, uniform NDF）。$α_p$ 参数不便于直接操作它的视觉感受非常不均匀。对于小的$α_p$ 值，小的数值变化具有大的视觉效果，但是可以在没有太多视觉影响的情况下显着改变大的值。出于这个原因，$α_p$通常通过非线性映射从用户操纵的参数导出。例如，$α_p= m^s$，其中s是0和1之间的参数值，m是给定应用中$α_p$ 的上限。这种映射被几个游戏使用，包括使命召唤：黑色行动，其中m被设置为值8192。</p>
<p>当BRDF参数的行为在感知上不均匀时，这种“接口映射”通常很有用。这些映射用于通过滑块或在纹理中绘制来设置参数。</p>
<p>Blinn-Phong NDF不是形状不变的，并且其$Λ$函数不存在分析形式。沃尔特等人。建议将贝克曼$Λ$函数与$α_p=2α_b^{-2}-2$参数等价结合使用。</p>
<p>在同一篇1977年的论文中，Blinn将Phong着色函数改编成了微型NDF，他提出了另外两个NDF。在这三个分布中，Blinn推荐了一个由Trowbridge和Reitz派生的分布。这一建议没有受到广泛关注，但30年后，Trowbridge-Reitz 分布被Walter等人独立重新发现，并将它命名为GGX distribution。这一次，种子扎根了。几年之内，GGX分布开始在电影和游戏行业中传播采用，而今天它可能是两者中最常用的分布。Blinn的建议似乎已经提前了30年，尽管“Trowbridge-Reitz distribution”在技术上是正确的名称，但我们在本书中使用了GGX名称，因为它已经确立。</p>
<p>GGX distribution是：</p>
<p>$$<br>
D(\boldsymbol{m})=\frac{\chi^+(\boldsymbol{n\cdot m})\alpha_g^2}{\pi(1+(\boldsymbol{n\cdot m})^2(\alpha^2_g-1))^2}.\tag{9.41}<br>
$$</p>
<p>由$α_g$参数提供的粗糙度控制类似于由Beckmann $α_b$ 参数提供的粗糙度控制。在迪士尼标准着色模型中，Burley将粗糙度控制以$α_g= r^2$暴露给用户，其中r是0到1之间的用户界面粗糙度参数值。将r作为滑块值公开意味着效果以更线性的方式变化。大多数使用GGX 分布的应用程序都采用了这种映射。</p>
<p>GGX分布是形状不变的，其Λ函数相对简单：</p>
<p>$$<br>
\Lambda(a)=\frac{-1+\sqrt{1+\frac{1}{a^2}}}{2}.\tag{9.42}<br>
$$</p>
<p>变量a仅作为$a^2$出现在公式9.42中的事实很方便，因为可以避免公式9.37中的平方根。</p>
<p>由于GGX distribution和Smith masking-shadowing function的普及，人们一直致力于优化两者的组合。Lagarde观察到用于GGX的高度相关的Smith $G_2$ （公式9.31）具有与镜面微平面BRDF的分母组合时抵消的项（公式9.34）。因此可以简化组合公式：</p>
<p>$$<br>
\frac{G_2(\boldsymbol{l,v})}{4\vert\boldsymbol{n\cdot l\vert\vert}\boldsymbol{n\cdot v\vert}}\Longrightarrow\frac{0.5}{\mu_o\sqrt{\alpha^2+\mu_i(\mu_i-\alpha^2\mu_i)}+\mu_i\sqrt{\alpha^2+\mu_o(\mu_o-\alpha^2\mu_o)}}.\tag{9.43}<br>
$$</p>
<p>为简洁起见，该等式使用变量替换$μ_i=(\boldsymbol{n·l})^+; μ_o=(\boldsymbol{n·v})^+$。Karis为GGX提出了一个近似形式的Smith G1函数：</p>
<p>$$<br>
G_1(\boldsymbol{s})\approx\frac{2(\boldsymbol{n\cdot s})}{(\boldsymbol{n\cdot s})(2-\alpha)+\alpha},\tag{9.44}<br>
$$</p>
<p>其中s可以用l或v替换。Hammon表明这种近似形式的$G_1$ 导致了由高度相关的Smith G2函数和镜面微平面BRDF分母组成的组合项的有效近似：</p>
<p>$$<br>
\frac{G_2(\boldsymbol{l,v})}{4\vert\boldsymbol{n\cdot l\vert\vert}\boldsymbol{n\cdot v\vert}}\approx\frac{0.5}{lerp(2\vert\boldsymbol{n\cdot l}\vert\vert\boldsymbol{n\cdot v}\vert,\vert\boldsymbol{n\cdot l}\vert+\vert\boldsymbol{n\cdot v}\vert,\alpha)},\tag{9.45}<br>
$$</p>
<p>它使用线性插值算子，$lerp(x, y, s)= x(1-s)+ ys$。</p>
<p>当比较图9.36中的GGX和Beckmann分布时，很明显两者具有根本不同的形状。GGX比Beckmann具有更窄的峰值，以及围绕这些峰值的更长的&quot;尾巴&quot;。在图的底部的渲染图像中，我们可以看到GGX的较长尾部在高光的核心周围产生雾霾或发光的外观。</p>
<p>许多真实世界的材质显示出类似的朦胧亮点，尾巴通常比GGX分布更长。见图9.37。这种认识一直是GGX分布日益普及的重要因素，同时也在不断寻找能够更准确地拟合测量材料的新分布。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.37.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.37</center>
<p>Burley提出了广义的Trowbridge-Reitz（GTR）NDF，其目标是允许更多地控制NDF的形状，特别是分布的尾部：</p>
<p>$$<br>
D(\boldsymbol{m})=\frac{k(\alpha,r)}{\pi(1+(\boldsymbol{n\cdot m})^2(\alpha_g^2-1))^\gamma}.\tag{9.46}<br>
$$</p>
<p>γ参数控制尾部形状。当γ= 2时，GTR与GGX相同。随着γ的值减小，分布的尾部变得更长，并且随着它的增加，它们变得更短。在高γ值时，GTR分布类似于Beckmann。$k(α,γ)$项是归一化因子，我们在一个单独的方程中给出，因为它比其他NDF更复杂：</p>
<p>$$<br>
k(\alpha,\gamma)=<br>
\begin{cases}<br>
\frac{(\gamma-1)(\alpha^2-1)}{(1-(\alpha^2)^{(1-\gamma)})},&amp;where;\gamma\ne1;and;\alpha\ne1,\\<br>
\frac{\alpha^2-1}{\ln(\alpha^2)},&amp;where;\gamma=1;and;\alpha\ne1,\\<br>
1,&amp;where;\alpha=1.<br>
\end{cases}\tag{9.47}<br>
$$</p>
<p>GTR分布不是形状不变的，这使得找到其Smith G2掩蔽阴影函数变得复杂。NDF出版后花了三年时间才发布了$G_2$ 的解决方案。这个$G_2$ 解决方案非常复杂，有一些针对某些$γ$值的解析解表（对于中间值，必须使用插值）。GTR的另一个问题是参数α和γ以非直观的方式影响感知的粗糙度和&quot;光韵&quot;。</p>
<p>学生的t分布（Student’s t-distribution, STD）和指数幂分布（exponential power distribution, EPD）NDF包括形状控制参数。与GTR相比，这些函数在其粗糙度参数方面是形状不变的。在撰写本文时，这些是新发布的，因此尚不清楚它们是否会在应用程序中使用。</p>
<p>不是增加NDF的复杂性，更好地匹配测量材料的替代解决方案是使用多个镜面波瓣。库克和托兰斯提出了这个想法。它是由Ngan进行的实验测试，他发现对于许多材料来说，添加第二个波瓣确实可以显着提高贴合度。皮克斯的PxrSurface材质有一个“粗糙的”波瓣用于此目的（与主镜面波瓣相连）。附加波瓣是一个完整的镜面微平面BRDF，包含所有相关参数和术语。Imageworks使用更多手术方法，使用两个GGX NDF混合，这些NDF作为扩展NDF暴露给用户，而不是整个单独的镜面BRDF公式。在这种情况下，所需的唯一附加参数是第二个粗糙度值和混合量。</p>
<h5 id="anisotropicnormaldistributionfunctions">Anisotropic Normal Distribution Functions（各向异性的法线分布函数）</h5>
<p>虽然大多数材质具有各向同性表面统计数据，但有些材料的微观结构具有显着的各向异性，这会显着影响其外观，例如图9.26。为了准确地渲染这些材质，我们需要BRDF，还有各向异性的NDF。</p>
<p>与各向同性NDF不同，不能仅用角度$θ_m$ 来计算各向异性NDF。需要其他方向信息。在一般情况下，需要将微平面法线$\boldsymbol{m}$变换为分别由法线，切线和双切线矢量$\boldsymbol{n，t}$和$\boldsymbol{b}$定义的局部框架或切线空间。请参见图6.32。实际上，此转换通常表示为三个单独的点积：$\boldsymbol{m·n}$，$\boldsymbol{m·t}$和$\boldsymbol{m·b}$。</p>
<p>将法线贴图与各向异性BRDF组合时，重要的是要确保法线贴图对切线和副切线矢量同样起作用。此过程通常通过将modified Gram-Schmidt应用于扰动法线n然后插值顶点切线和副切线向量$t_0$ 和 $b_0$（以下假设n已经标准化）来完成：</p>
<p>$$<br>
\begin{aligned}\boldsymbol{t'}=\boldsymbol{t}_0-(\boldsymbol{t_0\cdot\boldsymbol{n}})\boldsymbol{n}&amp;\Longrightarrow\boldsymbol{t}=\frac{\boldsymbol{t'}}{\Vert\boldsymbol{t'}\Vert},\\<br>
\left.\begin{array}<br>
\boldsymbol{\boldsymbol{b}}'=\boldsymbol{b_0-(b_0\cdot n)n},\\<br>
\boldsymbol{b''=b'-(b'\cdot t)t}\\<br>
\end{array}<br>
\right\rbrace<br>
&amp;\Longrightarrow\boldsymbol{b=\frac{b''}{\Vert\boldsymbol{b''}\Vert}}.<br>
\end{aligned}\tag{9.48}<br>
$$</p>
<p>或者，在第一行之后，可以通过取n和t的叉积来创建正交b向量。</p>
<p>对于诸如拉丝金属或卷发之类的效果，需要对切线方向进行逐像素修改，通常由切线图(tangent map)提供。此贴图是存储每像素切线的纹理，类似于法线贴图存储每像素法线的方式。切线图最常将切线矢量的二维投影存储在垂直于法线的平面上。此表示适用于纹理过滤，可以像普通贴图一样进行压缩。某些应用程序存储标量旋转量，用于围绕n旋转切向量。虽然这种表示更紧凑，但它容易出现纹理过滤失真，其中旋转角度从360°到0°环绕。</p>
<p>创建各向异性NDF的常用方法是概括现有的各向同性NDF。所使用的一般方法可以应用于任何形状不变的各向同性NDF，这是形状不变的NDF是优选的另一个原因。回想一下，各向同性形状不变的NDF可以用以下形式编写：</p>
<p>$$<br>
D(\boldsymbol{m})=\frac{\chi^+(\boldsymbol{n\cdot m})}{\alpha^2(\boldsymbol{n\cdot m})^4}g\left(\frac{\sqrt{1-(\boldsymbol{n\cdot m})^2}}{\alpha(\boldsymbol{n\cdot m})}\right),\tag{9.49}<br>
$$</p>
<p>g表示NDF形状的一维函数。各向异性版本是</p>
<p>$$<br>
D(\boldsymbol{m})=\frac{\chi^+(\boldsymbol{n\cdot m})}{\alpha_x\alpha_y(\boldsymbol{n\cdot m})^4}g\left(\frac{\sqrt{\frac{(\boldsymbol{t\cdot m})^2}{\alpha_x^2}+\frac{(\boldsymbol{b\cdot m})^2}{\alpha_y^2}}}{(\boldsymbol{n\cdot m})}\right),\tag{9.50}<br>
$$</p>
<p>参数$α_x$和$α_y$分别表示沿t和b方向的粗糙度。如果$α_x=α_y$，则公式9.50缩减回各向同性形式。</p>
<p>各向异性NDF的G2掩蔽阴影函数与各向同性NDF相同，除了变量a（传递到Λ函数）的计算方式不同：</p>
<p>$$<br>
a=\frac{\boldsymbol{n\cdot s}}{\sqrt{\alpha^2_x(\boldsymbol{t\cdot s})^2+\alpha_y^2(\boldsymbol{b\cdot s})^2}},\tag{9.51}<br>
$$</p>
<p>其中（如公式9.37所示）s代表v或l。</p>
<p>使用这种方法，可以为Beckmann NDF推导出各向异性版本：</p>
<p>$$<br>
D(\boldsymbol{m})=\frac{\chi^+(\boldsymbol{n\cdot m})}{\pi\alpha_x\alpha_y(\boldsymbol{n\cdot m})^4}exp\left(-\frac{\frac{(\boldsymbol{t\cdot m})^2}{\alpha_x^2}+\frac{(\boldsymbol{b\cdot m})^2}{\alpha_y^2}}{(\boldsymbol{n\cdot m})^2}\right),\tag{9.52}<br>
$$<br>
和GGX NDF：</p>
<p>$$<br>
D(\boldsymbol{m})=\frac{\chi^+(\boldsymbol{n\cdot m})}{\pi\alpha_x\alpha_y(\frac{(\boldsymbol{t\cdot m})^2}{\alpha_x^2}+\frac{(\boldsymbol{b\cdot m})^2}{\alpha_y^2}+(\boldsymbol{n\cdot m})^2)^2}.\tag{9.53}<br>
$$</p>
<p>两者都如图9.38所示。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.38.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.38</center>
<p>虽然参数化各向异性NDF的最直接的方法是使用各向同性粗糙度参数化两次，一次用于$α_x$ ，一次用于$α_y$ ，有时使用其他参数化。 在迪士尼原理着色模型中，各向同性粗糙度参数r与第二标量参数$k_{aniso}$组合，范围为[0,1]。因此，从这些参数计算$α_x$ 和$α_y$ 值：</p>
<p>$$<br>
\begin{align}<br>
k_{aspect}&amp;=\sqrt{1-0.9k_{aniso}},\\<br>
\alpha_x&amp;=\frac{r^2}{k_{aspect}},\\<br>
\alpha_y&amp;=r^2k_{aspect}.<br>
\end{align}\tag{9.54}<br>
$$</p>
<p>0.9因子将纵横比限制为10:1。</p>
<p>Imageworks使用不同的参数化，允许任意程度的各向异性：</p>
<p>$$<br>
\alpha_x=r^2(1+k_{aniso}),\\<br>
\alpha_y=r^2(1-k_{aniso}).\tag{9.55}<br>
$$</p>
<h4 id="982multiplebouncesurfacereflection">9.8.2 Multiple-Bounce Surface Reflection（多次反射的表面反射）</h4>
<p>如前面第9.7节所述，微平面BRDF框架不能解释从微表面多次反射（“反弹”）的光。这种简化会导致一些能量损失和过度变暗，特别是对于粗糙金属。</p>
<p>Imageworks使用的一种技术结合了以前工作中的元素来创建一个可以添加到BRDF的公式，以模拟多次反射表面反射：</p>
<p>$$<br>
f_{ms}(\boldsymbol{l,v})=\frac{\overline{F}\overline{R_{sF1}}}{\pi(1-\overline{R_{sF1}})(1-\overline{F}(1-\overline{R_{sF1}}))}(1-R_{sF1}(\boldsymbol{l}))(1-R_{sF1}(\boldsymbol{v})),\tag{9.56}<br>
$$</p>
<p>其中$R_{sF1}$是$f_{sF1}$的方向反照率（9.3节），它是将$F_0$ 设置为1的镜面BRDF项。函数$R_{sF1}$取决于粗糙度α和仰角θ。它相对平滑，因此可以在数值上预先计算（使用公式9.8或9.9）并存储在小的二维纹理中。Imageworks发现32×32分辨率就足够了。</p>
<p>函数$\overline{R_{sF1}}$是半球上$R_{sF1}$的余弦加权平均值。它仅依赖于α，因此可以存储在一维纹理中，或者可以将廉价的曲线拟合到数据中。 由于$R_{sF1}$关于n旋转对称，因此可以用一维积分计算$R_{sF1}$。 我们使用变量μ=cosθ（参见公式9.6）：</p>
<p>$$<br>
\begin{align}<br>
\overline{R_{sF1}}&amp;=\frac{\int_{s\in\Omega}R_{sF1}(s)(\boldsymbol{n\cdot s})ds}{\int_{s\in\Omega}R_{sF1}(\mu)\mu d\mu}=\frac1\pi\int_{\phi=0}^{2\pi}\int_{\mu=0}^{1}R_{sF1}(\mu)\mu d\mu d\phi \\<br>
&amp;=2\int^1_{\mu=0}R_{sF1}(\mu)\mu d\mu.<br>
\end{align}\tag{9.57}<br>
$$</p>
<p>最后，F是菲涅耳项的余弦加权平均值，以相同的方式计算：</p>
<p>$$<br>
\overline{F}=2\int^1_{\mu=0}F(\mu)\mu d\mu.\tag{9.58}<br>
$$</p>
<p>在广义Schlick形式（公式9.18）用于F的情况下，Imageworks为公式9.58提供了封闭形式的解决方案：</p>
<p>$$<br>
\overline{F}=\frac{2p^2F_{90}+(3p+1)F_0}{2p^2+3p+1}.\tag{9.59}<br>
$$</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.39.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.39 在所有行中，表面的粗糙度从左到右增加。 前两行显示金色材质。 第一行是在没有Imageworks多次反弹项的情况下渲染的，第二行是使用多次反弹项渲染的。 对于较粗糙的球体，差异最明显。 接下来的两行显示黑色介电材料。 第三行渲染时没有多次反弹项，第四行应用了多次反弹项。 这里的差异更微妙，因为镜面反射率要低得多。 （图片由Christopher Kulla提供。）</center>
<p>如果使用原始Schlick近似（公式9.16），则解决方案简化为：</p>
<p>$$<br>
\overline{F}=\frac{20}{21}F_0+\frac1{21}.\tag{9.60}<br>
$$</p>
<p>在各向异性的情况下，Imageworks使用$α_x$ 和$α_y$ 之间的中间粗糙度来计算$f_{ms}$。这种近似避免了增加$R_{sF1}$查找表的维度的需要，并且它引入的错误很小。Imageworks多次反射镜面反射术语的结果如图9.39所示。</p>
<h3 id="99brdfmodelsforsubsurfacescatteringbrdf">9.9 BRDF Models for Subsurface Scattering（次表面散射的BRDF模型）</h3>
<p>在上一节中，我们讨论了表面或镜面反射。在本节中，我们将讨论问题的另一面，即在表面下折射的光会发生什么。正如我们在9.1.4节中讨论的那样，这种光经过一些散射和吸收的组合，其中一部分从原始表面重新发射回来。 我们将在这里关注不透明电介质中局部次表面散射或漫反射表面响应的BRDF模型。 金属是无关紧要的，因为它们没有任何明显的次表面光相互作用。 第14章将介绍透明或具有全局次表面散射的电介质材质。</p>
<p>我们开始讨论漫反射模型，其中有关于漫反射颜色属性的部分以及此颜色在真实世界材质中可能具有的值。 在下面的小节中，我们将解释表面粗糙度对漫反射着色的影响，以及选择是否对给定材料使用光滑表面或粗糙表面着色模型的标准。 最后两个小节专门讨论光滑表面和粗糙表面模型本身。</p>
<h4 id="991subsurfacealbedo">9.9.1 Subsurface Albedo（次表面反照率）</h4>
<p>不透明电介质的次表面反照率$ρ_{ss}$是从表面逸出的光的能量与进入材料内部的光的能量之间的比率。 ρss的值在0（所有光被吸收）和1（没有光被吸收）之间并且可以取决于波长，因此ρss被建模为用于渲染的RGB矢量。 对于创作，ρss通常被称为表面的漫反射颜色，正常入射菲涅耳反射率$F_0$ 通常被称为镜面反射颜色。次表面反照率与第14.1节中讨论的散射反照率密切相关。</p>
<p>由于电介质传输大部分入射光而不是在表面反射它，因此次表面反照率$ρ_{ss}$通常更亮，因此在视觉上比镜面颜色$F_0$ 更重要。由于它是由与内部的镜面反射不同的物理过程产生的，而不是表面上的菲涅耳反射率$ρ_{ss}$通常具有与$F_0$ 不同的光谱分布（因此RGB颜色）。 例如，有色塑料由透明的透明基材组成，其内部嵌有颜料颗粒。 镜面反射的光将是未着色的，而漫反射的光将被颜料颗粒的吸收着色；例如，红色塑料球显示出白色高光。</p>
<p>次表面反照率可以被认为是“吸收和散射之间的竞争”，它会在有机会被散射回物体之前被吸收吗？这就是为什么液体上的泡沫比液体本身更亮，发泡过程不会改变液体的吸收率，但是添加大量的气液界面会大大增加散射量，这会导致大部分入射光在被吸收之前被散射，从而导致液体吸收。高次表面反照率和明亮的外观。新雪是反照率高的物质的另一个例子。雪粒和空气之间的界面有相当大的散射，但吸收很少，导致次表面反照率在可见光谱中为0.8或更高，白色油漆略少于0.7。日常生活中遇到的许多物质，如混凝土，石头和土壤，平均在0.15到0.4之间。煤是一种拥有极低的次表面反照率材质的例子，接近0.0。</p>
<p>许多材质在湿润时变暗的过程与液体泡沫实例相反。如果材质是多孔的，水会渗透到以前充满空气的空间中。电介质材质的折射率更接近水而不是空气。这种相对折射率的降低减少了材质内部的散射，并且光在离开材质之前行进了更长的距离（平均）。这种变化导致更多的光被吸收，并且次表面反照率变得更暗。</p>
<p>当从真实世界的表面获取$ρ_{ss}$的点值或纹理时，重要的是分离出镜面反射。这种提取可以通过仔细使用受控照明和偏振滤光器来完成。 为了获得准确的颜色，还应进行校准。</p>
<p>并非每个RGB三元组代表$ρ_{ss}$的合理（或甚至物理上可能）值。反射光谱比发射光谱功率分布更受限制：对于任何波长，它们永远不会超过1，并且它们通常非常光滑。这些限制定义了颜色空间中的体积，其中包含$ρ_{ss}$的所有合理的RGB值。即使是相对较小的sRGB色域也包含此体积以外的颜色，因此在设置$ρ_{ss}$的值时必须小心，以避免指定不自然饱和和明亮的颜色。除了降低真实感外，在预先计算全局照明时，这些颜色会导致过亮的二次反射（见第11.5.1节）。 孟等人的2015年论文是这个主题的一个很好的参考。</p>
<h4 id="992scaleofsubsurfacescatteringandroughness">9.9.2 Scale of Subsurface Scattering and Roughness（次表面散射和粗糙度的比例）</h4>
<p>用于局部次表面散射的一些BRDF模型考虑了表面粗糙度，通常使用具有漫射微BRDF $f_μ$ 的微平面理论，而有些则不然。使用哪种模型不仅仅取决于表面有多粗糙，尽管这是一种常见的误解。正确的决定因素涉及表面不规则性和次表面散射距离的相对大小。</p>
<p>见图9.40。如果微观几何不规则性大于次表面散射距离（图的左上角），那么次表面散射将表现出与微观几何相关的效果，例如逆向反射（第331页的图9.29）。对于这样的表面，应该使用粗糙表面漫射模型。如上所述，此类模型通常基于微平面理论，其中次表面散射被处理为每个微平面的局部，因此仅影响微BRDF $f_μ$ 。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.40.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.40 三个表面具有相似的NDF，但微观几何尺度与次表面散射距离之间的关系不同。在左上方，次表面散射距离小于表面不规则性。在右上方，散射距离大于表面不规则性。下图显示了具有多个尺度粗糙度的微表面。红色虚线表示仅包含大于次表面散射距离的微结构的有效表面。</center>
<p>如果散射距离都大于不规则性（图9.40的右上角），那么表面应该被认为是平坦的，以便对次表面散射进行建模，并且不会发生诸如逆向反射的现象。次表面散射不是微平面的局部散射，不能通过微平面理论建模。在这种情况下，应使用光滑表面漫反射模型。</p>
<p>在表面具有比散射距离更大和比粗糙度更小的中间情况下，应该使用粗糙表面漫射模型，但是具有仅包括大于散射距离的不规则性的有效表面。漫反射和镜面反射都可以用微平面理论建模，但每个都具有不同的粗糙度值。镜面术语将使用基于实际表面粗糙度的值，并且漫反射公式将使用较低的值，基于有效表面的粗糙度。</p>
<p>观察的规模也与此有关，因为它决定了“微观几何”的定义。例如，月球经常被引用作为应该使用粗糙表面漫射模型的情况，因为它表现出显着的逆向反射。当我们看到 来自地球的月亮，观察的规模是这样的，即使是一个5英尺的巨石也是“微观几何学”。因此，我们观察到诸如逆向反射的粗糙表面漫射效应就不足为奇了。</p>
<h4 id="993smoothsurfacesubsurfacemodels">9.9.3 Smooth-Surface Subsurface Models（光滑表面的次表面模型）</h4>
<p>在这里，我们将讨论光滑次表面模型。这些适用于表面不规则性小于次表面散射距离的材料建模。漫射阴影不直接受这些材料中表面粗糙度的影响。如果漫反射和镜面反射项是耦合的（本节中的某些模型就是这种情况），那么表面粗糙度可能会间接影响漫反射着色。</p>
<p>如9.3节所述，实时渲染应用程序通常使用朗伯项来模拟局部次表面散射。在这种情况下，BRDF项是$ρ_{ss}$ over π：</p>
<p>$$<br>
f_{diff}(\boldsymbol{l,v})=\frac{\rho_{ss}}{\pi}.\tag{9.61}<br>
$$</p>
<p>Lambertian模型没有考虑到在表面反射的光不能用于次表面散射的事实。为了改进该模型，应该在表面（镜面）和次表面（漫反射）反射项之间进行能量权衡。菲涅耳效应意味着这种表面 - 次表面能量权衡随入射光角度θi而变化。 随着入射角越来越大，漫反射率随着镜面反射率的增加而降低。 解释这种平衡的一种基本方法是将漫反射项乘以一个减去镜面反射项的菲涅耳部分。 如果镜面术语是平面镜子，则得到的漫反射公式是：</p>
<p>$$<br>
f_{diff}(\boldsymbol{l,v})=(1-F(\boldsymbol{n,l}))\frac{\rho_{ss}}{\pi}.\tag{9.62}<br>
$$</p>
<p>如果镜面项是一个microfacet BRDF，那么得到的散射公式是：</p>
<p>$$<br>
f_{diff}(\boldsymbol{l,v})=(1-F(\boldsymbol{h,l}))\frac{\rho_{ss}}{\pi}\tag{9.63}<br>
$$</p>
<p>公式9.62和9.63导致出射光的均匀分布，因为BRDF值不依赖于输出方向v。这种行为有一定意义，因为光在重新发射之前通常会经历多次散射事件，因此它的输出方向将是随机的。但是，有两个原因可以怀疑出射光的分布不均匀。首先，由于公式9.62中的漫反射BRDF项随入射方向而变化，因此亥姆霍兹互易意味着它必须通过输出方向改变。其次，光线必须在出路时经历折射，这将对出射光施加一些方向性偏好。</p>
<p>Shirley等人提出了平面耦合漫射项，解决了菲涅耳效应和表面 - 次表面反射率的权衡，同时支持节能和亥姆霍兹互易性。推导假设Schlick近似（公式9.16）用于菲涅耳反射：</p>
<p>$$<br>
f_{diff}(\boldsymbol{l,v})=\frac{21}{20\pi}(1-F_0)\rho_{ss}(1-(1-(\boldsymbol{n\cdot l})^+)^5)(1-<br>
(1-(\boldsymbol{n\cdot v})^+)^5)\tag{9.64}<br>
$$</p>
<p>公式9.64仅适用于镜面反射率是完美菲涅耳镜的表面。Ashikhmin和Shirley提出了一个可用于计算倒数，节能扩散项以与任何镜面项耦合的广义版本，并由Kelemen和Szirmay-Kalos进一步完善：</p>
<p>$$<br>
f_{diff}(\boldsymbol{l,v})=\rho_{ss}\frac{(1-R_{spec}(\boldsymbol{l}))(1-R_{spec}(\boldsymbol{v}))}{\pi(1-\overline{R_{spec}})}.\tag{9.65}<br>
$$</p>
<p>在这里，$R_{spec}$是镜面反射项的定向反照率（第9.3节），$\overline{R_{spec}}$是半球上的余弦加权平均值。可以使用公式9.8或9.9预先计算值$R_{spec}$并将其存储在查找表中。 $\overline{R_{spec}}$的计算方法与我们之前遇到的一个计算平均值的方式相似：$\overline{R_{sF1}}$ （公式9.57）。</p>
<p>公式9.65中的形式与公式9.56有一些明显的相似之处，这并不奇怪，因为Imageworks多次反弹镜面项源自Kelemen-Szirmay-Kalos耦合漫反射项。但是，有一个重要的区别。在这里，我们使用$R_{spec}$代替$R_{sF1}$，$R_{spec}$是包括菲涅耳在内的全镜面BRDF项的方向反照率，如果使用的话，还使用多次反射镜面项$f_{ms}$。这种差异增加了$R_{spec}$的查找表的维度，因为它不仅取决于粗糙度α和仰角θ，而且还取决于菲涅耳反射率。</p>
<p>在Imageworks实现的Kelemen-Szirmay-Kalos耦合漫射项中，使用了三维查找表，折射率为第三轴。他们发现在积分中包含多次反弹项使得$R_{spec}$比$R_{sF1}$更平滑，因此16×16×16表就足够了。图9.41显示了结果。</p>
<p>如果BRDF使用Schlick Fresnel近似并且不包括多次反射镜面项，那么$F_0$ 的值可以从积分中计算出来。这样做允许我们为$R_{spec}$使用二维表，在每个条目中存储两个量，而不是三维表，如Karis所讨论的。或者，Lazarov提出了一个适用于$R_{spec}$ 的分析函数，类似地将 $F_0$ 从积分中分解出来以简化拟合函数。</p>
<p>卡里斯和拉扎罗夫都使用镜面方向反照率$R_{spec}$用于与基于图像的照明相关的不同目的。有关该技术的更多详细信息，请参见第10.5.2节。如果两种技术都在同一个应用程序中实现，则可以对两者使用相同的表查找，从而提高效率。</p>
<p>这些模型是通过考虑表面（镜面）和次表面（漫反射）术语之间能量守恒的含义而开发的。其他模型是从物理原理开发出来的。其中许多模型依赖于Subrahmanyan Chandrasekhar（1910-1995）的工作，他开发了一种半无限，各向同性散射体积的BRDF模型。 如Kulla和Conty所证明的，如果平均自由路径足够短，则该BRDF模型是任意形状的散射体积的完美匹配。 Chandrasekhar BRDF可以在他的书中找到，尽管使用熟悉的渲染符号的更易于访问的形式可以在Dupuy等人的论文的方程中找到。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.41.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.41 第一行和第三行显示添加了朗伯的镜面公式。第二行和第四行显示与Kelemen-Szirmay-Kalos耦合漫射项一起使用的相同镜面公式。顶部两行的粗糙度值低于底部两行。在每行中，粗糙度从左到右增加。</center>
<p>由于它不包括折射，Chandrasekhar BRDF可用于仅建模索引匹配的表面。这些是两侧折射率相同的表面，如图9.11所示。要对非索引匹配的曲面进行建模，必须修改BRDF以考虑光线进入和离开曲面的折射。 这一修改是Hanrahan和Krueger以及Wolff的工作重点。</p>
<h4 id="994roughsurfacesubsurfacemodels">9.9.4 Rough-Surface Subsurface Models（粗糙表面次表面模型）</h4>
<p>作为迪士尼原则着色模型的一部分，Burley包含一个漫反射BRDF术语，旨在包括粗糙度效果和匹配测量过的材质：</p>
<p>$$<br>
f_{diff}(\boldsymbol{l,v})=\chi^+(\boldsymbol{n\cdot l})\chi^+(\boldsymbol{n\cdot v})\frac{\rho_{ss}}{\pi}((1-k_{ss})f_d+1.25k_{ss}f_{ss}),\tag{9.66}<br>
$$</p>
<p>其中：</p>
<p>$$<br>
\begin{align}<br>
f_d&amp;=(1+(F_{D90}-1)(1-\boldsymbol{n\cdot l})^5)(1+(F_{D90}-1)(1-\boldsymbol{n\cdot v})^5),\\<br>
F_{D90}&amp;=0.5+2\sqrt{\alpha}(\boldsymbol{h\cdot l})^2,\\<br>
f_{ss}&amp;=(\frac1{(\boldsymbol{n\cdot l})(\boldsymbol{n\cdot v})}-0.5)F_{ss}+0.5,\\<br>
F_{ss}&amp;=(1+(F_{SS90}-1))(1-\boldsymbol{n\cdot l})^5)(1+(F_{SS90}-1)(1-\boldsymbol{n\cdot v})^5),\\<br>
F_{SS90}&amp;=\sqrt\alpha(\boldsymbol{h\cdot l})^2,<br>
\end{align}\tag{9.67}<br>
$$</p>
<p>α是镜面粗糙度。 在各向异性的情况下，使用$α_x$ 和$α_y$ 之间的中间值。这个等式通常被称为迪斯尼漫射模型。</p>
<p>次表面项$f_{ss}$的灵感来自Hanrahan-Krueger BRDF，旨在作为远距离物体全球次表面散射的廉价替代品。漫反射模型基于用户控制的参数$k_{ss}$在$f_{ss}$和$f_d$粗糙漫反射项之间进行混合。</p>
<p>迪士尼漫反射模型已被用于电影和游戏（虽然没有次表面项）。完整的迪士尼漫射BRDF还包括一个光泽项，主要用于建模面料，但也有助于弥补由于缺乏多次反弹镜面项而导致的能量损失。迪士尼光泽项将在第9.10节中讨论。 几年后，Burley提出了一个更新的模型，旨在与全局次表面散射渲染技术相结合。</p>
<p>由于迪士尼漫反射模型使用与镜面BRDF项相同的粗糙度，因此可能难以对某些材质进行建模。见图9.40。但是，使用单独的漫反射粗糙度值将是一个微不足道的修改。</p>
<p>大多数其他粗糙表面漫射BRDF已经使用微平面理论开发，具有NDF $D$，micro-BRDF $f_μ$ 和masking-shadowing function $G_2$ 各种不同选择。这些模型中最着名的是Oren和Nayar提出的。Oren-Nayar BRDF使用Lambertian micro-BRDF，球形高斯NDF和Torrance-Sparrow“V-cavity”掩蔽阴影函数。完整形式的BRDF模拟一次二次弹跳。Oren和Nayar在他们的论文中还包括一个简化的“定性”模型。 多年来已经提出了对Oren-Nayar模型的一些改进，包括优化，调整以使“定性”模型更接近完整模型而不增加其成本，并将微BRDF改变为更准确的光滑表面漫反射模型。</p>
<p>Oren-Nayar模型假设一个微观曲面具有完全不同的正态分布和遮蔽阴影函数，而不是当前镜面模型中使用的那些。 使用各向同性GGX NDF和高度相关的Smith掩蔽阴影函数导出两个漫射微平面模型。 Gotanda的第一个模型是数值积分一般微平面方程（公式9.26）的结果，使用公式9.64中的镜面耦合漫反射项作为微观BRDF。然后将分析函数拟合到数值积分数据中。Gotanda的BRDF没有考虑构面之间的相互反射，拟合函数相对复杂。</p>
<p>使用相同的NDF，masking-shadowing function和micro-BRDF作为Gotanda，Hammon以数字方式模拟BRDF，包括互反射。他表明，相互反射对于这种微平面配置很重要，代表了粗糙表面总反射率的一半。然而，第二次反弹几乎包含所有缺失的能量，因此Hammon使用来自两次反弹模拟的数据。此外，可能因为添加了互反射来平滑数据，Hammon能够为模拟结果拟合一个相当简单的函数：</p>
<p>$$<br>
f_{diff}(\boldsymbol{l,v})=\chi^+(\boldsymbol{n\cdot l})\chi^+(\boldsymbol{n\cdot v})\frac{\rho_{ss}}{\pi}((1-\alpha_g)f_{smooth}+\alpha_gf_{rough}+\rho_{ss}f_{multi}),\tag{9.68}<br>
$$</p>
<p>其中：</p>
<p>$$<br>
\begin{align}<br>
f_{smooth}&amp;=\frac{21}{20}(1-F_0)(1-(1-\boldsymbol{n\cdot l})^5)(1-(1-\boldsymbol{n\cdot v})^5),\\<br>
f_{rough}&amp;=k_{facing}(0.9-0.4k_{facing})(\frac{0.5+\boldsymbol{n\cdot h}}{\boldsymbol{n\cdot h}}),\\<br>
k_{facing}&amp;=0.5+0.5(\boldsymbol{l\cdot v}),\\<br>
f_{multi}&amp;=0.3641\alpha_g,<br>
\end{align}\tag{9.69}<br>
$$</p>
<p>$α_g$ 是GGX镜面粗糙度。为清楚起见，此处的公式与Hammon的ppt略有不同。注意，$f_{smooth}$是来自公式9.64的耦合漫射BRDF，没有$ρ_{ss}/π$因子，因为它在公式9.68中相乘。Hammon讨论了“混合”BRDF，它可替代其他平滑表面漫射BRDF，以提高性能或改善与旧模型下创作的资产的兼容性。</p>
<p>总体而言，Hammon的漫射BRDF价格低廉且基于合理的理论原理，尽管他没有显示与测量数据的比较。需要注意的是，表面不规则性大于散射距离的假设是BRDF推导的基础，这可能会限制它可以精确建模的材料类型。见图9.40。</p>
<p>公式9.61中显示的简单朗伯项仍由许多实时渲染应用程序实现。除了兰伯特公式的低计算成本之外，与其他漫反射模型相比，它更容易使用间接和烘焙照明，并且它与更复杂的模型之间的视觉差异通常是微妙的。然而，对照片写实主义的不断追求推动了更准确模型的使用。</p>
<h3 id="910brdfmodelsforclothbrdf">9.10 BRDF Models for Cloth（布料的BRDF模型）</h3>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.42.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.42。使用为游戏Uncharted 4构建的布料系统的材料。左上方球体具有标准BRDF，其具有GGX microfacet镜面和朗伯漫反射。 上部中间球体使用织物BRDF。每个其他球体增加了不同类型的每像素变化，从左到右，从上到下：织物编织细节，织物老化，不完美细节和小皱纹。（UNCHARTED 4 A Thief's End c / TM 2016 SIE。由Naughty Dog LLC创建和开发。）</center>
<p>布料往往具有与其他类型材质不同的微观几何学。取决于织物类型，它可具有高度重复的编织微结构，从表面垂直突出的圆柱（螺纹）。因此，布料表面具有特征性的外观，通常需要专门的着色模型，例如各向异性镜面高光，粗糙散射（光线通过突出的半透明织物引起的明亮边缘效应），甚至是视线方向的色移（当不同颜色穿过织物时由细线引起的）。</p>
<p>除了BRDF之外，大多数织物都具有高频空间变化，这也是创造令人信服的布料外观的关键。 见图9.42。</p>
<p>布料BRDF模型分为三大类：从观察中创建的经验模型，基于微观理论的模型和微圆柱模型。我们将从每个类别中了解一些值得注意的例子。</p>
<h4 id="9101empiricalclothmodels">9.10.1 Empirical Cloth Models（经验布料模型）</h4>
<p>在Uncharted 2游戏中，布料表面使用以下漫反射BRDF术语：</p>
<p>$$<br>
f_{diff}(\boldsymbol{l,v})=\frac{\rho_{ss}}{\pi}(k_{rim}((\boldsymbol{v\cdot n})^+)^{\alpha_{rim}}+k_{inner}(1-(\boldsymbol{v\cdot n})^+)^{\alpha_{inner}}+k_{diff}),\tag{9.70}<br>
$$</p>
<p>其中$k_{rim}$，$k_{inner}$和$k_{diff}$是边缘照明术语的用户控制的缩放因子，分别是使前向（内）表面和朗伯术语变亮的术语。 此外，$α_{rim}$和$α_{inner}$控制边缘和内部项的衰减。此行为是非物理的，因为有几个与视图相关的效果，但没有一个依赖于光的方向。</p>
<p>相比之下，Uncharted 4中的布料使用微平面或微圆柱模型，取决于镜面类型的布料类型（详见以下两节）和漫反射公式的“包裹照明”经验次表面散射近似：</p>
<p>$$<br>
f_{diff}(\boldsymbol{l,v})(\boldsymbol{n\cdot l})^+\Rightarrow\frac{\rho_{ss}}{\pi}(\boldsymbol{c}_{scatter}+(\boldsymbol{n\cdot l})^+)^{\mp}\frac{(\boldsymbol{n\cdot l}+w)^\mp}{1+w}.\tag{9.71}<br>
$$</p>
<p>这里我们使用1.2节中引入的$(x)^\mp$符号，表示0和1之间的clamping。$f_{diff}\boldsymbol{(l, v)(n·l)^+}\Rightarrow\dots$ 表示此模型影响照明 以及BRDF。 箭头右侧的术语取代了左侧的术语。 用户指定的参数$c_{scatter}$是散射颜色，值w（范围[0,1]）控制包裹照明的宽度。</p>
<p>对于布料造型，迪士尼使用他们的散射BRDF项（第9.9.4节），并在模型粗糙散射中添加了光泽项：</p>
<p>$$<br>
f_{sheen}(\boldsymbol{l,v})=k_{sheen}\boldsymbol{c}_{sheen}(1-(\boldsymbol{h\cdot l})^+)^5,\tag{9.72}<br>
$$</p>
<p>其中$k_{sheen}$是一个调节光泽项强度的用户参数。光泽颜色$c_{sheen}$是白色和$ρ_{ss}$的亮度归一化值之间的混合（由另一个用户参数控制）。换句话说，$ρ_{ss}$除以其亮度以隔离其色调和饱和度。</p>
<h4 id="9102microfacetclothmodels">9.10.2 Microfacet Cloth Models（基于微面的布料模型）</h4>
<p>Ashikhmin等。提出使用倒置高斯NDF来模拟天鹅绒。该NDF在随后的工作中略有修改，其还提出了用于一般建模材料的微平面BRDF的变体形式，没有masking-shadowing项和修改的分母。</p>
<p>游戏The Order: 1886中使用的布料BRDF 将修改后的microfacet BRDF和来自Ashikhmin和Premoze后期报告的天鹅绒NDF的广义形式与来自公式9.63的扩散项组合在一起。广义天鹅绒NDF是</p>
<p>$$<br>
D(\boldsymbol{m})=\frac{\chi^+(\boldsymbol{n\cdot m})}{\pi(1+k_{amp}\alpha^2)}<br>
\left(<br>
1+\frac{k_{amp}exp(\frac{(\boldsymbol{n\cdot m})^2}{\alpha^2((\boldsymbol{n\cdot m})^2-1)})}{(1-(\boldsymbol{n\cdot m})^2)^2}<br>
\right),\tag{9.73}<br>
$$</p>
<p>其中α控制反向高斯的宽度，而$k_{amp}$控制其幅度。完整的布料BRDF是</p>
<p>$$<br>
f(\boldsymbol{l,v})=(1-F(\boldsymbol{h,l}))\frac{\rho_{ss}}{\pi}+\frac{F(\boldsymbol{h,l})D(\boldsymbol{h})}{4(\boldsymbol{n\cdot l}+\boldsymbol{n\cdot v}-(\boldsymbol{n\cdot l})(\boldsymbol{n\cdot v}))}.\tag{9.74}<br>
$$</p>
<p>这种BRDF的一种变体被用于游戏Uncharted 4中，用于粗糙的织物，如羊毛和棉。</p>
<p>Imageworks使用不同的反向NDF作为可以添加到任何BRDF的光泽项：</p>
<p>$$<br>
D(\boldsymbol{m})=\frac{\chi^+(\boldsymbol{n\cdot m})(2+\frac1\alpha)(1-(\boldsymbol{n\cdot m})^2)^{\frac1{2\alpha}}}{2\pi}.\tag{9.75}<br>
$$</p>
<p>尽管此NDF的Smith掩蔽阴影函数没有封闭形式的解决方案，但Imageworks能够使用分析函数逼近数值解。 Estevez和Kulla讨论了masking-shadowing function和光泽项与BRDF其余部分之间能量守恒的详细信息。有关使用Imageworks光泽项渲染的一些示例，请参见图9.43。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.43.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.43</center>
<p>到目前为止，我们看到的每种布料都限于特定类型的织物。下一节中讨论的模型试图以更一般的方式对布料进行建模。</p>
<h4 id="9103microcylinderclothmodels">9.10.3 Micro-Cylinder Cloth Models（微圆柱布料模型）</h4>
<p>用于布料的微型圆柱体模型与用于头发的模型非常相似，因此第14.7.2节中对头发模型的讨论可以提供额外的背景。这些模型背后的想法是假设表面覆盖有一维线。Kajiya和Kay为此案例开发了一个简单的BRDF模型，由Banks提供了坚实的理论基础。它被称为Kajiya-Kay BRDF或Banks BRDF。该概念基于以下观察：由一维线组成的表面在任何给定位置处具有无限数量的法线，由垂直于该位置处的切向量t的法线平面限定。 虽然从这个框架开发了许多较新的微型气缸模型，但由于其简单性，原始的Kajiya-Kay型号仍然有一些用途。例如，在Uncharted 4游戏中，Kajiya-Kay BRDF用于如丝绸和天鹅绒般闪亮的面料的镜面项。</p>
<p>Dreamworks使用相对简单且可由艺术家控制的微圆柱模型用于织物。纹理可用于改变粗糙度，颜色和线的方向，这可以指出表面平面，用于模制天鹅绒和类似的织物。可以为经线和纬线设置不同的参数，以模拟复杂的变色织物，例如闪光的绸缎。为了节能，该模型被标准化。</p>
<p>Sadeghi等。 提出了一种基于织物样品和单个螺纹测量的微气缸模型。 该模型还考虑了线程间的线程间屏蔽和阴影。</p>
<p>在某些情况下，实际的头发BSDF型号（第14.7节）用于布料。RenderMan的PxrSurface材质有一个“模糊”的波瓣，使用Marschner等人的头发模型中的R项（第14.7节）。由Wu和Yuksel在实时布料渲染系统中实现的一个模型来自头发，迪士尼用于动画电影。</p>
<h3 id="911waveopticsbrdfmodelsbrdf">9.11 Wave Optics BRDF Models（波光学的BRDF模型）</h3>
<p>我们在最后几节中讨论的模型依赖于几何光学，它将光视为在光线而不是波中传播。 如第303页所述，几何光学是基于任何表面不规则性小于波长或大于约100个波长的假设。</p>
<p>真实世界的表面并不那么乐观。它们往往在所有尺度上都有不规则性，包括1-100波长范围。 我们指的是具有纳米尺度等尺寸的不规则性，以区别于前面部分讨论的微观几何不规则性，这些不规则性太小而不能单独渲染但大于100个光波长。 纳米几何学对反射率的影响不能通过几何光学来建模。 这些效果取决于光的波动性质，并且需要波动光学（也称为物理光学）来对它们进行建模。</p>
<p>厚度接近光波长的表面层或膜也产生与光的波性相关的光学现象。</p>
<p>在本节中，我们将讨论波动光学现象，如衍射和薄膜干涉，讨论在现实中渲染看似相对平凡的材质时，会产生的令人惊讶的重要性。</p>
<h4 id="9111diffractionmodels">9.11.1 Diffraction Models（衍射模型）</h4>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.44.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.44</center>
<p>纳米尺度的测量会引起称为衍射（diffraction）的现象。为了解释它，我们利用了惠更斯—菲涅耳原理（Huygens-Fresnel principle），该原理指出波阵面（wavefront，具有相同波相的点集）的每个点都可以被视为新球面波的来源。见图9.44。当波遇到障碍时，惠更斯 - 菲涅耳原理表明它们会在拐角处稍微弯曲，这是衍射的一个例子。几何光学无法预测这种现象。在光入射到平面表面的情况下，几何光学确实可以正确地预测光将在单个方向上反射。也就是说，菲涅耳 - 惠更斯原理提供了额外的见解。它表明，表面上的球面波恰好恰好形成反射波阵面，通过相消干涉消除了所有其他方向的波。当我们观察具有纳米不规则性的表面时，这种洞察力变得重要。由于表面点的高度不同，表面上的球面波不再整齐排列。见图9.45。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.45.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.45 在左侧，我们看到平面波前入射到具有粗糙纳米尺度的表面。 在中心，我们看到根据菲涅耳 - 惠更斯原理在表面上形成的球面波。 在右边，我们看到在发生相长干涉和相消干涉之后，一些产生的波（红色）形成平面反射波。 剩余部分（紫色）被衍射，不同量的光在每个方向上传播，这取决于波长。</center>
<p>如图所示，光线在不同方向上散射。它的一部分被镜面反射，即，在反射方向上累加到平面波前。剩余的光以取向于纳米几何的某些性质的方向图衍射出来。镜面反射和衍射光之间的划分取决于纳米几何形状凸起的高度，或者更确切地说，取决于高度分布的变化。衍射光在镜面反射方向上的角度扩展取决于纳米几何形状凸起相对于光波长的宽度。有点违反直觉，更广泛的不规则导致更小的传播。如果不规则性大于100个光波长，则衍射光和镜面反射光之间的角度很小，可以忽略不计。尺寸减小的不规则性导致衍射光的更广泛扩散，直到不规则性变得小于光波长，此时不发生衍射。</p>
<p>在具有周期性纳米尺度的表面中，衍射最清晰可见，因为重复图案通过相长干涉增强衍射光，从而产生彩色彩虹色。 在CD和DVD光盘以及某些昆虫中可以观察到这种现象。 虽然衍射也发生在非周期性表面，但多年来计算机图形界已经假设其效果很轻微。 出于这个原因，除了一些例外，计算机图形文献多年来一直忽略衍射。</p>
<p>然而，Holzschuch和Pacanowski最近对测量材料的分析表明，许多材料中存在显着的衍射效应，并且可以解释用当前模型拟合这些材料的困难。同一作者的后续工作引入了一个结合了微平面和衍射理论的模型，通过使用一般的微平面BRDF（公式9.26）和微观BRDF来解释衍射。与此同时，Toisoul和Ghosh提出了捕获由周期性纳米几何测量产生的虹彩衍射效应的方法，以及用点光源和基于图像的照明实时渲染它们的方法。</p>
<h4 id="9112modelsforthinfilminterference">9.11.2 Models for Thin-Film Interference（薄膜干涉模型）</h4>
<p>薄膜干涉是当从薄的电介质层的顶部和底部反射的光路彼此干涉时发生的波光学现象。见图9.46。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.46.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.46</center>
<p>取决于波长和路径长度差之间的关系，不同波长的光或者相长干涉或相消干涉。由于路径长度差随角度变化，因此不同波长在相长干涉和相消干涉之间转换，最终结果是一个色彩斑斓的色移。</p>
<p>为了使这种效果发生薄膜需要很薄的原因与相干长度的概念有关。该长度是光波的副本可以移位并且仍然与原始波相干干涉的最大距离。该长度与光的带宽（bandwidth）成反比，光的带宽是其光谱功率分布（SPD）延伸的波长范围。激光具有极窄的带宽，具有极长的相干长度。可能是英里级别的，取决于激光的类型。这种关系是有意义的，因为被许多波长移位的简单正弦波仍将与原始波相干地干涉。如果激光是真正的单色，它将具有无限的相干长度，但实际上激光具有非零带宽。相反，具有极宽带宽的光将具有混沌波形。有意义的是，这种波形的副本需要在它停止与原始相干干涉之前仅移位一小段距离。</p>
<p>理论上，理想的白光是所有波长的混合物，其相干长度为零。然而，出于可见光光学的目的，人类视觉系统（仅在400-700nm范围内感测光）的带宽决定了相干长度，其约为1微米。因此，在大多数情况下，问题“胶片在它不再引起可见干涉之前有多厚？”的答案是“大约1微米”。</p>
<p>与衍射相似，多年来，薄膜干涉被认为是仅在肥皂泡和油渍等表面上发生的特殊情况效应。 然而，Akin指出，薄膜干涉确实为许多日常表面带来了微妙的色彩，并展示了如何模拟这种效果可以增加真实感。见图9.47。 他的文章引起了对基于物理的薄膜干涉的兴趣程度的显着增加，包括RenderMan的PxrSurface和Imageworks着色模型在内的各种着色模型都支持这种效果。</p>
<p>适用于实时渲染的薄膜干涉技术已经存在了一段时间。Smits和Meyer提出了一种有效的方法来解释一阶和二阶光路之间的薄膜干扰。他们观察到所得到的颜色主要是路径长度差的函数，其可以根据膜厚度，视角和折射率有效地计算。它们的实现需要具有RGB颜色的一维查找表。可以使用密集光谱采样计算表格的内容，并将其转换为RGB颜色作为预处理，这使得该技术非常快。在游戏“使命召唤：无限战争”中，不同的快速薄膜近似被用作分层材质系统的一部分。这些技术不能模拟薄膜中的多次反射光以及其他物理现象。Belcour和Barla提出了一种更准确，计算成本更高的技术，但仍以实时实施为目标。</p>
<h3 id="912layeredmaterials">9.12 Layered Materials（分层材质）</h3>
<p>在现实生活中，材料通常彼此叠加。表面可能被灰尘，水，冰或雪覆盖；出于装饰或保护的原因，它可能涂有漆或其他涂层;；或者它可以有多层作为其基本结构的一部分，例如许多生物材料。</p>
<p>最简单且最具视觉意义的分层情况之一是透明涂层，其是在一些不同材料的基底上的光滑透明层。一个例子是粗糙木材表面上的光滑涂层。迪士尼原则着色模型包括一个明确的公式，虚幻引擎，RenderMan的PxrSurface材质，以及Dreamworks Animation和Imageworks使用的着色模型也是。</p>
<p>透明涂层的最显着的视觉结果是由透明涂层和下面的基底反射的光产生的双重反射。当衬底是金属时，这种第二次反射最为显着，因为介电透明涂层和衬底的折射率之间的差异最大。当衬底是电介质时，其折射率接近透明涂层的折射率，导致第二次反射相对较弱。此效果类似于水下材料，如表9.4所示。</p>
<p>透明涂层也可以着色。 从物理角度来看，这种着色是吸收的结果。 根据比尔—朗伯定律（第14.1.2节），吸收的光量取决于光穿过透明涂层的路径长度。该路径长度取决于视图和光的角度，以及材料的折射率。更简单的清单实现（例如迪士尼原则模型和虚幻引擎中的实现）不会对此视图依赖进行建模。其他人也这样做，例如PxrSurface和Imageworks和Dreamworks着色模型中的实现。Imageworks模型还允许连接任意数量的不同类型的层。</p>
<p>在一般情况下，不同的层可以具有不同的表面法线。 一些例子包括在平坦路面上行驶的水流，在颠簸土壤顶部的平滑冰块，或覆盖纸板箱的皱纹塑料包裹。 电影业使用的大多数分层模型支持每层单独的法线。 这种做法在实时应用程序中并不常见，尽管虚幻引擎的clear-coat作为可选功能的实现支持它。</p>
<p>游戏“使命召唤：无限战争”使用了一种特别值得注意的分层材料系统。它允许用户合成任意数量的材质图层。它支持层之间的折射，散射和基于路径长度的吸收，以及每层不同的表面法线。结合高效的实施，该系统可实现前所未有的复杂实时材料，尤其是对于以60Hz运行的游戏而言令人印象深刻。见图9.48。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.48.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.48</center>
<h3 id="913blendingandfilteringmaterials">9.13 Blending and Filtering Materials（混合和过滤材质）</h3>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.49.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.49</center>
<p>材料混合是组合多种材料的特性（即BRDF参数）的过程。 例如，为了模拟一块带锈斑的金属板，我们可以绘制一个掩模纹理来控制锈斑位置，并用它来混合锈蚀的材料特性（镜面颜色$F_0$ ，漫反射颜色$ρ_{ss}$和粗糙度α）。金属，混合的每种材料也可以在空间上变化，参数存储在纹理中。混合可以作为预处理来完成，以创建新的纹理，通常称为“烘焙”，或者在着色器中即时实现。虽然表面法线n在技术上不是BRDF参数，但其空间变化对于外观很重要，因此材料混合通常还包括法线贴图混合。</p>
<p>为了理解为什么会出现这些伪像以及如何解决它们，请回想一下NDF是子像素表面结构的统计描述。当相机和表面之间的距离增加时，先前覆盖多个像素的表面结构可以减小到子像素大小，从凹凸图的范围移动到NDF的领域。这种转换与mipmap链密切相关，mipmap链将纹理细节的减少封装到子像素大小。</p>
<p>考虑对象的外观（如图9.49中左侧的圆柱体）进行建模以进行渲染。 外观建模总是假定一定的观察范围。 宏观（大规模）几何体被建模为三角形，中尺度（中等尺度）几何体被建模为纹理，并且小于单个像素的微观尺度几何体通过BRDF建模。</p>
<p>给定图像中显示的比例，将圆柱体建模为平滑网格（宏观尺度）并用法线贴图（中尺度）表示凸起是合适的。 选择具有固定粗糙度$α_b$ 的Beckmann NDF来模拟微尺度法线分布。这种组合表示在此尺度下很好地模拟了汽缸外观。但是，当观察规模发生变化时会发生什么？</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.50.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.50 如图9.49所示的表面的一部分。顶行显示法线分布（红色显示的平均法线）和隐含的微观几何。 底行显示了将四个NDF平均为一个的三种方法，如在mipmapping中所做的那样。 左边是地面实况（平均正态分布），中心分别显示平均（正常）和方差（粗糙度）的平均结果，右边显示匹配到平均NDF的NDF波瓣。</center>
<p>研究图9.50。顶部的黑框图显示了表面的一小部分，由四个法线贴图纹理覆盖。 假设我们以一定比例渲染表面，使得每个法线贴图纹素平均被一个像素覆盖。对于每个纹素，正态（即分布的平均值或平均值）显示为红色箭头，由Beckmann NDF包围，以黑色显示。法线和NDF隐式指定基础表面结构，如横截面所示。中间的大驼峰是法线贴图中的一个凸起，小摆动是微观表面结构。法线贴图中的每个纹素与粗糙度相结合，可以看作是收集纹素所覆盖的表面区域的法线分布。</p>
<p>现在假设相机已经从对象移动得更远，因此一个像素覆盖了所有四个法线贴图纹素。在该分辨率下表面的理想表示将精确地表示在每个像素覆盖的较大表面区域上收集的所有法线的分布。可以通过平均顶级mipmap的四个纹素中的NDF来找到此分布。左下图显示了这种理想的法线分布。如果用于渲染，该结果将最准确地表示在较低分辨率下表面的外观。</p>
<p>底部中心图显示了法线的单独平均值，每个分布的平均值和粗糙度的结果，粗糙度对应于每个的宽度。结果具有正确的平均法线（红色），但分布太窄。 此错误将导致表面显得过于平滑。 更糟糕的是，由于NDF非常狭窄，因此会以闪烁的高光形式引起混叠。</p>
<p>我们不能直接用Beckmann NDF表示理想的法线分布。但是，如果我们使用粗糙度图，贝克曼粗糙度$α_b$ 可以从纹素到纹素变化。想象一下，对于每个理想的NDF，我们发现定向贝克曼叶片在方向和总宽度上最接近。我们将贝克曼叶的中心方向存储在法线贴图中，并将粗糙度值存储在粗糙度图中。结果显示在右下角。 这个NDF更接近理想。 与简单的正常平均相比，圆柱体的外观可以更加忠实地表现出来，如图9.49所示。</p>
<p>为获得最佳结果，应将过滤操作（如mipmapping）应用于法线分布，而不是法线或粗糙度值。这样做意味着考虑NDF与法线之间关系的方式略有不同。通常，NDF定义在由法线贴图的每像素法线确定的局部切线空间中。然而，当在不同法线上过滤NDF时，将法线贴图和粗糙度贴图的组合定义为在底层几何表面的切线空间中定义倾斜的NDF（对于正常指向不平均的NDF）是更有用的。</p>
<p>早期尝试解决NDF过滤问题使用数值优化来将一个或多个NDF波瓣拟合到平均分布。这种方法存在鲁棒性和速度问题，并且今天使用不多。相反，当前使用的大多数技术通过计算法线分布的方差来工作。Toksvig巧妙地观察到，如果法线被平均而不是重新归一化，则平均法线的长度与正态分布的宽度成反比。也就是说，原始法线指向不同方向的越多，它们的平均值就越短。他提出了一种基于此正常长度修改NDF粗糙度参数的方法。用修正的粗糙度评估BRDF近似于滤波法线的扩散效应。</p>
<p>Toksvig的原始方程旨在与Blinn-Phong NDF一起使用：</p>
<p>$$<br>
\alpha'_p=\frac{\Vert\overline{\boldsymbol{n}}\Vert\alpha_p}{\Vert\overline{\boldsymbol{n}}\Vert+\alpha_p(1-\Vert\overline{\boldsymbol{n}}\Vert)},\tag{9.76}<br>
$$</p>
<p>其中$α_p$ 是原始粗糙度参数值，$α_p'$ 是修改值，$\Vert\overline{\boldsymbol{n}}\Vert$是平均法线的长度。该方程也可以与Beckmann NDF一起使用，通过应用等价等式$α_p=2α_b^{-2}-2$（来自Walter等人），因为两个NDF的形状非常接近。使用GGX的方法不那么简单，因为GGX和Blinn-Phong（或Beckmann）之间没有明确的等价。使用$α_b$ 的$α_b$ 等价在高光的中心给出相同的值，但高光外观是完全不同的。更令人不安的是，GGX分布的方差是不确定的，这使得这种基于方差的技术系列在与GGX一起使用时处于不稳定的理论基础上。尽管存在这些理论上的困难，但将公式9.76与GGX分布一起使用是相当普遍的，通常使用$α_p=2α_g^{-2}-2$。 这样做在实践中运作得相当好。</p>
<p>Toksvig的方法具有考虑GPU纹理过滤引入的正常方差的优点。它也适用于最简单的正常mipmapping方案，线性平均而不规范化。此功能对于动态生成的法线贴图尤其有用，例如水波纹，必须在运行时生成mipmap。 该方法不适用于静态法线贴图，因为它不能很好地适用于压缩法线贴图的主流方法。 这些压缩方法依赖于单位长度的正常情况。 由于Toksvig的方法依赖于平均法线变化的长度，因此与其一起使用的法线贴图可能必须保持未压缩。 即使这样，存储缩短的法线也会导致精度问题。</p>
<p>Olano和Baker的LEAN映射技术基于映射法线分布的协方差矩阵。与Toksvig的技术一样，它适用于GPU纹理过滤和线性mipmapping。它还支持各向异性法线分布。与Toksvig的方法类似，LEAN映射适用于动态生成的法线，但为了避免精度问题，与静态法线一起使用时需要大量存储。Hery等人独立开发了类似的技术。并在皮克斯的动画电影中使用，以渲染亚像素细节，如金属片和小划痕。LEAN映射的简单变体CLEAN映射需要较少的存储，代价是失去各向异性支持。LEADR映射扩展了LEAN映射，也考虑了位移映射的可见性效果。</p>
<p>实时应用程序中使用的大多数法线贴图都是静态的，而不是动态生成的。对于这样的映射，通常使用方差映射系列技术。在这些技术中，当生成法线贴图的mipmap链时，计算通过平均丢失的方差。希尔指出，Toksvig技术，LEAN映射和CLEAN映射的数学公式都可以用于以这种方式预先计算方差，这消除了这些技术在其原始形式中使用时的许多缺点。在某些情况下，预先计算的方差值存储在单独的方差纹理的mipmap链中。更常见的是，这些值用于修改现有粗糙度图的mipmap链。例如，这种方法用于游戏“使命召唤：黑色行动”中使用的方差图技术。通过将原始粗糙度值转换为方差值，将法线图中的方差相加，并将结果转换回粗糙度来计算修改的粗糙度值。对于游戏The Order：1886，Neubelt和Pettineo以类似的方式使用Han的技术。他们将法线贴图NDF与其BRDF镜面术语的NDF卷积，将结果转换为粗糙度，并将其存储在粗糙度图中。</p>
<p>为了以一些额外存储为代价来改善结果，可以在纹理空间x和y方向上计算方差并将其存储在各向异性粗糙度图中。 该技术本身仅限于轴对齐的各向异性，这在人造表面中是典型的，但在自然产生的表面中则较少。以存储一个或多个值为代价，也可以支持定向各向异性。</p>
<p>与原始形式的Toksvig，LEAN和CLEAN映射不同，方差映射技术不考虑GPU纹理过滤引入的方差。 为了弥补这一点，方差映射实现通常使用小滤波器将法线贴图的顶级mip卷积。当组合多个法线贴图（例如，细节法线贴图）时，需要注意正确地组合法线贴图的方差。</p>
<p>高曲率几何以及法线贴图可以引入法线方差。通过前面讨论的技术不能减轻由这种变化导致的伪像。存在一组不同的方法来解决几何法线方差。如果几何体上存在唯一的纹理贴图（通常是字符的情况，环境则不那么），那么几何曲率可以“烘焙”到粗糙度贴图中。曲率也可以使用像素着色器导数在运行中进行估算 如果正常缓冲区可用，则可以在渲染几何体时或在后处理过程中完成此估计。</p>
<p>到目前为止讨论的方法主要关注镜面响应，但正态方差也会影响漫反射。考虑法线方差对$\boldsymbol{n·l}$项的影响可以帮助提高漫反射和镜面反射的准确性，因为两者都在反射积分中乘以该因子。</p>
<p>方差映射技术将法线分布近似为平滑高斯波瓣。如果每个像素覆盖数十万个凸起，这是一个合理的近似值，因此它们都可以平滑地平均。然而，在许多情况下，许多像素仅覆盖几百或几千个凸起，这可能导致“硬质”外观。这方面的一个例子可以在图9.25中看到，这是一个图像序列，显示了一个球体，其中凸起的尺寸从图像到图像的尺寸减小。右下方的图像显示了当凸起足够小以平均成平滑高光时的结果，但左下和中下方的图像显示小于像素但不足以平滑平均的凸起。如果您要观察这些球体的动画渲染，那么嘈杂的高光会显示为从帧到帧闪烁的闪光。</p>
<p>如果我们绘制这样一个表面的NDF，它看起来就像图9.51中的左图。 随着球体的动画效果，h矢量在NDF上移动并越过亮区和暗区，从而产生“闪亮”的外观。如果我们在这个表面上使用方差映射技术，它将使用类似于图9.51右边的平滑NDF来有效地近似该NDF，从而失去了闪亮的细节。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C9/9.51.png" alt="Realtime Rendering 4th notes - chapter9"></p>
<center>图9.51</center>
<p>在电影工业中，这通常通过广泛的超级采样来解决，这在实时渲染应用中是不可行的，并且即使在离线渲染中也是不合需要的。 已经开发了几种技术来解决这个问题。 有些不适合实时使用，但可能为未来的研究提供了途径。 已经设计了两种用于实时实现的技术。 Wang和Bowles提出了一种技术，用于在Disney Infinity 3.0 游戏中渲染闪闪发光的雪。该技术旨在产生合理的闪亮外观，而不是模拟特定的NDF。 它适用于具有相对稀疏闪光的雪等材料。 Zirr和Kaplanyan的技术模拟了多尺度上的法线分布，在空间和时间上是稳定的，并且允许更多种类的外观。</p>
<p>我们没有空间来涵盖所有关于材料过滤的大量文献，因此我们将提到一些值得注意的参考资料。 Bruneton等。 提出了一种处理从几何到BRDF的海洋表面变化的技术，包括环境照明。Schilling讨论了一种类似方差映射的技术，该技术支持使用环境贴图进行各向异性着色。Bruneton和Neyret提供了该领域早期工作的全面概述。</p>
<h4 id="furtherreadingandresources">Further Reading and Resources</h4>
<p>McGuire的图形编码和Glassner的数字图像合成原理是本章所涉及的许多主题的良好参考。 Dutr'e的全局照明纲要的某些部分有点过时（特别是BRDF模型部分），但它是渲染数学（例如，球形和半球形积分）的良好参考。 Glassner和Dutr'e的参考资料均可在线免费获取。</p>
<p>对于想要更多地了解光与物质相互作用的读者，我们推荐Feynman无与伦比的讲座（可在线获取），这对我们在编写本章物理部分时的理解非常重要。其他有用的参考资料包括Fowles的现代光学介绍，这是一篇简短易懂的介绍性文本，以及Born和Wolf的光学原理，这是一本较重（比喻和字面）的书，提供了更深入的概述。Nassau的“物理学与化学”以极其彻底和细节的方式描述了物体颜色背后的物理现象。</p>
<p>（Chapter 9 End.）</p>
]]></content:encoded></item><item><title><![CDATA[Realtime Rendering 4th notes - chapter8]]></title><description><![CDATA[<p><a href="https://www.xx-ma.com/">(转载请注明出处，谢谢咯)</a></p>
<blockquote>
<p>&quot;Unweave a rainbow, as it erewhile made<br>
The tender-person’d Lamia melt into a shade. &quot; — John Keats</p>
</blockquote>
<p>前面章节中讨论的许多RGB颜色值表示光的强度和阴影。 在本章中，我们将了解由这些值测量的各种物理光量，为后续章节奠定基础，从更基于物理的角度讨论渲染。我们还将了解更多关于渲染过程经常被忽略的“后半部分”：将场景线性光量表示为最终显示颜色的转换。</p>
<h3 id="81lightquantities">8.1  Light Quantities（光量）</h3>
<p>任何基于物理渲染方法的第一步是以精确的方式将光量化。 首先提出的是辐射度学，因为这是与光的物理传输有关的核心领域。我们接下来讨论光度测量，光度测量处理由人眼的灵敏度加权（衡量过）的光值。我们对颜色的感知是一种心理物理（psychophysical）现象：物理刺激的心理感知。颜色感知在比色法一节中讨论。最后，我们讨论了使用RGB颜色值进行渲染的有效性。</p>
<h4 id="811radiometry">8.1.</h4>]]></description><link>http://xx-ma.com/rtr4-8/</link><guid isPermaLink="false">5bf2709cb67cfd1e0bec033e</guid><category><![CDATA[notes]]></category><category><![CDATA[graphics]]></category><category><![CDATA[realtime-rendering]]></category><category><![CDATA[code]]></category><dc:creator><![CDATA[Marianax]]></dc:creator><pubDate>Mon, 19 Nov 2018 08:19:04 GMT</pubDate><media:content url="http://xx-ma.com/content/images/2018/11/moren-hsu-359121-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<img src="http://xx-ma.com/content/images/2018/11/moren-hsu-359121-unsplash.jpg" alt="Realtime Rendering 4th notes - chapter8"><p><a href="https://www.xx-ma.com/">(转载请注明出处，谢谢咯)</a></p>
<blockquote>
<p>&quot;Unweave a rainbow, as it erewhile made<br>
The tender-person’d Lamia melt into a shade. &quot; — John Keats</p>
</blockquote>
<p>前面章节中讨论的许多RGB颜色值表示光的强度和阴影。 在本章中，我们将了解由这些值测量的各种物理光量，为后续章节奠定基础，从更基于物理的角度讨论渲染。我们还将了解更多关于渲染过程经常被忽略的“后半部分”：将场景线性光量表示为最终显示颜色的转换。</p>
<h3 id="81lightquantities">8.1  Light Quantities（光量）</h3>
<p>任何基于物理渲染方法的第一步是以精确的方式将光量化。 首先提出的是辐射度学，因为这是与光的物理传输有关的核心领域。我们接下来讨论光度测量，光度测量处理由人眼的灵敏度加权（衡量过）的光值。我们对颜色的感知是一种心理物理（psychophysical）现象：物理刺激的心理感知。颜色感知在比色法一节中讨论。最后，我们讨论了使用RGB颜色值进行渲染的有效性。</p>
<h4 id="811radiometry">8.1.1 Radiometry （辐射度学）</h4>
<p>辐射度学处理电磁辐射的测量。 将会在章节9.1中进行详细的讨论，这种辐射以波的形式传播。 不同波长（wavelengths）的电磁波——具有相同相位的两个相邻点之间的距离，例如，两个相邻峰值倾向于具有不同的特性。 在自然界中，电磁波存在于大范围的波长范围内，从长度小于百分之一纳米的伽马波到数万千米长的极低频（extreme low frequency，ELF）无线电波。 人类可以看到的波包含该范围的一小部分，从紫外光的约400纳米延伸到红光的700纳米多一点。见图8.1。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C8/8.1.png" alt="Realtime Rendering 4th notes - chapter8"></p>
<center>图8.1。可见光的波长范围，于完整电磁频谱内的位置。</center>
<p>辐射量的存在是为了电磁辐射的各个方面，包括：总能量、功率（随时间变化的能量）以及相对于面积、方向或两者兼有的功率密度。这些量汇总在表8.1中。</p>
<table>
<thead>
<tr>
<th>名称</th>
<th>符号</th>
<th>单位</th>
</tr>
</thead>
<tbody>
<tr>
<td>辐射通量（radiant flux）</td>
<td>$\Phi$</td>
<td>$watt(W)$</td>
</tr>
<tr>
<td>辐射照度（irradiance）</td>
<td>$E$</td>
<td>$W/m^2$</td>
</tr>
<tr>
<td>辐射强度（radiant intensity）</td>
<td>$I$</td>
<td>$W/sr$</td>
</tr>
<tr>
<td>辐射率（radiance）</td>
<td>$L$</td>
<td>$W/(m^2sr)$</td>
</tr>
</tbody>
</table>
<center> 表格8.1. 辐射度量学中的量和单位。</center>
<p>在辐射度量学中，最基本的单位是辐射通量$\Phi$。辐射通量是辐射能量随时间的流动——功率，以瓦特(watt，W)为单位。</p>
<p>辐射照度（Irradiance）是单位面积的辐射通量，即$d\Phi/dA$。辐射照度的定义是关于一个区域的，这个区域可能是空间中的一个假想区域，但通常是一个物体的表面。它的单位是瓦特每平方米$(W/m^2)$。</p>
<p>在我们进入下一个量之前，我们需要首先介绍立体角（solid angle）的概念，它是角度概念的三维扩展。 角度可以被认为是平面中连续方向组的大小的度量，弧度值相当于该组方向在半径为1的封闭圆上组成的弧度的长。同样，一个立体角测量的是三维空间中连续方向集的大小，以立体空间（steradians，缩写为“$sr$”）表示，它们由半径为1的封闭球体上的交集区域定义。立体角度用符号$\omega$表示。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C8/8.2.png" alt="Realtime Rendering 4th notes - chapter8"></p>
<center>图8.2 圆锥从球体的剖面图上取下的一个球面度（steradian）为1的圆锥。形状本身与测量没什么关系，而球体表面覆盖的面积才是关键。</center>
<p>在二维中，一个$2\pi$弧度的角覆盖了整个单位圆。将其扩展到三维空间，一个$4\pi$个立体角的立体角度将覆盖整个单位球面。一个立体角的大小如图8.2所示。</p>
<p>现在我们可以引入辐射强度(radiant intensity)，$I$，它是相对于方向的通量密度，更精确地说，对于立体角$(d\Phi/d\omega)$。单位是$w/sr$。</p>
<p>最后，辐射率，L，是一种测量单射线电磁辐射的方法。更精确地说，它是与面积和立体角度都相关的辐射通量密度$(d^2\omega/dAd\omega)$。这个面积是在垂直于光线的平面上测量的。如果在某一方向上的某一表面应用了辐射率，那么就必须使用余弦校正系数。你可能会遇见使用“投影区域”这个词，并参考校正因子来定义辐射率。</p>
<p>辐射率是传感器(如眼睛或相机)测量的东西(详见9.2节)，因此它对于渲染是至关重要的。计算着色方程的目的是计算一个沿着给定光线的辐射率，从着色表面的点到相机。沿着这条射线的L的值是以物理为基础的第5章中量$C_{shaded}$的等价物。辐射量的标准单位是瓦特/平方米/球面度（w/㎡/球度面）。</p>
<p>环境中的辐射可以被认为是五个变量(或包括波长在内的六个变量)的函数，称为辐射分布（radiance distribution）。其中三个变量指定位置，另外两个指定方向。这个函数描述了在空间中任何地方的所有光。一种考虑渲染过程的方法是，眼睛和屏幕定义一个点和一组方向(例如，穿过每个像素的光线)，这个函数在眼睛处对每个方向求值。基于图像的呈现，在第13.4节中讨论，使用了一个相关的概念，称为光场（light field）。</p>
<p>在着色方程中，辐射度通常以$L_o(\boldsymbol{x},\boldsymbol{d})$或$L_i(\boldsymbol{x},\boldsymbol{d})$的形式出现，这意味着辐射度分别从x点出去或进入x点。方向向量d表示光线的方向，按照惯例，它总是指向远离x的方向。虽然在$L_i$的情况下，这个规定可能会有些混乱，因为d点与光的传播方向相反，但是它便于计算，比如点积。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C8/8.3.png" alt="Realtime Rendering 4th notes - chapter8"></p>
<center>图8.3 SPDs（功率谱）是三种不同光波的光谱功率分布。最上面的功率谱来自一个绿色的激光器，它的光谱分布非常狭窄。中间的SPD是由相同的绿色激光和两个额外的激光（红色和蓝色）组成的光。这些激光的波长和相关强度相当于RGB激光投影机投出的自然白色光。底部的SPD是标准的**D65**光源，这是一个典型自然白色，可用于表示室外照明做参考。这种SPDs的能量在可见光谱中不断扩散，是自然采光的典型代表。</center>
<p>辐射的一个重要特性是它不受距离的影响，忽略了诸如雾这样的大气效应。换句话说，一个表面无论与观察者的距离有多远，都会有同样的亮度。当距离较远时，表面覆盖的像素较少，但每个像素的表面辐射是恒定的。</p>
<p>大多数光波包含了许多不同光波的混合。这是一个典型的可视化光谱功率分布(spectral power distribution，SPD)，其中显示了光的能量是如何分布在不同波长的。正如图8.3所示的三个示例。值得注意的是，尽管在图8.3的中间和底部的SPD图像之间存在显著差异，但它们被认为是相同的颜色。很明显，人眼是较差的光谱仪。我们将在8.1.3节详细讨论色觉。</p>
<p>所有辐射量都有光谱分布。由于这些分布是波长上的密度，它们的单位是原始数量除以纳米。例如，辐照度的光谱分布单位是每平方米每瓦特每纳米。</p>
<p>由于完整的spd对于渲染来说是笨拙的，特别是在交互速率下，在实践中辐射量被表示为RGB三元组。在8.1.3节中，我们将解释这些三元组与光谱分布的关系。</p>
<h4 id="812photometric">8.1.2 （photometric）光度学</h4>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C8/8.4.png" alt="Realtime Rendering 4th notes - chapter8"></p>
<center>图8.4 光度曲线 （纵坐标：光视效率；横坐标：纳米波长）</center>
<p>辐射度量学只涉及物理量，而不考虑人类的知觉。一个相关的领域，光度学，像放射学一样，只是它用人眼的灵敏度来衡量一切。辐射计算的结果通过乘以CIE光度曲线（CIE photometric curve）转换为光度单位，CIE光度曲线是一条钟形曲线，以555nm为中心的，代表眼睛对各种波长的光的响应。参见图8.4。</p>
<p>换算曲线和测量单位是光度学理论和放射学理论的唯一区别。每个辐射量都有一个等效的光量。表8.2显示了每个的名称和单位。这些单位都具有预期的关系(例如，勒克斯lux是每平方米流明)。虽然从逻辑上讲，流明是基本单位，但是从历史角度上讲，烛光被定义为基本单位，并且其他单位都是由他派生出来的。在北美，照明设计师用被废弃的“英尺蜡烛”(foot-candle，fc)来测量照度，而不是勒克斯(lux)。在这两种情况下，光照度是大多数照度计测量的指标，在照明工程中具有重要意义。</p>
<p>亮度通常用来描述表面的明亮度。例如，高动态范围(HDR)电视屏幕的峰值亮度通常在500到1000尼特(nits)之间。相比之下，“晴空”的亮度约为8000尼特(nits)，一个60瓦的灯泡约为12万个尼特(nits)，地平线上的太阳约为60万个尼特(nits)。</p>
<blockquote>
<p>1更准确的全称是“CIE光谱发光特性曲线”。 “明视的”指的是每平方公尺比3.4个大烛台更亮的照明条件。在这些条件下，眼睛的锥体细胞是活跃的。有一个相应的“暗视的”CIE曲线，中心在507纳米左右，这是指眼睛已经适应于每平方公尺低于0.034支烛台的暗状态——一个没有月光的夜晚或更暗的夜晚。在这些条件下，杆状细胞是活跃的。</p>
</blockquote>
<table>
<thead>
<tr>
<th>放射量(Radiometric Quantity)：单位</th>
<th>光照度(Photometric Quantity)：单位</th>
</tr>
</thead>
<tbody>
<tr>
<td>辐射通量：瓦特, $watt(W)$</td>
<td>光通量：流明（lumen，lm）</td>
</tr>
<tr>
<td>辐射度：$W/m^2$</td>
<td>照明度：勒克斯（lux，lx）</td>
</tr>
<tr>
<td>辐射亮度：$W/sr$</td>
<td>发光强度：坎德拉（candela，cd）</td>
</tr>
<tr>
<td>辐射率：$W/(m^2sr)$</td>
<td>亮度：$cd/M^2=nit$</td>
</tr>
</tbody>
</table>
<p>表8.2：辐射测量和光度测量的数量和单位。</p>
<h4 id="813">8.1.3 色度学</h4>
<p>在8.1.1节中，我们已经看到我们对光的颜色的感知与光的SPD(光谱功率分布)密切相关。我们还看到这不是简单的一对一对应关系。图8.3中底部和中间的spd完全不同，但是被认为是完全相同的颜色。色度学研究的就是光谱功率分布与颜色感知的关系。</p>
<p>人类能分辨大约1000万种不同的颜色。对于颜色感知，眼睛在视网膜上有三种不同类型的锥体受体，每种受体对不同波长的光线有不同的反应。其他动物有不同数量的颜色受体，有时多达15个。因此，对于一个特定的SPD，我们的大脑只接收到来自这些受体的三种不同的信号。这就是为什么只有三个数字可以精确地表示任何颜色刺激。</p>
<p>但是哪三个数字呢? 国际色标委员会(CIE)提出了一套测量颜色的条件标准，并利用它们进行了配色实验。在颜色匹配中，三种颜色的灯投射在白色屏幕上，使它们的颜色叠加在一起形成一个色块。匹配的测试颜色投影在这个色块的旁边。测试色块的波长为单一波长。然后观察者可以使用校准到一个加权范围[-1, 1]的旋钮来改变三种颜色的光，直到测试颜色匹配为止。匹配一些测试颜色需要加负权重，这样的权重意味着相应的光被添加到波长的测试颜色斑块中。图8.5显示了三种灯光的一组测试结果，分别为r、g和b。这些光几乎是单色的，每一种光的能量分布都集中在以下波长中的一种。r: 645 nm, g: 526 nm, b: 444 nm。将每组匹配权重与测试波长相关联的函数称为颜色匹配函数（color-matching functions）。</p>
<p>这些函数给出了一种将光谱功率分布转换为三个值的方法。给定单一波长的光，三种不同颜色的光的设置可以从图形、旋钮设置和创建的照明条件中读取，这些条件将让屏幕上的两个色块提供相同的感觉。对于任意的光谱分布，颜色匹配函数可以乘以极值，并且每个结果曲线下的面积(例如：积分)给出相对数量来设置彩色光以匹配光谱产生的感知颜色。明显不同的光谱分布可以分解为相同的三个分量，也就是说在观察者看来它们看起来是一样的。给出匹配权重值的光谱分布称为同色异谱色（metamers）。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C8/8.5.png" alt="Realtime Rendering 4th notes - chapter8"></p>
<center>图8.5。来自Stiles和Burch的r，g和b, 2度颜色匹配曲线。 这些颜色匹配曲线不应与颜色匹配实验中使用的光源的光谱分布混淆，后者是纯波长。</center>
<p>三个加权的r，g和b光不能直接表示所有可见颜色，因为它们的颜色匹配函数对于各种波长具有负权重。 CIE提出了三种不同的假设光源，其具有对所有可见波长都是正的颜色匹配函数。这些曲线是原始r，g和b颜色匹配函数的线性组合。这要求光源的光谱功率分布在某些波长处为负，因此这些光是不可实现的数学抽象。它们的颜色匹配函数表示为$x(λ)$，$y(λ)$和$z(λ)$，如图8.6所示。 颜色匹配函数$y(\lambda)$与光度曲线相同（图8.4），因为该曲线将辐射亮度转换为亮度。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C8/8.6.png" alt="Realtime Rendering 4th notes - chapter8"></p>
<center>图8.6。 Judd-Vos修改的CIE（1978）2度颜色匹配函数。 请注意，两个x是同一曲线的一部分。</center>
<p>与前一组颜色匹配函数一样，$x(λ)$，$y(λ)$和$z(λ)$用于通过乘法和积分将任何$SPD s(λ)$减少为三个数：</p>
<p>$$<br>
X=\int^{780}_{380}s(\lambda)\overline{x}d\lambda,<br>
;<br>
Y=\int^{780}_{380}s(\lambda)\overline{y}(\lambda)d\lambda,<br>
;<br>
Z=\int^{780}_{380}s(\lambda)\overline{z}(\lambda)d\lambda.\tag{8.1}<br>
$$</p>
<p>这些X，Y和Z三色值是在CIE XYZ空间中定义的颜色的权重。将颜色分成亮度和色度通常是方便的。色度是与其亮度无关的颜色特征。例如，尽管亮度不同，但两种蓝色，一种暗色和一种亮色可以具有相同的色相（chromaticity，色度）。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C8/8.7.png" alt="Realtime Rendering 4th notes - chapter8"></p>
<center>图8.7。 CIE RGB原色的RGB颜色立方体在XYZ空间中显示，其投影（紫色）显示在X + Y + Z = 1平面上。蓝色轮廓包围可能的色度值的空间。从原点辐射的每条线具有恒定的色度值，仅在亮度上变化。</center>
<p>为此，CIE通过将颜色投影到$X+Y+Z=1$平面上来定义二维色度空间。见图8.7。此空间中的坐标称为x和y，计算方式如下：</p>
<p>$$<br>
x=\frac{X}{X+Y+Z},\\<br>
y=\frac{Y}{X+Y+Z},\\<br>
z=\frac{Z}{X+Y+Z}=1-x-y.\tag{8.2}<br>
$$</p>
<p>z值不提供其他信息，因此通常省略。色度坐标x和y值的图表称为CIE 1931色度图。见图8.8。图中的曲线轮廓显示可见光谱的颜色所在的位置，连接光谱末端的直线称为紫色线(the purple line)。黑点表示光源D65的色度，这是一种常用的白点——用于定义白色或无色(achromatic)的色度。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C8/8.8.png" alt="Realtime Rendering 4th notes - chapter8"></p>
<center>图8.8。CIE 1931色度图。 曲线用相应纯色的波长标记。 白色三角形和黑点分别显示用于sRGB和Rec的色域和白点。709色空间。</center>
<p>总而言之，我们从一个实验开始，该实验使用了三个单波长光，并测量了需要多少光以匹配其他波长的光的外观。 有时这些纯的光必须添加到正在查看的样品中才能匹配。这给出了一组颜色匹配函数，它们被组合起来创建一个没有负值的新集合。有了这个非负的颜色匹配函数集，我们可以将任何光谱分布转换为定义颜色的色度和亮度的XYZ坐标，可以将其简化为xy以仅描述色度，保持亮度恒定。</p>
<p>给定一个色点（x, y），从白点穿过该点到边界（光谱或紫色线）绘制一条线。距离色点与距离区域边缘的比值是颜色的激发纯度（excitation purity）。区域边缘上的点定义了主波长（dominant wavelength）。图形学中很少遇到这些色度学上的术语。相反，我们使用饱和度（saturation）和色相（hue），它们分别与激发纯度和主波长松散地相关。在Stone和其他人的书中可以找到更精确的饱和度和色相定义。</p>
<p>色度图描述了一个平面。完全描述颜色需要第三个维度，Y值，也就是亮度。然后定义所谓的xyY坐标系。 色度图对于理解渲染中如何使用颜色以及渲染系统的限制非常重要。电视或计算机监视器通过使用R，G和B颜色值的一些设置来呈现颜色。每个颜色通道控制一个显示基色（display primary）发射具有特定光谱功率分布的光。三个原色中的每一个通过其相应的颜色值进行缩放，并且将它们相加在一起以产生观察者所感知的单个光谱功率分布。</p>
<p>色度图中的三角形表示典型电视或计算机监视器的色域（gamut）。三角形的三个角是原色，它是屏幕可以显示的最饱和的红色，绿色和蓝色。色度图的一个重要特性是这些限制颜色可以通过直线连接，以显示整个显示系统的极限。直线表示通过混合这三种原色可以显示的颜色限制。 白点表示当R，G和B颜色值彼此相等时由显示系统产生的色度。值得注意的是，显示系统的全部色域是三维体积。色度图仅显示该体积在二维平面上的投影。有关更多信息，请参阅Stone的书。</p>
<p>渲染中有几个值得注意的RGB空间，每个空间由R，G和B原色和白点定义。为了比较它们，我们将使用不同类型的色度图，称为<em>CIE 1976 UCS</em>（uniform chromaticity scale）图。该图是CIELUV颜色空间的一部分，CIEUV颜色空间由CIE（以及另一个颜色空间CIELAB）采用，旨在为XYZ空间提供更多感知统一的替代方案。在CIE XYZ空间中，两个一样的颜色对可以在距离上有最多不同20倍。CIELUV对此进行了改进，使比率降至最多四倍。为了比较RGB空间的色域，这种增加的感知均匀性使得1976年的图表比1931年更好。最近，对感知统一色彩空间的持续研究产生了ICTCP和Jzazbz空间。这些色彩空间在感知上比CIELUV更均匀，特别是对于现代显示器典型的高亮度和饱和色彩。然而，基于这些色彩空间的色度图尚未被广泛采用，因此我们在本章中使用CIE 1976 UCS图，例如图8.9的情况。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C8/8.9.png" alt="Realtime Rendering 4th notes - chapter8"></p>
<center>图8.9。CIE 1976 UCS图显示了三个RGB颜色空间的原色和白点：sRGB，DCI-P3和ACEScg。sRGB图可用于Rec.709也是如此，因为两个颜色空间具有相同的原色和白点。</center>
<p>在图8.9所示的三个RGB空间中，sRGB是迄今为止在实时渲染中最常用的。值得注意的是，在本节中，我们使用 &quot;sRGB颜色空间&quot;来指代具有sRGB原色和白点的线性颜色空间，而不是第5.6节中讨论的非线性sRGB颜色编码。大多数计算机监视器专为sRGB色彩空间设计，同样的原色和白点适用于Rec.709色彩空间，用于高清电视显示器，因此对于游戏机非常重要。但是，更多的显示器正在制作更宽的色域一些用于照片编辑的计算机显示器使用Adobe 1998色彩空间（未显示）。最初为故事片制作而开发的DCI-P3色彩空间正在被广泛使用。苹果公司在iPhone产品线中采用了这种色彩空间，其他制造商也纷纷跟进。虽然超高清（UHD）内容和显示器被指定使用极宽的Rec.2020色彩空间，但在很多情况下也会采用DCI-P3作为UHD的颜色空间。2020没有在图8.9中显示，但它的色域非常接近图中的色彩空间ACEScg。ACEScg色彩空间由电影艺术与科学学院（AMPAS）开发，用于电影计算机图形渲染。它不是用作显示颜色空间，而是用作渲染的工作颜色空间，在渲染后将颜色转换为适当的显示颜色空间。</p>
<p>虽然目前sRGB色彩空间在实时渲染中无处不在，但会有越来越多使用更宽的色彩空间。 最直接的好处是针对宽色域显示器的应用程序，但即使是使用sRGB或Rec.709显示的应用程序也有优势。当在不同的颜色空间中执行时，诸如乘法的常规渲染操作给出不同的结果，并且有证据表明在DCI-P3或ACEScg空间中执行这些操作比在线性sRGB空间中执行它们产生更准确的结果。</p>
<p>从RGB空间到XYZ空间的转换是线性的，可以使用从RGB空间的原色和白点导出的矩阵来完成。通过矩阵求逆和串联，可以导出矩阵以从XYZ转换到任何RGB空间，或者在两个不同的RGB空间之间转换。注意，在这种转换之后，RGB值可以是负值或大于1。这些是超出色域的颜色，即在目标RGB空间中不可再现。可以使用各种方法将这些颜色映射到目标RGB色域中。</p>
<p>一种常用的转换是将RGB颜色转换为灰度亮度值。由于亮度与Y系数相同，因此该操作仅是RGB到XYZ转换的“Y部分”。 换句话说，它是RGB系数和RGB到XYZ矩阵的中间行之间的点积。在sRGB和Rec.709的情况下，等式为：</p>
<p>$$<br>
Y=0.2126R+0.7152G+0.0722B\tag{8.3}<br>
$$</p>
<p>这再次让我们重温光度曲线，如图8.4所示。该曲线表示标准观察者的眼睛如何响应各种波长的光，乘以三原色的光谱功率分布，并且每个结果曲线都是整合的。得到的三个权重是上述亮度方程的形式。灰度强度值不等于红色，绿色和蓝色的原因是因为眼睛对各种波长的光具有不同的灵敏度。</p>
<p>比色法可以告诉我们两种颜色刺激是否匹配，但它无法预测它们的外观。给定XYZ颜色刺激的出现在很大程度上取决于诸如照明，周围颜色和先前条件等因素。诸如CIECAM02的颜色外观模型（Color appearance models，CAM）试图处理这些问题并预测最终的颜色外观。</p>
<p>颜色外观建模（Color appearance modeling）是更广泛的视觉感知领域的一部分，包括诸如masking之类的效果。 这是放置在物体上的高频率，高对比度图案倾向于隐藏瑕疵的地方。换句话说，诸如波斯地毯之类的纹理将有助于伪装色带和其他着色伪像，这意味着需要为这些表面花费较少的渲染工作。</p>
<h4 id="814renderingwithrgbcolorsrgb">8.1.4 Rendering with RGB Colors（使用RGB颜色渲染）</h4>
<p>严格地说，RGB值代表感知而不是物理量。使用它们进行基于物理的渲染在技术上是一个类别错误。正确的方法是对光谱量执行所有渲染计算，通过密集采样或投影到合适的基础上表示，并且仅在结束时转换为RGB颜色。</p>
<p>例如，最常见的渲染操作之一是计算从对象反射的光。物体的表面通常比其他波长更能反射某些波长的光，如光谱反射率曲线所述。计算反射光颜色的严格正确方法是将入射光的SPD乘以每个波长的光谱反射率，得到反射光的SPD，然后将其转换为RGB颜色。相反，在RGB渲染器中，灯光和曲面的RGB颜色相乘，以给出反射光的RGB颜色。在一般情况下，这不会给出正确的结果。为了说明，我们将看一个极端的例子，如图8.10所示。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C8/8.10.png" alt="Realtime Rendering 4th notes - chapter8"></p>
<center>图8.10。顶部曲线显示了设计用于投影屏幕的材料的光谱反射率。下面的两个图显示了具有相同RGB颜色的两个光源的光谱功率分布：中间图中的RGB激光投影仪和底部图中的D65标准光源。 屏幕材料将反射来自激光投影仪的大约80％的光，因为它具有与投影仪原色对齐的反射峰。 但是，它将反射不到20％的D65光源光，因为大部分光源的能量都在屏幕的反射峰值之外。 此场景的RGB渲染将预测屏幕将反射两种灯光的相同强度。</center>
<p>我们的示例显示了一个用于激光投影仪的屏幕材料。它在与激光投影仪波长匹配的窄带中具有高反射率，对于大多数其他波长具有低反射率。 这使其反射来自投影仪的大部分光线，但吸收来自其他光源的大部分光线。 在这种情况下，RGB渲染器将产生严重错误。</p>
<p>但是，图8.10所示的情况远非典型。实际中遇到的表面的光谱反射率曲线更平滑，如图8.11中所示。典型的光源SPD类似于D65光源，而不是示例中的激光投影仪。当光源SPD和表面光谱反射率都是平滑的时，RGB渲染引入的误差相对很小。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C8/8.11.png" alt="Realtime Rendering 4th notes - chapter8"></p>
<center>图8.11。 一个黄色香蕉的光谱反射率</center>
<p>在预测渲染（predictive rendering）应用程序中，这些微妙的错误可能很重要。例如，两个光谱反射率曲线在一个光源下可以具有相同的颜色外观，而不是另一个光源。例如，在绘制修复的车身部件时，这个问题，称为同色异谱失效（metameric failure）或光源同色异谱（illuminant metamerism），是一个严重的问题。在尝试预测此类效果的应用程序中，RGB渲染不合适。</p>
<p>然而，对于大多数渲染系统，尤其是那些不旨在产生预测模拟的交互式应用系统中，RGB渲染的效果令人惊讶。即使是离线渲染最近才开始采用光谱渲染，但它还远未普及。</p>
<p>本节仅涉及颜色科学的基础知识，主要是为了让人们了解光谱与颜色三元组的关系，并讨论器件的局限性。相关主题将渲染场景颜色转换为显示值，将在下一节中讨论。</p>
<br>
<h3 id="82scenetoscreen">8.2 Scene to Screen（场景到屏幕）</h3>
<p>本书接下来的几章主要讨论基于物理的渲染问题。给定虚拟场景，基于物理的渲染的目标是计算场景中存在的辐射，如果它是真实的。然而，在那时，工作还远未完成。显示器的帧缓冲区中的最终结果像素值仍然需要确定。 在本节中，我们将介绍关于此的一些注意事项。</p>
<h4 id="821highdynamicrangedisplayencodinghdr">8.2.1 High Dynamic Range Display Encoding（HDR显示编码）</h4>
<p>本节中的内容基于第5.6节，其中包含显示编码。 我们决定将高动态范围（HDR）显示的覆盖推迟到本节，因为它需要有关主题的背景知识，例如色域，这些在本书的那一部分尚未讨论过。</p>
<p>第5.6节讨论了标准动态范围（SDR）监视器的显示编码，SDR监视器通常使用sRGB显示标准，SDR电视使用Rec.709和Rec.1886年的标准。两组标准具有相同的RGB色域和白点（D65），以及有些相似（但不相同）的非线性显示编码曲线。它们也具有大致相似的参考白色亮度水平（对于sRGB为$80cd/m^2$，对于Rec.709/1886为$100cd/m^2$）。监视器和电视制造商并没有密切关注这些亮度规格，他们实际上倾向于制造具有更亮白色水平的显示器。</p>
<p>HDR显示使用Rec.2020和Rec.2100标准。Rec.2020定义了一个具有明显更宽色域的色彩空间，如图8.12所示，以及与Rec.709和sRGB颜色空间相同的白点（D65）。Rec.2100定义了两种非线性显示编码：感知量化器（perceptual quantizer, PQ）和混合对数伽马（hybrid log-gamma, HLG）。 HLG编码在渲染情况下使用不多，因此我们将重点关注PQ，它定义了峰值亮度值$10,000cd/m^2$。</p>
<p>从应用程序方面，有三条路径将图像传输到HDR显示器，但根据显示器和操作系统的不同，并非所有三个路径都可用：</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C8/8.12.png" alt="Realtime Rendering 4th notes - chapter8"></p>
<center>图8.12。CIE 1976 UCS图显示的Rec.2020和sRGB/Rec的色域和白点（D65）。709色空间。 还显示了DCI-P3色彩空间的色域用于比较。</center>
<ol>
<li>HDR10 —— 在HDR显示器以及PC和控制台操作系统中支持广泛。帧缓冲区格式为每像素32位，每个RGB通道有10个，alpha通道2个无符号整数位。它使用PQ非线性编码和Rec.2020色彩空间。每个HDR10显示模型都执行自己的色调映射，这并不是标准化或可记录的。</li>
<li>scRGB（线性变体）—— 仅在Windows操作系统上受支持。名义上它使用sRGB原色和白色级别，但两者都可以超过，因为标准支持小于0且大于1的RGB值。帧缓冲格式为每通道16位，并存储线性RGB值。 它可以与任何HDR10显示器一起使用，由驱动程序转换为HDR10。它主要用于方便和向后兼容sRGB。</li>
<li>杜比视界（Dolby Vision） —— 专有格式，尚未在显示器或任何控制台上广泛支持（撰写本文时）。它使用自定义12位每通道帧缓冲格式，并使用PQ非线性编码和Rec.2020色彩空间。显示内部色调映射在模型中标准化（但未记录）。</li>
</ol>
<p>Lottes指出实际上有第四种选择。如果仔细调整曝光和颜色，则HDR显示可以通过SDR信号通路以取得效果良好。</p>
<p>除了scRGB之外的任何选项，作为显示编码步骤的一部分，应用程序需要将像素RGB值从渲染工作空间转换为Rec.2020 —— 这需要一个3×3矩阵变 —— 并应用PQ编码，这比Rec.709或sRGB编码函数成本要高一些。Patry给出了PQ曲线的廉价近似值。在HDR显示器上合成用户界面（UI）元素时需要特别小心，以确保用户界面清晰易读并且处于舒适的亮度级别。</p>
<h4 id="822tonemapping">8.2.2 Tone Mapping（色调映射）</h4>
<p>在5.6节和8.2.1节中，我们讨论了显示编码，即将线性辐亮度值转换为显示硬件的非线性代码值的过程。显示编码应用的功能与显示器的电光学传递函数（EOTF）相反，可确保输入线性值与显示器发出的线性辐射亮度相匹配。 我们之前的讨论掩盖了渲染和显示编码之间发生的重要步骤，我们现在准备探索这一步骤。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C8/8.13.png" alt="Realtime Rendering 4th notes - chapter8"></p>
<center>图8.13。合成（渲染）图像的成像管线。我们渲染线性场景引用辐射值，色调映射转换为线性显示引用值。显示编码应用反EOTF将线性显示值转换为非线性编码值（代码），这些值将传递给显示器。最后，显示硬件应用EOTF将非线性显示值转换为从屏幕发射到眼睛的线性辐射。</center>
<p>色调映射是将场景辐射值转换为显示辐射值的过程。 在此步骤中应用的变换称为端到端传递函数或场景到屏幕变换。 图像状态的概念是理解色调映射的关键。有两种基本的图像状态。参考场景辐射值来定义场景引用图像，并且参考显示辐射值来定义显示引用图像。 图像状态与编码无关。 任一状态的图像可以线性或非线性地编码。 图8.13显示了图像，色调映射和显示编码如何在成像管线中组合在一起，处理从初始渲染到最终显示的颜色值。</p>
<p>关于色调映射的目标存在几种常见的误解。它并不是为了确保场景到屏幕的变换保持不变，在显示器处完美地再现场景辐射值。它也不会把场景的高动态范围中的每一点信息“压缩”到显示器的较低动态范围中，尽管考虑到场景和显示器动态范围之间的差异确实起着重要作用。</p>
<p>为了理解色调映射的目标，最好将其视为图像再现的一个例子。 图像再现的目的是在给定显示特性和观看条件的情况下，如果观察原始场景，观看者将具有的感知印象，创建尽可能接近地再现的显示的图像。见图8.14。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C8/8.14.png" alt="Realtime Rendering 4th notes - chapter8"></p>
<center>图8.14。 图像再现的目的是确保由再现（右）引起的感知印象尽可能接近原始场景（左）的感知印象。</center>
<p>存在一种具有略微不同目标的图像再现。优选图像再现（Preferred image reproduction）旨在创建在某种意义上比原始场景看起来更好的显示引用图像。优选图像再现将在后面的8.2.3节中讨论。</p>
<p>考虑到典型场景中的亮度范围超过显示能力几个数量级，再现与原始场景类似的感知印象的目标是具有挑战性的。 场景中至少一些颜色的饱和度（纯度）也可能远远超过显示能力。 然而，摄影，电视和电影确实设法产生令人信服的原始场景的感知形象，文艺复兴时期的画家也是如此。 通过利用人类视觉系统的某些属性，可以实现这一成就。</p>
<p>视觉系统补偿绝对亮度的差异，称为适应的能力。由于这种能力，尽管再现的亮度小于原始的1％，但是在昏暗的房间中在屏幕上显示的室外场景的再现可以产生与原始场景类似的感知。但是，由适应所提供的补偿是不完善的。 在较低的亮度水平下，感知的对比度降低（the Stevens effect，史蒂文斯效应），感知的“色彩”（the Hunt effect，亨特效应）也是如此。</p>
<p>然而，这种对比度的增加加剧了现有问题。由于场景的动态范围通常远大于显示器的动态范围，因此我们必须选择一个较窄的亮度值窗口来重现，该窗口上方和下方的值被剪切为黑色或白色。提升对比度进一步缩小了这个窗口。 为了部分抵消暗和亮值的削波，使用roll-off来补回一些阴影和高光细节。</p>
<p>所有这些都产生了S形（sigmoid）色调再现曲线，类似于光化学膜提供的曲线。这不是偶然的。柯达和其他公司的研究人员仔细调整了光化学薄膜乳液的性质，以产生有效和令人愉悦的图像再现。由于这些原因，形容词“filmic”经常出现在色调映射的讨论中。</p>
<p>曝光的概念对于色调映射至关重要。 在摄影中，曝光是指控制落在胶片或传感器上的光量。然而，在渲染中，曝光是在应用色调再现变换之前对场景引用图像执行的线性缩放操作。曝光的棘手方面是确定要应用的缩放因子。 色调再现变换和曝光紧密结合在一起。通常设计色调变换，期望它们将以某种方式的曝光应用于场景引用图像。</p>
<p>通过曝光缩放然后应用色调再现变换的过程是一种全局色调映射，其中相同的映射应用于所有像素。相反，局部色调映射过程基于周围像素和其他因素使用不同的像素到像素的映射。 实时应用程序几乎完全使用全局色调映射（有一些例外），因此我们将重点关注此类型，讨论第一次色调再现变换然后曝光。</p>
<p>重要的是要记住，场景引用图像和显示引用图像是根本不同的。物理操作仅在对场景引用的数据执行时有效。由于显示限制和我们讨论的各种感知效果，在两个图像状态之间总是需要非线性变换。</p>
<h5 id="tonereproductiontransform">Tone Reproduction Transform（色调再现变换）</h5>
<p>色调再现变换（Tone reproduction transforms）通常表示为将场景引用输入值映射到显示引用输出值的一维曲线。这些曲线可以独立地应用于R，G和B值或亮度。在前一种情况下，结果将自动显示在显示色域中，因为每个显示器提供的RGB通道值将介于0和1之间。但是，在RGB通道上执行非线性操作（尤其是剪切）可能会导致饱和度和色调发生偏移，除了所需的亮度变化。Giorgianni和Madden指出，饱和度的变化在感知上是有益的。大多数再现变换使用提升对比度（contrast boost）来抵消史蒂文斯效应（以及环绕和观看眩光效果），同事将抵消亨特效应。然而，色调偏移（hue shifts）通常被认为是不合需要的，并且现代色调变换试图通过在色调曲线之后应用额外的RGB调整来减少它们。</p>
<p>通过将色调曲线应用于亮度，可以避免（或至少减少）色调和饱和度偏移。但是，显示引用的颜色可能超出了显示器的RGB色域，在这种情况下，需要将其重新映射回来。</p>
<p>色调映射的一个潜在问题是将非线性函数应用于场景引用的像素颜色可能会导致某些抗锯齿技术出现问题。 第5.4.2节讨论了这个问题（以及解决它的方法）。</p>
<p>Reinhard色调再现算子（Reinhard tone reproduction operator）是用于实时渲染的早期色调变换之一。它使较暗的值大部分保持不变，而较亮的值渐近地变为白色。Drago等人提出了一种类似的色调映射算子。 能够调整输出显示亮度，这可能使其更适合HDR显示。Duiker创建了柯达电影响应曲线的近似值，用于视频游戏。此曲线后来由Hable修改以添加更多用户控件，并用于游戏Uncharted 2。Hable在此曲线上的演示具有影响力，导致在几个游戏中使用“Hable filmic curve”。Hable后来提出了一条新曲线，与之前的工作相比具有许多优点。</p>
<p>Day呈现出一种S形色调曲线，用于Insomniac Games的游戏，以及游戏“使命召唤：高级战争”。 Gotanda创造了色调变换，模拟电影和数码相机传感器的响应。 它们在游戏Star Ocean 4和其他游戏中得以应用。 Lottes指出，显示器闪光对显示器的有效动态范围的影响是显着的，并且高度依赖于室内照明条件。因此，为色调映射提供用户调整很重要。他提出了一种可以调整的色调再现变换，支持可以与SDR以及HDR显示器一起使用。</p>
<p>学院色彩编码系统（Academy Color Encoding System，ACES）由美国电影艺术与科学学院的科学技术委员会创建，作为管理电影和电视行业色彩的标准。ACES系统将场景到屏幕转换分为两部分。第一个是参考渲染变换（reference rendering transform，RRT），它将场景引用值转换为标准的，设备中立的输出空间中的显示引用值，称为输出颜色编码规范（output color encoding specification，OCES）。第二部分是输出设备变换（output device transform，ODT），它将颜色值从OCES转换为最终显示编码。有许多不同的ODT，每个ODT都是针对特定的显示设备和观看条件而设计的。RRT和合适的ODT的串联创建了整体变换。这种模块化结构便于处理各种显示类型和观看条件。Hart建议对需要支持SDR和HDR显示的应用进行ACES色调映射变换。</p>
<p>尽管ACES设计用于电影和电视，但其变形在实时应用中的使用越来越多。默认情况下，在虚幻引擎中启用ACES色调映射，Unity引擎也支持它。Narkowicz给出了符合SDR和HDR ODT的ACES RRT的廉价曲线，Patry也是如此。Hart提供了ACES ODT的参数化版本，以支持一系列设备。</p>
<p>使用HDR显示的色调映射需要注意，因为显示器也将应用他们自己的一些色调映射。Fry提供了Frostbite游戏引擎中使用的一组色调映射变换。它们为SDR显示器应用了相对激进的色调再现曲线，对于使用HDR10信号的显示器（具有基于显示器的峰值亮度的一些变化）使用相对柔和的色调再现曲线，使用Dolby Vision的显示器没有色调映射（换句话说，它们依赖于显示器应用的内置Dolby Vision色调映射。 Frostbite色调再现变换被设计为中性，没有明显的对比度或色调变化。目的是通过颜色分级应用任何所需的对比度或色调修改（第8.2.3节）。为此，色调再现变换应用于ICTCP色彩空间，其设计用于色度和亮度轴之间的感知均匀性和正交性。Frostbite变换对亮度进行色调映射，并随着亮度下降显示白色而逐渐降低色度。这提供了一个没有色相变化的干净变换。</p>
<p>具有讽刺意味的是，由于asset（例如火焰效果）被编写为利用其先前变换中的色调变化的样子，Frostbite团队最终修改了变换，使用户能够将某种程度的色调变换重新引入显示 - 参考颜色。 图8.15显示了Frostbite变换与本节中提到的其他几个变换的比较。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C8/8.15.png" alt="Realtime Rendering 4th notes - chapter8"></p>
<center>图8.15。应用了四种不同色调变换的场景。 差异主要见于圆圈区域，其中场景像素值特别高。 左上：clipping（加上sRGB OETF）；右上角：Reinhard；左下：Duiker；右下：Frostbite（色调保留版）。 Reinhard，Duiker和Frostbite转换了所有通过剪裁丢失的高光信息。然而，Reinhard曲线倾向于使图像的较暗部分去饱和，而Duiker变换增加了较暗区域的饱和度，这有时被认为是理想的特性。通过设计，Frostbite变换保留了饱和度和色调，避免了在其他三个图像的左下圆圈中可以看到的强烈的色调偏移。（图片由c 2018 Electronic Arts Inc.提供）</center>
<h5 id="exposure">Exposure（曝光）</h5>
<p>用于计算曝光的常用技术依赖于分析场景引用的亮度值。为避免卡顿，通常通过对前一帧进行采样来完成此分析。</p>
<p>根据Reinhard等人的推荐，在早期实现中使用的一个度量标准是对数平均场景亮度（ log-average scene luminance）。通常，通过计算帧的对数平均值来确定曝光。通过执行一系列下采样后处理过程来计算该对数平均值，直到计算出帧的最终单个值。</p>
<p>使用平均值往往对异常值过于敏感，例如，少量明亮像素可能影响整个帧的曝光。随后的实现通过改为使用亮度值的直方图来改善该问题。而不是平均值，直方图允许计算中值，这更加稳健。直方图中的附加数据点可用于改善结果。 例如，在The Orange Box(by Valve)中，基于第95百分位数和中位数的启发式方法用于确定曝光。Mittring描述了使用计算着色器生成亮度直方图的方法。</p>
<p>到目前为止所讨论的技术的问题在于像素亮度是用于驱动曝光的错误度量。如果我们看一下摄影实践，例如安塞尔亚当斯的区域系统以及如何使用入射光度计来设置曝光，很明显，最好单独使用照明（没有表面反照率的影响）来确定曝光。这样做是有效的，因为在第一次近似中，摄影曝光用于抵消照明。这导致主要显示对象的表面颜色的印刷品，其对应于人类视觉系统的颜色恒定性属性。以这种方式处理曝光也可确保将正确的值传递给色调变换。例如，电影或电视行业中使用的大多数色调变换被设计为将曝光的场景引用值0.18映射到显示引用值0.1，期望0.18表示18％的灰卡。主导场景照明。</p>
<p>虽然这种方法在实时应用程序中尚未普及，但它开始被视为使用。例如，游戏合金装备V：地面零点具有基于照明强度的曝光系统。在许多游戏中，基于已知的场景照明值，为环境的不同部分手动设置静态曝光水平。这样做可以避免意外的曝光动态变化。</p>
<h4 id="823colorgrading">8.2.3 Color Grading（颜色分级）</h4>
<p>在8.2.2节中，我们提到了首选图像再现的概念（preferred image reproduction），即产生在某种意义上比原始场景看起来更好的图像的想法。通常，这涉及对图像颜色的创造性操纵，这一过程称为颜色分级。</p>
<p>数字色彩分级已经在电影行业中使用了一段时间。早期的例子包括电影O Brother，Where Art Thou？（逃狱三王，2000）和Amélie（天使爱美丽，2001）。 颜色分级通常通过交互式操作示例场景图像中的颜色来执行，直到实现所需的创意“外观”。然后在镜头或序列中的所有图像上重新应用相同的操作序列。从电影到游戏，颜色分级现在被广泛使用。</p>
<p>Selan展示了如何从颜色分级或图像编辑应用程序“烘焙”任意颜色转换到三维颜色查找表（LUT）。这些表格通过使用输入R，G和B值作为x-，y-和z-坐标用于在表格中查找新颜色，因此可以用于从输入到输出颜色的任何映射，直到LUT分辨率的限制。Selan的烘焙过程首先采用一个统一的LUT（映射把所有输入颜色都转换为相同的颜色）并“切片”它以创建一个二维图像。然后将这个切片的LUT图像加载到颜色分级应用程序中，并将定义所需创意外观的操作应用于它。需要仅将颜色操作应用于LUT，避免模糊等空间操作。然后将编辑后的LUT保存，“打包”到三维GPU纹理中，并在渲染应用程序中使用，以对其应用相同的颜色转换。飞到渲染像素。我使用最小二乘最小化时，wanicki提供了一种在LUT中存储颜色变换时减少采样误差的巧妙方法。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C8/8.16.png" alt="Realtime Rendering 4th notes - chapter8"></p>
<center>图8.16。游戏Uncharted 4中的场景。顶部的屏幕截图没有颜色分级。其他两个屏幕截图均应用了颜色分级操作。为了说明的目的，选择极端的颜色分级操作（乘以高度饱和的青色）。在左下方的屏幕截图中，颜色分级应用于显示器引用（post-tone-mapping）图像，并且在右下方屏幕截图中，将其应用于场景引用（pre-tone-mapping）图像。</center>
<p>在后来的出版物中，Selan区分了两种执行颜色分级的方法。在一种方法中，对显示引用图像数据执行颜色分级。 另一方面，对通过显示变换预览的场景引用数据执行颜色分级操作。 尽管显示器引用的颜色分级方法更容易设置，但是对场景引用的数据进行分级可以产生更高保真度的结果。</p>
<p>当实时应用程序首次采用颜色分级时，显示器引用的方法占主导地位。然而，由于其更高的视觉质量，场景引用的方法已经获得了提拔。见图8.16。将颜色分级应用于场景引用数据还提供了通过将色调映射曲线烘焙到分级LUT中来节省一些计算的机会，如游戏Uncharted 4中所做的那样。</p>
<p>在LUT查找之前，场景引用的数据必须重新映射到范围[0,1]。 在Frostbite引擎中，感知量化器OETF用于此目的，尽管可以使用更简单的曲线。Duiker使用对数曲线，Hable建议使用一次或两次应用的平方根运算符。</p>
<p>Hable概述了常见的颜色分级操作和实现注意事项。</p>
<h4 id="furtherreadingandresources">Further Reading and Resources</h4>
<p>对于比色法和色彩科学，“圣经”是Wyszecki和Stiles的《Color Science》。其他良好的色度参考包括Fairchild的《HuntMeasuring Colour》和《Color Appearance Models》。</p>
<p>Selan的白皮书很好地概述了图像再现和“场景到屏幕”问题。 想要更多地了解这个主题的读者会发现Giorgianni和Madden的《Digital Color Management》和 Hunt的《The Reproduction of Colour》是很好的参考。安塞尔·亚当斯摄影系列中的三本书Ansel Adams Photography Series，尤其是《The Negative》，提供了对电影摄影艺术和科学如何影响图像再现理论和实践的理解。 最后，Reinhard等人著作《Color Imaging：Fundamentals and Applications》一书对整个研究领域进行了全面的概述。</p>
<p>(chapter 8 end.)</p>
]]></content:encoded></item><item><title><![CDATA[Realtime Rendering 4th notes - chapter7]]></title><description><![CDATA[<p><a href="https://www.xx-ma.com/">(转载请注明出处，谢谢咯)</a></p>
<blockquote>
<p>&quot;All the variety, all the charm, all the beauty<br>
of life is made up of light and shadow. &quot; — Tolstoy</p>
</blockquote>
<p>本章会讨论最重要且流行的阴影算法，以及一些体现了重要原则的不太流行的方法。本章中使用的术语如图（7.1）所示：</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C7/7.1.png" alt=""></p>
<center>（图7.1）光源，遮挡物occluders，接收物receivers，阴影shadow，本影umbra和半影penumbra。</center>
<p>遮挡物（occluders）是将阴影投射到接收者（receivers ）上的对象。点源光仅产生完全阴影区域，有时称为硬阴影（hard shadows）。如果使用面积或体积光源，则会产生柔和阴影（soft shadows）。然后，</p>]]></description><link>http://xx-ma.com/rtr4-7/</link><guid isPermaLink="false">5be7e68bb67cfd1e0bec0337</guid><category><![CDATA[notes]]></category><category><![CDATA[graphics]]></category><category><![CDATA[realtime-rendering]]></category><category><![CDATA[code]]></category><dc:creator><![CDATA[Marianax]]></dc:creator><pubDate>Sun, 11 Nov 2018 08:26:20 GMT</pubDate><media:content url="http://xx-ma.com/content/images/2018/11/samuel-zeller-15925-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<img src="http://xx-ma.com/content/images/2018/11/samuel-zeller-15925-unsplash.jpg" alt="Realtime Rendering 4th notes - chapter7"><p><a href="https://www.xx-ma.com/">(转载请注明出处，谢谢咯)</a></p>
<blockquote>
<p>&quot;All the variety, all the charm, all the beauty<br>
of life is made up of light and shadow. &quot; — Tolstoy</p>
</blockquote>
<p>本章会讨论最重要且流行的阴影算法，以及一些体现了重要原则的不太流行的方法。本章中使用的术语如图（7.1）所示：</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C7/7.1.png" alt="Realtime Rendering 4th notes - chapter7"></p>
<center>（图7.1）光源，遮挡物occluders，接收物receivers，阴影shadow，本影umbra和半影penumbra。</center>
<p>遮挡物（occluders）是将阴影投射到接收者（receivers ）上的对象。点源光仅产生完全阴影区域，有时称为硬阴影（hard shadows）。如果使用面积或体积光源，则会产生柔和阴影（soft shadows）。然后，每个阴影可以具有称为本影（umbra）的完全阴影区域和称为半影（penumbra）的部分阴影区域。软阴影的边缘是模糊的。但是，通过使用低通滤波器模糊硬阴影的边缘通常无法正确渲染它们。从图7.2中可以看出，遮挡物越接近接收器，软阴影就越清晰。柔和阴影的本影区域不等同于点源光产生的硬阴影。相反，当光源变大时，柔和阴影的本影区域尺寸减小，并且甚至可能消失，因为光源足够大或接收物距离遮挡物足够远。柔和阴影通常是优选的，因为半影边缘让观察者知道阴影确实是阴影。硬边阴影通常看起来不那么逼真，有时会被误解为实际的几何特征，例如表面的折痕。但是，硬阴影渲染速度比柔和阴影快。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C7/7.2.png" alt="Realtime Rendering 4th notes - chapter7"></p>
<center>（图7.2）混合了hard shadow和soft shadow的效果。由于遮挡物靠近接收器，因此来自板条箱的阴影是尖锐的。 人的阴影在接触点处是锐利的，随着到遮挡物的距离的增加而变软。 遥远的树枝给出柔和的阴影。</center>
<p>比半影更重要的是有阴影。 没有阴影作为视觉提示，一些场景往往难以令人信服或察觉。 正如Wanger所展示的那样，通常情况下有一个不准确的阴影也比没有要好，因为眼睛对阴影的形状是相当宽容的。 例如，在地板上作为纹理应用的模糊黑色圆圈可以将角色锚定到地面。</p>
<p>在以下部分中，我们将超越这些简单的建模阴影，并提供从场景中的遮挡物实时自动计算阴影的方法。 第一部分处理在平面上投射阴影的特殊情况，第二部分介绍更一般的阴影算法，即将阴影投射到任意表面上。 将覆盖硬阴影和柔和阴影。 总之，提出了一些适用于各种阴影算法的优化技术。</p>
<h3 id="71planarshadows">7.1 Planar Shadows（平面阴影）</h3>
<p>当对象将阴影投射到平面上时，会发生一个简单的阴影情况。 本节介绍了几种平面阴影算法，每种算法都具有阴影柔和度和真实感的变化。</p>
<h4 id="711projectionshadows">7.1.1 Projection Shadows（投射阴影）</h4>
<p>在该方案中，第二次渲染三维对象以创建阴影。可以导出矩阵，将对象的顶点投影到平面上。考虑图7.3中的情况，其中光源位于l，要投影的顶点为v，投影顶点位于p。我们将导出阴影平面为y = 0的特殊情况下的投影矩阵，然后将该结果推广到任何平面。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C7/7.3.png" alt="Realtime Rendering 4th notes - chapter7"></p>
<center>（图7.3）</center>
<p>我们首先推导出x坐标的投影。 从图7.3左侧的类似三角形中，我们得到了：</p>
<p>$$<br>
\frac{p_x-l_x}{v_x-l_x}=\frac{l_y}{l_y-v_y}\Longleftrightarrow p_x=\frac{l_yv_x-l_xv_y}{l_y-v_y}\tag{7.1}<br>
$$</p>
<p>以相同的方式获得z坐标：$p_z =(l_yv_z-l_zv_y)=(l_y-v_y)$，而y坐标为0。 现在这些方程可以转换成投影矩阵M：</p>
<p>$$<br>
\boldsymbol{M}=\begin{pmatrix}l_y&amp;-l_x&amp;0&amp;0\\0&amp;0&amp;0&amp;0\\0&amp;-l_z&amp;l_y&amp;0\\0&amp;-1&amp;0&amp;l_y\end{pmatrix}\tag{7.2}<br>
$$</p>
<p>简单可验证$\boldsymbol{M}\boldsymbol{v}=\boldsymbol{p}$，这说明$\boldsymbol{M}$是投影矩阵。</p>
<p>一般情况下，应该投射阴影的平面不是平面y = 0，而是$\pi:\boldsymbol{n}\cdot\boldsymbol{x}+d=0$。这种情况在图7.3的右侧部分中描述。 目标是再次找到一个将v向下投影到p的矩阵。 为此，在l处发出的射线穿过v与平面π相交。 这产生了预计的点p：</p>
<p>$$<br>
\boldsymbol{p}=\boldsymbol{l}-\frac{d+\boldsymbol{n}\cdot\boldsymbol{l}}{\boldsymbol{n}\cdot(\boldsymbol{v}-\boldsymbol{l})}(\boldsymbol{v}-\boldsymbol{l})\tag{7.3}<br>
$$</p>
<p>该等式也可以转换为投影矩阵，如公式7.4所示，满足$\boldsymbol{M}\boldsymbol{v}=\boldsymbol{p}$：</p>
<p>$$<br>
\boldsymbol{M}=<br>
\begin{pmatrix}<br>
\boldsymbol{n}\cdot\boldsymbol{l}+d-l_xn_x&amp;-l_xn_y&amp;-l_xn_z&amp;-l_xd\\<br>
-l_yn_x&amp;\boldsymbol{n}\cdot\boldsymbol{l}+d-l_yn_y&amp;-l_yn_z&amp;-l_yd\\<br>
-l_zn_x&amp;-l_zn_y&amp;\boldsymbol{n}\cdot\boldsymbol{l}+d-l_zn_z&amp;-l_xd\\<br>
-n_x&amp;-n_y&amp;-n_z&amp;\boldsymbol{n}\cdot\boldsymbol{l}<br>
\end{pmatrix}\tag{7.4}<br>
$$</p>
<p>正如预期的那样，如果平面为y = 0，则该矩阵变为公式7.2中的矩阵，即n =（0,1,0）且d = 0。</p>
<p>要渲染阴影，只需将此矩阵应用于在平面π上投射阴影的对象上，并使用深色和无照明渲染此投影对象。在实践中，您必须采取措施避免在接收它们的表面下方渲染投影。一种方法是向我们投影的平面添加一些偏差，以便阴影始终呈现在曲面的前面。</p>
<p>更安全的方法是首先绘制地平面，然后关闭z缓冲区绘制投影三角形，然后像往常一样渲染其余的几何体。 然后，投影的三角形总是绘制在地平面的顶部，因为没有进行深度比较。</p>
<p>如果地平面具有极限，例如，它是矩形，则投射的阴影可能落在其外部，从而打破了幻觉。要解决这个问题，我们可以使用模板缓冲区。 首先，将接收物绘制到屏幕和模板缓冲区。 然后，在关闭z缓冲区的情况下，仅在绘制接收器的位置绘制投影三角形，然后正常渲染场景的其余部分。</p>
<p>另一种阴影算法是将三角形渲染成纹理，然后将其应用于地平面。这种纹理是一种光照贴图，对下面的表面强度进行调节的纹理（见第11.5.1节）。可以看出，将阴影投影渲染到纹理的这种想法也允许弯曲表面上的半影和阴影。 这种技术的一个缺点是纹理可以被放大，单个纹素覆盖多个像素，打破了幻觉。</p>
<p>如果阴影不是逐帧改变，即光和影不相对于彼此移动，则可以重复使用该纹理。可节省计算资源。</p>
<p>所有阴影投射物必须位于灯和地面接收物之间。如果光源低于物体上的最高点，则产生antishadow，因为每个顶点通过光源的点投射。 正确的阴影和反光影如图7.4所示。 如果我们投影一个低于接收平面的对象，也会发生错误，因为它也应该不投射阴影。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C7/7.4.png" alt="Realtime Rendering 4th notes - chapter7"></p>
<center>（图7.4）左侧，显示正确的阴影，而在右图中，出现一个antishadow，因为光源位于对象的最顶端下方。</center>
<p>可以明确地剔除和修剪阴影三角形以避免这种伪影。接下来介绍的一种更简单的方法是使用现有的GPU管道来执行剪切投影。</p>
<h4 id="712softshadows">7.1.2 Soft Shadows（软阴影）</h4>
<p>通过使用各种技术，投影阴影也可以变得柔和。 在这里，我们描述了Heckbert和Herf生成柔和阴影的算法。 该算法的目标是在地平面上生成显示柔和阴影的纹理。 然后我们描述损失精度，但更快的方法。</p>
<p>只要光源有区域，就会出现柔和阴影。近似区域光效果的一种方法是使用放置在其表面上的几个点光源对其进行采样。对于这些点源光中的每一个，渲染图像并将其累积到缓冲器中。这些图像的平均值是具有柔和阴影的图像。 请注意，理论上，任何生成硬阴影的算法都可以与此累积技术一起使用以产生半影。 实际上，由于涉及的执行时间，以交互速率这样做通常是不行的。</p>
<p>Heckbert和Herf使用基于平截头体的方法来产生阴影。 这个想法是将光视为观察者，地平面形成截锥体的远剪切平面。 截锥体足够宽以包围遮挡物。</p>
<p>通过生成一系列地平面纹理来形成柔和阴影纹理。区域光源在其表面上进行采样，每个位置用于对表示地平面的图像进行着色，然后将阴影投射物投影到该图像上。 对所有这些图像求和并求平均以产生地平面阴影纹理。有关示例，请参见图7.5的左侧。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C7/7.5.png" alt="Realtime Rendering 4th notes - chapter7"></p>
<center>（图7.5）在左边，使用Heckbert和Herf的方法进行渲染，使用256个pass。 在右边，Haines的方法只使用一个pass。以Haines的方法来说，本影过于庞大，这在门口和窗户周围尤为明显。</center>
<p>采样区域光方法的一个问题是它看起来就是来自点源光的几个重叠阴影。此外，对于n个阴影pass，只能生成n + 1个不同的阴影。大量的pass可以给出准确的结果，但成本过高。该方法可以用于测试其他更快算法的质量是有用。</p>
<p>更有效的方法是使用卷积，即过滤。在某些情况下，从单个点生成的硬阴影进行模糊可能就足够了，并且可以产生可以与真实世界内容融合的半透明纹理。见图7.6。然而，在物体与地面接触的地方附近，均匀的模糊可能是不可信的。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C7/7.6.png" alt="Realtime Rendering 4th notes - chapter7"></p>
<center>（图7.6）投影。通过从上方渲染阴影投射然后模糊图像并将其渲染在地平面上来生成阴影纹理。（图像在Autodesk的A360查看器中生成，模型来自Autodesk的Inventor示例。）</center>
<p>还有许多其他方法可以提供更好的近似值，但需要额外花费。 例如，Haines以投影的硬阴影开始，然后渲染轮廓边缘的渐变从中心的黑暗到边缘的白色，以创建合理的半影。 请参见图7.5的右侧。 然而，这些半影在物理上是不正确的，因为它们也应该延伸到轮廓边缘内的区域。 Iwanicki借鉴了球面谐波（spherical harmonics）的思想，并用椭圆体近似遮挡角色，以提供柔和的阴影。所有这些方法都有各种近似和缺点，但比平均大量阴影图像要有效得多。</p>
<br>
<h3 id="72shadowsoncurvedsurfaces">7.2 Shadows on Curved Surfaces （在曲面上投影）</h3>
<p>将平面阴影的概念扩展到曲面的一种简单方法是使用生成的阴影图像作为投影纹理。从光的角度考虑阴影。无论光线看到什么都被照亮; 它没有看到的是阴影。假设遮挡物从光的视点呈现为黑色，而不是白色纹理。然后可以将该纹理投影到要接收阴影的表面上。 实际上，接收器上的每个顶点都有一个为其计算的（u，v）纹理坐标，并且纹理应用于它。 这些纹理坐标可以由应用程序显式计算。 这与前一部分中的地面阴影纹理略有不同，其中对象被投影到特定物理平面上。这里，图像是作为光的视图制作的，就像投影仪中的胶片帧一样。</p>
<p>渲染时，投影阴影纹理会修改接收器曲面。 它也可以与其他阴影方法结合使用，有时主要用于帮助感知对象的位置。例如，在游戏中，即使角色处于完全阴影中，主角也可能总是在其正下方给出一个投影。更精细的算法可以提供更好的结果。例如，Eisemann和Décoret假设一个矩形顶灯并创建一堆物体水平切片的阴影图像，然后将其转换为mipmap或类似图像。通过使用其mipmap，每个切片的相应区域与其距接收物的距离成比例，这意味着更远的切片将投射更柔和的阴影。</p>
<p>纹理投影方法存在一些严重的缺点。首先，应用程序必须识别哪些对象是遮挡物以及哪些物体是它们的接收物。 接收器必须由程序保持远离遮光器的光线，否则阴影是向后投射的。此外，遮挡对象不能遮挡自己。接下来的两个部分提供生成正确阴影的算法，而不需要这样干预或限制。</p>
<p>注意，通过使用预构建的投影纹理可以获得各种照明图案。聚光灯只是一个方形投影纹理，其内部有一个圆圈，用于定义光线。威尼斯百叶窗效果可以通过由水平线组成的投影纹理来创建。 这种类型的纹理称为光衰减遮罩（light attenuation mask），cookie纹理或图案图（gobo map）。 通过简单地将两个纹理相乘，预构建的图案可以与创建的投影纹理组合。 这些灯将在第6.9节中进一步讨论。</p>
<br>
<h3 id="73shadowvolumes">7.3 Shadow Volumes （阴影体积）</h3>
<p>1991年Heidmann提出，基于Crow的shadow volumes的方法可以通过巧妙地使用模板缓冲区将阴影投射到任意对象上。它可以在任何GPU上使用，因为唯一的要求是模板缓冲区。它不是基于图像的（与下面描述的阴影贴图算法不同），因此避免了采样问题，从而在任何地方产生正确的锐利阴影。 这有时可能是一个缺点。 例如，角色的衣服可能会产生褶皱，这些褶皱会产生失真严重的硬阴影。 由于成本不可预测，今天很少使用shadow volumes。 我们在这里给出算法的简要描述，因为它说明了一些重要的原则和基于这些的研究。</p>
<p>首先，想象一个点和一个三角形。 将线从一个点延伸到三角形的顶点到无穷大，产生无限的三面金字塔。 三角形下面的部分，即不包括该点的部分，是截头无限金字塔，上部是金字塔。 如图7.7所示。 现在想象一下这个点实际上是一个点光源。 然后，在截断金字塔的体积内（在三角形下面）的对象的任何部分都是阴影。 该体积称为shadow volumes。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C7/7.7.png" alt="Realtime Rendering 4th notes - chapter7"></p>
<center>（图7.7）左：来自点光源的线延伸通过三角形的顶点以形成无限金字塔。右：上部是金字塔，下部是无限截断的金字塔，也称为shadow volume。阴影体内的所有几何体都是阴影。</center>
<p>假设我们查看一些场景并跟踪从眼睛通过像素的光线，直到光线击中要在屏幕上显示的对象。当光线正在通往这个物体的路上时，每当它穿过正面的阴影体的面（即面向观察者的面）时递增一个计数器。 因此，每当光线进入阴影时，计数器就会递增。以相同的方式，每当光线穿过截头金字塔的背面时，我们减少相同的计数器。然后光线会从阴影中走出来。 我们继续，递增和递减计数器，直到射线击中要在该像素处显示的对象。 如果计数器大于零，则该像素处于阴影中；否则不在。当有多个投射阴影的三角形时，此原则也适用。见图7.8。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C7/7.8.png" alt="Realtime Rendering 4th notes - chapter7"></p>
<center>（图7.8）使用两种不同计数方法计算阴影体积交叉的二维侧视图。在z-pass体积计数中，当光线通过阴影体积的前面三角形时计数递增，而在通过背面三角形时离开时递减。因此，在A点，光线进入两个阴影体积为+2，然后留下两个体积，留下净计数为零，因此该点处于光照状态。在z-fail体积计数中，计数从表面开始（这些计数以斜体显示）。对于B点的光线，z-pass方法通过两个正面三角形给出+2计数，而z-fail通过两个背面三角形给出相同的计数。C点显示了如何对z-fail阴影卷进行限制。从点C开始的光线首先击中前面三角形，给出-1。然后它退出两个阴影体（通过它们的端盖，这种方法正常工作所需），计数为+1。计数不为零，因此该点在阴影中。两种方法始终为查看曲面上的所有点提供相同的计数结果。</center>
<p>使用光线执行此操作非常耗时。但是有一个更智能的解决方案：模板缓冲区可以为我们进行计数。首先，清除模板缓冲区。其次，将整个场景绘制到帧缓冲区中，仅使用未接受光照材质的颜色，将这些着色组件放入颜色缓冲区，深度信息放入z缓冲区。第三，关闭z缓冲区更新，写入颜色缓冲区（尽管仍然进行了z缓冲区测试），然后绘制阴影卷的正面三角形。在此过程中，模板操作设置为在绘制三角形的任何位置增加模板缓冲区中的值。第四，使用模板缓冲区完成另一次传递，这次只绘制阴影体积的背面三角形。对于此过程，在绘制三角形时，模板缓冲区中的值会减少。仅当渲染的阴影体面的像素可见时（即，未被任何真实几何体隐藏），才进行递增和递减。此时，模板缓冲区保持每个像素的阴影状态。最后，再次渲染整个场景，这次仅显示受光影响的活动材质的组件，并且仅在模板缓冲区中的值为0时显示。值为0表示光线从阴影射出的数量与进入阴影的次数一样多：即，该位置被光照亮。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C7/7.9.png" alt="Realtime Rendering 4th notes - chapter7"></p>
<center>（图7.9）Shadow volumes。在左侧，角色投下阴影。在右侧，显示被挤压的三角形。 （来自Microsoft SDK 示例）</center>
<p>这种计数方法是shadow volume背后的基本思想。该算法生成的阴影示例如图7.9所示。有一种有效的方法可以在一次pass中实现该算法。但是，当物体穿透相机的近平面时，会发生计数问题。 称为z-fail的解决方案涉及计算隐藏在可见表面后面而不是前面的交叉点。 图7.8显示了该替代方案的简要概述。</p>
<p>为每个三角形创建四边形会产生大量的成本。每个三角形将创建必须渲染的三个四边形，由一千个三角形组成的球体产生三千个四边形，并且每个四边形可以跨越屏幕。 一种解决方案是仅沿着对象的轮廓边缘绘制那些四边形，例如，我们的球体可能仅具有五十个轮廓边缘，因此仅需要五十个四边形。 几何着色器可用于自动生成此类轮廓边。Culling和clamping技术也可用于降低填充成本。</p>
<p>然而，阴影体积算法仍然有一个可怕的缺点：极端的可变性。想象一下，一个小的三角形在视野中。如果相机和灯光处于完全相同的位置，则阴影量成本最低。形成的四边形不会覆盖任何像素，因为它们是视图的边缘。只有三角形本身很重要。假设观众现在围绕三角形运行，将其保持在视野中。当相机远离光源时，阴影体四边形将变得更加明显并覆盖更多的屏幕像素，从而导致更多的计算发生。如果观察者碰巧移动到三角形的阴影中，阴影体积将完全填满屏幕，与我们的原始视图相比，需要花费相当多的时间来计算。这种可变性使得shadow volume在交互式应用程序中无法使用，因为一致的帧速率很重要。与其他情况一样，对光的观察可能导致算法成本的巨大，不可预测的速率跳跃。</p>
<p>由于这些原因，应用程序大部分都放弃了volume shadow。 然而，考虑到在GPU上访问数据的新方法和不同方式的不断发展，以及研究人员巧妙地重新利用这些功能，volume shadow可能有一天会重新被普遍使用。 例如，Sintorn等人，概述了该算法，这些算法可以提高效率并提出自己的分层加速结构。</p>
<p>下面提出的算法阴影映射具有更加可预测的成本，非常适合GPU，因此构成了许多应用程序中阴影生成的基础。</p>
<br>
<h3 id="74shadowmaps">7.4 Shadow Maps</h3>
<p>1978年，Williams提出可以使用基于z缓冲区的常见渲染器在任意对象上快速生成阴影。我们的想法是使用z缓冲区从投射阴影的光源位置渲染场景。 无论光线看到什么都被照亮，其余部分都在阴影中。生成此图像只需要z缓冲。可以关闭照明，纹理和写入颜色缓冲区。</p>
<p>z缓冲区中的每个像素现在包含最靠近光源的对象的z深度。 我们将z-buffer的全部内容称为shadow map，有时也称为shadow depth map或shadow buffer。要使用阴影贴图，场景将再次渲染，但这次是针对观察者的。 在渲染每个图元时，将其在每个像素处的位置与阴影贴图进行比较。 如果渲染点距离光源比阴影贴图中的对应值更远，则该点位于阴影中，否则不是。该技术通过使用纹理映射来实现。 见图7.10。 shadow mapping是一种流行的算法，因为它是相对可预测的。 构建阴影贴图的成本与渲染图元的数量大致呈线性关系，访问时间是不变的。在光和物体不移动的场景中，shadow mapping可以只生成一次并重复使用，例如用于计算机辅助设计。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C7/7.10.png" alt="Realtime Rendering 4th notes - chapter7"></p>
<center>（图7.10）阴影贴图。在左上方，通过将深度存储到视图中的表面来形成阴影图。在右上角，显示的眼睛看着两个位置。 在点va处看到球体，并且发现该点位于阴影图上的纹理像素a处。 存储在那里的深度不小于点va来自光，所以点被照亮。在点vb处击中的矩形（远远超过）比存储在纹理像素b处的深度更远，因此在阴影中。在左下方是从灯光的角度看场景，白色更远。在右下角是使用此阴影贴图渲染的场景。</center>
<p>当生成一个z缓冲区时，光可以仅在特定方向上像相机一样“观察”。对于诸如太阳之类的远距离定向光，光的视图被设置为包含将阴影投射到眼睛看到的观察体积中的所有物体。灯光使用正交投影，其视图需要设置足够宽和高的x和y，以查看这组对象。本地光源需要尽可能类似的调整。如果局部光线远离阴影投射物体，则单个视锥体可能足以包含所有这些。或者，如果灯光是聚光灯，则它具有与之相关的天然平截头体，其平截头体外的所有物体都被认为没有被照亮。</p>
<p>如果局部光源位于场景内并被阴影投射物包围，典型的解决方案是使用六视图立方体，类似于立方体环境映射。这被称为全向阴影贴图（omnidirectional shadow maps）。全方位地图的主要挑战是避免两个独立映射相遇的接缝处的失真。King和Newhall深入分析问题并提供解决方案，Gerasimov提供了一些实现细节。 Forsyth为全向光提供了一种通用的多平截头体分割方案，它还可以根据需要提供更多的阴影贴图分辨率。Crytek根据每个视图投影平截头体的屏幕空间覆盖率为点光源设置六个视图中每个视图的分辨率，所有映射都存储在纹理图集（atlas）中。</p>
<p>并非场景中的所有对象都需要渲染到灯光的视图体积中。首先，只需要渲染可以投射阴影的对象。 例如，如果已知地面只能接收阴影而不能投射阴影，则不必将其渲染到阴影贴图中。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C7/7.11.png" alt="Realtime Rendering 4th notes - chapter7"></p>
<center>（图7.11）</center>
<p>根据定义，阴影投射物在光的视锥体内。该视锥体可以通过多种方式加强或收紧，使我们能够忽视一些阴影投射物。摄像一组眼睛可见的阴影投射物。这组物体沿着光的视线方向存在一定的最大距离。超出此距离的任何东西都不能在可见接收物上投下阴影。类似地，可见接收物的集合可能小于光的原始x和y视图边界。见图7.11。另外，如果光源位于眼睛的视锥体内，则在这个额外的视锥体之外的任何物体都不能在接收物上投下阴影。仅渲染相关对象不仅可以节省渲染时间，还可以减小光锥体所需的尺寸，从而提高阴影贴图的有效分辨率，从而提高质量。 此外，如果光截头体的近平面尽可能远离光线，且远平面尽可能接近，则可以提高z缓冲区的有效精度（见第4.7.2节）。</p>
<p>阴影贴图的一个缺点是阴影的质量取决于阴影贴图的分辨率（以像素为单位）和z缓冲区的数值精度。由于在深度比较期间对阴影贴图进行采样，因此该算法易受混叠问题的影响，尤其是靠近对象之间的接触点。一个常见的问题是self-shadow aliasing，通常称为“surface acne”或“shadow acne”，三角形错误地遮蔽自身。这个问题有两个来源。一个是处理器精度的数值限制。另一个来源是几何的，因为点样本的值用于表示区域的深度。 也就是说，为光产生的样本几乎从不与屏幕样本处于相同的位置（例如，像素通常在它们的中心处被采样）。当光的存储深度值与观察到的表面深度进行比较时，光的值可能略低于表面的值，从而导致自阴影。这些错误的影响如图7.12所示。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C7/7.12.png" alt="Realtime Rendering 4th notes - chapter7"></p>
<center>（图7.12）</center>
<p>帮助避免（但不总是能消除）各种阴影图伪像的一种常用方法是引入偏差因子（bias factor）。当检查阴影贴图中找到的距离与被测位置的距离时，从接收物的距离中减去一个小的偏差。见图7.13。这种偏差可能是一个恒定的值，但是当接收物不是大部分面向光时，这样做可能会失败。更有效的方法是使用与接收物与光的角度成比例的偏置。表面越远离光线越倾斜，偏差越大，以避免问题。这种类型的偏差称为斜率偏差。可以通过使用诸如OpenGL的<code>glPolygonOffset</code>之类的命令来应用这两个偏差，以使每个多边形偏离光。请注意，如果表面直接面向光线，则不会向后偏置。因此，使用恒定偏置以及斜率标度偏差以避免可能的精度误差。坡度标度偏差(Slope scale bias)也经常被clamp在某个最大值，因为当表面迎着光接近侧视状态时，正切值可能非常高。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C7/7.13.png" alt="Realtime Rendering 4th notes - chapter7"></p>
<center>（图7.13）shadow bias。表面渲染为顶灯的阴影贴图，垂直线表示阴影贴图像素中心。封闭深度记录在×位置。我们想知道表面上以点显示的三个样品是否是亮的。每个最接近的阴影贴图深度值用相同的颜色×显示。在左侧，如果没有添加偏差，蓝色和橙色样本将被错误地确定为阴影，因为它们光线距离大于相应的阴影贴图深度。在中间，从每个样品中减去恒定的深度偏差，使每个样品更接近光。蓝色样本仍被视为阴影。在右侧，通过将每个多边形移动远离与其斜率成比例的光来形成阴影贴图。所有样本深度现在都比它们的阴影贴图深度更近，因此所有样本深度都会亮起。</center>
<p>Holbert引入了法线的偏移，它首先将接收者的世界空间位置沿着表面的法线方向稍微移动，与光的方向和几何法线之间的角度的正弦成比例。参考图7.24。这不仅会更改深度，还会更改在阴影贴图上测试样本的x坐标和y坐标。随着光的角度变得越来越浅，这种偏移会增加，希望样品在表面上方足够远以避免自阴影。该方法可视化为将样品移动到接收者上方的“虚拟表面”。此偏移是世界空间距离，因此Pettineo建议按阴影贴图的深度范围进行缩放。 Pesce建议沿摄像机视图方向偏置的想法，这也可以通过调整阴影贴图坐标来实现。其他偏置方法在7.5节中讨论，因为在那里提出的阴影方法也需要测试几个相邻的样本。</p>
<p>太多的偏差会导致一个称为漏光（light leaks）或彼得平移（Peter Panning）的问题，其中物体看起来略微漂浮在下面的表面之上。 出现这种伪影是因为物体接触点下方的区域（例如，脚下的地面）被向前推得太远而因此没有接收到阴影。</p>
<p>避免自阴影问题的一种方法是仅把背面渲染到阴影贴图。这种方案称为第二深度阴影映射（second-depth shadow mapping），适用于许多情况，特别是对于不能手动调整偏差的渲染系统。当对象是双面的，薄的或彼此接触时，会出现问题情况。如果物体是网格两侧可见的模型，例如棕榈叶或纸张，则可能发生自阴影，因为背面和正面位于相同位置。类似地，如果不执行偏置，则在轮廓边缘或薄物体附近可能发生问题，因为在这些区域中背面靠近前面。增加偏差可以帮助避免surface acne，但该方案更容易受到光泄漏的影响，因为在接触点处接收物和遮挡物的背面之间没有分离。见图7.14。选择哪种方案可能取决于具体情况。例如，Sousa等人发现对太阳阴影使用正面室内灯使用背面最适合他们的应用程序。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C7/7.14.png" alt="Realtime Rendering 4th notes - chapter7"></p>
<center>（图7.14）顶置光源的阴影贴图表面。 在左侧，面向灯光的表面（以红色标记）将被发送到阴影贴图。 表面可能被错误地确定为自身阴影（acne），因此需要偏离光线。在中间，只有背面三角形被渲染到阴影贴图中。向下推动这些遮挡物的偏差可能会让光线泄漏到地平面靠近位置a的地方;向前偏向可以使标记为b的轮廓边界附近的照明位置在阴影中被发觉。在右边，在每个位置处找到的最近的正面和背面三角形之间的中点处形成中间表面 在阴影贴图上。在c点附近可能发生漏光（这也可能发生在第二深度阴影贴图中），因为最近的阴影贴图样本可能位于此位置左侧的中间曲面上，因此该点会更接近光。</center>
<p>请注意，对于阴影贴图，对象必须是“防水的（watertight）”（即固体，封闭的，第16.3.3节），或者必须将前面和后面都渲染到地图，否则对象可能无法完全投射阴影。Woo提出了一种通用的方法，试图在字面上尝试使用正面或背面进行遮蔽。我们的想法是将实体对象渲染到阴影贴图中，并跟踪光线的两个最接近的表面。该过程可以通过深度剥离或其他与透明度相关的技术来执行。两个对象之间的平均深度形成一个中间层，其深度用作阴影贴图，有时称为双阴影贴图（dual shadow map）。如果物体足够厚，则可以最大限度地减少自阴影和漏光伪影。 Bavoil等，讨论了解决潜在工件的方法，以及其他实现细节。主要缺点是与使用两个阴影贴图相关的额外成本。迈尔斯讨论了遮挡和接收者之间的艺术家控制的深度层。</p>
<p>当观察者移动时，随着阴影投射物们的变化，灯光的视图体积通常会改变大小。这种变化反过来导致阴影在帧与帧之间略微移动。发生这种情况是因为灯光的阴影贴图从灯光中采集了一组不同的方向，并且这些方向与前一组不对齐。对于定向光，解决方案是强制生成的每个后续阴影贴图在世界空间中保持相同的相对纹素光束位置。也就是说，您可以将阴影贴图视为在整个世界中施加二维网格化参照系，每个网格单元表示地图上的像素样本。移动时，将为这些相同网格单元生成另一组阴影贴图。 换句话说，光的视图投影被强制到该网格以保持帧间的一致性。</p>
<h4 id="741resolutionenhancement">7.4.1 Resolution Enhancement（分辨率增强）</h4>
<p>与纹理的使用方式类似，理想情况下我们希望一个阴影贴图纹素覆盖一个图像像素。 如果我们将光源放置在与眼睛相同的位置，则阴影贴图与屏幕空间像素一对一地完美地映射（并且没有可见的阴影，因为光正好照亮了眼睛所看到的）。一旦光的方向改变，这个每像素比率就会改变，这可能会导致伪影。一个例子如图7.15所示。由于前景中的大量像素与阴影贴图的每个纹素相关联，因此阴影是块状的并且定义不明确。 这种不匹配称为透视混叠（perspective aliasing ）。如果表面和光接近侧视，但是面向观察者，则单个阴影贴图纹理像素也可以覆盖许多像素。这个问题被称为投射混叠（projective aliasing ）；见图7.16。 通过增加阴影贴图分辨率可以减少阻塞，但是会增加内存和处理成本。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C7/7.15.png" alt="Realtime Rendering 4th notes - chapter7"></p>
<center>（图7.15）使用标准阴影贴图创建左侧图像;使用LiSPSM创建右侧图像。显示每个阴影贴图的纹素的投影。 两个阴影贴图具有相同的分辨率，不同之处在于LiSPSM对光的矩阵进行改造以提供更接近观察者的更高采样率。（图片由维也纳科技大学Daniel Scherzer提供。）</center>
<p>还有另一种方法可以创建灯光的采样模式，使其更接近相机的模式。这是通过改变场景投影到光的方式来完成的。通常我们认为视图是对称的，视图矢量位于平截头体的中心。然而，视图方向仅定义视图平面，但不定义哪些像素被采样。定义平截头体的窗口可以在该平面上移动，倾斜或旋转，从而创建四边形，从而提供世界与视图空间的不同映射。四边形仍然以规则的间隔进行采样，因为这是线性变换矩阵的性质并通过GPU实现。可以通过改变光的视图方向和视窗的边界来修改采样率。参见图7.17。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C7/7.16.png" alt="Realtime Rendering 4th notes - chapter7"></p>
<center>（图7.16）在左边，灯几乎在头顶上。由于与眼睛视图相比分辨率较低，阴影的边缘有点粗糙。在右侧，光线靠近地平线，因此每个阴影纹理像素水平覆盖更多的屏幕区域，因此产生更多的锯齿状边缘。 （Github上由TheRealMJP的Shadows程序生成的图像。）</center>
<p>将光的视图映射到眼睛的位置有22个自由度。对这种解决方案空间的探索导致了几种不同的算法，这些算法试图将光的采样率更好地与眼睛的采样率相匹配。方法包括透视阴影贴图（perspective shadow maps ，PSM），梯形阴影贴图（trapezoidal shadow maps，TSM）和光空间透视阴影贴图（light space perspective shadow maps ，LiSPSM）。有关示例，请参见图7.15和图7.26。此类技术称为透视变形方法（perspective warping）。</p>
<p>这些矩阵变形算法的一个优点是除了修改光的矩阵之外不需要额外的工作。每种方法都有自己的优点和缺点，因为每种方法都可以帮助匹配某些几何情况的采样率和照明情况的采样率，但其他方法的这些速率较差。Lloyd等人分析了PSM，TSM和LiSPSM之间的等价性，用这些方法很好地概述了采样和混叠问题。当光的方向垂直于视图的方向（例如，在头顶）时，这些方案工作得最好，因为透视变换然后可以被移位以使更多的样本更靠近眼睛。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C7/7.17.png" alt="Realtime Rendering 4th notes - chapter7"></p>
<center>（图7.17）对于顶灯，左侧，地面上的采样与眼睛的速率不匹配。通过改变光的视图方向和右侧的投影窗口，采样率偏向于具有更接近眼睛的更高密度的纹理像素。</center>
<p>当光线位于相机前方并指向它时，矩阵变形技术无法提供帮助。 这种情况被称为dueling frusta，或更通俗地称为“车灯前的鹿”。眼睛附近需要更多的阴影贴图样本，但线性变形只能使情况变得更糟。不稳定的质量等问题，使得这种方法失宠。</p>
<p>在观察者所在位置添加更多样本的想法很好，导致算法为给定视图生成多个阴影贴图。当Carmack在Quakecon 2004的主题演讲中描述时，这个想法首次产生了明显的影响。Blow独立实现了这样一个系统。想法很简单：生成一组固定的阴影贴图（可能在不同的分辨率下），覆盖场景的不同区域。在Blow的方案中，四个阴影贴图嵌套在观察者周围。通过这种方式，可以为附近的物体提供高分辨率的地图，远离这些物体的分辨率会下降。Forsyth提出了一个相关的想法，为不同的可见对象集生成不同的阴影贴图。在他的设置中，避免了处理跨越两个阴影贴图之间边界的对象的过渡的问题，因为每个对象具有与其相关联的一个且仅一个的阴影贴图。Flagship工作室开发了一个融合这两个想法的系统。一个阴影贴图用于附近的动态对象，另一个阴影贴图用于观察者附近的静态对象的grid section，第三个阴影贴图用于整个场景中的静态对象。每帧生成第一个阴影贴图。由于光源和几何形状是静态的，因此其他两个只能生成一次。虽然所有这些特定的系统现在已经相当陈旧，但是针对不同对象和情况的多个映射的想法，一些预计算的和一些动态的，从那时起成为了开发算法的共同主题。</p>
<p>2006年，Engel，Lloyd等人和Zhang等人分别研究了一种相同的基本思想。这个想法是通过平行于视图方向的切片将视锥体的体积分成几个部分。见图7.18。随着深度的增加，每个连续的体积大约是前一个体积的深度范围的两到三倍。 对于每个视图体积，光源可以形成紧密限制它的平截头体，然后生成阴影贴图。通过使用纹理图集或数组，可以将不同的阴影贴图视为一个大的纹理对象，从而最大限度地减少缓存访问延迟。获得的质量改进的比较如图7.19所示。Engel这个算法的名称，级联阴影贴图（cascaded shadow maps，CSM），比Zhang的术语，平行分割阴影贴图（parallel-split shadow maps）更常用，但两者都出现在文献中并且实际上是相同的。</p>
<p>这种类型的算法很容易实现，可以覆盖具有合理结果的大型场景区域，并且是健壮的。dueling frusta问题可以通过以更接近眼睛的更高速率采样来解决，并且没有严重的最坏情况问题。由于这些优势，级联阴影映射在许多应用程序中使用。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C7/7.18.png" alt="Realtime Rendering 4th notes - chapter7"></p>
<center>（图7.18）在左侧，眼睛的视锥体被分成四个体积。在右侧，为体积创建边界框，这些边框决定了四个阴影贴图中每个阴影贴图为定向光渲染的体积。</center>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C7/7.19.png" alt="Realtime Rendering 4th notes - chapter7"></p>
<center>（图7.19）在左侧，场景的宽可视区域导致2048×2048分辨率的单个阴影贴图显示透视锯齿。在右侧，沿视轴放置的四个1024×1024阴影贴图显着提高了质量。在嵌入的红色框中显示围栏前角的缩放。（图片由香港中文大学张帆提供。）</center>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C7/7.20.png" alt="Realtime Rendering 4th notes - chapter7"></p>
<center>（图7.20）阴影级联可视化。紫色，绿色，黄色和红色代表最近到最远的级联。（图片由Unity Technologies提供。）</center>
<p>虽然可以使用透视变形将更多样本打包到单个阴影贴图的细分区域，但一般是为每个级联使用单独的阴影贴图。 如图7.18所示，图7.20从观察者的角度显示，每个地图覆盖的区域可以变化。较近的阴影贴图的较小视图体积可在需要它们的地方提供更多样本。确定z-深度范围如何在地图之间分配——称为z分区（z-partitioning）。一种方法是对数分区，其中每个级联映射使远距离与近距离平面距离的比率相同：</p>
<p>$$<br>
r=\sqrt[c]\frac fn\tag{7.5}<br>
$$</p>
<p>其中n和f是整个场景的近和远平面，c是级联图的数量，r是得到的比率。例如，如果场景最近的物体距离1米，最大距离为1000米，我们有三个级联图，则$r =\sqrt[3]{1000}=10$。则近平面和远平面距离最近的视图为1和10，下一个间隔是10到100来保持这个比例，最后一个是100到1000米。初始近深度对此分区有很大影响。如果近深度仅为0.1米，则10000的立方根为21.54，相当高的比例，例如0.1到2.154到46.42到1000。这意味着每个阴影贴图必须覆盖更大的区域，降低了精度。在实践中，这种分区为靠近近平面的区域提供了相当大的分辨率，如果该区域中没有物体则浪费。避免这种不匹配的一种方法是将分区距离设置为对数和等距离分布的加权混合，但如果我们能够确定场景的视图边界，那将更好。</p>
<p>挑战在于设置近平面。如果距离眼睛太远，则该平面可能会夹住物体，这是一种非常糟糕的失真。对于一个镜头，艺术家可以提前精确设置此值，但对于交互式环境，问题更具挑战性。 Lauritzen等人提出样本分布阴影图（sample distribution shadow maps，SDSM），它使用前一帧的z深度值，通过两种方法之一确定更好的分区。</p>
<p>第一种方法是查看z深度的最小值和最大值，并使用它们来设置近平面和远平面。这是使用GPU上的缩减操作（reduce operation）执行的，计算着色器或者一个其他着色器分析一系列更小的缓冲区，使用输出缓冲区返回作为输入，直到剩下1×1缓冲区。通常，将值增加一点以调整场景中对象的移动速度。除非采取纠正措施，否则从屏幕边缘进入的附近物体仍可能导致帧出现问题，但在下一个帧中将很快得到纠正。</p>
<p>第二种方法还是分析深度缓冲区的值，制作一个称为直方图（histogram）的图形，记录沿该范围的z深度分布。除了找到紧密的近和远平面之外，图形中可能还有其中根本没有任何物体的间隙。通常添加到此类区域的任何分区平面都可以捕捉到实际存在的对象，从而为级联映射集提供更多z深度精度。</p>
<p>在实践中，第一种方法是通用的，是快速的（通常每帧在每1毫秒范围内），并给出了良好的结果，因此它已被多种应用所采用。 见图7.21。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C7/7.21.png" alt="Realtime Rendering 4th notes - chapter7"></p>
<center>（图7.21）深度边界的影响。在左侧，没有使用特殊处理来调整近处和远处平面。右侧，SDSM用于查找更严格的边界。请注意每个图像左边缘附近的窗口框架，二楼花箱下方的区域以及一楼的窗口，由于松散的视图边界而导致的欠采样会导致失真。指数阴影贴图用于渲染这些特定图像，但提高深度精度的想法对于所有阴影贴图技术都很有用。（图片由Ready at Dawn Studios提供，版权归Sony Interactive Entertainment所有。）</center>
<p>由于这种方法很受欢迎，在提高效率和质量方面都付出了相当大的努力。如果阴影贴图的平截头体内没有任何变化，则不需要重新计算该阴影贴图。对于每个灯光，阴影投射物的列表可以通过查找哪些对象对灯光可见而预先计算，这些对象可以在接收物上投射阴影。由于很难感知阴影是否正确，因此可以对级联和其他算法采用一些捷径。一种技术是使用低LOD级别的模型作为代理实际投射阴影。另一种方法是去除微小的遮挡物。对于更远的阴影贴图可能比一帧一次的更新频率更低，因为理论上这些阴影不那么重要。大型的移动物体可能由于这种方式产生失真，因此需要谨慎使用。Day提出了一帧一帧“滚动”远处阴影贴图的想法，其思想是每个静态阴影贴图的大部分是可重复使用的帧，并且只有条纹可能会改变，因此需要渲染。诸如DOOM（2016）之类的游戏维护着大量的阴影贴图，只重新生成物体移动的图集。可以将更远的级联地图设置为完全忽略动态对象，因为这样的阴影可能对场景贡献很少。在某些环境中，可以使用高分辨率静态阴影贴图代替这些更远的级联，这可以显着减少工作量。稀疏纹理系统（第19.10.1节）可用于单个静态阴影贴图非常庞大的世界。级联阴影贴图可以与烘焙贴图纹理或其他更适合特定情况的阴影技术相结合。Valient的演示值得注意，因为它描述了针对各种视频游戏的不同影子系统定制和技术。第11.5.1节详细讨论了预计算的光影算法。</p>
<p>创建几个单独的阴影贴图意味着为每个阴影贴图运行一组几何图形。在一次通过中将遮挡物渲染到一组阴影贴图的想法已经建立了许多提高效率的方法。几何着色器可用于复制对象数据并将其发送到多个视图。实例几何着色器允许将对象输出到最多32个深度纹理。多视口扩展可以执行诸如将对象渲染到特定纹理阵列切片的操作。第21.3.1节在它们用于虚拟现实的背景下更详细地讨论了这些。视口共享技术的一个可能的缺点是生成的所有阴影贴图的遮挡物必须沿着管线发送，而不是发现与每个阴影贴图相关的集合。</p>
<p>你自己目前正处于世界各地数十亿光源的阴影中。只有少数几个光到达你。在实时渲染中，如果所有灯始终处于活动状态，则具有多个灯的大型场景可能会被计算淹没。如果一个空间体积在视锥体内但对眼睛不可见，则不需要计算遮挡该接收体积的物体。Bittner 等人从眼睛位置使用遮挡剔除（第19.7节）找到所有可见的阴影接收物，然后从光的角度将所有潜在的阴影接收物渲染到模板缓冲遮罩中。该遮罩编码从光中看到哪些可见的阴影接收器。为了生成阴影贴图，它们使用遮挡剔除从光源渲染对象，并使用遮罩来剔除没有接收物的对象。各种剔除策略也适用于灯光。由于辐照度随着距离的平方而下降，因此常见的技术是在一定的阈值距离之后剔除光源。例如，第19.5节中的门户剔除技术可以找到哪些灯影响哪些像素。这是一个活跃的研究领域，因为性能优势可能相当大。</p>
<br>
<h3 id="75percentagecloserfiltering">7.5 Percentage-Closer Filtering（百分比近似过滤）</h3>
<p>阴影贴图技术的一个简单扩展可以提供伪软阴影。此方法还可以帮助改善分辨率问题，当单个光样本单元覆盖许多屏幕像素时，这些问题会导致阴影看起来像块状。解决方案类似于纹理放大（第6.2.1节）。不是从阴影图中取出单个样本，而是检索四个最近的样本。该技术不在深度本身之间进行插值，而是在与表面深度进行比较的结果之间进行插值。也就是说，将表面的深度与四个纹素深度分别进行比较，然后针对每个阴影图样本确定该点在光或阴影中。然后对这些结果，即阴影为0，对光为1，然后进行双线性插值，以计算光实际对表面位置的贡献程度。这种过滤导致人为柔和的阴影。根据阴影贴图的分辨率，摄像机位置和其他因素，这些半影会发生变化。例如，较高的分辨率使边缘的柔和变窄。尽管如此，一点点半影和平滑总比没有好。</p>
<p>从阴影贴图中检索多个样本并混合结果的这种想法称为百分比近似过滤（percentage-closer filtering，PCF）。区域光产生柔和阴影。到达表面上的位置的光量是从该位置可见的光的面积的比例的函数。PCF试图通过反转过程来近似点源光（或平行光）的软阴影。不是从表面位置找到光的可见区域，而是从原始位置附近的一组表面位置找到准点光的可见性。见图7.22。名称“百分比 - 近距离过滤”指的是最终目标，即找到对光可见的样本的百分比。这个百分比用于遮蔽表面的光量。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C7/7.22.png" alt="Realtime Rendering 4th notes - chapter7"></p>
<center>（图7.22）在左侧，来自区域光源的棕色线条显示半影形成的位置。对于接收物上的单个点p，可以通过测试区域光表面上的一组点并找出未被任何遮挡物阻挡的点来计算所接收的照明量。在右边，点光源不会产生半影。PCF通过反转过程来近似区域光的影响：在给定位置，它在阴影图上的可比较区域上进行采样，以得出照亮多少样本的百分比。红色椭圆显示阴影贴图上采样的区域。理想情况下，该盘的宽度与接收物和遮挡物之间的距离成比例。</center>
<p>在PCF中，位置靠近表面位置，大约相同的深度，但在阴影贴图上的不同纹理元素位置处生成。 检查每个位置的可见性，然后将这些得到的布尔值（点亮或不亮）混合以获得柔和阴影。 请注意，此过程是非物理过程：此过程不依赖于直接对光源进行采样，而是依赖于在曲面本身上进行采样的想法。 到遮挡物的距离不会影响结果，因此阴影具有相似大小的半影。 尽管如此，该方法在许多情况下提供了合理的近似。</p>
<p>一旦确定了要采样的区域的宽度，重要的是以避免混叠伪像的方式进行采样。如何对附近的阴影贴图位置进行采样和过滤有很多变化。变量包括要采样的区域的宽度，要使用的样本数量，采样模式以及如何对结果进行加权。利用功能较少的API，可以通过类似于双线性插值的特殊纹理采样模式来加速采样过程，该模式访问四个相邻位置。不是混合结果，而是将四个样本中的每一个与给定值进行比较，并返回通过测试的比率。但是，以常规网格图案执行最近邻居采样会产生明显的伪像。使用联合双边滤波可以模糊结果，但保留物体边缘可以提高质量，同时避免阴影泄漏到其他表面上。 有关此过滤技术的更多信息，请参见第12.1.1节。</p>
<p>DirectX 10为PCF引入了单指令双线性过滤（single-instruction bilinear filtering）支持，从而提供更平滑的结果。与最近邻采样相比，这提供了相当大的视觉改善，但是来自常规采样的伪像仍然是个问题。最小化网格模式的一种解决方案是使用预先计算的泊松分布模式对区域进行采样，如图7.23所示。该分布将样本扩散出来，使得它们既不相邻也不是规则模式。众所周知，对于每个像素使用相同的采样位置，不管分布如何，都可以产生图案。通过围绕其中心随机旋转样本分布可以避免这种伪影，这会使混叠变成噪声。Castaño发现泊松采样产生的噪声因其平滑，风格化的内容而特别明显。他提出了一种基于双线性采样的高效加权采样方案。</p>
<p>PCF可能会导致自我遮蔽，漏光，acne和Peter Panning变得更糟。假设样本在阴影贴图上不超过一个纹素，斜率刻度偏差完全基于其与光的角度将表面推离光线。通过从表面上的单个位置在更宽的区域中取样，一些测试样品可能被真实表面阻挡。</p>
<p>已经发明并使用了一些不同的附加偏差因子，并且成功地降低了自阴影的风险。Burley描述了偏置锥，其中每个样本朝向与原始样本的距离成比例的光移动。 Burley建议斜率为2.0，并伴有小的恒定偏差。 见图7.24。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C7/7.24.png" alt="Realtime Rendering 4th notes - chapter7"></p>
<center>(图7.24)额外的阴影偏差方法。对于PCF，在原始样品位置（五个点的中心）周围采集了几个样品。所有这些样品都应该点亮。在左图中，形成偏置锥，样品向上移动。可以增加锥体的陡度以将样品拉到右侧足够近以便点亮，存在增加其他样品（未示出）中真正被遮蔽的光泄漏的风险。在中图中，所有样本都被调整为位于接收器的平面上。这适用于凸面，但在凹面处可能适得其反，如左侧所示。在右图中，正常的偏移使样本沿着表面的法线方向移动，与正常和光线之间的角度的正弦成比例。对于中心样本，可以认为这是移动到原始表面上方的假想表面。这种偏差不仅会影响深度，还会改变用于测试阴影贴图的纹理坐标。</center>
<p>Schüler，Isidoro和Tuft提出了基于观察的技术，即应使用接收物本身的斜率来调整其余样本的深度。在这三者中，Tuft的配方最容易应用于级联阴影贴图。Dou等人进一步完善和扩展这一概念，考虑z深度如何以非线性方式变化。 这些方法假设附近的样本位置在由三角形形成的同一平面上。被称为接收器平面深度偏差或其他类似术语，在许多情况下，该技术可以非常精确，因为该假想平面上的位置确实在表面上，或者如果模型是凸的则在其前面。如图7.24所示，凹陷附近的样本可能会隐藏。 已经使用常数，斜率，接收物平面，视图偏置和正常偏移偏置的组合来解决自阴影的问题，尽管仍然需要对每个环境进行手动调整。</p>
<p>PCF的一个问题是因为采样区域的宽度保持不变，阴影将显得均匀柔和，所有阴影都具有相同的半影宽度。 在某些情况下这可能是可以接受的，但在遮挡物和接收物之间存在接地的情况下看起来是不正确的。见图7.25。</p>
<br>
<h3 id="76percentageclosersoftshadows">7.6 Percentage-Closer Soft Shadows（百分比近似软阴影）</h3>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C7/7.25.png" alt="Realtime Rendering 4th notes - chapter7"></p>
<center>（图7.25）百分比近似过滤和百分比近似软阴影。在左侧，硬阴影与一点PCF过滤。在中间，恒定宽度柔和的阴影。在右侧，可变宽度的柔和阴影具有适当的硬度，物体与地面接触。（图片由NVIDIA Corporation提供。）</center>
<p>2005年，Fernando发布了一种名为百分比隐形软阴影（PCSS）的有影响力的方法。 它通过搜索阴影贴图上的附近区域来尝试找到所有可能的遮挡物。这些遮挡物与该位置的平均距离用于确定样本区域宽度：</p>
<p>$$<br>
w_{sample}=w_{light}\frac{d_r-d_o}{d_r}\tag{7.6}<br>
$$</p>
<p>其中$d_r$是接收物与光的距离，并进行平均封堵器距离。换句话说，随着平均遮挡物越来越远离接收物并且更靠近光线，样本的表面区域的宽度增大。检查图7.22并考虑移动遮挡物的效果，看看它是如何发生的。图7.2，7.25和7.26显示了示例。</p>
<p>如果找不到遮挡物，则该位置完全点亮，无需进一步处理。类似地，如果位置完全被遮挡，则处理可以结束。否则，继续对感兴趣的区域进行采样并计算光的近似贡献。为了节省处理成本，样本区域的宽度可用于改变采样的数量。 可以实现其他技术，例如，使用较低的采样率来获得不太重要的远距离软阴影。</p>
<p>这种方法的一个缺点是需要对阴影贴图的大小区域进行采样以找到遮挡物。 使用旋转的泊松盘模式可以帮助隐藏欠采样伪像。 Jimenez指出泊松采样在运动中可能是不稳定的，并且发现通过在抖动和随机之间使用函数形成的螺旋图案给出了帧到帧的更好结果。</p>
<p>Sikachev等人详细讨论了使用AMD推出的SM 5.0中的功能更快地实现PCSS，并且通常用它们的名称来称呼，接触硬化阴影（Contact Hardening Shadows，CHS）。 这个新版本还解决了基本PCSS的另一个问题：半影的大小受阴影贴图分辨率的影响。见图7.25。 首先生成阴影贴图的mipmap，然后选择最接近用户定义的世界空间内核大小的mip级别，可以最大限度地减少此问题。采样8×8区域以找到平均阻塞深度，仅需要16个<code>GatherRed()</code>纹理调用。一旦发现半影，较高分辨率的mip级别用于阴影的锐利区域，而较低分辨率的mip级别用于较软区域。</p>
<p>CHS已被用于大量视频游戏，并且研究仍在继续。 例如，Buades等人。 目前可分离的软阴影映射（SSSM），其中对网格进行采样的PCSS过程被分成可分离的部分，并且元素尽可能从像素到像素被重用。</p>
<p>已经证明有助于加速每像素需要多个样本的算法的一个概念是分层最小/最大阴影图。虽然阴影贴图深度通常无法平均，但每个mipmap级别的最小值和最大值都很有用。 也就是说，可以形成两个mipmap，一个保存每个区域中最大的zdepth（有时称为HiZ），一个最小。给定纹素位置，深度和要采样的区域，mipmap可用于快速确定完全亮起和完全阴影的条件。例如，如果纹素的z深度大于为mipmap的相应区域存储的最大z深度，则纹理元素必须处于阴影中——不需要进一步的样本。 这种类型的阴影贴图使得确定光可见度的任务更加有效。</p>
<p>诸如PCF的方法通过对附近的接收物位置进行采样来工作。PCSS的工作原理是找到附近遮挡物的平均深度。这些算法不直接考虑光源的面积，而是对附近的表面进行采样，并受阴影贴图的分辨率影响。PCSS背后的主要假设是平均阻滞剂是对半影大小的合理估计。当两个遮挡物（例如路灯和远处的山）在一个像素处部分遮挡同一表面时，这种假设被破坏并且可能导致伪影。理想情况下，我们希望确定从单个接收器位置可以看到多少区域光源。一些研究人员已经使用GPU探索了反投影。这个想法是将每个接收物的位置视为视点，将区域光源视为视图平面的一部分，并将遮挡物投射到该平面上。Schwarz和Stamminger以及Guennebaud等人总结以前的工作并提供自己的改进。 Bavoil等采用不同的方法，使用深度剥离来创建多层阴影贴图。反投影算法可以提供出色的结果，但每像素的高成本（到目前为止）意味着它们在交互式应用程序中尚未被采用。</p>
<br>
<h3 id="77filteredshadowmaps">7.7 Filtered Shadow Maps</h3>
<p>允许过滤生成的阴影贴图的一种算法是Donnelly和Lauritzen的方差阴影图（variance shadow map，VSM）。该算法将深度存储在一个贴图中，并将深度的平方存储在另一个贴图中。 生成映射时可以使用MSAA或其他抗锯齿方案。 这些贴图可以模糊，mipmap，放在SAT或使用任何其他方法。可以将这些贴图视为可过滤纹理是一个巨大的优势，因为从它们检索数据时可以使用所有采样和过滤技术。</p>
<p>我们将在这里深入描述VSM，以了解这个过程是如何工作的；此外，这种类型的测试也被用于此类算法中的所有方法。有兴趣了解该领域的读者应该访问相关的参考文献，我们也推荐Eisemann等人的书。</p>
<p>首先，对于VSM，深度图在接收物的位置被采样（仅一次）以返回最近的光遮挡物的平均深度。 当称为first moment的平均深度$M_1$，大于阴影接收物t上的深度时，接收物被认为是完全在光下。 当平均深度小于接收物的深度时，使用以下等式：</p>
<p>$$<br>
p_{max}(t)=\frac{\sigma^2}{\sigma^2+(t-M_1)^2}\tag{7.7}<br>
$$</p>
<p>其中$p_{max}$是光照样本的最大百分比，$σ^2$是方差，t是接收器深度，$M_1$是阴影图中的平均预期深度。 深度平方阴影贴图的样本$M_2$（称为second moment）用于计算方差：</p>
<p>$$<br>
\sigma^2=M_2-M_1^2\tag{7.8}<br>
$$</p>
<p>值$p_{max}$是接收物的可见性百分比的上限。实际照明百分比p不能大于该值。 这个上限来自<a href="https://en.wikipedia.org/wiki/Chebyshev%27s_inequality">切比雪夫不等式</a>（Chebyshev’s inequality）的变体。该方程试图使用概率理论估计表面位置上遮挡物的分布有多少超出表面与光的距离。Donnelly和Lauritzen表明，对于固定深度的平面遮挡物和平面接收物，$p=p_{max}$，因此公式7.7可以用作许多真实阴影情况的良好近似。</p>
<p>Myers说明了为什么这种方法有效。在阴影边缘处，区域上的方差增加。深度差异越大，方差越大。然后，$(t-M_1)^2$项是可见性百分比的重要决定因素。如果该值略高于零，则这意味着平均遮挡物深度比接收物稍微接近光，并且$p_{max}$接近1（完全点亮）。这将发生在半影的完全照亮的边缘。移动到半影中，平均遮挡物深度更接近光线，因此该项变得更大并且$p_{max}$下降。 同时，方差本身在半影内发生变化，从几乎为零的边缘变为最大的方差，其中遮挡物的深度不同并且平均分享该区域。 这些项相互抵消，在半影上产生线性变化的阴影。有关与其他算法的比较，请参见图7.26。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C7/7.26.png" alt="Realtime Rendering 4th notes - chapter7"></p>
<center>图7.26。在左上角，标准阴影贴图。 右上角，透视阴影映射，增加观察者附近的阴影贴图纹理密度。 左下角，百分比近似软阴影，当遮挡物与接收物的距离增加时软化阴影。右下角，方差阴影映射具有恒定的软阴影宽度，每个像素用单个方差图样本着色。 （图片由Nico Hempe，Yvonne Jung和Johannes Behr提供。）</center>
<p>方差阴影映射的一个重要特征是它可以以优雅的方式处理由于几何形状引起的表面偏差问题。 Lauritzen推导出如何使用曲面的斜率来修改second moment的值。数值稳定性的偏差和其他问题可能是方差映射的问题。例如，公式7.8从一个近似值中减去一个大值。这种类型的计算导致底层数值表示的精度缺乏问题被放大。使用浮点纹理有助于避免此问题。</p>
<p>整体上讲，由于有效利用了GPU的优化纹理功能，VSM花费的时间成本也换来了质量的显着提高。PCF需要更多的样本，更多的时间来避免产生更柔和的阴影时的噪点，但VSM只需要一个高质量的样本即可使用，以确定整个区域的效果并产生平滑的半影。这种能力意味着在算法的限制范围内，阴影可以在没有额外成本的情况下任意地变软。</p>
<p>与PCF一样，过滤内核的宽度决定了半影的宽度。通过找到接收物和最接近的遮挡物之间的距离，可以改变内核宽度，从而给出令人信服的柔和阴影。Mipmapped样本是半影的覆盖率差估计，宽度缓慢增加，产生四四方方的伪影。Lauritzen详述了如何使用SAT来提供更好的阴影。一个例子如图7.27所示。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C7/7.27.png" alt="Realtime Rendering 4th notes - chapter7"></p>
<center>（图7.27）方差阴影映射，其中到光源的距离从左到右增加。（来自NVIDIA SDK 10样本的图片，由NVIDIA Corporation提供。）</center>
<p>当两个或多个遮挡物覆盖接收物，并且一个遮挡物靠近接收物时，沿着半影区域的一个地方差异阴影映射分解。概率论中的切比雪夫不等式将产生与正确光百分比无关的最大光值。最接近的遮挡物，通过仅部分隐藏光，抛出等式的近似值。这导致轻微光泄漏（也叫出血），其中完全被遮挡的区域仍然接收光。见图7.28。通过在较小区域上采集更多样本，可以解决此问题，将方差阴影映射转换为PCF形式。与PCF一样，速度和性能会受到影响，但对于阴影深度复杂度较低的场景，方差映射效果很好。Lauritzen提供了一种艺术家控制的方法来改善问题，即将低百分比视为完全阴影并将剩余的百分比范围重新映射到0％到100％。这种方法可以减少轻度溢出，但总体上会缩小半影。虽然光线溢出是一个严重的限制，但VSM有利于从地形产生阴影，因为这种阴影很少涉及多个遮挡物。</p>
<p>由于能够使用过滤技术快速生成平滑阴影，引起了很多人对过滤阴影映射的极大兴趣；主要的挑战是解决各种出血问题。Annen等人提出了卷积阴影贴图（convolution shadow map）。扩展了Soler和Sillion的平面接收物算法背后的思想，其思想是在傅立叶展开中对阴影深度进行编码。与方差阴影映射一样，可以过滤此类映射。该方法收敛于正确的结果，因此减少了漏光问题。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C7/7.28.png" alt="Realtime Rendering 4th notes - chapter7"></p>
<center>（图7.28）左侧，应用于茶壶的方差阴影贴图。 右侧，一个三角形（未显示）在茶壶上投下阴影，在地面阴影中造成令人反感的瑕疵。（图片由Marco Salvi提供。）</center>
<p>卷积阴影映射的一个缺点是需要计算和访问几个项，这大大增加了执行和存储成本。Salvi和Annen等人同时并独立地提出了使用基于指数函数的单个术语的想法。 称为指数阴影贴图（exponential shadow map，ESM）或指数方差阴影贴图（exponential variance shadow map，EVSM），此方法将深度的指数与其second moment一起保存到两个缓冲区中。指数函数更接近于阴影贴图执行的阶梯函数（无论是否在光照下），因此这可以显着减少出血伪影。 它避免了卷积阴影映射具有的另一个问题，称为ringing，其中微小的光泄漏可能发生在刚刚超过原始封堵器深度的特定深度处。</p>
<p>存储指数值的限制是second moment可能变得非常大，以至使用浮点数会超出范围。为了提高精度，并允许指数函数更陡峭地下降，可以生成z深度，使得它们是线性的。</p>
<p>由于其与VSM相比具有更高的质量，并且与卷积图相比具有更低的存储性和更好的性能，因此指数阴影贴图方法引起了三种滤波方法的兴趣。Pettineo还注意到其他一些改进，例如使用MSAA改进结果和获得有限透明度的能力，并描述了如何使用计算着色器提高过滤性能。</p>
<p>最近，Peters和Klein引入了时刻阴影贴图（moment shadow mapping）。 它提供更好的质量，但代价是使用四个或更多moment，增加了存储成本。通过使用16位整数来存储moments，可以降低这种成本。Pettineo实现并将这种新方法与ESM进行比较，提供了探索许多变体的代码库。</p>
<p>级联阴影贴图技术可应用于过滤后的贴图以提高精度。级联ESM优于标准级联映射的优点是可以为所有级联设置单个偏置因子。Chen和Tatarchuk详细介绍了级联ESM遇到的各种漏光问题和其他工件，并提出了一些解决方案。</p>
<p>过滤后的贴图可以被认为是一种廉价的PCF形式，需要很少的样本。与PCF一样，这种阴影具有恒定的宽度。这些过滤后的方法都可以与PCSS结合使用，以提供可变宽度的半影。moment shadow mapping的扩展还包括提供光散射和透明效果的能力。</p>
<br>
<h3 id="78volumetricshadowtechniques">7.8 Volumetric Shadow Techniques（体积阴影技术）</h3>
<p>透明物体会衰减并改变光的颜色。对于某些透明对象集，可以使用与第5.5节中讨论的类似的技术来模拟这些效果。例如，在某些情况下，可以生成第二类阴影图。透明对象将呈现给它，并存储最接近的深度和颜色或alpha覆盖范围。如果接收器未被不透明阴影贴图阻挡，则测试透明度深度图，如果被遮挡，则根据需要检索颜色或覆盖范围。 这个想法让人联想到7.2节中的阴影和光线投影，存储的深度避免了投影到透明物体和光线之间的接收物上。 这些技术不能应用于透明对象本身。</p>
<p>自阴影对于物体（如头发和云）的真实渲染至关重要，它们都是小的或半透明的。单深度阴影贴图不适用于这些情况。Lokovic和Veach首先提出了深阴影贴图（deep shadow maps）的概念，其中每个阴影贴图纹素都存储了光线如何随深度下降的函数。该函数通常由不同深度处的一系列样本近似，每个样本具有不透明度值。地图中包含给定位置深度的两个样本用于查找阴影效果。GPU上的挑战在于有效地生成和计算这些功能。这些算法使用类似的方法，并遇到一些与顺序无关的透明度算法（第5.5节）中发现的类似挑战，例如忠实代表每个函数所需的数据的紧凑存储。</p>
<p>Kim和Neumann是第一个提出基于GPU的方法的人，他们称之为不透明阴影贴图（opacity shadow maps）。 仅存储固定深度集合处生成的不透明物体。Nguyen和Donnelly给出了这种方法的更新版本，产生了如图17.2所示的图像。然而，深度切片都是平行且均匀的，因此需要许多切片来隐藏由于线性插值导致的切片不透明度伪像。 Yuksel和Keyser通过创建更接近模型形状的不透明度贴图来提高效率和质量。这样做可以减少所需的层数，因为每层的计算对最终图像更重要。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C7/17.2.png" alt="Realtime Rendering 4th notes - chapter7"></p>
<center>（图17.2）</center>
<p>为了避免不得不依赖于固定切片设置，提出了更多自适应技术。 Salvi等引入了自适应体积阴影贴图（adaptive volumetric shadow maps），其中每个阴影贴图纹素都存储不透明度和层深度。像素着色器操作用于在光栅化时有损压缩数据流（表面不透明度）。这避免了需要无限量的内存来收集所有样本并在集合中处理它们的问题。该技术类似于深阴影贴图，但压缩步骤在像素着色器中即时完成。将函数表示限制为小的，固定数量的存储的不透明度/深度，使得GPU上的压缩和检索更有效。成本高于简单混合，因为需要读取，更新和回写曲线，并且它取决于用于表示曲线的点数。在这种情况下，该技术还需要支持UAV和ROV功能的最新硬件（3.8节结束）。有关示例，请参见图7.29。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C7/7.29.png" alt="Realtime Rendering 4th notes - chapter7"></p>
<center>（图7.29）使用自适应体积阴影贴图进行头发和烟雾渲染。</center>
<p>自适应体积阴影映射方法用于游戏GRID2中的逼真烟雾渲染，平均成本低于2毫秒/帧。 Fürst等人。 描述并提供用于实现视频游戏的深阴影贴图的代码。它们使用链接列表来存储深度和alpha，并使用指数阴影贴图在点亮和阴影区域之间提供软转换。</p>
<p>阴影算法的探索仍在继续，各种算法和技术的综合变得越来越普遍。 例如，Selgrad等人。研究使用链表存储多个透明样本，并使用带有分散写入的计算着色器来构建地图。他们的工作使用深阴影图概念，以及过滤后的地图和其他元素，这为提供高质量的柔和阴影提供了更通用的解决方案。</p>
<br>
<h3 id="79irregularzbuffershadowsz">7.9 Irregular Z -Buffer Shadows（不规则的Z缓冲阴影）</h3>
<p>由于一些原因，很多阴影贴图方法都很受欢迎。它们的成本是可预测的，并且增加的场景大小时可以很好地扩展，最坏情况与图元数量成线性关系。它们很好地映射到GPU上，因为它们依靠光栅化来定期对灯光的世界观进行采样。 然而，由于这种离散采样，出现问题是因为眼睛看到的位置不与光看到的位置一一对应。 当光对表面的采样频率低于眼睛时，会出现各种混叠问题。 即使采样率相当，也存在偏差问题，因为表面采样的位置与眼睛看到的位置略有不同。</p>
<p>阴影体积提供精确的分析解决方案，因为灯光与曲面的交互会产生一组三角形定义任何给定位置是亮起还是阴影。在GPU上实现算法时，不可预测的成本是一个严重的缺点。近年来探索的改进令人着迷，但尚未在商业应用中拥有“存在证据”。</p>
<p>另一种分析阴影测试方法可能具有长期潜力：射线追踪。 在第11.2.2节中详细描述，基本思想很简单，特别是对于阴影。从接收物位置向光线射出光线。 如果发现任何阻挡光线的物体，则接收物处于阴影中。大多数快速光线跟踪物的代码专用于生成和使用分层数据结构，以最小化每条光线所需的对象测试数量。每帧为动态场景构建和更新这些结构是一个已有数十年历史的主题和持续研究的领域。</p>
<p>另一种方法是使用GPU的光栅化硬件来查看场景，但是不仅仅是z深度，也存储关于光的每个网格单元中的遮挡物的边缘信息。例如，想象在每个阴影贴图纹素处存储与网格单元重叠的三角形列表。这样的列表可以通过保守光栅化生成，其中如果三角形的任何部分与像素重叠，则三角形生成片段，而不仅仅是像素的中心（第23.1.2节）。这种方案的一个问题是通常需要限制每个纹素的数据量，这反过来可能导致确定每个接收器位置的状态的不准确性。鉴于GPU的现代链表原则，每个像素可以存储更多数据。但是，除了物理内存限制之外，在每个texel的列表中存储可变数量的数据的问题是GPU处理可能变得非常低效，因为单个warp可能有一些需要检索和处理许多项的片段线程，其余的线程都空闲，没有工作要做。设计一个着色器以避免由于动态“if”语句和循环导致的线程分歧对性能至关重要。</p>
<p>选择在阴影贴图中存储三角形或其他数据还是测试接收物对于他们的位置需翻转问题，存储接收器位置，然后针对每个三角形测试。这种保存接收者位置的概念，首先由Johnson等人研究。Aila和Laine称之为不规则的z缓冲区（IZB）。该名称有点误导，因为缓冲区本身具有法线，原始阴影贴图的形状。相反，缓冲区的内容是不规则的，因为每个阴影贴图纹素将在其中存储一个或多个接收物的位置，或者可能根本不存在。 见图7.30。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C7/7.30.png" alt="Realtime Rendering 4th notes - chapter7"></p>
<center>（图7.30）不规则的z缓冲区。在左上角，来自眼睛的视图在像素中心处生成一组点。示出了形成立方体面的两个三角形。在右上角，这些点是从灯光的视图中显示的。在左下角，加上了阴影贴图网格。对于每个纹素，生成其网格单元内所有点的列表。在右下角，通过保守地光栅化对红色三角形执行阴影测试。接触到的每个纹理元素，以浅红色显示，其列表中的所有点都针对三角形进行测试以获得光的可见性。（底层光栅图像由Timo Aila和Samuli Laine提供。）</center>
<p>使用Sintorn等人和Wyman等人提出的方法，多次通过算法创建IZB并测试其内容以获得来自光的可见性。首先，从眼睛渲染场景，以找到从眼睛看到的表面的z深度。这些点被转换为灯光的场景视图，并且由光线的平截头体形成了紧密的边界。然后将这些点存储在光的IZB中，每个点放置在其相应纹理元素的列表中。请注意，某些列表可能是空的，即灯光可以看到但没有眼睛看到的表面。将遮挡物保守光栅化到灯的IZB以确定是否隐藏任何点，因此在阴影中。保守光栅化确保即使三角形不覆盖浅纹理像素的中心，也会对它可能重叠的点进行测试。</p>
<p>可见性测试发生在像素着色器中。测试本身可以看作是光线跟踪的一种形式。光线是从图像点到光的位置生成的。 如果一个点在三角形内并且比三角形的平面更远，则它是隐藏的。 一旦所有遮挡物被光栅化，光能见度结果用于遮蔽表面。该测试也称为视锥体跟踪，因为三角形可以被认为是定义视锥体，检查包含在其体积中的点。</p>
<p>仔细编码对于使这种方法与GPU良好协作至关重要。Wyman等人注意到他们的最终版本比最初的原型快两个数量级。这种性能提升一部分来源于直接的算法改进，例如剔除表面法线背离光线的图像点（因此总是不亮）并避免为空纹理像素生成碎片。其他性能提升来自于改进GPU的数据结构，以及通过在每个纹理元素中使用短的，相似长度的点列表来最小化线程差异。图7.30显示了一个低分辨率阴影贴图，其中包含用于说明目的的长列表。理想的是每个列表一个图像点。分辨率越高，列表越短，但也会增加遮挡物生成的需要计算的碎片数量。</p>
<p>从图7.30的左下图可以看出，由于透视效应，地面上的可见点密度在左侧比右侧高得多。 使用级联阴影贴图可以使更多的光照贴图分辨率更接近眼睛，从而有助于降低这些区域中的列表尺寸。</p>
<p>这种方法避免了其他方法的采样和偏差问题，并提供了完美的阴影。出于美学和感知的原因，通常需要柔和的阴影，但是附近的遮挡物可能存在偏差问题，例如Peter Panning。Story和Wyman探索混合阴影技术。核心思想是使用遮挡距离来混合IZB和PCSS阴影，当遮挡物靠近时使用硬阴影结果，当更远时使用柔和阴影。见图7.31。阴影质量通常对附近的对象最重要，因此通过仅在选定的子集上使用此技术可以降低IZB成本。 该解决方案已成功用于video game。本章从这样的图像开始，如图7.2所示。</p>
<br>
<h3 id="710otherapplications">7.10 Other Applications（其他应用）</h3>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C7/7.31.png" alt="Realtime Rendering 4th notes - chapter7"></p>
<center>(图7.31)在左侧，PCF为所有对象提供均匀柔化的阴影。在中间，PCSS通过距离遮挡物的距离来软化阴影，但是与箱子左角重叠的树枝阴影会产生伪影。在右侧，来自IZB的锐利阴影与来自PCSS的柔和混合产生了改善的结果。（图片由育碧提供）</center>
<p>将阴影贴图视为定义一定体积的空间，将光与暗分离，也可以帮助确定要遮挡物体的哪些部分。Gollent描述了CD Projekt的地形阴影系统如何计算每个区域仍然被遮挡的最大高度，然后可以用来不仅遮蔽地形，还遮挡场景中的树木和其他元素。为了找到每个高度，为太阳渲染可见区域的阴影贴图。然后检查每个地形高度场位置以获得来自太阳的可见性。如果在阴影中，通过将世界高度增加固定步长直到太阳进入视野然后执行二分搜索来估计太阳首次可见的高度。换句话说，我们沿着垂直线行进并迭代以缩小与阴影贴图的表面相交的位置，该表面将光与暗分开。插入相邻高度以在任何位置找到该遮挡高度。图7.32中可以看到用于地形高度场软阴影的这种技术示例。</p>
<p>值得一提的最后一种方法是渲染屏幕空间阴影（screen-space shadows）。由于分辨率有限，阴影贴图通常无法在小特征上产生准确的遮挡。在渲染人脸时，尤其严重，因为我们特别容易注意到。例如，发光的鼻孔（如果没有打算）看起来很不和谐。虽然使用更高分辨率的阴影贴图或仅针对感兴趣区域的单独阴影贴图可以提供帮助，但另一种可能性是利用已存在的数据。在大多数现代渲染引擎中，在渲染过程中可以使用来自相机视角的深度缓冲，来自较早的预处理。存储在其中的数据可以视为高度场。通过对该深度缓冲器进行迭代采样，我们可以执行射线行进过程（第6.8.1节）并检查朝向光的方向是否未被遮挡。虽然价格昂贵，因为它涉及重复采样深度缓冲，但这样做可以为切割场景中的特写镜头提供高质量的结果，其中花费额外几毫秒通常是合理的。该方法由Sousa等人提出。并且今天在许多游戏引擎中使用。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C7/7.32.png" alt="Realtime Rendering 4th notes - chapter7"></p>
<center>(图7.32)地形点亮了第一次看到太阳的高度，为每个高度位置计算。请注意阴影边缘的树木是如何正确遮蔽的。</center>
<p>☆总结一下这一章，阴影贴图是迄今为止用于投射到任意表面形状上的阴影的最常用算法。级联阴影贴图可在大面积（例如室外场景）中投射阴影时提高采样质量。通过SDSM找到近平面的良好最大距离可以进一步提高精度。百分比近似滤波（PCF）为阴影提供了一些柔和度，百分比近似软阴影（PCSS）及其变体提供了接触处的硬化，IZS可以提供精确的硬阴影。过滤后的阴影贴图可提供快速的软阴影计算，并且当遮挡物远离接收物时也能正常工作，就像地形一样。最后，屏幕空间技术可以用于额外的精度，但成本显着。</p>
<p>在本章中，我们将重点放在当前应用程序中使用的关键概念和技术上。每个都有自己的优势，选择哪个取决于世界大小，组成（静态内容与动画），材质类型（不透明，透明，头发或烟雾），灯光的数量和类型（静态或动态；本地或远程；点光，聚光灯还是区域光），以及诸如底层纹理是否可以隐藏失真。GPU功能不断发展和完善，因此我们期望在未来几年内继续看到能够很好地映射的新硬件算法。例如，第19.10.1节中描述的稀疏纹理技术已应用于阴影贴图存储以提高分辨率。在一种创造性的方法中，Sintorn，Kämpe和其他人探索了将光的二维阴影图转换为三维体素集（参见13.10节）的想法。使用体素的一个优点是它可以被分类为亮的还是在阴影中的，因此需要最少的存储空间。高度压缩的稀疏体素八叉树表示存储大量灯光和静态遮挡物的阴影。 Scandolo等将他们的压缩技术与使用双阴影贴图的基于区间的方案相结合，提供更高的压缩率。 Kasyan使用体素锥体跟踪（cone-traced shadows，第13.10节）从区域光源生成柔和阴影。有关示例，请参见图7.33。更多锥形跟踪阴影显示在第585页的图13.33中。</p>
<h3 id="furtherreadingandresources">Further Reading and Resources</h3>
<p>我们在本章中的重点是基本原理以及影子算法需要什么样的质量和可预测的质量和性能才能对交互式渲染有用。 我们已经避免对这个渲染领域的研究进行详尽的分类，因为有两个文本可以解决这个问题。 Eisemann等人的“实时阴影”一书。 直接关注交互式渲染技术，讨论各种算法及其优势和成本。SIGGRAPH 2012课程提供了本书的摘录，同时还添加了对新作品的参考。 他们的SIGGRAPH 2013课程的演示文稿可在网站www.realtimeshadows.com上找到。Woo和Poulin的书“Shadow Algorithms Data Miner”概述了用于交互式和批量渲染的各种阴影算法。 这两本书都提供了该领域数百篇研究论文的参考。</p>
<p>Tuft的文章对常用的阴影贴图技术和所涉及的问题进行了很好的概述。 Bjørge提供了一系列适用于移动设备的流行阴影算法，以及比较各种算法的图像。Lilley的演示文稿对实际阴影算法进行了全面而广泛的概述，重点是GIS系统的地形渲染。Pettineo和Castaño的博客文章对于他们的实用技巧和解决方案以及演示代码库特别有价值。见Scherzer等 有关专门针对硬阴影的工作的简短摘要。 Hasenfratz等人对软阴影算法的调查已过时，但涵盖了广泛的早期工作。</p>
<p>(chapter 7 end.)</p>
]]></content:encoded></item><item><title><![CDATA[Realtime Rendering 4th notes - chapter6]]></title><description><![CDATA[<p><a href="https://www.xx-ma.com/">(转载请注明出处，谢谢咯)</a></p>
<blockquote>
<p>&quot;All it takes is for the rendered image to look right.&quot; — Jim Blinn</p>
</blockquote>
<p>在计算机图形学中，纹理化是一个过程，它使用一些图像，函数或其他数据源，选取一个表面，并在每个位置修改其外观。例如在一个由两个三角形组成的矩形上，贴上砖块图样的贴图，使得矩形看起来像砖墙的外观。</p>
<p>然而，由于缺乏几何形状，贴上纹理的砖墙并不令人信服。如，砂浆应该是无光泽的，砖是有光泽的。可以使用粗糙度贴图来表示这些信息，作为第二张贴图使用在表面上。</p>
<p>观察者现在会看到有光泽的砖和没有光泽的砂浆，但每个砖面都是平坦的。通过使用凹凸贴图可以改变砖的法线，似的他们在渲染时看起来起伏。</p>
<p>但砖块应该在砂浆上投下阴影，且在砂浆上面伸出来，在视野中遮挡后面。视差贴图能够通过修改模型三角的高度来取代表面。</p>
<h3 id="61thetexturingpipeline">6.1 The Texturing Pipeline（贴图管线）</h3>
<p>贴图是一种有效的改变表面材质或者模型本身的技术。可以通过单一像素考虑贴图的作用。</p>]]></description><link>http://xx-ma.com/rtr4-6/</link><guid isPermaLink="false">5bdc14b6b67cfd1e0bec032e</guid><category><![CDATA[notes]]></category><category><![CDATA[graphics]]></category><category><![CDATA[realtime-rendering]]></category><category><![CDATA[code]]></category><dc:creator><![CDATA[Marianax]]></dc:creator><pubDate>Fri, 02 Nov 2018 09:28:48 GMT</pubDate><media:content url="http://xx-ma.com/content/images/2018/11/rawpixel-666934-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<img src="http://xx-ma.com/content/images/2018/11/rawpixel-666934-unsplash.jpg" alt="Realtime Rendering 4th notes - chapter6"><p><a href="https://www.xx-ma.com/">(转载请注明出处，谢谢咯)</a></p>
<blockquote>
<p>&quot;All it takes is for the rendered image to look right.&quot; — Jim Blinn</p>
</blockquote>
<p>在计算机图形学中，纹理化是一个过程，它使用一些图像，函数或其他数据源，选取一个表面，并在每个位置修改其外观。例如在一个由两个三角形组成的矩形上，贴上砖块图样的贴图，使得矩形看起来像砖墙的外观。</p>
<p>然而，由于缺乏几何形状，贴上纹理的砖墙并不令人信服。如，砂浆应该是无光泽的，砖是有光泽的。可以使用粗糙度贴图来表示这些信息，作为第二张贴图使用在表面上。</p>
<p>观察者现在会看到有光泽的砖和没有光泽的砂浆，但每个砖面都是平坦的。通过使用凹凸贴图可以改变砖的法线，似的他们在渲染时看起来起伏。</p>
<p>但砖块应该在砂浆上投下阴影，且在砂浆上面伸出来，在视野中遮挡后面。视差贴图能够通过修改模型三角的高度来取代表面。</p>
<h3 id="61thetexturingpipeline">6.1 The Texturing Pipeline（贴图管线）</h3>
<p>贴图是一种有效的改变表面材质或者模型本身的技术。可以通过单一像素考虑贴图的作用。着色的计算时吧材质颜色和光照以及其他因素一起考虑的结果。纹理通过修改着色方程中的值来改变着色结果。这些值通常基于表面上的位置。对于砖墙的例子，表面上的点被对应替换为贴图颜色。贴图纹理中的像素通常被称为纹素（texel，用来与屏幕像素pixel区分）。</p>
<p>贴图的过程可以用流水线来描述。</p>
<p>空间中的位置是贴图过程的起点，这个位置可以在世界空间中，但更常见于模型空间。当模型移动时，贴图随之移动。使用Kershaw的话说，之后一个投影函数应用于空间中该点，产生一系列称作纹理坐标（texture coordinates）的值，用于访问纹理。这个过程称作mapping（映射）。</p>
<p>在使用这些值访问纹理之前，会有一个或者若干映射函数（corresponder function）用于把贴图坐标转换到纹理空间（texture space）。这些纹理空间用于从纹理获取值。如它们可能是数组的index从纹理中返回一个像素。然后，可以通过value transform function对检索到的值进行转换，然后使用转换后的值修改表面的属性，如材质或者法线。下图展示了单个纹理在应用时执行的过程，管线之所以复杂是因为每个步骤都要为用户提供相应的控制，并非所有步骤都需要始终激活：</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/6.2.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<p>使用这个管线，当三角形使用了砖墙贴图，表面生成了一个样本：</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/6.3.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<p>首先找到$(x,y,z)$坐标，在这里是$(-2.3,7.1,88.2)$。然后对该位置应用投影函数，使坐标转换到二维空间，parameter space（也就是uv空间）。这里的投影函数相当于正交投影，就像将砖墙图像照射到三角形表面的投影仪。转换后的结果是0到1的值对。这里是$(0.32,0.29)$。纹理坐标用于查找图像在此位置的颜色。在本例中，砖纹理的分辨率是256×256，所以用$(u,v)\times256=(91.92,74.24)$，丢弃小数部分，查找到砖纹理中对应$(81,74)$的点的颜色为$(0.9,0.8,0.7)$。纹理颜色属于sRGB颜色空间，因此如果要在着色器方程中使用该颜色，需要转换为线性空间，得出$(0.787,0.604,0.448)$。</p>
<h4 id="611theprojectorfunction">6.1.1 The Projector Function（投影函数）</h4>
<p>纹理过程的第一步是获取表面位置投影到uv空间。通常，建模软件可以允许艺术家对每个顶点定义uv坐标。这通常是通过投影函数（projector function）或网格展开算法（mesh unwrapping algorithms）实现的。建模程序中常用的函数包括球面，柱面和平面投影。</p>
<p>其他输入可用于投影功能。如，表面法线可用于选择六个平面投影方向用于平面。匹配纹理的问题常出现在接缝处。Geiss讨论了一种混合它们使用的技术。Tarini等提出了polycube maps，一个模型映射到多个cube projection上面。</p>
<p>其他projector functions根本就不是投影，而是作为表面创建和曲面细分的隐含部分。例如，参数化曲面具有一组作为定义自然存在的uv。纹理坐标也可以由各种不同类型的参数生成，如视图方向，表面温度等等。投影函数的目标是生成纹理坐标，基于位置的函数只是一种方法。</p>
<p>非交互式渲染器通常将这些投影函数称为渲染过程的一部分。单个投影函数有可能足以满足整个模型，但有时需要细分模型应用各种类型的投影：</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/6.5.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<p>在实时渲染中，projector functions 常用于建模阶段，投影的结果存储在顶点中。不过有时在vertex shader或者pixel shader中应用投影函数是有好处的。这样可以提高精度，并有助于实现各种效果，动画。有些方法拥有自己专用的projector functions，如环境贴图。</p>
<ul>
<li>球形投影将点投射到以某个点为中心的假想球体上，此投影与Blinn和Newell的环境映射方案相同。</li>
<li>圆柱投影的u方向纹理坐标与球面投影相同，v方向沿着圆柱轴向。对具有自然轴的物体如旋转表面很有用。当曲面几乎垂直于圆柱轴时会发生扭曲。</li>
<li>平面投影沿着一个方向平行投射。它使用正交投影。应用贴花时很有用。</li>
</ul>
<p>对于平面上的扭曲，艺术家需要手动拆分模型为接近的形状。有一些工具使用unwrapping减少这种扭曲，或生成一组接近最佳的平面投影，等等，以辅助这一过程。目标是为每个多边形赋予更均匀的纹理区域，同事保持更多的网格链接。连接性很重要，因为分离的边缘会在采样时产生失真。unwarapping 的过程是一个非常重要的研究方向，称为mesh parameterization，下图展示了上图的uv展开效果：</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/6.6.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<p>纹理坐标有时是一个三维体积。在这种情况下，纹理坐标表示为$(u,v,w)$，其中w是沿投影方向的深度。还有些系统使用四个坐标，指定为$(s,t,r,q)$，q用作齐次坐标中的第四个值。它的作用类似于电影或者幻灯片投影机，投影纹理的大小随着距离而增加。例如，投射一个带图案的（使用图案遮光罩的）聚光灯到舞台或者表面上。</p>
<p>另一种重要的纹理坐标空间类型是方向性的，其中空间中的每个点都由一个输入的方向向量访问。这种空间的可视化的一种方法是使用在单位球面上的点，每个点处的法线表示用于访问该位置的纹理方向。使用方向参数化的最常见的纹理类型是立方体贴图。</p>
<p>还有就是一维纹理图像也是有用的。比如地形模型上，着色可以通过高度来决定，例如，低的地方是绿色，高的山峰是白色。线条也可以贴图，如渲染雨的时候，使用很多带透明度的贴过图的长线。还有转换值的时候，可以把一维纹理作为查找表。</p>
<p>由于可以将多个纹理应用于表面，因此可能需要定义多组纹理坐标。这些纹理坐标在表面上插值用于检索纹理，然而在插值之前，这些纹理坐标由映射函数（corresponder function）变换。</p>
<h4 id="612thecorresponderfunction">6.1.2 The Corresponder Function（映射函数）</h4>
<p>映射函数（corresponder function）将纹理坐标转换为纹理空间位置。它们可以灵活地将纹理应用于表面。映射函数的一个例子是使用API来选择纹理的一部分进行显示；只有这部分图像用于后续操作。</p>
<p>另一种类型的映射函数是矩阵变换，其可以应用于顶点或像素着色器。 这使得能够在表面上让纹理平移，旋转，缩放，剪切或投射。令人惊讶的是，纹理变换的顺序与人们期望的顺序相反。 这是因为纹理变换实际上会影响确定图像所在位置的空间变换。图像本身不是被转换的对象，定义图像位置的空间改变了。</p>
<p>另一类映射函数能控制图像的应用方式。我们知道uv在[0, 1]范围。 但是在这个范围之外会发生什么？这由映射函数确定。 在OpenGL中，这种类型的映射函数称为“wrap模式”（wrapping mode），在DirectX中，它被称为“纹理寻址模式”（texture addressing mode）。这种类型的常见映射函数有：</p>
<ul>
<li>wrap（DirectX），repeat（OpenGL）或tile：图像在表面重复；在算法上，纹理坐标的整数部分被删除。常为默认设置。</li>
<li>mirror：图像在表面重复，但是每次重复一次要翻转一次。正常反转正常...</li>
<li>clamp或者clamp to edge：重复纹理边缘。当双线性插值发生在纹理边缘附近时，可以避免纹理发生意外的取样。</li>
<li>border（DirectX）或clamp to border（OpenGL）：纹理坐标在[0, 1]使用单独定义的边框颜色进行渲染。可以很好地将贴花渲染到单色表面上，例如，因为纹理边缘将与边框颜色平滑地融合。</li>
</ul>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/6.7.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<p>在DirectX中，还有一个镜像一次模式，它沿着纹理坐标的0值镜像纹理一次，然后clamp。对于对称贴花很有用。</p>
<p>重复平铺纹理可以像场景中添加更多的视觉细节，但是重复多次后眼睛会识别出重复图案。避免这种问题的常见方法是将纹理与非平铺的纹理组合。这种方法可以扩展，如Andersson 描述的商业地形渲染系统。 在该系统中，基于地形类型，高度，坡度和其他因素组合多个纹理。纹理图像也与几何模型（例如灌木和岩石）放置在场景中的位置相关联。</p>
<p>避免周期性的另一个选择是使用着色器程序来实现随机重组纹理图案的专用映射函数。 Wang tiles就是这种方法的一个例子。Wang tiles set是一组可以匹配边缘的方形tile。 在纹理化过程中随机选择tile。Lefebvre和Neyret提出了一个类似的映射函数，使用dependent texture reads和表格避免图案重复。</p>
<h4 id="613texturevalues">6.1.3 Texture Values（纹理值）</h4>
<p>在使用映射函数产生纹理空间坐标后，坐标用于获得纹理值。 对于图像纹理，这是通过访问纹理从图像中检索纹理元素信息来完成的。 图像纹理构成了实时工作中绝大部分的纹理使用，但也可以使用程序纹理（procedural texture）。 在程序纹理的情况下，从纹理空间位置获得纹理值的过程不涉及存储器查找，而是涉及函数的计算。</p>
<p>从纹理中拿到的值可以先被变换再进行使用。可以在shader中执行这些变换。一个常见的示例是将数据从无符号范围$(0, 1)$映射到$(-1.0,1.0)$，用于对存储在颜色贴图中的法线进行着色。</p>
<br>
<h3 id="62imagetexturing">6.2 Image Texturing（图像贴图）</h3>
<p>在本节中，我们特别关注快速采样和过滤纹理图像的方法。5.4.2节讨论了锯齿问题，特别是在渲染对象边缘方面。 纹理也可能存在采样问题，但它们出现在正在渲染的三角形的内部。</p>
<p>使用例如<code>texture2D</code>传入坐标值来访问纹理。GPU负责将uv转换到纹素坐标。不同API中的纹理坐标系有两个差异。DirectX的左上角是$(0,0)$，右下角是$(1,1)$。顶行是文件中的第一行。在OpenGL中，纹素$(0,0)$位于左下方，y轴和DirectX相反。纹素有整数坐标，但我们经常想要访问纹素之间的位置并在它们之间进行混合。这提出了像素中心的浮坐标是什么的问题。 Heckbert 讨论了如何使用两个系统：截断（truncating）和舍入（rounding）。DirectX 9将每个中心定义为$(0,0)$，它使用rounding。这个系统有些令人困惑，因为左上角像素的左上角，在DirectX的原点，值为$(0.5,0.5)$。DirectX 10更改为了和OpenGL系统一样，其中纹素的中心具有小数值$(0.5,0.5)$：truncating，或更准确地说，flooring，其中小数被丢弃。flooring是一个更自然的系统，可以很好地映射到语言，如在像素$(5,9)$中，为u坐标定义了5.0到6.0的范围，为v坐标定义了9.0到10.0的范围。</p>
<p>有个值得解释的术语<em>dependent texture read</em>，它有两个定义。第一种尤其适用于移动设备：当通过类似<code>texture2D</code>访问纹理时，只要像素shader计算纹理坐标而不是使用从顶点着色器传入的未修改的坐标，就会触发dependent texture read。注意，这包括对输入纹理坐标的任何更改，甚至是交换uv这样的简单操作。在那些不支持OpenGL ES 3.0的较旧的移动GPU上，当着色器没有执行dependent texture read时，有更高的运行效率，因为纹素数据接下来可以被预取（prefatch）。对于早期的桌面GPU，这个术语的另一个较旧的定义尤为重要。在这里，当一个纹理的坐标取决于某些先前纹理值的结果时，触发dependent texture read。例如，一个纹理可能会更改着色法线，这反过来会更改用于访问cube map的坐标。早期GPU上的此类功能有限甚至不存在。今天，这些reads可能会对性能产生影响，具体取决于批量计算的像素数量以及其他因素。（具体参照23.8节）</p>
<p>GPU中使用的纹理图像大小通常为$2^m×2^n$个纹素，其中m和n是非负整数。 这些被称为二次幂（power-of-two ,POT）纹理。 现代GPU可以处理任意大小的非二次幂（non-power-of-two , NPOT）纹理，这允许将生成的图像视为纹理。 但是，一些较旧的移动GPU可能不支持对NPOT纹理的mipmapping操作（第6.2.2节）。 图形加速器对纹理大小有不同的上限。 如，DirectX 12最多允许$16384^2$的纹素。</p>
<p>假设我们有一个256×256纹素大小的纹理，我们希望将它用作一个正方形的纹理。只要屏幕上的投影方块大小与纹理大致相同，方块上的纹理看起来几乎就像原始图像一样。但是如果投影的方块覆盖了原始图像所包含的十倍像素（称为放大，magnification），或者如果投影的方块仅覆盖屏幕的一小部分（缩小，minification），会发生什么？答案是，它取决于您决定使用哪种采样和过滤方法来处理这两种不同的情况。<br>
本章讨论的图像采样和过滤方法适用于从每个纹理读取的值。 然而，期望的结果是防止最终渲染图像中的混叠，理论上需要对最终像素颜色进行采样和滤波。 这里的区别在于将输入过滤到着色方程或过滤其输出。 只要输入和输出是线性相关的（对于诸如颜色的输入都是如此），则过滤单个纹理值等同于过滤最终颜色。但是，存储在纹理中的许多着色器输入值（例如曲面法线和粗糙度值）与输出具有非线性关系。标准纹理过滤方法可能无法很好地处理这些纹理，从而导致锯齿。 第9.13节讨论了过滤此类纹理的改进方法。</p>
<h4 id="621magnification">6.2.1 Magnification（放大）</h4>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/6.8.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<p>一个尺寸为48×48纹素的纹理在贴在正方形上，贴图尺寸太小，底层图形系统必须将它放大处理。最常用的放大滤波技术是最近邻（nearest neighbor，实际使用的滤波器是box filter）和双线性插值（bilinear interpolation）。 还有立方卷积（cubic convolution），它使用4×4或5×5像素阵列的加权和。可以实现更高质量的放大。尽管目前通常不能获得对立方卷积（也称为双三次插值，bicubic interpolation）的本机硬件支持，但它可以在着色器程序中执行。</p>
<p>上图左侧使用的是最邻近方法。这种放大技术的特征是各个纹素可能变得明显。这种效应称为像素化（pixelation ），因为该方法在放大时从离每个像素中心最近的纹理像素值取，导致块状外观。 虽然这种方法的质量有时很差，但每个像素只需要获取一个纹素。</p>
<p>在该图的中间图像中，使用双线性插值（有时称为线性插值）。对于每个像素，这种滤波找到四个相邻纹素并在二维中线性插值以找到该像素的混合值。结果是模糊的，使用最近邻法的大部分锯齿都消失了。 作为实验，尝试在眯眼时查看左图，因为这与低通滤波器具有大致相同的效果，并且可以更多地显示脸部。</p>
<p>回到砖纹理示例，我们不丢弃小数部分，得到$(p_u,p_v)=(81.92,74.24)$。使用OpenGL的左下原点纹素坐标系，因为它匹配标准的笛卡尔坐标系。我们的目标是在四个最接近的纹素之间进行插值，使用其纹理像素中心定义一个纹理像素有大小的坐标系。为了找到最近的四个像素，我们从样本位置减去像素中心坐标$(0.5,0.5)$，给出$(81.92,74.24)$。 丢弃分数，四个最接近的像素范围从$(x,y)=(81,73)$到$(x + 1, y + 1)=(82,74)$。 对于我们的例子，小数部分$(0.42,0.74)$是样本相对于由四个纹素中心形成的坐标系的位置。 我们将此位置表示为$(u', v')$。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/6.9.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<p>将纹理访问函数定义为$t(x,y)$，其中x和y是整数，并返回纹素的颜色。任何位置$(u',v')$的双线性插值颜色可以计算为两步过程。首先，底部纹素$t(x,y)$和$t(x+1,y)$水平插值（使用u'），类似地，对于最顶部的两个纹素，$t(x,y+1)$和$t(x+1,y+1)$。对于底部纹素，我们得到$(1-u')t(x,y)+u't(x+1,y)$（图中底部绿色圆），对于顶部，$(1-u')t(x,y+1)+u't(x+1,y+1)$（图中顶部绿色圆）。 然后垂直插入这两个值（使用v'），则$(p_u,p_v)$处的双线性插值颜色$\boldsymbol{b}$是</p>
<p>$$<br>
\begin{align}<br>
\boldsymbol{b}(p_u,p_v)=&amp;(1-v')((1-u')\boldsymbol{t}(x,y)+u'\boldsymbol{t}(x+1,y))\\<br>
&amp;+v'((1-u')\boldsymbol{t}(x,y+1)+u'\boldsymbol{t}(x+1,y+1))\\<br>
=&amp;(1-u')(1-v')\boldsymbol{t}(x,y)+u'(1-v')\boldsymbol{t}(x+1,y)\\<br>
&amp;+(1-u')v'\boldsymbol{t}(x,y+1)+u'v'\boldsymbol{t}(x+1,y+1)<br>
\end{align}\tag{6.1}<br>
$$</p>
<p>直观地说，靠近我们样本位置的纹素将更多地影响其最终值。这确实是我们在这个等式中看到的。 右上角纹素$(x+1,y+1)$具有$u'v'$的影响。注意对称性：右上角的影响等于左下角和样本点形成的矩形区域。 回到我们的例子，这意味着从这个纹素中检索的值将乘以$0.42\times0.74$，$0.3108$。 从该纹素顺时针方向看，其他乘数为$0.42\times0.26$,$0.5\times0.26$和$0.58\times0.74$，这些权重的所有四个总和为1.0。</p>
<p>伴随放大会造成模糊，常见解决方案是使用细节纹理。 这些纹理代表精细的表面细节，从手机上的划痕到地形上的灌木丛。 这种细节作为单独的纹理覆盖在放大的纹理上，以不同的比例。 细节纹理的高频重复图案与低分辨率放大的纹理相结合，具有类似于使用单个高分辨率纹理的视觉效果。</p>
<p>双线性插值在两个方向上线性插值。 但是，不需要线性插值。 假设纹理由棋盘图案中的黑白像素组成。 使用双线性插值可在纹理上提供不同的灰度样本。 通过重新映射使得，例如，所有低于0.4的灰色都是黑色，所有高于0.6的灰色都是白色，而中间的灰色被拉伸以填充间隙，纹理看起来更像棋盘，同时也给出了纹素之间的一些过度：</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/6.10.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<p>使用更高分辨率的纹理会产生类似的效果。例如，假设每个棋盘方格由4×4纹素组成，而不是1×1。围绕每个棋盘的中心，插值颜色将是完全黑色或白色。</p>
<p>在图6.8的右侧，使用了双三次滤波器，并且很大程度上消除了剩余的块效应。 应该注意的是，双三次滤波器比双线性滤波器更昂贵。 但是，许多高阶滤波器可以表示为重复线性插值（另见第17.1.1节）。 因此，纹理单元中用于线性插值的GPU硬件可以通过多次查找来利用。</p>
<p>如果认为双立方滤波器太昂贵，Quílez提出了一种使用平滑曲线在一组2×2纹素之间进行插值的简单技术。 我们首先描述曲线然后描述技术。两条常用曲线是平滑曲线和五次曲线：</p>
<p>$$<br>
\underbrace{s(x)=x^2(3-2x)}_{smoothstep}\quad and \quad \underbrace{q(x)=x^3(6x^2-15x+10)}_{quintic}\tag{6.2}<br>
$$</p>
<p>这些对于您希望从一个值平滑插值到另一个值的许多情况非常有用。 smoothstep曲线具有$s'(0)=s'(1)=0$的特性，并且在0和1之间平滑。五次曲线具有相同的属性，但$q''(0)= q''(1)= 0$， 即，二阶导数在曲线的开始和结束处也是0：</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/6.11.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<p>该技术首先计算，首先将样本乘以纹理维度并加0.5。保留整数部分以供稍后使用，并将分数存储在u'和v'中，其范围为[0, 1]。 然后将$(u',v')$变换为$(t_u,t_v)=(q(u'),q(v'))$，仍然属于$[0,1]$。最后，减去0.5并重新加入整数部分；然后将得到的u坐标除以纹理宽度，并且类似地处理v。此时，新纹理坐标与GPU提供的双线性插值查找一起使用。 请注意，此方法将在每个纹素处给出平稳，这意味着如果纹素位于RGB空间中的平面上，那么这种类型的插值将提供平滑但仍然是阶梯状的外观，但可能并非总是期望这样。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/6.12.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<p>上图表示放大一维纹理的四种不同方法。 橙色圆圈表示纹素的中心以及纹素值（高度）。 从左到右依次为：最近邻，线性，使用每对相邻纹素之间的五次曲线，以及使用三次插值。</p>
<h4 id="622minification">6.2.2 Minification（缩小）</h4>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/6.13.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<p>当纹理最小化时，几个纹素可能会覆盖像素的单元格，如图所示。 要为每个像素获取正确的颜色值，应该集成影响像素的纹素的效果。 然而，很难准确地确定特定像素附近的所有纹素的确切影响，并且实际上不可能实时地完成。（每个像素单元包含多个棋盘格）</p>
<p>由于这种限制，GPU上使用了几种不同的方法。一种方法是使用nearest neighbor，和相应的放大滤波器完全相同，即，它选择在像素单元的中心可见的纹素。此过滤器可能会导致严重的失真问题。接近地平线的地方出现失真，因为只有影响像素的许多纹素中的一个被选择来表示表面。当表面相对于观察者移动时，这种伪影甚至更明显，并且是所谓的temporal aliasing的一种表现。</p>
<p>另一种通常可用的滤波器是双线性插值，还是与放大滤波器完全一样。 此过滤器仅略微优于nearest neighbor的缩小方法。 它混合了四个纹素而不是仅使用一个，但是当一个像素受到超过四个纹素的影响时，过滤器很快就会失败并产生锯齿。</p>
<p>更好的解决方案是可能的。如第5.4.1节所述，可以通过采样和滤波技术解决失真问题。纹理的信号频率取决于其纹理像素在屏幕上的间隔距离。由于奈奎斯特极限，我们需要确保纹理的信号频率不大于采样频率的一半。 例如，假设图像由交替的黑线和白线组成，每条间隔一个纹素。 然后波长为两个纹素宽（从黑线到黑线），因此频率为1/2。为了在屏幕上正确显示该纹理，频率必须至少为2×1/2，即至少一个像素/纹素。因此，对于一般的纹理，每个像素最多应该只有一个纹素，以避免混叠。</p>
<p>为了实现这一目标，要么像素的采样频率增加，要么纹理频率降低。前一章中讨论的抗锯齿方法提供了增加像素采样率的方法。 然而，这些仅使采样频率有限地增加。 为了更全面地解决这个问题，已经开发了各种纹理缩小算法。</p>
<p>所有纹理抗锯齿算法背后的基本思想都是相同的：预处理纹理并创建数据结构，这将有助于快速计算一组纹素在一个像素中的近似。对于实时工作，这些算法具有使用固定数量的时间和资源来执行的特征。以这种方式，每个像素拍摄固定数量的样本并组合了（可能大量的）纹素的效果。</p>
<h5 id="mipmapping">Mipmapping</h5>
<p>最受欢迎的纹理抗锯齿方法称为mipmapping。它以某种形式在现在生产的所有图形加速器上实现。 “Mip”代表“multum in parvo”，拉丁语，代表“很小的地方有很多东西”：对于将原始纹理重复过滤为较小图像的过程而言，这是一个不错的名字。</p>
<p>当使用mipmapping minimization filter时，在实际渲染发生之前，使用一组小号纹理来扩充原始纹理。纹理（原始纹理属于level zero）被下采样到原始区域的四分之一，每个新纹素值通常被计算为原始纹理中四个相邻纹素的平均值。新的，level-one纹理有时被称为原始纹理的子纹理。递归地执行缩小，直到纹理的一个或两个维度等于一个纹素。 如图，整个图像集通常称为mipmap chain：</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/6.15.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<p>生成高质量mipmap的两个重要因素是良好的滤波和伽马校正。生成mipmap级别的常用方法是采用每组2×2的纹素并对其进行平均以获得mip纹素值。 使用的过滤器是盒式过滤器。 这可能导致质量差，因为它具有不必要地模糊低频的效果，同时保持一些导致失真的高频。最好使用Gaussian，Lanczos，Kaiser或类似的滤波器；这些滤波器都有免费的源代码，且一些GPU支持的API可以提供更好的过滤。在纹理的边缘部分，需要注意过滤期间是重复纹理还是简单拷贝。</p>
<p>对于在非线性空间中编码的纹理（例如大多数颜色纹理），在过滤时忽略伽马校正，这将影响纹理的感知亮度。 当您离物体越来越远并且使用未经校正的mipmaps时，物体整体看起来会更暗，对比度和细节也会受到影响。因此，将这些纹理从sRGB转换为线性空间是非常重要的，在该空间中执行所有mipmap过滤，并将最终结果转换回sRGB颜色空间进行存储。大多数API都支持sRGB纹理，因此将在线性空间中正确生成mipmap并将结果存储在sRGB中。 访问sRGB纹理时，首先将它们的值转换为线性空间，以便正确执行放大和缩小。</p>
<p>如前所述，一些纹理与最终的着色颜色有着根本的非线性关系。这通常会导致过滤问题，由于过滤了数百或数千个像素，因此mipmap生成对此问题特别敏感。通常需要专门的mipmap生成方法以获得最佳结果。这些方法详见9.13节。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/6.16.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<p>纹理化时访问此结构的过程很简单。屏幕像素包围纹理本身的一块区域。当像素的区域投影到纹理上时，它包含一个或多个纹素。使用像素的单元格边界来演示并不严格正确，但这里使用它来简化演示过程。单元格外的纹理可以影响像素的颜色。目标是大致确定纹理对像素的影响程度。有两种常用的计算方法用于计算$d$（OpenGL称为$\lambda$，也称为细节的纹理级别，texture level of detail）。一种是使用由像素单元形成的四边形的较长边缘来近似像素的覆盖范围；另一种方法是用四个微分的最大绝对值$\partial{u}/\partial{x}$，$\partial{v}/\partial{x}$，$\partial{u}/\partial{y}$和$\partial{v}/\partial{y}$。每个微分表示纹理坐标相对于屏幕轴的变化量。例如，$\partial{u}/\partial{x}$是一个像素沿x屏幕轴的u纹理值的变化量。有关这些方程的更多信息，请参阅Williams的原始文章或Flavell或Pharr的文章。麦考马克等人讨论通过最大绝对值方法引入混叠，并且提出了一个替代公式。Ewins等人，分析了几种质量相当的算法的硬件成本。</p>
<p>使用Shader Model 3.0或更高版本的像素着色器程序可以使用这些渐变值。由于它们基于相邻像素中的值之间的差异，因此在受动态流控制影响的像素着色器中无法访问它们。对于要在这样的部分中执行的纹理读取（例如，在循环内部），必须更早地计算导数。请注意，由于顶点着色器无法访问渐变信息，因此需要在顶点着色器本身中计算渐变或细节级别，并在使用顶点纹理时将其提供给GPU。</p>
<p>计算坐标$d$的目的是确定沿着mipmap的金字塔轴采样的位置。目标是像素到纹素比率至少为$1:1$以实现奈奎斯特率。这里的重要原则是当像素单元包括更多纹素和$d$增加时，访问纹理的较小的模糊版本。$(u,v,d)$三元组用于访问mipmap。值$d$类似于纹理级别，但是$d$不是整数值，而是具有级别之间距离的小数值。上面的纹理级别和d位置下面的级别被采样。$(u,v)$位置用于从这两个纹理级别中的每一个中检索双线性插值样本。 然后，根据从每个纹理级别到$d$的距离，对得到的样本进行线性插值。整个过程称为三线性插值(trilinear interpolation)，并按像素执行。</p>
<p>用户控制可以通过细节偏差水平（level of detail bias，LOD偏差）控制d坐标。这是一个要添加到d的值，因此它会影响纹理的清晰度。如果我们进一步向上移动金字塔（增加d），纹理将看起来更模糊。 任何给定纹理的良好LOD偏差将随图像类型和使用方式而变化。例如，开始时有些模糊的图像可能使用负偏差，而用于纹理化的不良过滤（混叠）合成图像可能使用正偏差。可以为整个纹理或像素着色器中的每个像素指定偏差。为了更精细的控制，用户可以提供用于计算它的d坐标或导数。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/6.14.2.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<p>mipmapping的好处在于，不是试图对影响单个像素的所有纹素进行求和，而是访问和插入预组合的纹素集。无论缩小量多少，此过程都需要一定的时间。然而，mipmapping有几个缺陷。一个主要的是过度模糊（overblurring ）。想象一下像素单元在u方向上覆盖大量纹素，在v方向上只有少数纹素。这种情况通常发生在观察者沿着几乎边缘的纹理表面看时。事实上，可能需要沿纹理的一个轴和另一个轴放大缩小。访问mipmap的效果是检索纹理上的方形区域；检索矩形区域是不可能的。为了避免失真，我们选择纹理上像素单元的近似覆盖范围的最大度量。这导致检索的样本通常相对模糊。这种效果可以上图中看到。向右延展的线显示过度模糊。</p>
<h5 id="summedareatable">Summed-Area Table</h5>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/6.17.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<p>避免过度模糊的另一种方法是求和区域表（SAT）。要使用此方法，首先要创建一个数组，该数组的大小与纹理的大小相同，但包含更多的存储颜色精度位（如，对RGB中的每个16位或更多位）。在该数组中的每个位置，必须计算并存储由该位置和纹理元素$(0,0)$（原点）形成的矩形中所有相应纹理的纹素的总和。在贴图期间，像素单元在纹理上的投影由矩形约束。然后访问SAT以确定该矩形的平均颜色，该矩形作为像素的纹理颜色传回。 使用图6.17中所示矩形的纹理坐标计算平均值。这是使用下述公式完成的：</p>
<p>$$<br>
\boldsymbol{c}=\frac{\boldsymbol{s}[x_{ur},y_{ur}]-\boldsymbol{s}[x_{ur},y_{ll}]-\boldsymbol{s}[x_{ll},y_{ur}]+\boldsymbol{s}[x_{ll},y_{ll}]}{(x_{ur}-x_{ll})(y_{ur}-y_{ll})}\tag{6.3}<br>
$$</p>
<p>这里，x和y是矩形的纹素坐标，$\boldsymbol{s}[x, y]$是该纹素的总面积值。该等式通过获取从右上角到原点的整个区域的总和，然后通过减去相邻角落的贡献来减去区域A和B。区域C已被减去两次，因此它被添加回左下角。 注意，$(x_{ll},y_{ll})$是区域C的右上角，$(x_{ll}+1,y_{ll}+1)$是边界框的左下角。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/6.14.3.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<p>使用求和区域表的结果如上图。 到达地平线的线条在右边缘附近更锐利，但中间的对角线交叉线仍然过于模糊。 问题在于，当沿着其对角线观察纹理时，生成大矩形，其中许多纹素位于计算的像素附近。例如，想象一个长而细的矩形，表示像素单元的背投影（back-projection）整个纹理的对角线上。 将返回整个纹理矩形的平均值，而不仅仅是像素单元格内的平均值。</p>
<p>求和面积表是所谓的各向异性过滤（anisotropic filtering）算法的一个例子。 此类算法在非正方形区域上检索纹素值。但是，SAT能够在主要水平和垂直方向上最有效地完成此任务。另请注意，对于大小为16×16或更小的纹理，求和区域表至少需要两倍的内存，更大的纹理需要更高的精度。</p>
<p>可以在现代GPU上实现总和区域表，其在合理的总存储器成本下提供更高的质量。改进的过滤器对于高级渲染技术的质量至关重要。例如，Hensley等人提供了一种有效的实现方式，并显示了求和区域采样如何改善光泽反射。 其他使用区域采样的算法可以通过SAT改进，例如景深，阴影图和模糊反射。</p>
<h5 id="unconstrainedanisotropicfiltering">Unconstrained Anisotropic Filtering（无约束各向异性过滤）</h5>
<p>对于当前的图形硬件，进一步改进纹理过滤的最常用方法是重用现有的mipmap硬件。基本思想是像素单元被反投影（back-projected），然后对纹理上的这个四边形（quad）进行多次采样，然后组合样本。如上所述，每个mipmap样本都有一个位置和一个与之相关的方形区域。该算法不是使用单个mipmap样本来近似这个四边形的覆盖范围，而是使用几个方块来覆盖四边形。四边形的较短边可用于确定d（与mipmapping不同，经常使用较长边）；这使得每个mipmap样本的平均面积更小（并且模糊度更低）。四边形的长边用于创建平行于长边和四边形中间的各向异性线。当各向异性的量在1：1和2：1之间时，沿着该线取两个样本。在较高的各向异性比率下，沿轴线采集更多样本。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/6.18.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<p>该方案允许各向异性线在任何方向上运行，因此不具有SAT的限制。它也不需要比mipmap更多的内存来存储纹理，因为它使用mipmap算法进行采样。各向异性过滤的一个例子如图：</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/6.19.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<p>（后面介绍了一些拓展方法，略）</p>
<h4 id="623volumetextures">6.2.3 Volume Textures（体积纹理）</h4>
<p>图像纹理的直接扩展是通过$(u,v,w)$（或$(s,t,r)$）访问的三维图像数据。 例如，医学成像数据可以生成为三维网格；通过移动多边形通过该网格，可以查看这些数据的二维切片。一个相关的想法是以这种形式表示体积光。通过找到其在该体积内的位置的值以及光的方向来找到表面上的点上的照明。</p>
<p>大多数GPU支持体积纹理的mipmapping。 由于在单个体积纹理的mipmap level内进行滤波涉及三线性插值，因此在mipmap level之间进行滤波需要四线性插值（quadrilinear interpolation）。由于这涉及对来自16个纹素的结果进行平均，因此可能导致精度问题，这可以通过使用更高精度的体积纹理来解决。 Sigg和Hadwiger讨论了这个问题和与体积纹理相关的其他问题，并提供了执行过滤和其他操作的有效方法。</p>
<p>尽管体积纹理具有明显更高的存储要求并且过滤成本更高，但它们确实具有一些独特的优势。可以跳过为三维网格找到二维参数的复杂过程，因为三维位置可以直接用作纹理坐标。这避免了二维参数化通常发生的变形和接缝问题。体积纹理也可用于表示诸如木材或大理石的材质的体积结构。用这种材质纹理化的模型就像是用这种材质雕刻的。</p>
<p>使用体积纹理进行表面纹理处理是非常低效的，因为绝大多数样本都不使用。 Benson和Davis和DeBry等人讨论将纹理数据存储在稀疏八叉树结构中。该方案非常适合交互式三维绘画系统，因为表面在创建时不需要为其分配明确的纹理坐标，并且八叉树可以将纹理细节保持在任何所需的水平。Lefebvre等讨论在现代GPU上实现八叉树纹理的细节。Lefebvre和Hoppe讨论了将稀疏体积数据打包成明显更小的纹理的方法。</p>
<h4 id="624cubemaps">6.2.4 Cube Maps（立方纹理）</h4>
<p>另一种类型的纹理是立方体纹理（cube texture、cube map），其具有六个正方形纹理，每个纹理与立方体的一个面相关联。使用三分量纹理坐标向量访问立方体贴图，该向量指定从多维数据集中心向外指向的光线的方向。光线与立方体相交的点如下所述。纹理坐标中最大的分量选择相应的面（例如，矢量$(-3.2,5.1,-8.4)$选择-z面）。剩余的两个坐标除以最大坐标的绝对值，即8.4。它们现在的范围从-1到1，然后重新映射到[0,1]以便计算纹理坐标。例如，坐标$(-3.2,5.1)$被映射到$((-3.2/8.4+1)/2,(5.1/8.4+1)/2)\approx(0.31,0.80)$。立方体贴图可用于表示作为方向函数的值，最常用于环境映射。</p>
<h4 id="625texturerepresentation">6.2.5 Texture Representation（纹理表示）</h4>
<p>在应用程序中处理许多纹理时，有几种方法可以提高性能。纹理压缩在6.2.6节中描述，而本节的重点是纹理图集（texture atlas），纹理数组（texture arrays）和无绑定纹理（bindless textures），所有这些都旨在减少在渲染时更改纹理的成本。在第19.10.1节和第19.10.2节中，描述了纹理流（texture streaming）和转码（transcoding）。</p>
<p>为了能够让GPU批处理尽可能多的工作，最好尽可能少地改变状态。为此，可以将几个图像放入一个较大的纹理中，称为纹理图集。请注意，子纹理的形状可以是任意的。Nöll和Stricker描述了如何优化子纹理在图集中的放置。由于mipmap上层可能包含几个分离的，不相关的形状，因此还需要注意mipmap的生成和访问。Manson和Schaefer 提出了一种通过把表面参数加入考虑来优化mipmap创建的方法，这可以产生明显更好的结果。Burley和Lacewell 提出了一个名为Ptex的系统，其中细分曲面中的每个四边形都有自己的小纹理。优点是，这避免了在网格上分配唯一纹理坐标，并且在纹理图集的断开部分的接缝上没有伪影。为了能够跨四边形进行过滤，Ptex使用邻接数据结构（adjacency data structure）。虽然最初的目标是生产渲染（production rendering），但Hillesland提出了打包的Ptex，它将每个面的子纹理放入纹理图集中，并使用相邻面的填充以避免在过滤的时候间接处理。 Yuksel提出了网格颜色纹理，改进了Ptex。Toth实现一种方法来为Ptex类系统提供高质量的过滤。</p>
<p>使用图集的一个难点是wrapping/repeat和mirror模式，它们不会正确地影响子纹理而只影响整个纹理。在为图集生成mipmap时可能会出现另一个问题，其中一个子纹理可能会渗入另一个子纹理。但是，这可以避免，通过在将每个子纹理放置到大型纹理图集并对子纹理使用二次幂分辨率之前，分别为每个子纹理生成mipmap层次结构。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/6.20.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<p>这些问题的一个更简单的解决方案是使用称为纹理数组的API结构，它完全避免了mipmapping和repeat modes的任何问题。如上图，左为纹理图集，右为纹理数组。纹理数组中的所有子纹理都需要具有相同的尺寸，格式，mipmap层次结构和MSAA设置。 像纹理图集一样，设置只对纹理数组执行一次，然后可以使用着色器中的索引访问任何数组元素。 这比绑定每个子纹理快5倍。</p>
<p>bindless texture可以帮助避免状态更改造成的消耗，由API支持。如果没有bindless texture，则使用API将纹理绑定到特定纹理单元。一个问题是纹理单元数量的上限，这使程序员的事情变得复杂。 驱动程序确保纹理驻留在GPU端。对于bindless texture，纹理数量没有上限，因为每个纹理只与其数据结构的64位指针（有时称为句柄）相关联。可以以许多不同的方式访问这些句柄，例如，通过uniform，通过变量，通过其他纹理，或来自着色器存储缓冲对象（SSBO）。应用程序需要确保纹理驻留在GPU端。无绑定纹理避免了驱动程序中的任何类型的绑定成本，这使得渲染更快。</p>
<h4 id="626texturecompression">6.2.6 Texture Compression（纹理压缩）</h4>
<p>一种直接解决内存和带宽问题以及缓存问题的解决方案是固定速率的纹理压缩（texture compression）。 通过让GPU在运行中解码压缩纹理，纹理可以需要更少的纹理内存，因此增加了有效的高速缓存大小。至少同样重要的是，这样的纹理使用起来更有效，因为它们在访问时消耗更少的存储器带宽。相关但不同的用例是添加压缩以提供更大的纹理。例如，在$512^2$分辨率下使用每个纹素3个字节的非压缩纹理将占用768 kB。 使用纹理压缩，压缩比为6:1，$1024^2$的纹理将仅占用512 kB。</p>
<p>在图像文件格式中使用了各种图像压缩方法，例如JPEG和PNG，但在硬件中实现这些方法的解码成本很高。 S3开发了一种称为S3纹理压缩（S3TC）的方案，它被选为DirectX的标准称作DXTC，在DirectX 10中称为它被称为BC（Block Compression ）。此外，它也是OpenGL的标准，因为几乎所有的GPU都支持它。它具有创建尺寸固定的压缩图像，具有独立编码的部分，并且解码简单（因此快速）的优点。图像的每个压缩部分可以独立于其他部分处理。没有共享查找表或其他依赖项，这简化了解码。</p>
<p>DXTC/BC压缩方案有七种变体，它们共享一些共同的属性。编码在4×4纹素块（texel blocks）上完成，也称为tiles。每个块都是单独编码的。编码基于插值。对于每个编码量，存储两个参考值（例如，颜色）。为块中的16个纹素中的每一个保存插值因子。它沿两个参考值之间的线选择一个值。 压缩仅需要存储两种颜色以及每个像素的短索引值。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/t6.1.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<p>七种变体之间的确切编码有所不同，上表总结了这些变体。请注意，DXT表示DirectX 9中的名称，BC表示DirectX 10及更高版本中的名称。从表中可以看出，BC1有两个16位参考RGB值（5位红色，6位绿色，5位蓝色），每个纹素都有一个2位插值因子可选取参考值中的一个或两个中间值。与未压缩的24位RGB纹理相比，这表示纹理压缩比为6：1。BC2以与BC1相同的方式对颜色进行编码，但为了量化（原始）alpha，bits-per-texel（bpt）增加4位。对于BC3，每个块具有以与DXT1块相同的方式编码的RGB数据。此外，使用两个8位参考值和每纹素3位内插因子对alpha数据进行编码。每个纹素可以选择参考α值中的一个或六个中间值中的一个。BC4有一个通道，在BC3中编码为alpha。BC5包含两个通道，每个通道按BC3编码。</p>
<p>BC6H用于高动态范围（HDR）纹理，其中每个纹素最初每个R，G和B通道具有16位浮点值。 此模式使用16个字节，结果为8 bpt。 它有一种模式用于单行（类似于上面的技术），另一种模式用于两行，其中每个块可以从一小组分区中进行选择。两种参考颜色也可以进行增量编码以获得更好的精度，并且根据使用的模式也可以具有不同的精度。 在BC7中，每个块可以有一到三行，并存储8 bpt。 目标是8位RGB和RGBA纹理的高质量纹理压缩。 它与BC6H共享许多属性，但是LDR纹理的格式，而BC6H用于HDR。 注意，BC6H和BC7分别在OpenGL中称为BPTC FLOAT和BPTC。 这些压缩技术可应用于立方体或体积纹理以及二维纹理。</p>
<p>这些压缩方案的主要缺点是它们是有损的（lossy）。 也就是说，通常无法从压缩版本中检索原始图像。 在BC1 -BC5的情况下，仅使用四个或八个内插值来表示16个像素。 如果图块中包含大量不同的值，则会有一些损失。实际上，如果正确使用，这些压缩方案通常可提供可接受的图像保真度。</p>
<p>BC1 - BC5的一个问题是用于块的所有颜色都位于RGB空间的直线上。例如，红色，绿色和蓝色不能在单个块中表示。BC6H和BC7支持更多线，因此可以提供更高的质量。</p>
<p>对于OpenGL ES，选择另一种称为爱立信纹理压缩（Ericsson texture compression，ETC）的压缩算法包含在API中。 该方案具有与S3TC相同的特性，即快速解码，随机接入，无间接查找和固定速率。它将4×4纹素的块编码成64位，即每个纹素使用4位。基本思想如图所示。 每个2×4块（或4×2，取决于哪个给出最佳质量）存储基色。 每个块还从一个小的静态查找表中选择一组四个常量，并且块中的每个纹素都可以选择添加该表中的一个值。 这会修改每个像素的亮度。 图像质量与DXTC相当：</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/6.21.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<p>在包含在OpenGL ES 3.0中的ETC2中，未使用的位组合用于向原始ETC算法添加更多模式。未使用的比特组合是压缩表示（例如，64 bits），其解压缩到与另一压缩表示相同的图像。例如，在BC1中，将两个参考颜色设置为相同是没有用的，因为这将指示恒定颜色块，只要一个参考颜色包含该恒定颜色，就可以获得该颜色块。在ETC中，一种颜色也可以从具有带符号数的第一种颜色进行增量编码，因此该计算可以上溢或下溢。这种情况用于发信号通知其他压缩模式。 ETC2添加了两种新模式，四种颜色，每个块以不同的方式导出，最终模式是RGB空间中用于处理平滑过渡的平面。爱立信alpha压缩（EAC）[1868]用一个分量（例如，alpha）压缩图像。此压缩类似于基本的ETC压缩，但仅适用于一个分量，并且生成的图像每个纹素存储4位。它可以选择与ETC2结合使用，此外，还可以使用两个EAC通道来压缩法线（下面将详细介绍此主题）。所有ETC1，ETC2和EAC都是OpenGL 4.0核心配置文件，OpenGL ES 3.0，Vulkan和Metal的一部分。</p>
<p>法线贴图的压缩（在第6.7.2节中讨论）需要一些注意。为RGB颜色设计的压缩格式通常不适用于法线的xyz数据。 大多数方法利用已知法线为单位长度的事实，并进一步假设其z分量为正（对于切线空间法线的合理假设）。 这允许仅存储法线的x分量和y分量。 z分量是动态导出的：</p>
<p>$$<br>
n_z=\sqrt{1-n_x^2-n_y^2}\tag{6.4}<br>
$$</p>
<p>这本身导致适度的压缩量，因为只存储了两个分量，而不是三个。由于大多数GPU本身不支持三分量纹理，因此这也避免了浪费一个分量的可能性。 通常通过将x和y分量存储在BC5/3Dc格式纹理中来实现进一步压缩。 见下图。 由于每个块的参考值划分了最小和最大x和y分量值，因此可以将它们视为在xy平面上定义边界框。 三位插值因子允许在每个轴上选择八个值，因此bounding box被划分为可能法线的8×8网格。 或者，可以使用两个EAC通道（对于x和y），然后如上所述计算z。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/6.22.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<p>在不支持BC5 / 3Dc或EAC格式的硬件上，常见的后备是使用DXT5格式纹理并将这两个分量存储在绿色和alpha分量中（因为它们以最高精度存储）。 其他两个分量未使用。</p>
<p>PVRTC是Imagination Technologies的硬件名为PowerVR的纹理压缩格式，其最广泛的用途是用于iPhone和iPad。 它为每个纹理元素提供2位和4位的方案，并压缩4×4纹素的块。 关键思想是提供图像的两个低频（平滑）信号，这些信号是使用相邻的纹素数据块和插值获得的。 然后，每个纹素1或2比特用于在图像上的两个信号之间进行插值。</p>
<p>自适应可缩放纹理压缩（Adaptive scalable texture compression ，ASTC）的不同之处在于它将n×m个纹素的块压缩成128位。 块大小范围从4×4到12×12，这导致不同的比特率，从每个纹素开始低至0.89比特，并且每个纹素高达8比特。 ASTC使用各种技巧进行紧凑索引表示，并且每个块可以选择行数和端点编码。 此外，ASTC可以处理每个纹理1-4个通道以及LDR和HDR纹理的任何内容。ASTC是OpenGL ES 3.2及更高版本的一部分。</p>
<p>上面提出的所有纹理压缩方案都是有损的，并且当压缩纹理时，可以在该过程上花费不同的时间量。 在压缩上花费几秒甚至几分钟，就可以获得更高的质量；因此，这通常作为离线预处理完成，并存储以供以后使用。 或者，可以花费几毫秒，结果质量较低，但纹理可以近乎实时地压缩并立即使用。 一个例子是天空盒（第13.3节），当云可能稍微移动时，它每隔一秒左右重新生成一次。由于使用固定功能硬件完成，因此解压缩速度非常快。 这种差异称为数据压缩不对称，其中压缩比解压缩花费相当长的时间。</p>
<p>Kaplanyan提出了几种可以改善压缩纹理质量的方法。对于包含颜色和法线贴图的纹理，建议使用每个分量16位创作贴图。对于颜色纹理，然后执行直方图重新正规化（在这16位上），然后使用着色器中的比例和偏置常量（每个纹理）反转其效果。直方图归一化是一种将图像中使用的值展开以跨越整个范围的技术，这实际上是一种对比度增强。每个组件使用16位确保在重新规范化之后直方图中没有未使用的时隙，这减少了许多纹理压缩方案可能导致的带状伪像。如下图所示。此外，如果75％的像素高于116/255，Kaplanyan建议为纹理使用线性色彩空间，否则将纹理存储在sRGB中。对于法线贴图，他还指出BC5/3Dc经常独立于y压缩x，这意味着并不总能找到最佳法线。相反，他建议对法线使用以下误差度量：</p>
<p>$$<br>
e=\arccos{(\frac{\boldsymbol{n}\cdot\boldsymbol{n}_c}{\Vert\boldsymbol{n}\Vert\Vert\boldsymbol{n}_c\Vert})}\tag{6.5}<br>
$$<br>
其中n是原始法线，$\boldsymbol{n}_c$是相同法线的压缩后解压缩的结果。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/6.23.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<p>（从左到右依次为原始贴图，每个分量8bits的DXT1，每分量16bits且在着色器中renormalization的DXT1）</p>
<p>应当注意，还可以在不同的颜色空间中压缩纹理，这可以用于加速纹理压缩。 常用的变换是RGB -&gt; YCoCg：<br>
$$<br>
\begin{pmatrix}Y\\C_o\\C_g\end{pmatrix}=\begin{pmatrix}1/4&amp;1/2&amp;1/4\\1/2&amp;0&amp;-1/2\\-1/4&amp;1/2&amp;-1/4\end{pmatrix}\begin{pmatrix}R\\G\\B\end{pmatrix}\tag{6.6}<br>
$$<br>
其中Y是亮度，Co和Cg是色度。 逆变换也很便宜：</p>
<p>$$<br>
G=(Y+C_g),t=(Y-C_g),R=t+C_o,B=t-C_o\tag{6.7}<br>
$$</p>
<p>相当于一些补充。 这两个变换是线性的，可以看出，公式6.6是矩阵向量乘法，它本身是线性的（见方程4.1和4.2）。 这很重要，因为不是将RGB存储在纹理中，而是可以存储YCoCg;纹理硬件仍然可以在YCoCg空间中执行滤波，然后像素着色器可以根据需要转换回RGB。 应该注意的是，这种变换本身是有损的。</p>
<p>还有另一种可逆的RGB-&gt;YCoCg变换：</p>
<p>$$<br>
\begin{cases}C_o=R-b\\t=B+(C_o\gg1)\\C_g=G-t\\Y=t+(C_g\gg1)\end{cases}\Longleftrightarrow\begin{cases}t=Y-(C_g\gg1)\\G=C_g+t\\B=t-(C_o\gg1)\\R=B+C_o\end{cases}\tag{6.8}<br>
$$<br>
其中$\gg$表示向右位移。这意味着可以在24位RGB颜色和相应的YCoCg表示之间来回转换而没有任何损失。应当注意，如果RGB中的每个分量具有n位，则Co和Cg各自具有n + 1位以保证可逆变换；Y只需要n位。 Van Waveren和Casta~no使用有损YCoCg变换在CPU或GPU上实现对DXT5/BC3的快速压缩。它们将Y存储在alpha通道中（因为它具有最高的精度），而Co和Cg存储在RGB的前两个组件中。由于Y分别存储和压缩，压缩变得很快。对于Co和Cg组件，他们找到一个二维边界框并选择产生最佳结果的方框对角线。请注意，对于在CPU上动态创建的纹理，也可以更好地压缩CPU上的纹理。当通过GPU上的渲染创建纹理时，通常最好也压缩GPU上的纹理。 YCoCg变换和其他亮度 - 色度变换通常用于图像压缩，其中色度分量在2×2像素上取平均值。这使存储减少了50％，并且通常工作正常，因为色度趋于缓慢变化。 Lee-Steere和Harmon通过转换为色调饱和度值（HSV），在x和y中将色调和饱和度下采样4倍，并将值存储为单通道DXT1纹理，更进一步。 Van Waveren和Castaño也描述了压缩法线贴图的快速方法。</p>
<p>Griffin和Olano的一项研究表明，当几个纹理应用于具有复杂着色模型的几何模型时，纹理的质量通常可以很低，不会有任何可察觉的差异。 因此，根据使用情况，可以接受质量下降。 Fauconneau提供了DirectX 11纹理压缩格式的SIMD实现。</p>
<br>
<h3 id="63proceduraltexturing">6.3 Procedural Texturing（程序贴图）</h3>
<p>给定纹理空间位置，执行图像查找是生成纹理值的一种方式。 另一种是执行函数，从而定义程序纹理。</p>
<p>虽然程序纹理通常用于离线渲染应用程序，但图像纹理在实时渲染中更为常见。 这是由于现代GPU中图像纹理硬件的极高效率，它可以在一秒钟内执行数十亿次纹理访问。 然而，GPU架构正朝着更便宜的计算和（相对）更昂贵的存储器访问发展。这些趋势使得程序纹理在实时应用中得到了更多的应用。</p>
<p>考虑到体积图像纹理的高存储成本，体积纹理是程序纹理化的一种特别有吸引力的应用。 这种纹理可以通过各种技术合成。最常见的一种是使用一个或多个噪声函数来生成值。 见下图。噪声函数通常以二次幂的频率进行采样，称为octaves。每个octave都有一个权重，通常随着频率的增加而下降，这些加权样本的总和称为湍流扰动（turbulence）函数。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/6.24.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<p>由于评估噪声函数的成本，三维阵列中的格点通常被预先计算并用于内插纹理值。有各种方法使用颜色缓冲区混合来快速生成这些数组。 Perlin提供了一种快速，实用的方法来对噪声函数进行采样，并展示了一些用途。 Olano提供噪声生成算法，允许在存储纹理和执行计算之间进行权衡。麦克尤恩等人。开发用于计算着色器中的经典噪声和单纯噪声的方法，无需任何查找，并且可以使用源代码。 Parberry使用动态编程来分摊几个像素的计算，以加速噪声计算。 Green提供了一种更高质量的方法，但是对于近交互式应用程序来说更是如此，因为它使用50个像素着色器指令进行单个查找。 Perlin提出的原始噪声功能可以改进。 Cook和DeRose提供了一种称为小波噪声的替代表示，它避免了混叠问题，只是评估成本略有增加。 Liu等人使用各种噪声函数来模拟不同的木材纹理和表面处理。我们还推荐了Lagae等人在本主题中提供的最新报告。</p>
<p>其他程序方法也是可能的。例如，通过测量从每个位置到一组散落在空间中的“特征点”的距离来形成细胞纹理。 以各种方式映射得到的最近距离，例如，改变颜色或着色法线，产生看起来像细胞，石板，蜥蜴皮和其他自然纹理的图案。 Griffiths讨论了如何有效地找到最近邻居并在GPU上生成细胞纹理。</p>
<p>另一种类型的程序纹理是物理模拟的结果或一些其他交互过程的结果，例如水波纹或散布的裂缝。在这种情况下，程序纹理可以在对动态条件的反应中产生无限可变性。</p>
<p>当生成二维程序纹理时，参数化问题可能比创作纹理带来更多困难，因为创作纹理可以通过手动修正伪像。一种解决方案是通过将纹理直接合成到表面上来完全避免参数化。在复杂曲面上执行此操作在技术上具有挑战性，并且是一个活跃的研究领域。见Wei等人有关此领域的概述。</p>
<p>抗锯齿程序纹理比抗锯齿图像纹理更难，也可以说更容易。 一方面，诸如mipmapping之类的预计算方法不可用，这给程序员带来了负担。另一方面，程序纹理作者具有关于纹理内容的“内部信息”，因此可以定制它以避免混叠。 对于通过对多个噪声函数求和而创建的程序纹理尤其如此。每个噪声函数的频率是已知的，因此可以丢弃任何会导致混叠的频率，实际上使计算成本更低。有许多用于对其他类型的程序纹理抗锯齿的技术。多恩等人讨论了先前的工作并提出了一些重构纹理函数的过程，以避免高频，即带限。</p>
<br>
<h3 id="64textureanimation">6.4 Texture Animation（贴图动画）</h3>
<p>应用于表面的图像不必是静态的。 例如，视频源可以用作逐帧变化的纹理。</p>
<p>纹理坐标也不必是静态的。 应用程序设计人员可以在网格数据本身或通过顶点或像素着色器中应用的函数明确地逐帧更改纹理坐标。想象一下，瀑布已被建模，并且已经使用看起来像落水的图像进行纹理化。假设v坐标是流动的方向。为了使水移动，必须从每个连续帧上的v坐标中减去一定量。此操作使纹理本身看起来向前移动的效果。</p>
<p>通过将矩阵应用于纹理坐标可以创建更精细的效果。除了平移之外，还允许使用线性变换，例如缩放，旋转和剪切，图像变形和变形变换以及投影。通过在CPU或着色器中应用函数，可以创建更多精细的效果。</p>
<p>通过使用纹理混合技术，可以实现其他动画效果。例如，从大理石纹理开始，淡入进肉体纹理，就可以让雕像活过来。</p>
<br>
<h3 id="65materialmapping">6.5 Material Mapping（材质映射）</h3>
<p>纹理的常见用途是修改影响着色方程的材质属性。真实世界的物体通常具有在其表面上变化的材质特性。要模拟此类对象，像素着色器可以从纹理中读取值，并在计算着色方程之前使用它们来修改材质参数。最常由纹理修改的参数是表面颜色。此纹理称为albedo color map或diffuse color map。但是，任何参数都可以通过纹理进行修改：替换它，将其相乘或以其他方式更改它。例如，在下图中，三个不同的纹理应用于曲面，替换常量值。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/6.25.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<p>可以使用纹理来控制像素着色器本身的流程和功能，而不是修改等式中的参数。通过使一个纹理指定表面的哪个区域具有哪种材质，可以将具有不同着色方程和参数的两种或更多种材料应用于表面，从而让每种材料执行不同的代码。 例如，具有一些生锈区域的金属表面可以使用纹理来指示生锈所在的位置，以区分光亮部分。</p>
<p>如表面颜色的着色模型输入与着色器的最终颜色输出呈线性关系。因此，可以使用标准技术对包含这些输入的纹理进行滤波，并避免混叠。包含非线性着色输入的纹理（如粗糙度或凹凸贴图（第6.7节））需要更加小心以避免混叠。着色方程的滤波技术可以改善这种纹理的结果。第9.13节讨论了这些技术。</p>
<br>
<h3 id="66alphamapping">6.6 Alpha Mapping（透明度映射）</h3>
<p>使用alpha混合或alpha测试可以将alpha值用于许多效果，例如有效渲染树叶，爆炸和远处物体等等。本节讨论纹理与alphas的使用，各种限制和解决方案。</p>
<p>一种与纹理相关的效果贴花（decaling）。举个例子，假设您想在茶壶上放一朵花的照片。你不想要整个画面，而只想要花朵所在的部分。通过为纹素分配一个值为0的alpha，你可以使它透明，这样它就没有效果了。因此，通过正确设置贴花纹理的alpha，可以用贴花replace或blend底层表面。通常，夹具对应器功能与透明边框一起使用以将贴花的单个副本（相对于重复纹理）应用于表面。下图显示了如何实现decaling的示例。有关贴花的更多信息，请参见第20.2节。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/6.26.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<p>（实现贴花的一种方法。 首先使用场景渲染帧缓冲区，然后渲染一个box，对于box内的所有点，将贴花纹理投影到帧缓冲区内容。 最左边的纹素是完全透明的，因此它不会影响帧缓冲。 黄色纹理像素不可见，因为它会被投射到表面的隐藏部分。）</p>
<p>alpha的一个类似应用是制作cutouts。 假设您制作灌木的贴花图像并将其应用于场景中的矩形。 原理与贴花相同，除了不是与下面的表面齐平，灌木将被绘制在其后面的任何几何形状的顶部。通过这种方式，您可以使用单个矩形渲染具有复杂轮廓的对象。</p>
<p>在灌木的情况下，如果您围绕它旋转着观察，则导致问题，因为衬套没有厚度。一个方法是复制这个灌木矩形并沿着树干旋转90度。这两个矩形形成了一个廉价的三维灌木丛，有时被称为“十字树”（cross tree），从地面看，效果不错。见下图。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/6.27.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<p>Pelzer讨论了一个类似的配置，使用三个cutout（第13.6节），我们讨论了一种叫做广告牌（billboarding）的方法，它用于将这种渲染减少到一个矩形。如果观察者移动到地面以上，则从上方看到灌木是两个cutout时，错觉就会破裂。请参见下图。为了解决这个问题，可以以不同的方式添加更多cutout，如切片，分支，层以提供更有说服力的模型。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/6.28.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<p>结合alpha贴图和纹理动画可以产生令人信服的特殊效果，例如闪烁的火把，植物生长，爆炸和大气效果。</p>
<p>使用alpha贴图渲染对象有多种方式。Alpha blending（第5.5节）允许使用带小数的透明度值，这样可以对对象边缘以及部分透明对象进行抗锯齿处理。但是，alpha混合需要在不透明的三角形渲染之后再渲染需要混合的三角形，并且按照从前到后的顺序渲染。简单的交叉树是两个剪切纹理的示例，不使用渲染顺序是正确的，因为每个四边形位于另一个的一部分的前面。即使理论上可以排序并获得正确的顺序，这样做通常也是低效的。 例如，田地可能有成千上万的草叶片，由切口代表。 每个网格对象可以由许多单独的叶片制成。对每个叶片进行排序是非常不切实际的。</p>
<p>在渲染时，可以通过几种不同的方式改善此问题。一种是使用alpha测试，这是在像素着色器中有条件地丢弃具有低于给定阈值的α值的片段的过程：</p>
<pre><code>if (texture.a &lt; alphaThreshold) discard; \\ (6.9)
</code></pre>
<p>其中texture.a是纹理查找的alpha值，参数alphaThreshold是用户提供的阈值，用于确定将丢弃哪些片段。这种二元可见性测试使三角形能够以任何顺序呈现，因为透明片段被丢弃。我们通常希望对alpha为0.0的任何片段执行此操作。丢弃完全透明的片段还有一个额外的好处，即可以节省更多的着色器处理和合并成本，同时避免错误地将z缓冲区中的像素标记为可见。对于cutout，我们经常将阈值设置为高于0.0，比如0.5或更高，然后采取进一步的步骤，然后完全忽略alpha值，而不是使用它进行混合。这样做可以避免乱序伪像（out-of-order artifacts）。但是，质量很低，因为只有两级透明度（完全不透明和完全透明）可用。另一个解决方案是为每个模型执行两次pass：一次用于solid cutouts，写入z-buffer，另一次用于半透明样本，而不写入z-buffer。</p>
<p>alpha测试还有另外两个问题，即放大倍数太大和缩小太多。当alpha测试与mipmapping一起使用时，如果处理方式不同，效果可能会令人难以置信。下图顶部显示了一个示例，其中树的叶子变得比预期更透明。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/6.29.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<p>这可以用一个例子来解释。假设我们有一个具有四个alpha值的一维纹理，即（0.0,1.0,1.0,0.0）。通过平均，下一个mipmap级别变为（0.5,0.5），然后最高级别为（0.5）。 现在，假设我们使用$\alpha_t=0.75$。 当访问mipmap级别0时，可以显示4个中的1.5纹素将在discard测试中留存。但是，当访问接下来的两个级别时，从0.5 &lt; 0.75开始，所有内容都将被丢弃。有关另一个示例，见下图：</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/6.30.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<p>（在顶部是具有混合的叶子图案的不同mipmap级别，较高级别为了可见性而缩放。 在底部显示将使用0.5为阈值的alpha测试进行处理的mipmap，显示对象在后退时如何具有更少的像素。）</p>
<p>Castaño提供了一个在mipmap创建过程中完成的简单解决方案。 对于mipmap级别k，覆盖范围$c_k$定义为：</p>
<p>$$<br>
c_k=\frac1{n_k}\sum\limits_i(\alpha(k,i)\gt\alpha_t)\tag{6.10}<br>
$$</p>
<p>其中$n_k$是mipmap级别k中的纹素的数量，$\alpha(k,i)$是来自像素i处的mipmap级别k的alpha值，并且$\alpha_t$是等式6.9中的用户提供的α阈值。这里，我们假设$\alpha(k,i)\gt\alpha_t$的结果如果为真则为1，否则为0。 注意，k = 0表示最低的mipmap级别，即原始图像。 对于每个mipmap级别，我们然后找到新的mipmap阈值$\alpha_k$，而不是使用$α_t$，使得$c_k$等于$c_0$（或尽可能接近）。这可以使用二进制搜索来完成。 最后，mipmap级别k中所有纹素的alpha值按$α_t=α_k$缩放。此方法用于上上图底部，并且在NVIDIA的纹理工具中支持此方法。Golus提供了一个不修改mipmap的变体，但是随着mipmap级别的增加，alpha会在着色器中放大。</p>
<p>Wyman和McGuire提出了一个不同的解决方案，其中公式6.9中的代码行在理论上被替换为：</p>
<pre><code>if (texture.a &lt; random()) discard; \\ (6.11)
</code></pre>
<p>随机函数在[0,1]中返回一个统一值，这意味着平均来说这将得到正确的结果。 例如，如果纹理查找的alpha值为0.3，则将以30％的几率丢弃该片段。这是一种随机透明的形式，每个像素有一个样本。在实践中，随机函数被替换为hash函数以避免时间和空间高频噪声：</p>
<pre><code class="language-glsl">float hash2D(x,y) {return fract(1.0e4*sin(17.0*x+0.1*y)*(0.1+abs(sin(13.0*y+x))));} \\ 6.12
</code></pre>
<p>通过对上述函数的嵌套调用形成三维hash，即，<code>float hash3D(x,y,z) {return hash2D(hash2D(x,y),z); }</code>，返回[0,1)。hash的输入是object-space坐标除以object-space坐标的x和y中屏幕空间导数最大的那一个，然后clamping。需要进一步注意以获得z方向上的运动的稳定性，并且该方法最好与temporal抗锯齿技术相结合。这种技术随着距离逐渐消失，因此近距离我们根本没有任何随机效应。这种方法的优点是每个片段平均是正确的，而Castaño的方法为每个mipmap级别创建一个单独的$α_k$。 但是，此值可能会因每个mipmap级别而异，这可能会降低质量并需要艺术家干预。</p>
<p>Alpha测试显示放大倍数下的纹波伪影，可以通过将alpha贴图预先计算为距离场来避免。</p>
<p>Alpha to coverage，以及类似的特征透明度自适应抗锯齿，获取片段的透明度值并将其转换为覆盖像素内的样本个数。这个想法就像screen-door透明度（5.5节），但是是在子像素级别。想象一下，每个像素有四个样本位置，一个片段覆盖一个像素，但是25％透明（75％不透明）。alpha to coverage模式使片段变得完全不透明，但它只覆盖四个样本中的三个。此模式对于重叠草叶的切割纹理非常有用。由于绘制的每个样本都是完全不透明的，因此最接近的样本将沿着其边缘以一致的方式隐藏其后面的对象。 由于α混合已关闭，因此对正确混合半透明边缘像素不需要进行排序。</p>
<p>Alpha到覆盖范围有利于抗锯齿alpha测试，但在alpha混合时会失真。例如，具有相同alpha覆盖百分比的两个alpha混合片段将使用相同的子像素模式，这意味着一个片段将完全覆盖另一个片段而不是与其混合。Golus讨论了使用<code>fwidth()</code>着色器指令为内容提供更清晰的边缘。 见下图。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/6.31.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<p>（-&gt; alpha test, alpha blend, alpha to coverage, and alpha to coverage with sharpened edges）</p>
<p>对于alpha映射的任何使用，了解双线性插值如何影响颜色值非常重要。想象一下彼此相邻的两个纹素：rgbα=（255,0,0,255）是纯红色，其邻居rgbα=（0,0,0,2）是黑色且几乎完全透明。正好在两个纹素之间的位置的rgbα是什么？简单的插值给出（127,0,0,128），结果rgb值是一个&quot;暗淡的&quot;红色。但是，这个结果实际上并不是暗淡的，它是一个完整的红色，被它的alpha预乘。对于正确的插值，你需要确保被插值的颜色在插值之前已经被alpha预乘。例如，假设几乎透明的邻居设置为rgbα=（0,255,0,2），一个稍稍显绿的色调。这种颜色不预乘时，将给出结果（127,127,0,128），插入微小的绿色调突然将变为一个黄色样本。预乘后的这个相邻纹素是（0,2,0,2），再进行插值时，它给出了正确的预乘结果（127,1,0,128）。这个结果更有意义，产生的预乘颜色大部分是红色的，带有难以察觉的绿色色调。</p>
<p>忽略双线性插值的结果给出预乘结果可能导致贴花和cutout对象周围的黑边。&quot;暗淡的&quot;红色结果被管线的其余部分视为未经预乘的颜色，边缘变为黑色。即使使用alpha测试，这种效果也可以看到。最好的策略是在双线性插值完成之前进行预乘。WebGL API支持这一点，因为合成对于网页很重要。但是，双线性插值通常由GPU执行，并且在执行此操作之前，着色器无法对纹素值进行操作。图像不会以PNG等文件格式预乘，因为这样做会失去颜色精度。这两个因素在使用alpha映射时默认会导致黑边缘。一个常见的解决方法是预处理cutout图像，绘制透明，“黑色”纹素使用来自附近不透明纹理像素的颜色。所有透明区域通常需要通过手动或自动方式重新绘制，以便mipmap级别也可以避免边缘问题。值得注意的是，在形成具有α值的mipmap时应使用预乘值。</p>
<br>
<h3 id="67bumpmapping">6.7 Bump Mapping（凹凸映射）</h3>
<p>本节描述了一大类小规模细节表示技术，我们统称为凹凸贴图。 通常通过修改每像素着色例程来实现所有这些方法。 它们比单独的纹理映射提供更多的三维外观，但不添加任何其他几何体。</p>
<p>对象上的细节可以分为三个尺度：覆盖许多像素的宏观特征，跨越几个像素的中观特征，以及远小于像素的微观特征。这些类别在某种程度上是流动的，因为观看者可以在动画或交互式会话期间在许多距离处观察相同的对象。</p>
<p>宏观几何体由顶点和三角形或其他几何图元表示。 在创建三维角色时，肢体和头部通常以宏观尺度建模。微观几何体被封装在着色模型中，该模型通常在像素着色器中实现，并使用纹理贴图作为参数。所使用的着色模型模拟表面的微观几何形状的相互作用，例如，闪亮的物体在显微镜下是光滑的，而漫射的表面在微观上是粗糙的。角色的皮肤和衣服看起来具有不同的材质，因为它们使用不同的着色器，或者在这些着色器中使用至少不同的参数。</p>
<p>中间尺度描述了这两个尺度之间的所有内容。它包含无法使用单个三角形进行高效渲染的复杂细节，但又足以让观察者区分表面上几个像素的曲率的个别变化。角色脸上的皱纹，肌肉细节，衣服上的褶皱和接缝都是中尺度的。一系列统称为凹凸贴图技术的方法通常用于中尺度建模。这些调整像素级别的阴影参数，使得观察者感觉到远离基本几何形状的小扰动，其实际上保持平坦。不同类型的凹凸贴图之间的主要区别在于它们如何表示细节特征。变量包括现实主义的水平和细节特征的复杂性。例如，数字艺术家通常将细节雕刻到模型中，然后使用软件将这些几何元素转换为一个或多个纹理，例如凹凸纹理和可能是crevice-darkening纹理。</p>
<p>Blinn在1978年提出了在纹理中编码中尺度细节的想法。 他观察到，如果在着色期间，我们用一个略微扰动的表面法线取代真实的表面，则表面似乎具有小尺度细节。 他将描述扰动的数据存储到曲面法线的数组中。</p>
<p>关键的想法是，我们不是使用纹理来改变照明方程中的颜色分量，而是访问纹理来修改表面法线。表面的几何法线保持不变；我们只修改照明方程中使用的法线。此操作没有物理等效物；我们对表面法线进行了更改，但表面本身在几何意义上保持平滑。正如每个顶点具有法线给出三角形之间表面平滑的错觉，修改每个像素的法线会改变三角形表面本身的感知，而不会修改其几何形状。</p>
<p>对于凹凸贴图，法线必须相对于某个参照系改变方向。为此，在每个顶点处存储tangent frame，也称为切线空间。 该参照系用于将光转换为表面位置的空间（反之亦然）以计算扰动法线的效果。对于应用了法线贴图的多边形表面，除了顶点法线之外，我们还存储了所谓的切线（tangent）和次切线（bitangent）向量。bitangent vector也被错误地称为binormal vector（次法线向量，副法线...）。</p>
<p>切线和切线矢量表示法线贴图本身在对象空间中的轴，因为目标是将光线转换为与贴图相关。见下图：</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/6.32.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<p>这三个向量，normal n，tangent t和bitangent b，形成基矩阵：</p>
<p>$$<br>
\begin{pmatrix}<br>
t_x&amp;t_y&amp;t_z&amp;0\\<br>
b_x&amp;b_y&amp;b_z&amp;0\\<br>
n_x&amp;n_y&amp;n_z&amp;0\\<br>
0&amp;0&amp;0&amp;1\\<br>
\end{pmatrix}\tag{6.13}<br>
$$</p>
<p>该矩阵（有时缩写为TBN）将光的方向（对于给定的顶点）从世界空间转换为切线空间。这些矢量不必彼此真正垂直，因为法线贴图本身可能会扭曲以适合表面。然而，非正交基导致了纹理的偏斜，这可能意味着需要更多的存储并且还可能具有性能影响，因为，矩阵不能通过简单的转置反转。一种节省记忆的方法是在顶点处仅存储切线和副切线，并使用它们的叉积来计算法线。然而，这种技术只有在矩阵的左右手性始终相同时才有效。模型通常是对称的：飞机，人类，文件柜和许多其他物体。由于纹理消耗大量内存，因此它们通常镜像到对称模型上。因此，仅存储对象纹理的一侧，但纹理映射将其放置在模型的两侧。在这种情况下，切线空间的左右手性在两侧是不同的，并且不能被假设。如果在每个顶点存储额外的信息以指示左右手性，则仍然可以避免在这种情况下存储法线。如果置位，则该位用于抵消切线和bitangent的交叉积，以产生正确的法线。如果切线框架是正交的，也可以将基存储为四元数（第4.3节），这两者都更节省空间并且可以节省每个像素的一些计算。可能会有轻微的质量损失，但在实践中很少见到。</p>
<p>切线空间的概念对于其他算法很重要。正如下一章所讨论的，许多着色方程仅依赖于曲面的法线方向。然而，与表面相比，诸如拉丝铝或天鹅绒之类的材料也需要知道观察者和照明的相对方向。切线框架可用于定义材料在曲面上的方向。Lengyel和Mittring的文章对该领域进行了广泛的报道。 Schüler提出了一种在像素着色器中即时计算切线空间基础的方法，无需在每个顶点存储预先计算的切线框架。Mikkelsen改进了这种技术，并推导出一种不需要任何参数化的方法，而是使用表面位置的导数和高度场的导数来计算扰动法线。然而，与使用标准切线空间映射相比，这样的技术可以导致细节显着减少，并且可能产生艺术工作流问题。</p>
<h4 id="671blinnsmethods">6.7.1 Blinn‘s Methods</h4>
<p>Blinn的原始凹凸贴图方法在纹理中的每个纹素处存储两个有符号值$b_u$和$b_v$。这两个值对应于沿u和v图像轴改变法线的量。 也就是说，这些通常是双线性插值的纹理值用于缩放垂直于法线的两个矢量。这两个向量被添加到法线以改变其方向。$b_u$和$b_v$描述了表面朝向该点的方向。见下图。这种类型的凹凸贴图纹理称为偏移矢量凹凸贴图（offset vector bump map）或偏移贴图（offset map）。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/6.33.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<p>表示凸起的另一种方法是使用高度场来修改曲面法线的方向。 每个单色纹理值表示高度，因此在纹理中，白色是高区域而黑色是低区域（反之亦然）。有关示例，请参见下图。这是第一次创建或扫描凹凸贴图时使用的常见格式，它也是由Blinn于1978年引入的。高度场用于导出u和v符号值，类似于第一种方法中使用的值。通过获取相邻列之间的差异来获得u的斜率以及相邻行之间的差异获得v。一种变体是Sobel滤波器，它为直接相邻的行列提供更大的权重。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/6.34.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<h4 id="672normalmapping">6.7.2 Normal Mapping</h4>
<p>凹凸贴图的常用方法是直接存储法线贴图。算法和结果在数学上与Blinn的方法相同；只有存储格式和像素着色器计算会发生变化。</p>
<p>法线贴图编码（x,y,z）映射到[-1,1]。例如，对于8-bits纹理，x轴值0表示-1.0，并且255表示1.0。如下图所示。颜色[128,128,255]，浅蓝色表示用于所示颜色映射的平坦表面，法线为[0,0,1]。</p>
<p>法线贴图最初是以世界空间引入的，在实践中很少使用。对于那种类型的映射，扰动是直截了当的：在每个像素处，从贴图中检索法线并直接使用它以及一个光的方向来计算表面上该位置的着色。也可以在对象空间中定义法线贴图，以便可以旋转模型，然后法线仍然有效。但是，world和object-space表示将纹理绑定到特定方向的特定几何体，这限制了纹理重用。</p>
<p>相反，扰动法线通常在切线空间中检索，即相对于表面本身。这允许表面变形，以及正常纹理的最大重用。切线空间法线贴图也可以很好地压缩，因为z分量的符号（与未扰动的表面法线对齐的符号）通常可以假定为正。</p>
<p>可以使用法线贴图来提高实际效果：</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/6.36.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<p>与过滤颜色纹理相比，过滤法线贴图是一个难题。通常，法线和阴影颜色之间的关系不是线性的，因此标准滤波方法可能导致混叠。想象一下，看着由闪亮的白色大理石块组成的楼梯。在某些角度，楼梯的顶部或侧面捕捉光线并反射出明亮的镜面高光。但是，楼梯的平均法线是45度角；它将捕捉与原始楼梯完全不同的方向的亮点。如果在没有正确过滤的情况下渲染具有清晰镜面反射高光的凹凸贴图，则会出现令人分心的闪光效果。</p>
<p>Lambertian 表面是一种特殊情况，法线贴图对着色具有几乎线性的影响。Lambertian 着色几乎完全是点积，这是线性运算。对一组法线求平均值并使用结果执行点积相当于平均各个点积与法线：</p>
<p>$$<br>
\boldsymbol{l}\cdot(\frac{\sum_{j=1}^n\boldsymbol{n}_j}{n})=\frac{\sum_{j=1}^n(\boldsymbol{l}\cdot\boldsymbol{n}_j)}{n}\tag{6.14}<br>
$$</p>
<p>请注意，平均向量在使用前未标准化。 公式6.14表明，标准滤波和mipmap几乎可以为Lambertian曲面生成正确的结果。但结果不太正确，因为Lambertian 着色方程不是点积；它是一个clamped点积：$max(l\cdot{n},0)$。clamp操作使其非线性。这会使表面过度变暗以掠过光线方向，但实际上这通常并不令人反感。需要注意的是，一些通常用于法线贴图的纹理压缩方法（例如从其他两个重建z-component）不支持非单位长度法线，因此使用非标准化法线贴图可能会造成压缩困难。</p>
<p>在非Lambertian表面的情况下，通过将输入以组的形式使用shader过滤而不是单独过滤法线贴图，可以产生更好的结果。第9.13节讨论了这样做的技巧。</p>
<p>最后，从高度图$h(x,y)$导出法线贴图可能很有用。首先，使用居中差异计算x和y方向上的导数的近似值：</p>
<p>$$<br>
h_x(x,y)=\frac{h(x+1,y)-h(x-1,y)}{2},h_y(x,y)=\frac{h(x,y+1)-h(x,y-1)}{2}\tag{6.15}<br>
$$</p>
<p>然后texel(x,y)处的非标准化法线为（必须注意纹理的边界）：</p>
<p>$$<br>
\boldsymbol{n}(x,y)=(-h_x(x,y),-h_x(x,y),1)\tag{6.16}<br>
$$</p>
<p>通过使凹凸能够将阴影投射到它们自己的表面上，可以使用horizon mapping（水平映射）来进一步增强法线贴图。这是通过预先计算其他纹理来完成的，每个纹理与沿着表面平面的方向相关联，并为每个纹素存储该方向上的水平角度。</p>
<br>
<h3 id="68parallaxmapping">6.8 Parallax Mapping（视差映射）</h3>
<p>凸点和法线贴图的问题在于凸块永远不会随着视角移动位置，也不会相互阻挡。 例如，如果你沿着一个真正的砖墙看，在某个角度，你将看不到砖之间的砂浆。墙的凹凸贴图永远不会显示这种类型的遮挡，因为它只会改变法线。最好让凸起实际上影响在每个像素上渲染表面上的位置。</p>
<p>视差映射的概念由Kaneko于2001年引入，并由Welsh进行了改进和推广。视差指的是当观察者移动时物体的位置相对移动的想法。 当观察者移动时，凸起应该看起来具有高度。视差映射的关键思想是通过检查发现的可见高度来对像素中应该看到的内容进行有根据的猜测。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/6.37.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<p>对于视差映射，bumps存储在高度场纹理中。当在给定像素处查看表面时，在该位置处检索高度场值并移动纹理坐标以检索表面的不同部分。移位量取决于检索的高度和眼睛与表面的角度。见上图。</p>
<p>高度值要么存储在单独的纹理中，要么打包在未使用的颜色或某些其他纹理的alpha通道中（在将不相关的纹理打包在一起时必须小心，因为这会对压缩质量产生负面影响）。在移动坐标之前，高度场值被缩放和偏斜。缩放确定高度场在表面上方或下方延伸的高度，并且偏差给出了“水平面”没有发生位移的高度。给定纹理坐标位置p，调整后的高度场高度h，以及具有高度值$v_z$和水平分量$\boldsymbol{v}_{xy}$的归一化视图向量$\boldsymbol{v}$，新的视差调整纹理坐标$p_{adj}$是</p>
<p>$$<br>
\boldsymbol{p}_{adj}=\boldsymbol{p}+\frac{h\cdot\boldsymbol{v}_{xy}}{v_z}\tag{6.17}<br>
$$</p>
<p>注意，与大多数着色方程不同，这里进行计算的空间与视图向量有关，需要在切线空间中。</p>
<p>虽然这是一个简单的近似，但如果凸起高度变化相对较慢，这种变换在实践中效果相当好。 然后，附近的相邻纹素具有相同的高度，因此使用原始位置的高度作为对新位置高度的估计的想法是合理的。然而，这种方法在浅视角下会分崩离析。当视图矢量靠近表面的地平线时，较小的高度变化会导致较大的纹理坐标偏移。近似失败，因为检索到的新位置与原始表面位置的高度相关性很小或没有。</p>
<p>为了改善这个问题，Welsh提出了偏移限制的想法。 这个想法是限制移动量永远不会大于检索到的高度：</p>
<p>$$<br>
\boldsymbol{p}'_{adj}=\boldsymbol{p}+h\cdot\boldsymbol{v}_{xy}\tag{6.18}<br>
$$</p>
<p>请注意，此等式比原始等式更快。几何学上的解释是高度定义了一个半径，超出该半径，位置不能移动：</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/6.38.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<p>在陡峭（面朝上）的角度下，该方程几乎与原始方程相同，因为$v_z$接近1。在小角度处，偏移的效果受到限制。 在视觉上，这使得凹凸在较小角度处减小，但这比纹理的随机采样要好得多。当视图改变时，纹理游泳也存在问题，或者对于立体渲染，观察者同时感知两个必须提供一致深度提示的视点。即使存在这些缺点，具有偏移限制的视差映射仅花费一些额外的像素着色器程序指令，并且相对于基本法线贴图提供了相当大的图像质量改进。 Shishkovtsov通过在凹凸贴图法线方向上移动估计位置来改善视差遮挡的阴影。</p>
<h4 id="681parallaxocclusionmapping">6.8.1 Parallax Occlusion Mapping（视差遮蔽映射）</h4>
<p>凹凸贴图不会根据高度场修改纹理坐标；它只改变某个位置的着色法线。视差映射提供了高度场效果的简单近似，假设像素处的高度与其邻居的高度大致相同。这个假设很快就会崩溃。起伏也可以永远不会相互遮挡，也不会投下阴影。我们想要的是在像素处可见的内容，即视图矢量首先与高度场相交的位置。</p>
<p>为了更好地解决这个问题，一些研究人员提出沿着视图矢量使用光线行进，直到找到（近似）交叉点。这项工作可以在像素着色器中完成，其中高度数据可以作为纹理访问。我们将这些方法的研究归结为视差映射技术的子集，这些技术以某种方式利用光线行进。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/6.39.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<p>这些类型的算法被称为视差遮挡映射（POM）或浮雕映射（relief mapping）方法，以及其他名称。关键思想是首先沿投影矢量测试固定数量的高度场纹理样本。通常在掠射角处为视图光线生成更多样本，因此不会错过最近的交叉点。检索沿射线的每个三维位置，将其转换为纹理空间，并进行处理以确定其是否高于或低于高度场。一旦找到高度场下方的样本，其下面的量和前一个样本的数量用于查找交叉点位置。然后使用附加的法线贴图，颜色贴图和任何其他纹理，使用该位置对表面进行着色。多层高度场可用于产生悬垂，独立重叠表面和双面浮雕图；见13.7节。高度场追踪方法也可用于使凹凸不平的表面将阴影投射到自身上。有关比较，请参见下图：</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/6.40.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<p>（Parallax mapping without ray marching (left) compared to with ray marching (right) ）</p>
<p>关于这个主题有很多文献。 虽然所有这些方法沿着光线行进，但存在一些差异。 可以使用简单的纹理来检索高度，但也可以使用更高级的数据结构和更高级的rootfinding方法。 一些技术可能涉及着色器丢弃像素或写入深度缓冲器，这可能损害性能。 下面我们总结一大堆方法，但请记住，随着GPU的发展，最好的方法也是如此。 这个“最佳”方法取决于内容和光线行进期间完成的步骤数。</p>
<p>确定两个常规样本之间的实际交叉点的问题是寻根问题（root-finding problem）。 在实践中，高度场更多地被视为深度场，矩形的平面定义了表面的上限。 这样，平面上的初始点高于高度场。 找到上面的最后一点，并在第一点下面，高度场的表面，Tatarchuk使用割线方法的一个步骤来找到近似解。 Policarpo等。 在两个点之间使用二进制搜索，找到更接近的交叉点。 Risser等通过使用割线方法迭代来加速收敛。 定期采样可以并行完成，而迭代方法需要较少的整体纹理访问，但必须等待结果并执行较慢的相关纹理提取。 简单粗暴的方法似乎总体上表现良好。</p>
<p>频繁地对高度场进行采样至关重要。 McGuire和McGuire建议对mipmap查找进行偏置并使用各向异性mipmap来确保对高频高度场（如表示尖峰或头发的高度场）进行正确采样。 还可以以比法线贴图更高的分辨率存储高度场纹理。 最后，一些渲染系统甚至不存储法线贴图，更喜欢使用交叉滤波器从高度场导出法线。公式16.1显示了该方法。</p>
<p>提高性能和采样精度的另一种方法是不以规则的间隔对高度场进行采样，而是尝试跳过介入的空间。Donnelly将高度场预处理成一组体素，在每个体素中存储距离高度场表面有多远。以这种方式，可以快速跳过居间空间，代价是每个高度场的更高存储量。王等人使用五维位移映射方案来保持从所有方向和位置到地面的距离。这允许复杂的曲面，自阴影和其他效果，代价是相当大的存储量。Mehra和Kumar使用定向距离图进行类似的目的。 Dummer提出，并且Policarpo和Oliveira改进了锥步映射（cone step mapping）的想法。这里的概念是还为每个高度场位置存储锥形半径（cone radius）。该半径定义了光线上的间隔，其中与高度场最多有一个交点。此属性允许沿着光线快速跳过而不会遗漏任何可能的交叉点，但代价是需要依赖纹理读取。另一个缺点是创建锥形步骤图所需的预计算，使得该方法不能用于动态改变高度场。 Schroders和Gulik提出了四叉树浮雕映射（quadtree relief mapping），这是一种在遍历期间跳过卷的分层方法。Tevs等使用“最大mipmap”允许跳过，同时最大限度地降低预计算成本。 Drobot还使用存储在mipmap中的四叉树结构来加速遍历，并提出了一种在不同高度域之间进行混合的方法，其中一种地形类型转换为另一种。</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/6.41.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<p>上述所有方法的一个问题是，视线沿着对象的轮廓边缘时显示原始表面的平滑轮廓。见上图。关键是渲染的三角形定义了像素着色器程序应该评估哪些像素，而不是表面实际所在的位置。此外对于曲面，轮廓问题变得更加复杂。 Oliveira和Policarpo描述并开发了一种方法，该方法使用二次轮廓近似技术。Jeschke等人和Dachsbacher等人，都提供了更加通用和强大的方法（并回顾以前的工作），以正确处理轮廓和曲面。首先由Hirche探索，一般的想法是将网格中的每个三角形向外挤出并形成棱镜。渲染此棱镜会强制计算可能出现高度场的所有像素。这种类型的方法称为shell映射，因为展开的网格在原始模型上形成单独的shell。通过在与光线相交时保留棱镜的非线性特性，可以实现高度场的无伪像渲染，尽管计算成本很高。这种技术的令人印象深刻的用途如图：</p>
<p><img src="http://xx-ma.com/content/images/2018/RTR4-C6/6.42.png" alt="Realtime Rendering 4th notes - chapter6"></p>
<br>
<h3 id="69texturedlights">6.9 Textured Lights（贴图光照）</h3>
<p>纹理还可用于为光源添加视觉丰富度，并允许复杂的强度分布或聚光灯功能。对于将所有照明限制在圆锥或平截头体上的光，可以使用投影纹理来调制光强度。这允许形状聚光灯，图案灯，甚至“幻灯片投影仪“效果。这些灯通常被称为图案灯或饼干灯，在专业剧院和电影照明中使用的剪切术语之后。参见第7.2节投影映射的讨论以类似的方式用于投射阴影。</p>
<p>对于不限于平截头体但在所有方向上照明的光，可以使用立方体图来调制强度，而不是二维投影纹理。 一维纹理可用于定义任意距离衰减函数。 结合二维角度衰减图，这可以允许复杂的体积照明模式。 更普遍的可能性是使用三维（体积）纹理来控制光的衰减。 这允许任意体积的效果，包括光束。此技术是内存密集型的（与所有体积纹理一样）。如果光的效果量沿三个轴对称，则通过将数据镜像到每个八分圆中，可以将内存占用减少八倍。</p>
<p>可以将纹理添加到任何灯光类型以启用其他视觉效果。 纹理灯允许艺术家轻松控制照明，艺术家可以简单地编辑所使用的纹理。</p>
<br>
<h3 id="furtherreadingandresources">Further Reading and Resources</h3>
<ul>
<li>赫克伯特已经对纹理映射理论进行了很好的调查，并对该主题进行了更为深入的报道。</li>
<li>Szirmay-Kalos和Umenhoffer对视差遮挡映射和置换方法进行了很好的全面调查。有关正常表示的更多信息可以在Cigolle等人的着作中找到。</li>
<li>使用OpenGL的高级图形编程一书使用纹理算法广泛覆盖了各种可视化技术。有关三维程序纹理的广泛报道，请参阅纹理和建模：程序方法。</li>
<li>“可编程图形硬件高级游戏开发”一书有很多关于实现视差遮挡映射技术的细节，Tatarchuk的演示和Szirmay-Kalos以及Umenhoffer的调查也是如此。</li>
<li>对于程序纹理（和建模），我们在互联网上最喜欢的网站是Shadertoy。 显示屏上有许多有价值且引人入胜的程序纹理功能，您可以轻松修改任何示例并查看结果。</li>
<li>访问本书的网站realtimerendering.com，获取许多其他资源。</li>
</ul>
<p>（chapter 6 end.）</p>
]]></content:encoded></item><item><title><![CDATA[Realtime Rendering 4th notes - chapter5]]></title><description><![CDATA[<p><a href="https://www.xx-ma.com/">(转载请注明出处，谢谢咯)</a><br>
&quot;A good picture is equivalent to a good deed.&quot; — Vincent Van Gogh</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/10/5.1-1.png" class="kg-image"></figure><p>渲染3D物体时，我们不仅需要模型的几何形体，还需要我们渴望的视觉效果。根据应用需要，可以有从写实风格到多种多样的为创造而生的风格（两种风格如上图）。本章主要介绍两个都涉及到的知识点。</p>
<h3 id="51shadingmodels">5.1 Shading Models（着色模型）</h3>
<p>确定渲染对象外观的第一步，是选择着色模型（shading model）来描述对象的颜色，应如何根据表面方向，视图方向和光照等因素而变化。</p>
<p>例如我们使用Gooch shading model，它是一种非真实感渲染的形式。旨意提高技术绘图中的清晰程度。主要思想是将表面法线与光线的位置进行比较。如果法线指向光线，则用较暖的色调为表面着色；反之，则使用冷色调。两者之间的角度在这些色调之间进行插值，色调由用户提供。如下图所示。shading model通常具有控制模型外观变化的属性。</p>]]></description><link>http://xx-ma.com/rtr4-5/</link><guid isPermaLink="false">5bc9c035b67cfd1e0bec0327</guid><category><![CDATA[notes]]></category><category><![CDATA[graphics]]></category><category><![CDATA[realtime-rendering]]></category><category><![CDATA[code]]></category><dc:creator><![CDATA[Marianax]]></dc:creator><pubDate>Fri, 19 Oct 2018 12:31:37 GMT</pubDate><media:content url="http://xx-ma.com/content/images/2018/10/jeremy-galliani-507685-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<img src="http://xx-ma.com/content/images/2018/10/jeremy-galliani-507685-unsplash.jpg" alt="Realtime Rendering 4th notes - chapter5"><p><a href="https://www.xx-ma.com/">(转载请注明出处，谢谢咯)</a><br>
&quot;A good picture is equivalent to a good deed.&quot; — Vincent Van Gogh</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/10/5.1-1.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter5"></figure><p>渲染3D物体时，我们不仅需要模型的几何形体，还需要我们渴望的视觉效果。根据应用需要，可以有从写实风格到多种多样的为创造而生的风格（两种风格如上图）。本章主要介绍两个都涉及到的知识点。</p>
<h3 id="51shadingmodels">5.1 Shading Models（着色模型）</h3>
<p>确定渲染对象外观的第一步，是选择着色模型（shading model）来描述对象的颜色，应如何根据表面方向，视图方向和光照等因素而变化。</p>
<p>例如我们使用Gooch shading model，它是一种非真实感渲染的形式。旨意提高技术绘图中的清晰程度。主要思想是将表面法线与光线的位置进行比较。如果法线指向光线，则用较暖的色调为表面着色；反之，则使用冷色调。两者之间的角度在这些色调之间进行插值，色调由用户提供。如下图所示。shading model通常具有控制模型外观变化的属性。设置它们是第二步。下图模型只有一种属性，表面颜色。</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/10/5.2-1.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter5"></figure><p>像很多着色模型一样，此示例也受观察方向，光照方向，曲面方向影响。在着色中，这三个方向都表示为单位向量。如下图。</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/10/5.3-1.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter5"></figure><p>现在我们已经定义了shading model的所有输入，现在我们看一下数学定义：</p>
<p>$$<br>
\boldsymbol{c}_{\text{shaded}}=s\boldsymbol{c}_{\text{highlight}}+(1-s)(t)\boldsymbol{c}_{\text{warm}}+(1-t)\boldsymbol{c}_{\text{cool}}<br>
\tag{5.1}<br>
$$</p>
<p>在这个等式中，有如下中间计算过程：</p>
<p>$$<br>
\begin{align}<br>
\boldsymbol{c}_{\text{cool}}&amp;=(0,0,0.55)+0.25\boldsymbol{c}_{\text{surface}},\\<br>
\boldsymbol{c}_{\text{warm}}&amp;=(0.3,0.3,0)+0.25\boldsymbol{c}_{\text{surface}},\\<br>
\boldsymbol{c}_{\text{highlight}}&amp;=(1,1,1),\\<br>
t&amp;=\frac{\boldsymbol{n}\cdot\boldsymbol{l}+1}{2},\\<br>
\boldsymbol{r}&amp;=2(\boldsymbol{n}\cdot\boldsymbol{l})\boldsymbol{n}-\boldsymbol{l},\\<br>
s&amp;=(100(\boldsymbol{r}\cdot\boldsymbol{v})-97)^\mp<br>
\end{align}\tag{5.2}<br>
$$</p>
<p>此定义中的一些数学表达式也经常出现在其他着色模型中。<code>Clamping</code>操作可以使值归0或夹在0和1之间，在着色中很常见。在高光的混合因子s计算式中，我们了用$x^\mp$符号代表<code>Clamping</code>0到1的操作。点积运算出现了3次，这是一种经常出现的模式，表示两个向量对齐的程度，也是两个向量夹角的余弦值。另一种常见的着色操作是基于0和1之间的标量值在两种颜色之间线性插值，它的形式是$t\boldsymbol{c}_a+(1-t)\boldsymbol{c}_b$，这里的t从1到0，颜色值从$\boldsymbol{c}_a$到$\boldsymbol{c}_b$。此模式在上面的模型中出现了两次。首先在冷暖色中间进行插值，然后用它们的结果和高光颜色进行插值。线性插值很常见，在每种着色语言中都是内置函数，一般叫<code>lerp</code>或者<code>mix</code>。</p>
<p>$\boldsymbol{r}=2(\boldsymbol{n}\cdot\boldsymbol{l})\boldsymbol{n}-\boldsymbol{l}$计算$\boldsymbol{l}$根据法线$\boldsymbol{n}$的反射，大多着色语言也内置反射功能。</p>
<br>
<h3 id="52lightsources">5.2 Light Sources（光源）</h3>
<p>场景中可能有多个光照也可能没有光照，这取决于需求。每个光照都有自己的颜色，光照强度，大小，形状等。</p>
<p>使着色模型以二进制方式对光的存在与否作出反应，对应光源的开关，被着色的表面也会有两种不同外观。光源距离，阴影，表面是否背离光源（通过$\boldsymbol{n,l}$的夹角判断）或它们的组合，可以用以区分二者。然后是连续光强的情况。可以直接表示为刚才两种情况之间的差值，这意味着强度是一个范围，可能是0到1，或者是一个没有范围界限的值用其他方式影响着色。后者的一种常见方式是把着色模型分成亮和不亮两个部分，然后用光强$k_{\text{light}}$线性缩放亮的部分。</p>
<p>$$<br>
\boldsymbol{c}_{\text{shaded}}=f_{\text{unlit}}(\boldsymbol{n,v})+k_{\text{light}}f_{\text{lit}}(\boldsymbol{l,n,v})<br>
\tag{5.3}<br>
$$</p>
<p>可以简单的扩展为一个RGB光$\boldsymbol{c}_{\text{light}}$:</p>
<p>$$<br>
\boldsymbol{c}_{\text{shaded}}=f_{\text{unlit}}(\boldsymbol{n,v})+\boldsymbol{c}_{\text{light}}f_{\text{lit}}(\boldsymbol{l,n,v})<br>
\tag{5.4}<br>
$$</p>
<p>对于多光源则是：</p>
<p>$$<br>
\boldsymbol{c}_{\text{shaded}}=f_{\text{unlit}}(\boldsymbol{n,v})<br>
+<br>
\sum\limits^n_{i=i}\boldsymbol{c}_{\text{light}_i}f_{\text{lit}}(\boldsymbol{l_i,n,v})<br>
\tag{5.5}<br>
$$</p>
<p>通常这种shading model描述的是不直接来自明确放置光源的情况，比如来自天空的光或者周围物体的反射光。</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/10/5.4-1.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter5"></figure><p>如上图，光对表面的影响可以看做是一组光线，其中光线的密度与光强度相对应，以用于表面着色。上图展示了一个亮面的横截面，沿该截面撞击表面的光线之间的密度，与$\boldsymbol{l,n}$之间的夹角余弦成反比。在这里我们就可以看到，定义与光线行进方向相反的矢量$\boldsymbol{l}$是方便的。否则我们要在计算余弦点积的时候取负。</p>
<p>更准确的说，光的密度（也就是光对表面着色的贡献量）是与$(\boldsymbol{n\cdot{l}})$成正比的，其中$(\boldsymbol{n\cdot{l}})\gt{0}$。负的值表示光在背面没有影响，所以我们要在使用点积的时候将它执行<code>Clamping</code>到0的操作，得到：</p>
<p>$$<br>
\boldsymbol{c}_{\text{shaded}}=f_{\text{unlit}}(\boldsymbol{n,v})<br>
+<br>
\sum\limits^n_{i=i}(\boldsymbol{l_i}\cdot\boldsymbol{n})^+<br>
\boldsymbol{c}_{\text{light}_i}f_{\text{lit}}(\boldsymbol{l_i,n,v})<br>
\tag{5.6}<br>
$$</p>
<p>支持多光源的着色模型通常使用公式5.5的结构，或者基于物理的模型所需的公式5.6。5.6对于风格化的模型也是有利的，因为它有助于确保光照的整体性，尤其是对于背离光或被遮蔽的表面。但有些模型不适合这种结构，他们将使用公式5.5的结构。</p>
<p>对于函数$f_{\text{lit}}()$最简单的选择是给定一个颜色</p>
<p>$$<br>
f_{\text{lit}}()=\boldsymbol{c}_{\text{surface}}\tag{5.7}<br>
$$</p>
<p>得到：</p>
<p>$$<br>
\boldsymbol{c}_{\text{shaded}}=f_{\text{unlit}}(\boldsymbol{n,v})<br>
+<br>
\sum\limits^n_{i=i}(\boldsymbol{l_i}\cdot\boldsymbol{n})^+<br>
\boldsymbol{c}_{\text{light}_i}\boldsymbol{c}_{\text{surface}}<br>
\tag{5.8}<br>
$$</p>
<p>这个模型的亮部对应<em>Lambertian</em>着色模型。该模型以理想的漫反射表面为前提进行工作，也就是完全无光泽的表面。<em>Lambertian</em>模型可以单独用于简单着色，他是许多着色模型中的关键组成部分。从公式5.3-5.6，我们看到光源影响着色模型看两个参数：指向光源的向量$\boldsymbol{l}$和光源颜色$\boldsymbol{c}_{\text{light}}$。各种不同类型的光源，也区别于这两个参数在场景中如何变化。</p>
<h4 id="521directionlights">5.2.1 Direction Lights（平行光）</h4>
<p>平行光（方向光）是最简单的光源。$\boldsymbol{l}$和$\boldsymbol{c}_{\text{light}}$都是恒定的，除了可以通过阴影进行衰减。平行光没有确切位置。</p>
<p>对于相对于场景大小较远的灯光如太阳光或者二十英尺远照射小桌面上面的模型时，应使用平行光。</p>
<p>稍作扩展，我们可以保持$\boldsymbol{l}$恒定，改变$\boldsymbol{c}_{\text{light}}$的值。出于性能或者创作需要，这样应用通常需要把光限制在场景的特定部分。例如一个区域可以定义为两个嵌套的盒形体积，其中外框的$\boldsymbol{c}_{\text{light}}=(0,0,0)$，内部$\boldsymbol{c}_{\text{light}}={\text{any constant}}$。两个盒子之间的区域使用这两个值的差值。</p>
<h4 id="522punctuallights">5.2.2 Punctual Lights（点源光）</h4>
<p>Punctual Lights不是按时约会的灯（punctual直译英文为准时的），而是有确切位置的灯。我们使用的punctual来自拉丁语，意为点（point），用于表述所有源自单一位置的一类光源。我们使用术语点光（point light）来表示一种特殊类型的发射器，一种在所有方向均匀照射光的发射器。因此，点光和聚光灯是两种形式的点源光。光源方向$\boldsymbol{l}$随当前着色表面点$\boldsymbol{p}_0$相对于点源光的位置$\boldsymbol{p}_{\text{light}}$的关系而变化：</p>
<p>$$<br>
\boldsymbol{l}=\frac{\boldsymbol{p}_{\text{light}}-\boldsymbol{p}_0}<br>
{\Vert\boldsymbol{p}_{\text{light}}-\boldsymbol{p}_0\Vert}\tag{5.9}<br>
$$</p>
<p>这也是一种常见的操作，通常内置于着色语言中。但有时需要使用这个值的中间结果，这需要多步操作：</p>
<p>$$<br>
\begin{align}<br>
\boldsymbol{d}&amp;=\boldsymbol{p}_{\text{light}}-\boldsymbol{p}_0,\\<br>
r&amp;=\sqrt{\boldsymbol{d}\cdot\boldsymbol{d}},\\<br>
\boldsymbol{l}&amp;=\frac{\boldsymbol{d}}{r}<br>
\end{align}<br>
\tag{5.10}<br>
$$</p>
<p>想要找到向量的长度我们只需用它自身的点积开方。我们需要的中间值是$r$，表示光源和当前着色表面点的距离。除了用于标准化$\boldsymbol{l}$外，还需要$r$的值计算作为距离因素计算光线的衰减。</p>
<h5 id="pointomnilights">Point/Omni Lights（点光）</h5>
<p>在所有方向上均匀发光的点源光被称为点光源（point light）或者全向灯（omni light）。对于点光源，唯一的变化源是上面提到的距离衰减。</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/10/5.5-1.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter5"></figure><p>上图解释了为何出现衰减（变暗）的情况。在给定表面处，光线间的间隔$\boldsymbol{d}$和光距离表面的距离$\boldsymbol{r}$成正比，与余弦因子不同，这种间距的增加在物体表面沿着两个维度发生，因此光线密度（以及光的颜色$\boldsymbol{c}_{\text{light}}$）和距离的平方倒数$(1/r^2)$成正比。我们设$\boldsymbol{c}_{\text{light}_0}$为$\boldsymbol{c}_{\text{light}}$在光与表面间固定距离为$r_0$时的值：</p>
<p>$$<br>
\boldsymbol{c}_{\text{light}}(r)=\boldsymbol{c}_{\text{light}_0}(\frac{r_0}{r})^2\tag{5.11}<br>
$$</p>
<p>上述等式称为平方反比光衰减（inverse-square light attenuation），它虽然从技术上讲是正确的距离衰减，但有些问题使得这个等式不适用于实际着色。</p>
<p>问题一：当距离较小时，$r$趋近于0，$\boldsymbol{c}_{\text{light}}$趋近于无穷。所以解决办法一般是增加一个很小的值$\epsilon$在分母处：</p>
<p>$$<br>
\boldsymbol{c}_{\text{light}}(r)=\boldsymbol{c}_{\text{light}_0}(\frac{r_0^2}{r^2+\epsilon})\tag{5.12}<br>
$$</p>
<p>$\epsilon$的值取决于应用，如虚幻引擎取$\epsilon=1cm$。</p>
<p>使用在CryEngine和寒霜中的另一种修改，是对r取一个极小值，做Clamp操作：</p>
<p>$$<br>
\boldsymbol{c}_{\text{light}}(r)=\boldsymbol{c}_{\text{light}_0}(\frac{r_0}{max(r,r_{\text{min}})})^2\tag{5.13}<br>
$$</p>
<p>相比第一种方法，第二种方法不那么随意且$r_{\text{min}}$的值具有物理解释：发光物体的对象半径。也就是小于该值的物体表面将穿过光源本身，这是不可能的。</p>
<p>问题二：发生在距离相对较大的位置，问题不在于效果而是性能。因为虽然距离增加光强减少，但永远不会是0。为了避免光在边界处产生锋利的截断，最优的选择还是修正函数的导数与值在同一个距离值时到达0。一种解决方法是将方程乘以一个具备所需性质的windowing function。虚幻和寒霜都使用了这样一个函数：</p>
<p>$$<br>
f_{\text{win}}(r)=(1-(\frac{r}{r_{\text{max}}})^4)^{+2}\tag{5.14}<br>
$$</p>
<p>$+2$的意思是先判断值若为负，就先归零，然后再做平方操作。下图显示了windowing function和两者相乘的结果：</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/10/5.6-1.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter5"></figure><p>应用程序的要求会影响我们选择什么样的函数。例如，当距离衰减函数在一个相对低的空间频率（spatial frequency ）中采样时（如光照贴图和逐顶点光照），在$r_{\text{max}}$处导数为0是很重要的。CryEngine不使用光照贴图或顶点光照，所以它做了更简单的调整，把$0.8r_{\text{max}}$到$r_{\text{max}}$间的衰减转换成线性衰减。</p>
<p>对于有些应用，匹配平方反比曲线不是那么重要，所以简化前面的函数如下：</p>
<p>$$<br>
\boldsymbol{c}_{\text{light}}(r)=\boldsymbol{c}_{\text{light}_0}f_\text{dist}(r)\tag{5.15}<br>
$$</p>
<p>$f_\text{dist}(r)$可以叫做距离衰减函数，受性能影响。如Just Cause 2需要计算成本极低的光照。需要一个简单的距离衰减函数，但也要避免顶点光照的失真：</p>
<p>$$<br>
f_{\text{dist}}(r)=(1-(\frac{r}{r_{\text{max}}})^2)^{+2}\tag{5.16}<br>
$$</p>
<p>其他情况下，距离衰减函数也可能由创造性因素驱动。比如虚幻引擎有两种光衰减：一个平方反比模式（5.12），以及指数衰减模式，可以调整以创建各种衰减曲线。游戏古墓丽影使用样条编辑器创作衰减曲线，从而更好的控制曲线的形状。</p>
<h5 id="spotlights">Spotlights（聚光灯）</h5>
<p>不同于点光源，几乎所有真实世界的光源的照明都随着方向和距离变化。这种变化可以描述为方向衰减函数$f_{\text{dir}}(\boldsymbol{l})$，它与距离衰减函数相结合，以定义光强度的变化：</p>
<p>$$<br>
\boldsymbol{c}_{\text{light}}=\boldsymbol{c}_{\text{light}_0}f_{\text{dist}}(r)f_{\text{dir}}(\boldsymbol{l})\tag{5.17}<br>
$$</p>
<p>不同的$f_{\text{dir}}(\boldsymbol{l})$可以产生不同的效果。聚光灯的方向衰减函数在聚光灯的方向矢量周围具有旋转对称性，因此可以可以写为$\boldsymbol{s}$与反转光矢量$-\boldsymbol{l}$之间的角度$\theta_s$表示的函数。</p>
<p>大多数聚光灯函数使用由$\theta_s$描述，这是在着色中最常见的形式。聚光灯通常具有本影角$\theta_u$（umbra angle），它限制光，使得所有$\theta_s\ge\theta_u$都有$f_{\text{dir}}(\boldsymbol{l})=0$。该角度可以用类似先前所见$r_{\text{max}}$类似的方法进行剔除操作。还有半影角$\theta_p$（penumbra angle），定义了光锥内部具有完全光照强度的内椎部分。如图：</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/10/5.7-1.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter5"></figure><p>方向衰减函数的定义基本相似。例如，应用在寒霜引擎（Frostbite）中的函数$f_{\text{dir}_F}(\boldsymbol{l})$和应用在<code>three.js</code>中的$f_{\text{dir}_T}(\boldsymbol{l})$：</p>
<p>$$<br>
\begin{align}<br>
t&amp;=(\frac{\cos\theta_s-\cos\theta_u}{\cos\theta_p-\cos\theta_u})^\mp,\\<br>
f_{\text{dir}_F}(\boldsymbol{l})&amp;=t^2,\\<br>
f_{\text{dir}_T}(\boldsymbol{l})&amp;=smoothstep(t)=t^2(3-2t)<br>
\end{align}<br>
\tag{5.18}<br>
$$</p>
<p><code>smoothstep()</code>是一个在着色中常用于平滑差值的函数，在多数着色语言中内置。</p>
<h5 id="otherpunctuallights">Other Punctual Lights（其他点源光）</h5>
<p>还有许多其他方式可以使点源光的光值变化。$f_{\text{dir}}(\boldsymbol{l})$函数并不限于上述讨论的简单衰减函数，它可以表示任何类型的方向变化，包括从真实世界测量的以表格形式展示的复杂形势。照明工程学会（IES）为此类测量定义了标准格式。其配置文件可以从许多照明制造商处获得，并已经应用于游戏如Killzone: Shadow Fall等。古墓丽影中有一种点源光，它沿着x,y,z轴应用独立的衰减函数。同样是古墓丽影中，可以使用曲线随时间改变光强度，比如用来产生闪烁的火炬。</p>
<h4 id="523otherlighttypes">5.2.3 Other Light Types（其他光源）</h4>
<p>定向光和点源光的主要区别在于如何计算光的方向$\boldsymbol{l}​$。通过使用其他方法来计算光的方向可以定义不同类型的光。例如古墓丽影中的胶囊光（capsule light）使用线段为源，对于每个着色点，使用线段上距离最近的点计算光源方向。</p>
<p>到目前为止讨论的光都为抽象光，但实际上光源是具有尺寸和形状的，他们从多个方向照射表面着色点。在渲染中，这种灯称作区域光照（Area-light），在实际使用中呈上升趋势。区域光照技术分为两类：使用区域光被部分遮挡来模拟阴影的边缘软化和模拟区域光对表面着色的影响。第二类照明对于光滑的镜面最为明显，其中光的形状和大小可以在其反射中清晰的辨别。考虑光域近似值的技术已经被开发出来了，相对效率更高，可以看到日后广泛的应用。</p>
<br>
<h3 id="53implementingshadingmodels">5.3 Implementing Shading Models（实现着色模型）</h3>
<h4 id="531frequencyofevaluation">5.3.1 Frequency of Evaluation（求值频率）</h4>
<p>在设计着色过程的执行时，计算需要根据他们的求值频率（frequency of evaluation）进行分割。首先确定给定计算结果在整个绘制过程中是否恒定。在这种情况下，计算可以在CPU上由应用程序执行并将结果以uniform shader输入的形式传递给图形API。</p>
<p>即使这一类别中，也有很多可能的frequency of evaluation，从&quot;once ever&quot;开始。这种情况最简单的一个例子是着色方程中常量子表达式，只要是很少有变化因素的计算都可以应用，如硬件配置和安装选项等。甚至在编译着色器时可以计算出结果的，就不需要设置一个uniform shader输入。或者，计算可以在离线的预处理pass中，在安装时或应用程序加载时进行。</p>
<p>另一种情况是着色计算的结果在应用程序运行时变化的，但是由于缓慢不需要每帧更新的。如，取决于虚拟世界中时间的照明因素。如果计算成本很高，可能需要在多帧上分摊计算压力。</p>
<p>再有其他情况包括每帧执行一次的计算，例如串联视图和透视矩阵；或每个模型一次，例如更新依赖于位置的模型照明参数；又或者每次绘制调用一次，如更新模型中每种材质的参数。按照求值频率对uniform shader输入进行分组对应用程序的执行效率很有用，并且还可以通过减少每帧更新提高GPU性能。</p>
<p>如果着色计算的结果在绘制调用中发生改变，则无法通过uniform shader输入将其传入着色器。相反，它必须由第三章中描述的一个可编程着色器阶段计算，如果需要，可以通过不同的着色器输入传递到其他阶段。理论上，阴影计算可以在任何可编程阶段上执行，每个阶段对应的求值频率：</p>
<ul>
<li>Vertex Shader：每个曲面细分前的顶点求值一次</li>
<li>Hull Shader：每个表面patch求值一次</li>
<li>Domain Shader：每个曲面细分后的顶点求值一次</li>
<li>Geometry Shader：每个图元求值一次</li>
<li>Pixel Shader：每个像素求值一次</li>
</ul>
<p>实践中，着色通常在逐像素的在Pixel Shader中计算，但计算着色器的使用现在正越来越普遍。其他阶段主要用于几何操作，如变换和变形。逐顶点的计算在低密度的顶点分布时容易产生问题，如下图（左为逐顶点计算结果，中间为逐像素计算结果，右图为顶点分布情况）：</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/10/5.9-1.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter5"></figure><p>如果拆分到顶点和像素两个阶段分别计算高光和其余的部分，理论上可以节省计算也不会产生视觉上的失真问题。但在实践中并不好。</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/10/5.10-1.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter5"></figure><p>注意，虽然顶点着色器始终生成单位长度的表面法线，差值后也会改变其长度，如上图左侧，所以需要在像素着色器中将法线重新标准化。即使这样，顶点着色器生成的法线长度仍然很重要。如果法线长度在顶点间变化显著，这将使插值偏斜。所以要在插值前后各使用一次标准化，即在顶点着色器和像素着色器中。</p>
<p>与表面法线不同，指向特定位置的矢量通常不进行插值（如观察方向和光源方向）。相反，这里的插值在像素着色器中进行，然后标准化。如果需要使用这些向量的插值，请不要事先标准化，这将产生错误的结果，如图：</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/10/5.11-1.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter5"></figure><p>当在顶点着色器中进行坐标变换的时候，我们需要判断要统一成什么坐标系是适当的。这通常是基于整个渲染系统（考虑性能，灵活性，简单性等）做出的判断。如，若渲染场景包含大量灯光，则可以选择世界空间以避免灯光位置变换。或者，相机空间可能是首选，以更好的优化和观察方向相关的像素着色器操作，并可能提高精度。</p>
<p>虽然大多数着色器的实现遵循上述观点，但肯定还有例外。如有些应用使用逐图元的着色用于风格化表现，这种风格通常被称为平面着色（flat shading），如下图（肯塔基0号路）：</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/10/kentuckyroutezero-gas-station-night-1.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter5"></figure><p>原则上可以在几何着色器中执行平面着色，但近期的实现都通常是在顶点着色器中完成的。这是通过将每个图元的属性与其第一个顶点相关联，然后禁用顶点插值实现的。禁用插值导致了第一个顶点的值传递个图元中所有像素。</p>
<h4 id="532implementationexample">5.3.2 Implementation Example（实现示例）</h4>
<p>这里的实现很像公式5.1，Gooch模型，但是处理成可以用于多光源的情况，如下：</p>
<p>$$<br>
\boldsymbol{c}_{\text{shaded}}=\frac12\boldsymbol{c}_{\text{cool}}+<br>
\sum\limits^n_{i=1}(\boldsymbol{l}_i\cdot\boldsymbol{n})^+\boldsymbol{c}_{\text{light}_i}(s_i\boldsymbol{c}_{\text{highlight}}+(1-s_i)\boldsymbol{c}_{\text{warm}})<br>
\tag{5.19}<br>
$$</p>
<p>以及中间过程：</p>
<p>$$<br>
\begin{align}<br>
\boldsymbol{c}_{\text{cool}}&amp;=(0,0,0.55)+0.25\boldsymbol{c}_{\text{surface}},\\<br>
\boldsymbol{c}_{\text{warm}}&amp;=(0.3,0.3,0)+0.25\boldsymbol{c}_{\text{surface}},\\<br>
\boldsymbol{c}_{\text{highlight}}&amp;=(2,2,2),\\<br>
\boldsymbol{r}_{\text{i}}&amp;=2(\boldsymbol{n}\cdot\boldsymbol{l}_i)\boldsymbol{n}-\boldsymbol{l}_i,\\<br>
s_i&amp;=(100(\boldsymbol{r}_i\cdot\boldsymbol{v})-97)^\mp<br>
\end{align}<br>
\tag{5.20}<br>
$$</p>
<p>这个等式符合公式5.6提供的多光源结构，这里重复一下：</p>
<p>$$<br>
\boldsymbol{c}_{\text{shaded}}=f_{\text{unlit}}(\boldsymbol{n,v})<br>
+<br>
\sum\limits^n_{i=i}(\boldsymbol{l_i}\cdot\boldsymbol{n})^+<br>
\boldsymbol{c}_{\text{light}_i}f_{\text{lit}}(\boldsymbol{l_i,n,v})<br>
\tag{5.6}<br>
$$</p>
<p>在这里：</p>
<p>$$<br>
\begin{align}<br>
f_{\text{unlit}}(\boldsymbol{n,v})&amp;=\frac12\boldsymbol{c}_{\text{cool}},\\<br>
f_{\text{lit}}(\boldsymbol{l_i,n,v})&amp;=s_i\boldsymbol{c}_{\text{highlight}}+(1-s_i)\boldsymbol{c}_{\text{warm}}<br>
\end{align}<br>
\tag{5.20}<br>
$$</p>
<p>在大多典型渲染应用中，诸如$\boldsymbol{c}_{\text{surface}}$这类的变化的属性值一般存在顶点数据中，或者更常见的存在纹理中。此示例为了简单假设这个值不变。</p>
<p>此实现将使用着色器的动态分支（dynamic branching）功能来遍历所有光源。虽然这种简单的方法可以很好地适用于简单场景，但是对于大型或者几何复杂的场景还是不行的（放到后面章节说）。为了简单，我们只支持点光源。</p>
<p>着色模型不是孤立实现的，而是在渲染框架的上下文中实现的。示例在一个简单的WebGL 2应用中实现。秉承由内向外的顺序，先是像素着色器，然后是顶点，最后是应用程序段的API调用。</p>
<pre><code class="language-glsl">// 首先定义输入输出
// 像素着色器输入匹配顶点着色器输入，也就是这些输入是差值后的结果
// 顶点坐标和顶点法线均为世界坐标
in vec3 vPos；
in vec3 vNormal;
out vec4 outColor;

// uniform inputs
// 这里为了简介期间，我们只显示两个与光源相关的
// 点光源，这里定义为vec4而不是vec3是为了符合GLSL std140标准

struct Light {
	vec4 position;
    vec4 color;
};
struct LightUBlock {
    Light uLights[MAXLIGHTS];
};

// draw call中实际有效的灯光数量
uniform uint uLightCount;
</code></pre>
<p>接下来我们看一下pixel shader的代码：</p>
<pre><code class="language-GLSL">// 这里的uWarmColor和uFUnlit都是uniform inputs
// GLSL内置函数：
// - reflect(v1,v2) 得出v1在v2定义平面上的反射
// - clamp(x,min,max) 
// - mix(v1，v2，s) 基于第三个值在前两个值间插值
// - normalize(v) 归一化

vec3 lit(vec3 l, vec3 n, vec3 v) {
    vec3 r\_l = reflect(-l, n);
	float s = clamp(100.0 * dot (r\_l, v) - 97.0, 0.0, 1.0);
	vec3 highlightColor = vec3(2, 2, 2);
	return mix(uWarmColor, highlightColor, s);
}

void main() {
    vec3 n = normalize(vNormal);
    vec3 v = normalize(uEyePosition.xyz - vPos);
    outColor = vec4(uFUnlit, 1.0);
    
    for (uint i = 0u; i &lt; uLightCount; i++) {
        vec3 l = normalize(uLights[i].position.xyz - vPos);
        float Ndl = clamp(dot(n, l), 0.0, 1.0);
        outColor.rgb += Ndl * uLights[i].color.rgb * lit(l,n,v);
    }
}
</code></pre>
<p>现在我们来看一下vertex shader：</p>
<pre><code class="language-GLSL">// 我们这里忽略uniform inputs，研究一下输入输出
// 注意这里的输出和像素着色器中的输入要匹配
layout (location=0) in vec4 position;
layout (location=1) in vec4 normal;
out vec3 vPos;
out vec3 vNormal;

// ...

void main () {
	// 坐标变换的操作
    vec4 worldPosition = uModel * position;
    vPos = worldPosition.xyz;
    vNormal = ( uModel * normal ).xyz;
    
    // 注意这里vNormal并没有执行标准化，因为原始网格的长度就是1
    // 也并没有执行缩放或者顶点混合一类会改变法线的操作
    
    // gl\_position是由光栅化阶段使用的特殊变量
    // 也是所有顶点着色器的必要输出
    gl\_Position = viewProj * worldPosition; 
}
</code></pre>
<p>在应用程序中，每个着色器阶段都是单独设置的，然后绑定到程序对象上。</p>
<p>以下是pixel shader在应用程序中设置的代码（使用WebGL API）：</p>
<pre><code class="language-javascript">var fSource = document.getElementById(&quot;fragment&quot;).text.trim();

var maxLights = 10;
fSource = fSource.replace(/MAXLIGHTS/g,maxLights.toString());

var fragmentShader = gl.createShader(gl.FRAGMENT\_SHADER);
gl.shaderSource(fragmentShader,fSource);
gl.compileShader(fragmentShader);
</code></pre>
<h4 id="533materialsystems">5.3.3 Material Systems（材质系统）</h4>
<p>渲染框架通常很少只实现一个着色器。需要专门的系统处理应用程序使用的各种材质，着色模型和着色器。材质有时也能描述非视觉属性，比如碰撞。</p>
<p>着色器和材质并不一定是一一对应关系。在不同渲染条件下，相同材质可能对应不同着色器。着色器也可以由多种材质共享。最简单的例子是参数化的材质，由材质模板和材质实例构成。模板可以表述为一个类，而实例对应每个参数应用后对应的特定材质。一些渲染框架允许更复杂的分层结构，材质模板由多个级别的模板生成（如虚幻）。</p>
<p>参数可以通过uniform输入来解析，或者在编译时替换值解析。常见的编译时参数是Boolean值，用于开关指定材质特征。艺术家可以通过材质面板进行操作，如减少远距离物体的一些效果。</p>
<p>材料系统最重要的任务之一是将各种着色器功能划分为单独的元素并控制它们的组合方式。在许多情况下，这种组合方式是有用的，比如：</p>
<ul>
<li>用刚性变换，顶点混合，变形，曲面细分，实例化和裁剪拆分处理几何过程，单独创作并按需组合。</li>
<li>使用如像素舍弃或者像素融合来组合表面着色。</li>
<li>着色模型本身用于组合。</li>
<li>选择材质中独立的特征，选取逻辑，和其余着色器部分。</li>
<li>先计算光源和参数的结果，然后和着色模型组合。</li>
</ul>
<p>如果图形API提供着色器代码的模块化作为核心功能该多方便啊。遗憾的是与CPU代码不同，GPU着色器不允许代码片段的后编译链接。每个着色器阶段作为一个单元进行编译。这种阶段的分离提供了有限的模块化，这符合上述列表的第一条。但是符合得并不完美，因为每个着色器也执行其他操作，其他类型的合成也仍需处理。鉴于这些限制，材质系统实现这些组合的唯一方法是在源代码级别。这主要涉及字符串操作，例如链接和替换，通常通过c风格的预处理指令执行，如<code>#include</code>,<code>#if</code>和<code>#define</code>。</p>
<p>早期的渲染系统具有较少数量的着色器变体，并且通常每个都是手动编写的。随着变体数量的增加，这种方法变得不切实际。这就是模块化和组合性如此重要的原因。</p>
<p>设计用于处理着色器变体的系统时要解决的第一个问题是 选择在运行时通过动态分支执行还是在编译时通过条件预处理执行。旧的硬件可能不支持运行时，所以都通过编译时完成。如今，许多功能性变量，例如灯的数量，都是在运行时处理的。但向着色器过多会导致寄存器使用数量的增加以及性能的降低。所以编译时变量仍然很有价值。</p>
<p>举例说，我们设想一个支持3种不同类型灯光的应用程序。点光，方向光和聚光灯。聚光灯支持列表照明模式和其他复杂功能，需要大量的着色器代码实现。然而，假设聚光灯使用较少，应用中不到5%的灯使用这种类型。以前的处理方法是针对三种光每种可能的数量和组合进行编译，以避免动态分支。现在不需要这样，但是编译两个单独的变化量仍然是有益的，一个用于聚光灯数量大于等于1时，另一个用于聚光灯数量为0时。这样第二种情况不但经常被使用，而且有较低的寄存器占用，从而获得高性能。</p>
<p>现代材质系统同时采用两种方法，尽管不需要完全使用编译时，但总体复杂性和变体数量仍在增加，因此仍需要编译大量着色器变体。如<em>Destiny: The Taken King</em>中的某些区域，单帧中使用了超过9000个编译的着色器变体。可能的变体数量可能更多，如Unity渲染系统具有接近1000亿可能的着色器变体。只编译实际使用的变体，但必须重新设计着色器编译系统以处理大量可能的变体。</p>
<p>材质系统的设计人员采用不同的策略来实现这些设计目标。虽然这些策略有时在系统架构方面是互斥的，但这些策略可以在同一系统中进行组合。策略包括：</p>
<ul>
<li>代码重用：在共享文件中实现函数，使用<code>#include</code>访问。</li>
<li>减法：一个着色器，通常称作<em>übershader</em>或者<em>supershader</em>，聚合了大量的功能，使用编译时和动态分支的组合删除未使用的部分，并在互斥的选择中切换。</li>
<li>加法：各种功能被定义为具有输入和输出的连接器节点并组合在一起。类似于代码重用。（见下图，虚幻引擎中的材质编辑器）</li>
<li>基于模板：定义一个接口。这比添加策略更正式，通常用于更大的功能块。接口一个常见例子是着色模型参数的计算和着色模型本身计算的分离。虚幻引擎具有不同的材质域，包括用于计算着色模型参数的Surface domain和Light Function域，给定光源计算一个标量值用于调整$\boldsymbol{c}_{\text{light}}$。Unity中也存在类似的表面着色器结构，注意延迟着色技术强制执行一个类似的结构，由G缓冲区作为接口。</li>
</ul>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/10/steven-downer-screen-monitor-1-1.jpg" class="kg-image" alt="Realtime Rendering 4th notes - chapter5"></figure><p>除了组合之外，现代材质系统还有其他几个重要的设计考虑因素，例如需要支持多平台且着色器代码重复最少。这包括考虑平台，着色语音和API之间的性能和功能差异。Destiny的着色系统是这类问题的代表性解决方案。它使用专用的预处理器层，该层采用自定义着色语言方案编写的着色器。通过与平台无关的材质，自动转换为不同着色语言和实现。虚幻和Unity都有类似的系统。</p>
<p>材质系统还需要确保良好的性能。除了编辑着色变体外，还有些常见的优化方法。Destiny着色系统和虚幻引擎都自动检测draw call中的常量计算，并将其移出着色器。另一个例子是Destiny中使用的scoping system，用于区分不同频率更新的常量（如每帧一次，每个光照一次，每个对象一次），并在适当的时间更新每组常量以减少API开销。</p>
<h3 id="54aliasingandantialiasing">5.4 Aliasing and Antialiasing（失真与抗锯齿）</h3>
<p>三角形以像素显示时，在屏幕网格单元格中以存在或不存在显示，则产生锯齿（the jaggies）或动画时的<em>the crawlies</em>。这种问题更正式的叫法为失真（Aliasing），而抗锯齿技术是为了解决它存在的。</p>
<h4 id="541samplingandfilteringtheory">5.4.1 Sampling and Filtering Theory （采样和过滤器理论）</h4>
<p>渲染图像本质上是一个采样任务。这是因为图像的生成是对三维场景进行采样以获得图像中每个像素（离散像素阵列）颜色值的过程。使用纹理映射，必须在不同条件下重新采样纹理像素以达到最好效果。为了生成动画的图片序列，需要以均匀时间间隔采样动画。为了简单起见，下述大多数材质以一维方式呈现。理论对2D维度同样适用。</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/10/5.15-1.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter5"></figure><p>上图展示了如何以均匀间隔对连续信号进行采样的过程，即离散化。采样过程的目标是以数字的方式表示信息。这样可以减少信息量。但是，需要重建采样信号以恢复原始信号，这是通过过滤采样信号实现的。失真出现于采样完成后。</p>
<p>一个典型的失真现象是旋转的车轮，由于轮辐移动速度比移动速度快得多，因此可能视觉上旋转不正确，可能向后或者不转，如下图所示，这称为时间混叠（temporal aliasing）。</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/10/5.16-1.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter5"></figure><p>一个常见的图形学例子是光栅化线段的锯齿闪烁，闪烁称作<em>fireflies</em>，以及棋盘格图案的贴图缩小时。</p>
<p>当信号以太低的频率采样时发生混叠，采样信号的频率看起来低于原始信号的频率，如下图所示。对于要正确采样的信号，采样频率必须要大于被采样信号最大频率的两倍。这被称为<a href="https://zh.wikipedia.org/wiki/%E9%87%87%E6%A0%B7%E5%AE%9A%E7%90%86">采样定理</a>，采样频率叫做奈奎斯特率或奈奎斯特极限（Nyquist rate/limit）。使用了术语最大频率意味着信号必须有频带限制。</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/10/5.17-1.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter5"></figure><p>一个三维场景在以点采样渲染时几乎从没有频带限制。三角形边缘，阴影边界和其他现象的边缘产生不连续变化的信号，因此产生无限的频率。此外，无论采样的密度如何，物体还是可以足够小，以至于不会被采样。我们还总是用点采样，所以不可能完全避免混叠问题。不过有时我们可以知道信号何时有频带限制。一个例子是将纹理应用于表面，相对于像素的采样率，我们可以计算纹理的采样率。如果此频率低于奈奎斯特极限，则可以正确采样，若频率太高则需要算法限制纹理。</p>
<h5 id="reconstruction">Reconstruction（重建）</h5>
<p>给定有频带限制的采样信号，我们现在将讨论如何使用过滤器将采样信号重建为原始信号，三种常见的滤波器如下图所示，注意滤波器的面积始终为1，否则可能会使重建信号增大或减小（box filter，tent filter，sinc filter）。</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/10/5.18-1.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter5"></figure><p>box filter是最差的过滤器，如图，box filter放置在每个样本点上，进行缩放，使得最高点与样本点重合。所有这些缩放位移后的盒子函数总和为最终结果：</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/10/5.19-1.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter5"></figure><p>tent filter在点之间进行插值，由于结果是连续的，所以优于box filter：</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/10/5.20-1.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter5"></figure><p>tent filter的平滑度很差，为了获得完美重建，需要使用立项的低通滤波器（low-pass filter）。信号的频率分量是一个正弦波：$\sin(2\pi f)$，其中$f$是该分量的频率。所以，低通滤波器移除频率高于滤波器定义的特定频率的所有频率分量。 直观地，低通滤波器去除了信号的尖锐特征，即滤波器模糊了它。 理想的低通滤波器是sinc滤波器：</p>
<p>$$<br>
sinc(x)=\frac{\sin(\pi x)}{\pi x}\tag{5.22}<br>
$$</p>
<p>傅里叶分析理论（<a href="https://zhuanlan.zhihu.com/p/19763358">这里附上一篇文章</a>）解释了为什么正弦滤波器是理想的低通滤波器。理想的低通滤波器是<a href="https://zh.wikipedia.org/wiki/%E9%A0%BB%E5%9F%9F">频域</a>中的box filter，当它与信号相乘时，它会去除滤波器宽度以上的所有频率。 将box filter从频域转换为<a href="http://xx-ma.com/rtr4-5/">空间域</a>会产生$sinc$函数。 同时，乘法运算被转换为卷积函数。</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/10/5.21-1.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter5"></figure><p>使用sinc filter后消除了频率高于采样率1/2的所有正弦波。假设采样频率是$f_s$，也就是采样间的距离为$1/f_s$，此时完美的过滤器是$sinc(f_s x)$，它消除了所有高于$f_s/2$的频率。但是sinc滤波器的宽度是无限的，在一些地方为负数，所以在实践中很少使用。</p>
<p>有一些广泛使用的滤波函数介于这些极端之间，如高斯滤波器。</p>
<p>在使用任何滤波器后，我们获得了连续的信号。然而我们不能直接显示连续信号，但我们可以使用它们将连续信号重新采样到另一种尺寸，即放大信号或减小信号。</p>
<h5 id="resampling">Resampling（重采样）</h5>
<p>重采样用于放大或缩小采样信号。 假设原始样本点位于整数坐标（0, 1, 2...），即样本之间的间隔为单位长度。此外，假设在重新采样之后，我们希望新样本点均匀地定位，样本之间的间隔为a。对于a&gt; 1，进行缩小（下采样），对于a&lt;1，进行放大（上采样）。</p>
<p>放大相对较容易，只需以期望的间隔重新采样重建的信号：</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/10/5.22-1.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter5"></figure><p>缩小时，就不能这么做了。原始信号频率对于采样频率过高以至于避免不了混叠的情况。这时需要用$sinc(x/a)$重构，然后对结果重采样。在图像上类似于先模糊它（去高频），然后以低分辨率重采样图像：</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/10/5.23-1.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter5"></figure><h4 id="542screenbasedantialiasing">5.4.2 Screen-Based Antialiasing（基于屏幕的抗锯齿）</h4>
<p>如果采样和过滤不当，三角形的边缘会产生明显的失真。阴影边界，镜面高光以及颜色快速变化的地方可能也会导致类似的问题。这些算法基于共同的线程，都是基于屏幕的，也就是他们仅对渲染管线输出的样本进行操作。没有最好的抗锯齿技术，它们在质量，捕获尖锐细节，移动期间的表现，内存消耗，GPU需求和速度方面各有不同优势。</p>
<p>之前提到的三角形边缘锯齿问题，如果每个屏幕网格单元使用更多样本并以某种方式混合这些样本，可以计算出更好的像素颜色：</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/10/5.24-1.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter5"></figure><p>基于屏幕的抗锯齿方案的一般策略，是对屏幕进行一种采样模式，然后对样本进行加权求和已产生像素颜色$p$：</p>
<p>$$<br>
\boldsymbol{p}(x,y)=\sum\limits^n_{i=1}w_i\boldsymbol{c}(i,x,y)\tag{5.23}<br>
$$</p>
<p>其中n是采样数量，$\boldsymbol{c}(i,x,y)$是样本颜色，$w_i$是在区间$[0,1]$中的权重，每份样本都会为最终的像素颜色提供影响。样本的位置取决于它的序号1,...,n，并且函数也可能会用到像素位置的整数位置$(x,y)$。换句话说，每个样本在屏幕网格中采样的位置是不同的，并且是可选的，采样模式在像素间变化。在实时渲染系统中以及大多数其他渲染系统中，样本通常是点样本。所以，函数$\boldsymbol{c}$可以看成是两个函数。首先，一个函数$\boldsymbol{f}(i,n)$检索需要采样的屏幕上浮点$(x_f,y_f)$的位置。然后对该位置进行采样，即检索该精确点处的颜色。选择了采样方案且渲染管线被配置去计算特定子像素位置处的样本，通常基于每帧（每应用）的设定。</p>
<p>抗锯齿中的另一个变量是$w_i$，即权重，这些权重的总和为1。实时渲染系统中使用的大多数方法给他们的样本提供了均匀的权重，即$w_i=\frac1n$。图形硬件的默认模式，即以像素中心为单个样本的模式，是上述方程最简单的情况。</p>
<p>对每个像素采集多于一个完整样本的抗锯齿算法称作超级采样（supersampling）或过采样（oversampling）。概念上最简单的全场景抗锯齿（full-scene antialiasing，FSAA）也称为超级抗锯齿（supersampling antialiasing，SSAA），以更高的分辨率渲染场景，然后过滤相邻的样本以创建图像。例如，假设需要1280×1024像素的图像。如果在屏幕外渲染2560×2048的图像，然后在屏幕上平均每个2×2像素区域，得到最终图像。每个像素有4个样本，使用box filter进行过滤。这种方法的成本很高，因为所有子样本都必须着色过滤，且每个样本携带着一个z-buffer。FSAA的主要优势是简洁。这个方法可以简化成仅在一个屏幕轴向以二倍采样，称为1×2或2×1超级采样。通常，使用2次幂分辨率和box filter是为了简单。NVIDIA的动态超分辨率（dynamic super resolution）功能是一种更精细的超级采样形式，场景以更高的分辨率渲染且应用了13样本的高斯滤波器用于生成显示的图像。</p>
<p>一个与超级采样相关的采样方法基于累计缓冲器（accumulation buffer）的想法。该方法使用一个和原始图像分辨率一样大小的缓冲区，但每个颜色通道都会多一些bits。为了获得2×2的采样，生成了4个图像，根据需要在屏幕x，y方向移动半个像素。每个生成的图像都是基于在网格单元内不同位置的采样。每帧重复渲染场景然后把结果拷贝到屏幕所花费的额外成本对于实时渲染系统而言非常昂贵。当性能不是那么重要的时候，这可以用来生成更高质量的图像，因为每个像素都可以使用任意数量的摆放在任何地方的样本。累积缓冲区曾经是一个独立的硬件，他直接在OpenGL API中受到支持，但在3.0版本中弃用。在现代GPU上，累积缓冲区可以通过pixel shader通过更高精度的颜色格式实现。</p>
<p>当物体边缘，镜面高光和锐利阴影等现象导致颜色突然变化时，需要额外的样本。可以让阴影边缘柔和，高光更平滑来避免失真。可以增加特定物体类型的尺寸，如电线，从而保证他们在任何方向都至少覆盖着1像素。物体边缘是主要的采样问题。可以通过渲染时检测对象边缘并考虑他们的影响，但这通常更慢且不稳定。但是，保守光栅化（conservative rasterization）和光栅顺序视图（rasterizer order views）开辟了新的可能性。</p>
<p>超级采样和累积缓冲之类的技术通过生成完全由单独计算着色和深度来实现的样本来工作。增益较低成本较高，因为每个样本都需要通过像素着色器。</p>
<p>多重采样抗锯齿（MSAA）减少了对每个像素一次的着色和样本间共享结果的消耗。例如，像素可以拥有每个片段四个$(x,y)$样本位置，每个样本具有他们自己的颜色和深度，但是对于应用于像素的每个片段，像素着色器只计算一次。如果片段覆盖了所有MSAA样本位置，则在像素中心处计算着色样本。相反如果覆盖样本位置较少，则可以移动着色样本的位置以更好表示覆盖位置。例如，这样可以避免着色采样从纹理边缘脱离。这种调整位置称为质心采样（centroid sampling） 或质心插值（centroid interpolation），如果启用，由GPU自动完成。质心采样避免了脱离三角形（off-triangle）的问题但可能导致微分计算返回不正确的值。</p>
<p>MSAA着重于更快速的对片段于像素中的覆盖进行着色以及共享着色结果。通过进一步解耦采样和覆盖操作，节省了更多内存。反过来说，更少的内存操作更快的渲染速度。NVIDIA于2006年推出了覆盖采样抗锯齿（coverage sampling antialiasing ，CSAA），AMD随后推出了增强质量抗锯齿（enhanced quality antialiasing，EQAA）。这些技术通过仅存储片段的覆盖范围来高速采样。例如，EQAA的&quot;2f4x&quot;模式存储两个颜色和深度，与四个采样位置共享：</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/10/5.26-2.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter5"></figure><p>颜色和深度不再为特定采样位置存储而是存储为一个表。然后每个位置只需要一个bit来指定两个存储位置中哪一个和它关联。指定覆盖位置的样本决定了每个片段对最终像素颜色的贡献值。如果超过了存储颜色数量，则逐出之前存储过的一个颜色值然后标记样本为未知。这种样本在最终颜色值中没有贡献。对于大多数场景，只有很少一部分像素包含3个或者更多的使用不同着色方式的可见不透明片段，所以这种方法在实际应用中表现良好。不过，为了获取最高质量，Forza Horizon 2采用了4×MSAA，而不是效率更好的EQAA。</p>
<p>当所有的几何体都渲染到多样本缓冲区后，执行解析（resolve）操作。此过程将样本颜色平均在一起以确定最终颜色。需注意，在高动态范围颜色值（HDR）时当使用multisampling会出现问题。在这种情况下，为了避免失真一般需要先把值进行色调映射（tone-map）再解析。这是十分昂贵的，所以可以使用更简单的近似色调映射方法。</p>
<p>默认情况下，MSAA使用box filter进行解析。2007年ATI推出了custom filter antialiasing（CFAA），具有使用tent filter的能力。这种模式已经被EQAA取代。在现代GPU上，pixel shader或者compute shader都可以访问MSAA样本并使用所需的任何重建滤波器，包括从周围的像素样本中采集的滤波器。较宽的滤波器可以减少混叠，但会丢失清晰的细节。使用自定义过滤器比默认的box filter更消耗性能，而且更宽的过滤器内核也会导致增加样本的访问成本。</p>
<p>NVIDIA的内置TXAA支持类似的方式，于一个比一像素更宽的范围使用更好的重建滤波器以得到更好的结果。它和较新的MFAA（multiframe antialiasing）方案都使用temporal antialiasing（TAA），这是一类使用先前帧的结果改善图像的技术。它允许程序员每帧设置MSAA的采样模式。这种技术可以解决诸如旋转车轮之类的混叠问题，并且可以改进边缘的渲染质量。</p>
<p>想象一下，通过生成一系列图像来“手动”执行采样模式，其中每个渲染使用像素内的不同位置采样。这种偏移是通过在投影矩阵上附加微小的平移来完成的。生成的图像越多结果越好。这种使用多个偏移图像的概念在TAA算法中使用。一个图像的生成，可能使用MSAA或其他方法，并混合先前的图像，通常2-4帧即可。较旧的图像可以指数级的减少权重，不过如果观看者和场景不动就会有发光的效果，所以一般仅对上一帧和当前帧进行相等的加权。对于每个帧在不同的子像素位置取样的样本，这些样本的加权比单帧更好的估算了边缘覆盖。因此，使用最近两帧平均的系统可以得到更好的结果。每帧都不需要额外的样本，这很吸引人。甚至可以使用temporal sampling生成较低分辨率的图像，该图像被放大到显示器的分辨率。另外需要许多样本以获得良好结果的照明方法或者其他技术，也可以改为每帧使用更少的样本，因为结果将在若干帧结果上混合。</p>
<p>虽然在没有额外采样成本的前提下为静态场景提供了抗锯齿，但是这种算法有一些问题。如果帧的权重不均匀，则静态场景中的对象可以呈现出微光。快速移动的物体或快速的相机移动可能导致重影，即由于先前帧的贡献而留在物体后面的拖尾。重影的一种解决方案是对缓慢移动的对象执行这种抗锯齿。另一个重要的方法是使用重投影（reprojection）来更好的关联前一帧和当前帧的对象。在这种方案中，对象生成运动矢量，它存储在单独的速度缓冲区中。从当前像素位置减去矢量以找到该对象的表面位置在前一帧的像素颜色。当前帧中的表面上的样本不太可能被丢失。由于不需要额外样本，所以需要较少的额外工作，因此近年来人们对这种算法采用广泛。还有需要注意的是，延迟渲染（deferred shading）与MSAA和其他一些多重采样方法不兼容。还有大量的根据使用场景和目标不同的改善质量与避免失真的技术（参照原书后引用）。</p>
<h5 id="samplingpatterns">Sampling Patterns（采样模式）</h5>
<p>有效的采样模式是减少失真的关键因素。Naiman发现水平方向和垂直方向的边缘失真是让人最难受的，45度的其次。旋转网格超级采样（rotated grid supersampling，RGSS）使用旋转的方形图案在像素内提供更多像素给垂直和水平方向。</p>
<p>RGSS模式是拉丁超立方体或<em>N-rooks sampling</em>，也就是n个样本放置在一个n×n的网格内，每行每列都有一个样本。与常规的2×2采样模式相比，这种模式和常规的2×2模式相比，对于捕获近乎垂直和水平的边缘更好，边缘更容易覆盖偶数量的样本，给出更少的影响级别。</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/10/5.25-5-1.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter5"></figure><p>N-rooks是创建一个好的采样模式的开端，但这还不够。例如，样本可以都是沿子像素网格的对角线排列，这会对几乎平行于该对角线的边缘给出不好的结果：</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/10/5.27-1.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter5"></figure><p>为了更好的采样，我们希望避免将两个样本放在彼此附近，且样本均匀分布在整个区域。为了形成这样的模式，诸如<a href="https://zh.wikipedia.org/wiki/%E6%8B%89%E4%B8%81%E8%B6%85%E7%AB%8B%E6%96%B9%E6%8A%BD%E6%A0%B7">拉丁超立方采样</a>（Latin hypercube sampling，LHS）的分层采样技术与诸如抖动（jittering），<a href="https://baike.baidu.com/item/Halton%20sequence">霍尔顿序列</a>（Halton sequences）和<a href="https://en.wikipedia.org/wiki/Supersampling#Poisson_disc">泊松分布采样</a>（Poisson disc sampling）等其他方法的结合。</p>
<p>在实践中，GPU制造商通常将采样模式硬连接到其硬件中以进行多重采样抗锯齿。下图显示了一些实践中使用的MSAA采样模式（从左到右依次是2x,4x,6x(AMD)和8x(NVDIA)采样）：</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/10/1539344812973.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter5"></figure><p>对于Temporal antialiasing，覆盖模式是程序员想要的，因为样本位置可以逐帧变化。例如，Karis发现基本的Halton序列比任何GPU提供的MSAA模式都好使。Halton序列在空间中生成样本，这些样本看起来是随机的，但是差异很小，可就是说，它们很好的在空间中分布，没有一个聚簇在一起。虽然子像素网格的方式得到了更好的对每个三角形如何覆盖网格的近似，但它并不理想。场景可以由屏幕上任意小的对象组成，这就意味着没有采样率可以完美的捕获它们。如果这些小物体或特征形成了图案，则以恒定间隔采样会产生<a href="https://zh.wikipedia.org/zh/%E8%8E%AB%E5%88%97%E6%B3%A2%E7%B4%8B">莫尔条纹</a>（Moiré fringes ）和其他干涉图案。</p>
<p>一种解决方案是使用随机取样（stochastic sampling），他提供更随机的模式。想象一下远处的细齿梳子，每个像素包含几根齿。一个普通的采样模式可能导致严重的失真。拥有比较无序的采样模式可以打破这些模式。随机化倾向于用噪声代替重复的失真效应。具有较少结构的图案有帮助，但当重复像素到像素时它仍会出现混叠。一种解决方案是在每个像素处使用不同的采样模式，或者随时间改变每个采样位置。交错采样，每组像素具有一个不同的采样模式，在过去是几十年中偶尔会在硬件中得到支持。例如ATI的<em>SMOOTHVISION</em>允许每个像素多达16个样本和多达16个不同的用户定义采样模式，这些模式可以打乱后重复使用。使用交错随机采样可以最大程度减少每个像素使用相同模式时形成的混叠伪影。</p>
<p>一些其他GPU支持的算法值得注意。NVDIA的&quot;Quincunx&quot;是一种允许样本影响多个像素的实施抗锯齿方案。&quot;Quincunx&quot;（梅花形）指的是五个物体的排列，四个形成一个正方形，第五个在中心。梅花形多重采样抗锯齿使用此模式，将四个外部样本放在像素的角落。 每个角样本值被分配给其四个相邻像素。每个角样本权重为1/8，中心权重为1/2。这种共享使得，每个像素平均只需要两个样本，但结果明显优于双样本FSAA方法。这种模式近似于2D中的tent filter，优于box filter。</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/10/5.25-4-1.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter5"></figure><p>Quincunx采样也可以用于temporal antialiasing，通过使用每个像素一个样本。每一帧从前一帧的每个轴平移半个像素，平移的方向在帧之间交替。前一帧提供像素角上的样本，然后使用双线性插值快速计算每个像素的贡献值。结果与当前帧平均。该方案在对齐移动对象上的问题依然存在，但是该方案本身很容易编码，且仅适用一个样本，就得到了很好的效果。</p>
<p>当在单帧中使用时，Quincunx的成本较低。RGSS模式更好的捕获水平和垂直边缘。FLIPQUAD模式结合了这两种优势，最初是为了移动图形开发的。每个像素两个样本，质量类似于RGSS。如下图所示（把RGSS模式下的采样点位移到边界和其他像素共享）：</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/10/5.29-1.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter5"></figure><p>和Quincunx一样，双样本的FLIPQUAD模式也可以用于temporal antialiasing并扩展到两个帧。</p>
<h5 id="morphologicalmethods">Morphological Methods（形态学方法）</h5>
<p>失真通常来自边缘，如几何形，锐利阴影或明亮的高光形成的。可以利用与失真相关的知识结构来处理失真问题，以得到更好的效果。2009年，Reshetov提出了形态抗锯齿（morphological antialiasing，MLAA）。&quot;morphological&quot;指&quot;与结构或形状有关&quot;。Reshetov的论文重新研究了多重采样方法的替代方案，强调搜索和重建边缘。这种形式的抗锯齿作为后处理来执行。 也就是说，渲染以通常的方式完成，然后将结果传送到生成抗锯齿结果的过程。 自2009年以来，已开发出多种技术。那些依赖于额外缓冲区（深度法线等）的那些可以提供更好的结果，例如子像素重建抗锯齿（subpixel reconstruction antialiasing，SRAA），但后来仅适用于几何边缘的抗锯齿。分析方法，如几何缓冲抗锯齿（geometry buffer antialiasing，GBAA）和距离边缘抗锯齿（distance-to-edge antialiasing，DEAA），让渲染器计算关于三角形边缘所在位置的附加信息，如，边缘离像素中心的距离。一般只需要颜色缓冲区，这意味着他们还可以改善阴影，高光、或者各种之前应用的后处理技术（post-processing，如轮廓渲染）的边缘。例如，定向局部抗锯齿（directionally localized antialiasing，DLAA）基于垂直边缘应该水平模糊，水平边缘应该垂直模糊。</p>
<p>更精细的边缘检测形式试图找到可能包含任何角度边缘的像素并确定其覆盖范围。检查潜在边缘周围的邻域，目标是尽可能重建原始边缘所在的位置。然后可以使用边缘对像素的影响来混合相邻像素的颜色：</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/10/5.30-1.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter5"></figure><p>Iourcha等人通过检查MSAA样本来提高边缘识别的结果。边缘检测和混合可以提供比基于样本的算法更好的结果。例如，每个像素使用4个样本的技术只能提供5个混合级别（覆盖了几个），检测到边缘的部分可以拥有更多样本位置以得到更好的结果。</p>
<p>基于图像的算法有一些坑。首先，如果两个对象之间的色差低于算法的阈值，则可能检测不到边缘。存在三个或更多个不同表面重叠的像素难以解释。具有高对比度或高频元素的表面，其颜色在像素之间快速变化，可能导致算法错过边缘。特别是，当对其应用形态抗锯齿时，文本质量通常会受到影响。对象的角落可能是一个挑战，一些算法赋予它们圆润的外观。假设边缘是直的也会对曲线产生不利影响。 单个像素的改变可能导致重建边缘的方式发生大的变化，这可能会在帧到帧之间产生明显的伪像。改善该问题的一种方法是使用MSAA来改善边缘检测。</p>
<p>形态抗锯齿方案仅使用提供的信息。 例如，宽度上比像素宽的物体（例如电线或绳索）如果覆盖不到中心位置，可能在屏幕上具有间隙。 在这种情况下，采用更多样本可以提高质量；仅使用基于图像的抗锯齿是不行的。 此外，执行时间可以根据查看的内容而变化。</p>
<p>综上，基于图像的方法可以通过适度的内存和处理成本提供抗锯齿支持，因此它们被用于许多应用程序中。color-only的版本也与渲染管道分离，使其易于修改或禁用，甚至作为GPU驱动程序选项公开。两种最流行的算法是快速近似抗锯齿（fast approximate antialiasing，FXAA）和亚像素形态抗锯齿（subpixel morphological antialiasing，SMAA），部分原因是它们都提供了健硕且免费的为了多种硬件实现的源代码实现。两种算法都仅使用颜色作为输入，SMAA具有能够访问MSAA样本的优点。每个都提供了丰富的设置，在速度和质量之间进行权衡。成本通常在每帧1到2毫秒的范围内。最后，两种算法也可以利用temporal antialiasing。 Jimenez 提出了一种改进的SMAA实现，比FXAA更快，并描述了时间抗锯齿方案。最后推荐Reshetov和Jimenez 对形态学技术的研究及其在游戏中应用。</p>
<br>
<h3 id="55transparencyalphaandcompositingalpha">5.5 Transparency, Alpha, and Compositing（透明，alpha，合成）</h3>
<p>光线穿过半透明物体有许多不同的方式。 对于渲染算法，这些可以大致分为基于光的效果和基于视图的效果。 基于光的效果是指那些会使光线衰减或转向的物体，导致场景中的其他物体也被点亮并改变了渲染的方式。 基于视图的效果是半透明物体本身被渲染的效果。</p>
<p>本节将讨论最简单的基于视图的透明度，其中半透明对象充当其后面对象颜色的衰减器。 更精细的基于视觉和光的效果，例如磨砂玻璃，光的弯曲（折射），由于透明物体的厚度引起的光衰减，以及由于视角引起的反射率和透射率变化将在后面的章节中讨论。</p>
<p>透明效果的一种实现方法称为screen-door transparency。 我们的想法是使用像素对齐的棋盘填充的模式渲染透明三角形。 也就是说，三角形每隔一个像素渲染一次，从而使其后面的对象部分可见。 通常屏幕上的像素足够接近，棋盘图案本身不可见。 该方法的主要缺点是通常只能在屏幕的一个区域上令人信服地呈现一个透明对象。 例如，如果透明的红色对象和透明的绿色对象呈现在蓝色对象的顶部，则三种颜色中只有两种可以出现在棋盘图案上。 此外，50％的棋盘是有限的。 其他较大的像素掩模可用于提供其他百分比，但这些我们趋向于产生可检测的模式。 同样的想法用于剪切纹理边缘的抗锯齿，但在子像素级别，使用称为alpha to coverage的特征。</p>
<p>由Enderton等人介绍，随机透明度使用子像素screen-door遮罩结合随机采样。 通过使用随机点的模式来表示片段的α覆盖情况，创建合理但有噪声的图像。 见下图，每个像素需要大量样本才能使结果看起来合理，并且所有子像素样本都需要相当大的存储量。 有吸引力的是它不需要混合，并且抗锯齿，透明度和产生像素部分覆盖的任何步骤都由单一机制实现。</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/10/5.31-1.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter5"></figure><p>大多数透明度算法将透明对象的颜色与其后面对象的颜色混合在一起。 为此，需要α混合的概念。 当在屏幕上呈现对象时，RGB颜色和z缓冲深度与每个像素相关联。 还可以为对象覆盖的每个像素定义另一个称为alpha（α）的分量。 Alpha是描述给定像素的对象片段的不透明度和覆盖度的值。1.0的alpha表示对象是不透明的，完全覆盖了像素的感兴趣区域; 0.0表示像素根本不被遮挡，即片段完全透明。</p>
<p>像素的alpha可以表示不透明度，覆盖率或两者，取决于具体情况。 例如，肥皂泡的边缘可能覆盖了像素的四分之三，0.75，并且几乎是透明的，有9/10的光通过眼睛，也就是十分之一的不透明，0.1。 那么它的α将是0.75×0.1 = 0.075。 但是，如果我们使用MSAA或类似的抗锯齿方案，则样本本身会考虑覆盖范围。 四分之三的样品会受到肥皂泡的影响。 在这些样本中，我们将使用0.1不透明度的值作为alpha。</p>
<h4 id="551blendingorder">5.5.1 Blending Order（混合顺序）</h4>
<p>要使对象显示为透明，它将在现有场景的顶部呈现，其alpha小于1.0。 由对象覆盖的每个像素将从像素着色器接收结果RGBα（也称RGBA）。 将此片段的值与原始像素颜色混合通常使用over运算符完成：</p>
<p>$$<br>
\boldsymbol{c}_o=\alpha_s\boldsymbol{c}_s+(1-\alpha_s)\boldsymbol{c}_d\quad[\boldsymbol{over}\text{ operator}]\tag{5.24}<br>
$$</p>
<p>其中$\boldsymbol{c}_s$是透明对象（source）的颜色，$\alpha_s$是对象的alpha，$\boldsymbol{c}_d$是混合前的像素颜色（destination），$\boldsymbol{c}_o$是由于将透明对象放在现有场景上而产生的颜色。 渲染管线传入$\boldsymbol{c}_s,\alpha_s$，像素的原始颜色$\boldsymbol{c}_d$被$\boldsymbol{c}_o$替换。 如果输入的RGBα实际上是不透明的（$\alpha= 1.0$），则该等式简化为通过对象的颜色完全替换像素的颜色。</p>
<p>over运算符为渲染对象提供半透明的外观。使用over运算模拟薄纱面料的真实效果。 织物后面的物体的视图部分模糊：织物的线是不透明的。 在实践中，松散的织物具有随角度变化的α覆盖。</p>
<p>over运算符在模拟其他透明效果时并不好，最明显的是透过彩色玻璃或塑料观察。 在现实世界中保持在蓝色物体前面的红色滤光片通常使蓝色物体看起来很暗，因为该物体反射的光可以通过红色滤光片。 见下图（左为织物，右为滤光片）。当over用于混合时，结果是红色和蓝色的一部分加在一起。 最好将两种颜色相乘，并在透明物体上添加反射。</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/10/5.32-1.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter5"></figure><p>在基本的混合阶段运算符中，over是常用于透明效果的运算符。另一个操作是加法混合（additive blending），像素值被简单地求和：</p>
<p>$$<br>
\boldsymbol{c}_o=\alpha_s\boldsymbol{c}_s+\boldsymbol{c}_d\tag{5.25}<br>
$$</p>
<p>这种混合模式可以很好地适用于发光效果，如闪电或火花，只会使后面的像素变亮。但是，此模式计算出来的透明度看上去不正确，因为不透明的表面看起来没有过滤。对于若干层的半透明表面，如烟或火，additive混合具有使颜色更饱和的效果。</p>
<p>要正确渲染透明对象，我们需要在不透明对象之后绘制它们。 这是通过在关闭混合的状态下首先渲染所有不透明对象，然后使用over混合渲染透明对象来完成的。从理论上讲，我们总是可以打开over，因为1.0的不透明alpha会给出源颜色并隐藏目标颜色，但这样做会更消耗资源。</p>
<p>z-buffer的限制是每个像素仅存储一个物体对象。如果多个透明对象在相同的像素重叠，则单独的z-buffer无法等待并稍后解析所有可见对象。当在任何给定像素上对透明表面应用over时，通常需要以从后到前的顺序呈现。不这样做会给出错误的视觉感受。实现这种排序的一种方法是通过质心沿视图方向的距离对单个对象进行排序，但在一些情况下会有问题。首先，这种顺序只是一种近似，因此被排到更远的对象可能位于被排到更近的对象的前面。对于所有视角，互穿的物体不可能在per-mesh的层面上解决。</p>
<p>尽管如此，由于其简单性和速度，以及不需要额外的内存或特殊的GPU支持，仍然经常被使用。 如果执行，通常最好在执行透明度时关闭z-depth替换。也就是说，z-buffer仍然正常测试，但留下的表面不会改变存储的z深度；留下最接近的不透明表面的深度。 这样，当相机旋转改变排序顺序时，所有透明对象将至少以某种形式出现，而不是突然出现或消失。 其他技术也可以帮助改善外观，例如每次绘制每个透明网格两次，首先渲染背面然后渲染前面。</p>
<p>也可以修改上方等式，使前后混合得到相同的结果。 这种混合模式称为under运算符：</p>
<p>$$<br>
\boldsymbol{c}_o=\alpha_d\boldsymbol{c}_d+(1-\alpha_d)\alpha_s\boldsymbol{c}_s\quad[\boldsymbol{under}\text{ operator}],\\<br>
\boldsymbol{a}_o=\alpha_s(1-\alpha_d)+\alpha_d=\alpha_s-\alpha_s\alpha_d+\alpha_d\tag{5.26}<br>
$$</p>
<p>请注意，under运算符要求目标保持alpha值，而over不是。换句话说，在下面混合的表面是透明的，因此需要具有$\alpha$值。 under的公式就像结束了，但是交换了source和destination。 另外，请注意计算$\alpha$的公式是与顺序无关的，因为source和destination $\alpha$可以交换，结果是相同的最终$\alpha$。</p>
<p>$\alpha$的等式来自于将片段的$\alpha$视为覆盖率。Porter和Duff 注意到，由于我们不知道任何一个片段的覆盖区域的形状，我们假设每个片段与其$\alpha$成比例地覆盖另一个片段。例如，如果$\alpha_s=0.7$，则像素以某种方式被分成两个区域，其中0.7被源片段覆盖而0.3不被覆盖。 无需其他知识，例如$\alpha_d=0.6$的destination片段将与source段成比例地重叠。 该公式具有几何解释，如下图：</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/10/5.34-1.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter5"></figure><h4 id="552orderindependenttransparency">5.5.2 Order-Independent Transparency（顺序无关的透明度）</h4>
<p>under运算通过将所有透明对象绘制到单独的颜色缓冲区，然后使用over运算将这些透明对象绘制到场景上面。 under运算符的另一个用途是用于执行称为深度剥离（depth peeling）的顺序无关透明度（Order-Independent Transparency，OIT）算法。与顺序无关意味着应用程序不需要执行排序。深度剥离背后的想法是使用两个z-buffer和多个pass。首先，进行一个pass，使所有表面（透明的和不透明的）的z深度存储在第一个z-buffer中。在第二个pass中，渲染所有透明对象。</p>
<p>如果对象的z深度与第一个z-buffer中的值匹配，我们知道这是最近的透明对象，并将其RGBA保存到单独的颜色缓冲区。我们还通过保存超过第一个z深度且最近的透明对象的z深度来“剥离”这个层。这个z深度是第二最接近的透明对象的深度。连续传递继续剥离并使用under添加透明层。我们在经过一些次后停止，然后将透明图像混合到不透明图像的顶部。</p>
<p>已经开发了关于该方案的若干变体。 例如，Thibieroz给出了一种back to front工作的算法，其优点是能够立即混合透明值，这意味着不需要单独的alpha通道。 深度剥离的一个问题是需要知道要有多少pass可以捕获所有透明层。 一个硬件解决方案是提供一个像素绘制计数器，提供渲染过程中写入了多少像素；当没有像素通过pass时，渲染就完成了。 使用under的优点是眼睛首先看到的透明层先渲染。每个透明表面总是增加它所覆盖的像素的alpha值。如果像素的alpha值接近1.0，则混合的贡献使像素几乎不透明，因此更远的对象将具有可忽略的效果。 font-to-back剥离可以比back-to-font短，当通过渲染的像素数量低于某个最小值时，或者达到了指定数量的pass时。这对于从back-to-font的剥离不起作用，因为最接近的（通常是最重要的）层是最后绘制的，因此可能会因提前终止而丢失。</p>
<p>虽然深度剥离是有效的，但它可能很慢，因为每层剥离是所有透明物体的单独渲染通道。 Bavoil和Myers提出了双深度剥离（dual depth peeling），在每个pass中每次剥离两个深度层，最近和最远的，从而将渲染通道的数量减少一半。 刘等人，探索一种桶排序（bucket sort）方法，其在单个pass中捕获多达32个层。这种方法的一个缺点是它需要相当大的内存来保持所有层的排序顺序。 通过MSAA或类似方法进行抗锯齿会增加天文数字的成本。</p>
<p>以可以交互的速率正确地将透明对象混合在一起的问题不是我们所缺乏的算法问题，而将这些算法有效地映射到GPU是。1984年，Carpenter提出了A-buffer，另一种形式的多重采样。</p>
<p>在A-buffer中，每个被渲染的三角形都会为每个屏幕网格单元创建一个覆盖遮罩，可能是完全或者不完全覆盖的。每个像素存储所有相关片段的列表。不透明的片段可以剔除它们后面的片段，类似于z-buffer。所有片段都是为了透明表面存储的。一旦形成所有列表，通过遍历片段并解析每个样本产生最终结果。</p>
<p>通过DirectX 11 中公开的新功能，可以在GPU上创建片段的链接列表。使用的功能包括无序访问视图（unordered access views，UAV）和原子操作（atomic operations）。MSAA抗锯齿得以实现是因为具有访问覆盖遮罩和计算每个样本像素着色器的能力。该算法通过栅格化每个透明表面并在长数组中插入生成的片段来工作。除了颜色和深度之外，还生成一个单独的指针结构，将每个片段链接到前一个片段。然后执行单独的pass，渲染屏幕四边形，以便在每个像素处计算像素着色器。此着色器通过跟踪链接检索每个像素处的所有透明片段。检索到的每个片段依次与先前的片段一起排序。然后将该排序列表反向混合到前面以给出最终的像素颜色。由于通过像素着色器执行混合，因此如果需要，可以为每个像素指定不同的混合模式。 GPU和API的不断发展通过降低使用原子运算符的成本来提高性能。</p>
<p>A-buffer的优点是只分配了每个像素所需的片段，GPU上的链表实现也是如此。这在某种意义上可能也是一个缺点，因为在开始渲染帧之前所需的存储量是未知的。一个具有头发，烟雾或其他可能存在许多重叠透明表面物体的场景会产生大量片段。Andersson 指出，对于复杂的游戏场景，最多50个透明网格物体（如树叶），和最多 200个半透明颗粒可能重叠。</p>
<p>GPU通常具有内存资源，例如预先分配的buffer和array，链表方法也不例外。用户需要确定内存够用，内存不足会导致明显的问题。Salvi和Vaidyanathan 提出了一种解决这个问题的方法，多层alpha混合（multi-layer alpha blending），使用英特尔引入的称为像素同步（pixel synchronization）的GPU功能。见下图，此功能提供可编程混合，比原子操作开销少。</p>
<p>他们的方法改进了存储和混合，以便在内存耗尽时优雅地降级。粗略的排序顺序可以使他们的计划受益。 DirectX 11.3引入了光栅化顺序视图（第3.8节，rasterizer order views），它是一种buffer，允许在支持此功能的任何GPU上实现此透明度方法。移动设备具有类似的技术，称为tile local storage，允许它们实现多层alpha混合。然而这种机制具有可能昂贵的性能成本。</p>
<p>这种方法建立在Bavoil等人引入的k-buffer的基础之上。 其中前几个可见层被保留并排序，更深的层被丢弃或合并。 Maule等人，使用k缓冲区并通过使用加权平均来处理这些更远的深层。 加权和（weighted sum）和加权平均（weighted average）透明度技术是与顺序无关的（order-independent），是单pass的，并且几乎在每个GPU上都可以运行。 问题是他们没有考虑对象的排序。 因此，例如，使用阿尔法来表示覆盖范围，一条薄纱蓝色围巾上面的薄纱红色围巾呈现紫罗兰色，而不是看到一条带有蓝色透过的红色围巾。 虽然近似不透明的物体会产生不好的结果，但这类算法对于可视化非常有用，并且适用于高度透明的表面和粒子：</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/10/5.37-1.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter5"></figure><p>在加权和透明度中：</p>
<p>$$<br>
\boldsymbol{c}_o=\sum\limits^n_{i=1}(\alpha_i\boldsymbol{c}_i)+\boldsymbol{c}_d(1-\sum\limits^n_{i=1}\alpha_i)\tag{5.27}<br>
$$</p>
<p>其中n是透明表面的数量，$\boldsymbol{c}_i$和$\alpha_i$表示透明度值的集合，$\boldsymbol{c}_d$是场景的不透明部分的颜色。 当渲染透明表面时，两个和被累积并分别存储，并且在透明度pass结束时，在每个像素处计算等式。 这种方法的问题是第一个和可能产生大于（1.0,1.0,1.0）的颜色值，背景颜色可能会有反色问题，因为α的总和可以超过1.0。</p>
<p>加权平均方程避免了这些问题：</p>
<p>$$<br>
\boldsymbol{c}_{\text{sum}}=\sum\limits^n_{i=1}(\alpha_i\boldsymbol{c}_i),<br>
\alpha_{\text{sum}}=\sum\limits^n_{i=1}\alpha_i,\\<br>
\boldsymbol{c}_{\text{wavg}}=\frac{\boldsymbol{c}_{\text{sum}}}{\alpha_{\text{sum}}},<br>
\alpha_{\text{avg}}=\frac{\alpha_{\text{sum}}}{n},\\<br>
u=(1-\alpha_{\text{avg}})^n,\\<br>
\boldsymbol{c}_o=(1-u)\boldsymbol{c}_{\text{wavg}}+u\boldsymbol{c}_d\tag{5.28}<br>
$$</p>
<p>第一行表示在透明度渲染期间生成的两个单独buffer中的结果。每个表面对于$\boldsymbol{c}_{\text{sum}}$提供一个由透明度决定的权重；几乎不透明的表面会产生更多的颜色，而几乎透明的表面几乎没有影响。 通过将$\boldsymbol{c}_{sum}$除以$\alpha_{sum}$，我们得到加权平均透明度颜色。 值$\alpha_{avg}$是所有$\alpha$值的平均值。 对于n个透明表面，值u是对该平均$\alpha$应用n次之后目标的估计可见性（不透明场景）。 最后一行实际上是over运算符，$(1-u)$表示源的$\alpha$。</p>
<p>加权平均值的一个限制是，对于相同的alpha，无论顺序如何，它都会均匀地混合所有颜色。McGuire和Bavoil引入了加权混合顺序无关透明度，以提供更有说服力的结果。 在他们的公式中，到表面的距离也会影响权重，更接近的表面会产生更大的影响。 此外，不是对$\alpha$进行平均，而是通过将项$(1-\alpha_i)$相乘并从1减去来计算u，给出表面集的真实α覆盖。 这种方法可以产生更具视觉效果的结果，如下图（两个不同角度摄像机位置的效果）：</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/10/5.38-1.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter5"></figure><p>缺点是在较大的场景中，彼此靠近的物体可能具有距离几乎相等的权重，使得结果与加权平均的方法不太一样。 此外，随着相机与透明物体的距离发生变化，深度权重也会随着变化，但这种变化是渐进的。</p>
<p>McGuire和Mara将这种方法扩展到包含可信的透射颜色（transmission color）效果。如前所述，本节中讨论的所有透明度算法都会混合各种颜色而不是过滤它们，模仿像素覆盖率。为了给出滤色器效果，像素着色器读取不透明场景，并且每个透明表面将其在该场景中覆盖的像素乘以其颜色，将结果保存到第三个缓冲区。在解析透明缓冲区时，使用此缓冲区（其中不透明对象现在由透明对象着色）代替不透明场景。此方法有效，因为与模拟覆盖范围的透明度不同，投射彩色与顺序无关。</p>
<p>还有其他算法使用了此处介绍的几种技术中的元素。 例如，Wyman把先前的工作按照内存要求，插入和合并方法，是否使用了alpha或几何覆盖以及如何处理丢弃片段来进行分类。 他通过寻找先前研究中的差距，提出了两种新方法。 他的随机分层alpha混合方法使用k-buffers，加权平均和随机透明度。 他的另一种算法是Salvi和Vaidyanathan方法的变体，使用覆盖遮罩而不是alpha。</p>
<p>鉴于各种类型的透明内容，渲染方法和GPU功能，没有完美的渲染透明对象的解决方案。 我们将感兴趣的读者引用到Wyman的论文和Maule等人的交互式透明度算法的更详细的调查。McGuire的演示给出了更广泛的视野，贯穿其他相关现象，如体积照明，彩色透射和折射，本书稍后将对此进行更深入的讨论。</p>
<h4 id="553premultipliedalphasandcompositing">5.5.3 Premultiplied Alphas and Compositing（预乘透明度和合成）</h4>
<p>over运算符还用于将照片或渲染物体混合在一起。此过程称为合成（Compositing）。在这种情况下，在每个像素的对象alpha值与RGB颜色值一起存储。由alpha通道形成的图像有时被称为遮罩（matte）。它显示了对象的轮廓形状。 然后可以使用该RGBA图像将其与其他这样的元素或背景混合。</p>
<p>使用合成RGBA数据的一种方法是使用预乘透明度（premultiplied alpha，也称为associated alpha）。也就是说，RGB值在使用之前乘以alpha值。 这使得合成方程更有效：</p>
<p>$$<br>
\boldsymbol{c}_o=\boldsymbol{c}'_s+(1-\alpha_s)\boldsymbol{c}_d\tag{5.29}<br>
$$</p>
<p>其中$\boldsymbol{c}'_s$是预乘后的源，取代公式5.25中的$\alpha_s\boldsymbol{c}_s$。 预乘alpha还可以在不改变混合状态的情况下使用over和additive混合，因为在混合期间添加了源颜色。请注意，对于预乘的RGBA值，RGB分量通常不大于alpha值，但可以这样做可以创建特别明亮的半透明颜色。</p>
<p>渲染合成图像与预乘alphas自然地吻合。在黑色背景上渲染的使用抗锯齿的不透明对象默认提供预乘值。 假设白色（1,1,1）三角形覆盖其边缘的一些像素的40％。执行（非常精确的）抗锯齿后，像素值将被设置为0.4的灰度，也就是我们将保存（0.4,0.4,0.4）作为该像素的颜色。如果存储，则α值也将是0.4，这是三角形覆盖的区域。 RGBA值将是（0.4,0.4,0.4,0.4），也就是预乘值。</p>
<p>存储图像的另一种方式是使用 unmultiplied alphas（也称unassociated alphas，nonpremultiplied）。也就是：RGB值没有乘以alpha值。对于白色三角形示例，颜色将为（1, 1, 1, 0.4）。 它具有存储三角形原始颜色的优点，但在显示之前，此颜色始终需要与存储的alpha相乘。 每当执行过滤和混合时，最好使用预乘，因为诸如线性插值之类的操作无法使用未经过相乘的alpha进行正确的操作，会产生诸如物体边缘周围的黑色条纹之类的伪像。预乘允许更干净的的理论处理。</p>
<p>对于图像处理程序，非预乘alpha可用于对照片遮罩而不会影响基础图像的原始数据。此外，非预乘alpha意味着可以使用颜色通道的完整精度范围。也就是说，必须注意将未经过相乘的RGBA值正确地与线性空间转换。 例如，没有浏览器可以正确地执行此操作，也不适合这样做，因为预计会出现错误的行为。 支持alpha的图像文件格式包括PNG（仅限非预乘alpha），OpenEXR（仅预乘alpha）和TIFF（两种类型的alpha）。</p>
<p>与alpha通道相关的概念是色相抠像（chroma-keying）。 这是一个来自视频制作的术语，其中演员在绿色或蓝色屏幕上拍摄并与背景混合。 在电影工业中，这个过程称为绿幕或蓝幕。 这里的想法是指定特定的色相（用于电影工作）或精确值（用于计算机图形）被认为是透明的；只要检测到背景，就会显示背景。 这允许通过仅使用RGB颜色给图像赋予轮廓形状；不需要存储alpha。 该方案的一个缺点是物体在任何像素处完全不透明或透明，即，α实际上仅为1.0或0.0。 例如，GIF格式允许一种颜色指定为透明。</p>
<br>
<h3 id="56displayencoding">5.6 Display Encoding（显示编码）</h3>
<p>当我们计算照明，纹理或其他操作的效果时，假定使用的值是线性的。这意味着加法和乘法按预期工作。 但是，我们必须考虑的是，为了避免各种视觉伪像，显示缓冲区和纹理使用非线性编码。一个简单的解释是：着色器输出颜色在$[0,1]$范围内，并做1/2.2次幂，执行所谓的伽马校正（gamma correction）。对输入的纹理和颜色做相反的事情。在大多数情况下，可以让GPU帮你搞定。</p>
<p>我们从CRT显示器开始。在数字成像的早期，CRT显示器是主流。 这些器件在输入电压和显示辐射之间表现出幂次关系。 随着施加到像素的能量水平增加，发射的辐射不会线性增长，但与上升到大于1的幂成比例。 例如，假设幂为2。相对于一个设置为1.0的像素，设置为50％的像素将发出四分之一的光量，$0.5^2=0.25$。尽管LCD和其他显示技术具有与CRT不同的固有色调响应曲线，但它们是用转换电路制造的，这使得它们模仿CRT的响应。</p>
<p>该幂函数几乎匹配人类视觉的亮度敏感度的倒数。 这种幸运的巧合的结果是编码大致上是感知一致的（perceptually uniform）。也就是说，一对编码值N和N + 1之间的感知差异在可显示范围内大致恒定。使用阈值对比（threshold contrast）测量，我们可以在宽范围的条件下检测亮度差异约1％。 当颜色存储在有限精度的显示缓冲区中时，这种接近最佳的值分布可最大限度地减少条带伪影。纹理使用相同的编码，同样具备这样的优势。</p>
<p>显示传递函数（display transfer function）描述了display buffer中的数字值与显示器发出的辐射亮度之间的关系。因此，它也被称为电光传递函数（electrical optical transfer function, EOTF）。显示传输功能是硬件的一部分，计算机显示器，电视和电影放映机有不同的标准。 对于过程的最终，图像和视频捕获设备，还存在一个标准的传递函数，称为光电传递函数（optical electric transfer function, OETF）。</p>
<p>当编码线性颜色值用于显示时，我们的目标是抵消显示传递函数的作用，使得我们计算的任何值将发射相应的辐射水平。 例如，如果我们的计算值加倍，我们希望输出辐射亮度加倍。 为了保持这种关系，我们应用显示传递函数的反函数来抵消其非线性效应。这种使显示器响应曲线无效的过程也称为伽马校正（gamma correction）。 解码纹理值时，我们需要应用显示传递函数来生成用于着色的线性值：</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/10/5.39-1.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter5"></figure><p>个人计算机显示器的standard transfer function由称为sRGB的颜色空间规范定义。大多控制GPU的API可以设置为在从纹理读取值或写入颜色缓冲区时自动应用正确的sRGB转换。 如第6.2.2节所述，mipmap的生成也考虑sRGB编码。通过首先转换为线性值然后执行插值，纹理值之间的双线性插值将正常工作。通过将存储的值解码回线性值，混合新值，然后对结果进行编码，可以正确完成Alpha混合。</p>
<p>在渲染的最后阶段应用转换非常重要，此时将值写入frame buffer以进行显示。如果在显示编码后应用post-processing，则会以非线性值进行计算，这通常是不正确的，经常会造成失真。显示编码可以被认为是一种压缩的形式，最能保持值所对应的感知效果。一个好方法是我们使用线性值来执行物理计算，每当我们想要显示结果或访问可显示的图像（如颜色纹理）时，我们需要将数据转入或转出其显示编码形式，使用适当的编码或解码变换。</p>
<p>如果您确实需要手动应用sRGB，则可以使用标准转换公式或一些简化版本。实际上，显示器由每个颜色通道的多个位控制。例如，对于消费级显示器为8-bits，给出一组从0到255的范围。 这里我们将显示编码的级别表示为范围[0.0, 1.0]，忽略浮点数。线性值也在这个区间[0.0, 1.0]，表示浮点数。我们用x表示这些线性值，用y表示存储在帧缓冲器中的非线性编码值。要将线性值转换为sRGB非线性编码值，我们应用sRGB显示传递函数的反转：</p>
<p>$$<br>
y=f^{-1}_{sRGB}(x)=<br>
\begin{cases}<br>
1.055x^{1/2.4}-0.055,\quad &amp;where\quad x&gt;0.0031308,\\<br>
12.92x,&amp;where \quad x\le0.0031308<br>
\end{cases}\tag{5.30}<br>
$$</p>
<p>x表示线性RGB三元组的通道。该等式应用于每个通道，这三个生成的值驱动显示。如果手动应用转换功能，请务必小心。 一个错误来源是使用编码颜色而不是其线性形式，另一个错误是使用解码或编码两次。</p>
<p>上面的两个变换表达式的下面那个是一个简单的乘法，它源于数字硬件的需要，使变换完全可逆。上面的表达式，将值提升为幂，几乎适用于整个x的输入范围[0.0, 1.0]。考虑到偏移和缩放，这个函数非常接近一个更简单的公式：</p>
<p>$$<br>
y=f^{-1}_{\text{display}}(x)=x^{1/\gamma}\tag{5.31}<br>
$$</p>
<p>其中$\gamma=2.2$。这就是gamma correction的基础。</p>
<p>正如必须对计算值进行编码以进行显示一样，拍摄的图像或者摄像机捕获的图像在用于计算之前必须转换为线性值。在显示器或电视上看到的任何颜色都有一些显示编码的RGB三元组，您可以从屏幕截图或颜色选择器中获取。这些值是以PNG，JPEG和GIF等文件格式存储的格式，可以直接发送到frame buffer以便在不进行转换的情况下显示在屏幕上。 换句话说，无论在屏幕上看到什么，都是按照定义显示编码数据。在着色计算中使用这些颜色之前，我们必须将此编码形式转换回线性值。我们需要从显示编码到线性值的sRGB变换：</p>
<p>$$<br>
x=f_{sRGB}(y)=<br>
\begin{cases}<br>
(\frac{y+0.055}{1.055})^{2.4},&amp;where\quad y\gt0.04045,\\<br>
\frac{y}{12.92},&amp;where\quad y\le0.04045,<br>
\end{cases}\tag{5.32}<br>
$$</p>
<p>y表示标准化的显示通道值，即存储在图像或frame buffer中的值，表示为范围[0.0, 1.0]。此解码函数与我们之前的sRGB公式相反。这意味着如果纹理被shader读取并且输出后没有变化，它将像预期的那样与处理前相同。解码功能与显示传输功能相同，因为存储在纹理中的值已被编码以正确显示。我们转换为了线性值，而不是显示。</p>
<p>更简单的gamma display transfer function是公式5.31的反函数：</p>
<p>$$<br>
x=f_{display}(y)=y^\gamma\tag{5.33}<br>
$$</p>
<p>有时会看到更简单的转换对，尤其是在移动应用或浏览器应用上：</p>
<p>$$<br>
y = f^{-1}_{simpl}(x)=\sqrt{x},\\<br>
x = f_{simpl}(y)=y^2\tag{5.34}<br>
$$</p>
<p>也就是说，取平方根进行显示，并将该值乘以其自身的倒数。 虽然粗略近似，但这种转换比完全忽略问题更好。</p>
<p>如果我们不执行伽玛矫正，则较小的线性值在屏幕上会显得更暗淡。也就是色相会偏离。因为$\gamma= 2.2$。我们希望从显示的像素发出与线性计算值成比例的辐射，这意味着我们必须将线性值提高到1/2.2次幂。</p>
<p>线性值0.1给出0.351,0.2给出0.481,0.5给出0.730。 如果不进行编码，这些使用的值将导致显示器发出的辐射度低于所需的辐射率。请注意，0.0和1.0始终保持不变。 在使用伽马校正之前，暗表面颜色通常由建模场景的人员人为地提升，以抵消显示变换。</p>
<p>忽略伽马校正的另一个问题，是用非线性值执行了物理的线性辐射值正确的阴影计算。 如下图，两个光源叠加的效果，左侧的没有使用gamma矫正，导致中间叠加后的部分显得格外亮，而右边使用了伽马校正的显得很恰当：</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/10/5.40-1.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter5"></figure><p>忽略伽马校正也会影响抗锯齿边缘的质量。例如，假设三角形边缘覆盖四个屏幕网格单元。 三角形的归一化辐射亮度为1（白色），背景为0（黑色）。从左到右，网格单元分别被覆盖$\frac18,\frac38,\frac58,\frac78$。所以如果我们使用box filter，我们希望辐射度为0.125,0.375,0.625和0.875。正确的抗锯齿应该执行在线性值的基础上，如果不是，则会导致边缘变形：</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/10/5.41-1.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter5"></figure><p>sRGB标准创建于1996年，已成为大多数计算机显示器的标准。 然而，从那时起，显示技术已经发展。 已经开发出更亮并且可以显示更广泛颜色的监视器。 第8.1.3节讨论了彩色显示和亮度，第8.2.1节介绍了高动态范围显示的显示编码。 Hart的文章特别全面的讨论了有关高级显示的更多内容。</p>
<br>
<h4 id="furtherreadingandresources">Further Reading and Resources</h4>
<ul>
<li>Pharr等人，更深入地讨论采样模式和抗锯齿。</li>
<li>Teschner的课程笔记显示了各种采样模式生成方法。</li>
<li>Drobot贯穿了之前关于实时抗锯齿的研究，解释了各种技术的属性和性能。</li>
<li>关于各种形态抗锯齿方法的信息可以在相关SIGGRAPH课程的注释中找到。</li>
<li>Reshetov和Jimenez提供了游戏中使用的形态学和有关temporal antialiasing的最新内容。</li>
<li>对于透明度研究，McGuire的演讲和Wyman的作品。</li>
<li>Blinn的文章“什么是像素“在讨论不同的定义时提供了几个计算机图形领域的精彩之旅。</li>
<li>Blinn的脏像素和符号，符号，符号书包括一些关于过滤和抗锯齿的介绍性文章。</li>
<li>关于alpha，合成和伽马校正的文章.Jimenez的演讲详细介绍了用于抗锯齿的最先进技术。</li>
<li>Gritz和d'Eon对伽马校正问题进行了很好的总结。</li>
<li>Poynton的书给出了各种媒体中伽马校正的可靠报道，以及其他与颜色相关的主题。</li>
<li>Selan的白皮书是一个较新的来源，解释了显示编码及其在电影行业的应用，以及许多其他相关信息。</li>
</ul>
<p>（Chapter 5 end.）</p>
]]></content:encoded></item><item><title><![CDATA[Realtime Rendering 4th notes - chapter4]]></title><description><![CDATA[实时渲染第四版翻译笔记第四章
realtime rendering 4th edition chapter 4 translation and note.
图形学 图形学基础]]></description><link>http://xx-ma.com/rtr4-4/</link><guid isPermaLink="false">5bab655eb67cfd1e0bec031e</guid><category><![CDATA[realtime-rendering]]></category><category><![CDATA[code]]></category><category><![CDATA[graphics]]></category><category><![CDATA[notes]]></category><dc:creator><![CDATA[Marianax]]></dc:creator><pubDate>Wed, 26 Sep 2018 14:16:09 GMT</pubDate><media:content url="http://xx-ma.com/content/images/2018/09/nasa-45068-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<img src="http://xx-ma.com/content/images/2018/09/nasa-45068-unsplash.jpg" alt="Realtime Rendering 4th notes - chapter4"><p><a href="https://www.xx-ma.com/">(转载请注明出处，谢谢咯)</a></p>
<blockquote>
<p>&quot;What if angry vectors veer Round your sleeping head, and form. There’s never need to fear Violence of the poor world’s abstract storm.&quot; - Robert Penn Warren</p>
</blockquote>
<p>transform（变换）是能把点，向量，颜色转换的一种运算（或操作）。可用来定位，重塑，动画：物体灯光或者摄像机。</p>
<p>线性变换（linear transform）可以保持向量加法和标量乘法，即要满足$ f(x) + f(y) = f (x+y)$, $ kf(x) = f(kx)$ 。如$f(x)=5x$是把一个向量的每个元素都乘以5的变换。所有线性变换都可以用一个3×3的矩阵表示。</p>
<p>平移操作如f(v) = v + v'不是线性变换，但这是一个有用的操作平移，可以用仿射变换（affine transform）来完成线性变换和平移的组合。一般表示为一个4×4的矩阵。</p>
<p>用$v=(v_x\ v_y\ v_z\ 0)^\top$表示方向向量，用$v=(v_x\ v_y\ v_z\ 1)^\top$表示坐标点。</p>
<p>所有平移旋转缩放反射和剪切矩阵都是仿射矩阵。一个仿射变换可以是仿射变换序列的级联。</p>
<br>
<h3 id="41basictransform">4.1 Basic Transform（基本变换）</h3>
<table>
<thead>
<tr>
<th>符号</th>
<th>名称</th>
<th>特性</th>
</tr>
</thead>
<tbody>
<tr>
<td>$T(t)$</td>
<td>translation matrix 平移矩阵</td>
<td>移动点。仿射矩阵。</td>
</tr>
<tr>
<td>$R_x(\rho)$</td>
<td>rotation matrix 旋转矩阵</td>
<td>围绕x轴旋转$\rho$弧度。正交，仿射。</td>
</tr>
<tr>
<td>$R$</td>
<td>rotation matrix 旋转矩阵</td>
<td>任意旋转矩阵。正交，仿射。</td>
</tr>
<tr>
<td>$S(s)$</td>
<td>scaling matrix 缩放矩阵</td>
<td>沿x,y,z轴缩放s倍。仿射矩阵。</td>
</tr>
<tr>
<td>$H_{ij}(s)$</td>
<td>shear matrix 剪切矩阵</td>
<td>关于组件$j$，以剪切因子$s$剪切组件$i$。$i,j\in {x,\ y,\ z}$，仿射矩阵。</td>
</tr>
<tr>
<td>$E(h,p,r)$</td>
<td>Euler transform 欧拉变换</td>
<td>由欧拉角的head(yaw),pitch,roll决定的方向矩阵。正交，仿射。</td>
</tr>
<tr>
<td>$P_o(s)$</td>
<td>orthographic projection 正交投影</td>
<td>平行投影到一个平面或一个体积。仿射矩阵。</td>
</tr>
<tr>
<td>$P_p(s)$</td>
<td>perspective projection 透视矩阵</td>
<td>透视投影到一个平面或一个体积。</td>
</tr>
<tr>
<td>$slerp(\boldsymbol{\hat{q}},\boldsymbol{\hat{r}},t)$</td>
<td>slerp transform</td>
<td>根据给定四元数$\boldsymbol{\hat{q}}$和$\boldsymbol{\hat{r}}​$,以及参数t创建一个插值的四元数。</td>
</tr>
</tbody>
</table>
<br>
<h4 id="411translation">4.1.1 Translation（平移）</h4>
<p>变换向量$\boldsymbol{t} = (t_x,\ t_y,\ t_z) =&gt; \boldsymbol{T}(\boldsymbol{t}) = \boldsymbol{T}(t_x,\ t_y,\ t_z) = \begin{pmatrix} 1\ 0\ 0\ t_x\\0\ 1\ 0\ t_y\\ 0\ 0\ 1\ t_z\\0\ 0\ 0\ 1\\ \end{pmatrix}$</p>
<p>给定一个点$\boldsymbol{p}=(p_x,\ p_y,\ p_z,\ 1)$, 使用$\boldsymbol{T}(\boldsymbol{t})$变换后为$\boldsymbol{p'}(p_x + t_x,\ p_y + t_y,\ p_z + t_z,\ 1)$，</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/09/4.1.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter4"></figure><p>给定一个方向向量$\boldsymbol{v}=(v_x,\ v_y,\ v_z,\ 0)$, 使用$\boldsymbol{T}(\boldsymbol{t})$变换后没有变化。但是其它仿射变换对方向和点都起到相同的作用。在本书中我们使用column-major的形式书写矩阵，关于column-major和row-major只是矩阵的阅读顺序不同。详见<a href="https://en.wikipedia.org/wiki/Row-_and_column-major_order">https://en.wikipedia.org/wiki/Row-_and_column-major_order</a>。</p>
<br>
<h4 id="412rotation">4.1.2 Rotation（旋转）</h4>
<p>类似平移变换，旋转变换也是一个刚体变换。定位矩阵（orientation matrix）是一个和摄像机视角或物体相关联的旋转矩阵，定义了他们空间中的方向，也就是向上和向前的方向。</p>
<p>2D空间中，设一个向量$\boldsymbol{v} = (v_x,\ v_y)$，我们用参数化的形式表示为$\boldsymbol{v} = (r\cos{\theta},\ r\sin{\theta})$。若想旋转此向量$\phi$弧度（逆时针方向），我们会得到$u=(r\cos(\theta+\phi),\ r\sin(\theta+\phi))$，公式可以重写为：</p>
<p>$$<br>
\boldsymbol{u} = \begin{pmatrix}<br>
r\cos(\theta+\phi)\\<br>
r\sin(\theta+\phi)<br>
\end{pmatrix}<br>
= \begin{pmatrix}<br>
r\cos\theta\cos\phi-\sin\theta\sin\phi \\<br>
r\sin\theta\cos\phi+\cos\theta\sin\phi<br>
\end{pmatrix}<br>
= \underbrace{\begin{pmatrix}<br>
\cos\theta &amp; -\sin\phi \\<br>
\sin\theta\ &amp; \cos\phi<br>
\end{pmatrix}}_{\boldsymbol{R}(\phi)}<br>
\underbrace{\begin{pmatrix}<br>
r\cos\theta \\<br>
r\sin\theta<br>
\end{pmatrix}}_{\boldsymbol{v}} \\\\<br>
{\boldsymbol{v}} = \boldsymbol{R}(\phi)\boldsymbol{v}<br>
$$<br>
即推导出2D空间的旋转矩阵。</p>
<p>3D空间中一般用$\boldsymbol{R}_x(\phi)$,$\boldsymbol{R}_y(\phi)$,$\boldsymbol{R}_z(\phi)$表示绕对应轴旋转的旋转矩阵，他们是：<br>
$$<br>
\boldsymbol{R}_x(\phi) = \begin{pmatrix}<br>
1 &amp; 0 &amp; 0 &amp; 0 \\<br>
0 &amp; \cos\phi &amp; -\sin\phi &amp; 0 \\<br>
0 &amp; \sin\phi &amp; \cos\phi &amp; 0 \\<br>
0 &amp; 0 &amp; 0 &amp; 1<br>
\end{pmatrix},<br>
$$</p>
<p>$$<br>
\boldsymbol{R}_y(\phi) = \begin{pmatrix}<br>
\cos\phi &amp; 0 &amp; \sin\phi &amp; 0 \\<br>
0 &amp; 1 &amp; 0 &amp; 0 \\<br>
-\sin\phi &amp; 0 &amp; \cos\phi &amp; 0 \\<br>
0 &amp; 0 &amp; 0 &amp; 1<br>
\end{pmatrix},<br>
$$</p>
<p>$$<br>
\boldsymbol{R}_z(\phi) = \begin{pmatrix}<br>
\cos\phi &amp; -\sin\phi &amp; 0 &amp; 0 \\<br>
\sin\phi &amp; \cos\phi &amp; 0 &amp; 0 \\<br>
0 &amp; 0 &amp; 1 &amp; 0 \\<br>
0 &amp; 0 &amp; 0 &amp; 1<br>
\end{pmatrix}<br>
$$<br>
去掉最下和最右的一行和一列，我们得到一个3×3的矩阵，他们对角线的和（trace）都是：<br>
$$<br>
tr(\boldsymbol{R}) = 1 + 2\cos\phi<br>
$$<br>
所有旋转矩阵或他们任意数量的串联都是正交的。$\boldsymbol{R}_i^{-1}(\phi) = \boldsymbol{R}_i(-\phi)$。</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/09/4.2.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter4"></figure><p>上图是使物体以p为中心沿着z轴旋转$\phi$弧度一个object的例子：$\boldsymbol{X}=\boldsymbol{T}(\boldsymbol{p})\boldsymbol{R}_z(\phi)\boldsymbol{T}(\boldsymbol{-p})$，注意矩阵的顺序。</p>
<br>
<h4 id="413scaling">4.1.3 Scaling（缩放）</h4>
<p>以$\boldsymbol{s}=(\boldsymbol{s}_x,\boldsymbol{s}_y,\boldsymbol{s}_z)$为因子分别沿着对应轴进行缩放。</p>
<p>$$<br>
\boldsymbol{S}(\boldsymbol{s}) = \begin{pmatrix}<br>
s_x &amp; 0 &amp; 0 &amp; 0	\\<br>
0 &amp; s_y &amp; 0 &amp; 0	\\<br>
0 &amp; 0 &amp; s_z &amp; 0 \\<br>
0 &amp; 0 &amp; 0 &amp; 1<br>
\end{pmatrix}<br>
$$</p>
<p>如果$s_x = s_y = s_z$，则称该矩阵为均匀的（uniform）反之则是不均匀的（nonuniform）。$\boldsymbol{S}^{-1}(s) = \boldsymbol{S}(1/s_x,\ 1/s_y,\ 1/s_z)$。在齐次坐标中，右下角的1称为$w$-分量，若$\boldsymbol{S}$代表uniform scaling，$\boldsymbol{S'}$可以代表齐次坐标下的缩放矩阵。</p>
<p>$$<br>
\boldsymbol{S}(\boldsymbol{s}) = \begin{pmatrix}<br>
5 &amp; 0 &amp; 0 &amp; 0 \\<br>
0 &amp; 5 &amp; 0 &amp; 0 \\<br>
0 &amp; 0 &amp; 5 &amp; 0 \\<br>
0 &amp; 0 &amp; 0 &amp; 1<br>
\end{pmatrix},<br>
\boldsymbol{S'}(\boldsymbol{s}) = \begin{pmatrix}<br>
1 &amp; 0 &amp; 0 &amp; 0 \\<br>
0 &amp; 1 &amp; 0 &amp; 0 \\<br>
0 &amp; 0 &amp; 1 &amp; 0 \\<br>
0 &amp; 0 &amp; 0 &amp; 1/5<br>
\end{pmatrix}<br>
$$</p>
<p>若s的三个值中有一个或三个负值，则得到一个反射矩阵（reflection matrix），或者称为镜像矩阵（mirror matrix）。如果只有两个值为负值，则相当于旋转180度。串联一个旋转矩阵和一个镜像矩阵得到的还是镜像矩阵：</p>
<p>$$<br>
\underbrace{<br>
\begin{pmatrix}<br>
\cos(\pi/2) &amp; \sin(\pi/2) \\<br>
-\sin(\pi/2) &amp; \cos(\pi/2)<br>
\end{pmatrix}<br>
}_{\text{rotation}}<br>
\underbrace{<br>
\begin{pmatrix}<br>
1 &amp; 0 \\<br>
0 &amp; -1<br>
\end{pmatrix}<br>
}_{\text{reflection}}<br>
= \begin{pmatrix}<br>
0 &amp; -1 \\<br>
-1 &amp; 0<br>
\end{pmatrix}<br>
$$</p>
<p>镜像矩阵常常导致一些问题，比如一个逆时针方向排序顶点的三角形用镜像矩阵变换后会变成顺时针顶点排序。想要确定矩阵为镜像矩阵，可以计算左上3×3矩阵的<a href="https://zh.wikipedia.org/wiki/%E8%A1%8C%E5%88%97%E5%BC%8F">行列式</a>为负。</p>
<p>沿给定方向缩放，给定方向向量$(\boldsymbol{f^x},\boldsymbol{f^y},\boldsymbol{f^z})$，首先构造矩阵$\boldsymbol{F} = \begin{pmatrix}<br>
​    \boldsymbol{f^x} &amp; \boldsymbol{f^x} &amp; \boldsymbol{f^x} &amp; 0 \\<br>
​    0 &amp; 0 &amp; 0 &amp; 1<br>
\end{pmatrix}$，则变换为：<br>
$$<br>
\boldsymbol{X} = \boldsymbol{FS(s)F}^\top<br>
$$<br>
<br></p>
<h4 id="414shearing">4.1.4 Shearing（剪切）</h4>
<p>剪切矩阵可以用在游戏中模拟整个场景扭曲的迷幻效果或者扭曲一个模型的外观等。有6个基本的剪切模型，分别是$H_{xy}(s),H_{xz}(s),H_{yx}(s),H_{yz}(s),H_{zx}(s),H_{zy}(s)$.第一个下标指定哪个坐标值发生变化，第二个下标指定执行变化的坐标。如下图，y，z值不变，x值改变，新的x值为s与x、z的乘积：</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/09/4.3-1.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter4"></figure><p>该矩阵可写为：(s的位置由x指定了第index=0行，z指定了第index=2列)</p>
<p>$$<br>
\boldsymbol{H}_{xz}(s) = \begin{pmatrix}<br>
1 &amp; 0 &amp; s &amp; 0 \\<br>
0 &amp; 1 &amp; 0 &amp; 0 \\<br>
0 &amp; 0 &amp; 1 &amp; 0 \\<br>
0 &amp; 0 &amp; 0 &amp; 1 \\<br>
\end{pmatrix}<br>
$$</p>
<p>用它和一个已知点$\boldsymbol{p}$做乘法，得到$(p_x+sp_z\ p_y\ p_z)^\top$。$\boldsymbol{H}^{-1}_{ij}(s)=\boldsymbol{H}_ij(-s)$。</p>
<p>可以把剪切矩阵做一点改变，意为两个下标所指坐标值都要由第三个坐标剪切：</p>
<p>$$<br>
\boldsymbol{H'}_{xy}(s,t) = \begin{pmatrix}<br>
1 &amp; 0 &amp; s &amp; 0 \\<br>
0 &amp; 1 &amp; t &amp; 0 \\<br>
0 &amp; 0 &amp; 1 &amp; 0 \\<br>
0 &amp; 0 &amp; 0 &amp; 1 \\<br>
\end{pmatrix}<br>
$$</p>
<p>用一种通用的方法可以写作: $\boldsymbol{H}_{ij}(s,t)=\boldsymbol{H}_{ik}(s)\boldsymbol{H}_{jk}(t)$ ，这里的k是第三个坐标，任何满足$\vert\boldsymbol{H}\vert=1$的剪切变换都可以保持体积。</p>
<p>一般对任意矩阵$\boldsymbol{MN}$有$\boldsymbol{MN}\not=\boldsymbol{NM}$:</p>
<br>
<h4 id="415concatenationoftransforms">4.1.5 Concatenation of Transforms（变换的串联）</h4>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/09/4.4.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter4"></figure><p>由于矩阵乘法的不可交换性，矩阵的串联和矩阵相乘的顺序相关，如上图所示。</p>
<p>把矩阵串联成一个的主要目的在于执行效率。合成后的矩阵为：（顺序从右到左执行）</p>
<p>$$<br>
\boldsymbol{C} = \boldsymbol{TRS}\ =&gt;\  \boldsymbol{TRSp} = \boldsymbol{(T(R(Sp)))}<br>
$$</p>
<p>该矩阵可以满足结合律的需求，例如拆分为$\boldsymbol{(TR)(Sp)}$。</p>
<br>
<h4 id="416therigidbodytransform">4.1.6 The Rigid-Body Transform</h4>
<p>只有位移和方向发生变化的变换。</p>
<ul>
<li>$\boldsymbol{X}=\boldsymbol{T(t)R}$，</li>
<li>$\boldsymbol{X}^{-1}=\boldsymbol{(T(t)R)}^{-1}=\boldsymbol{R}^{-1}\boldsymbol{T}(t)^{-1}=\boldsymbol{R}^\top\boldsymbol{T(-t)}$<br>
<br></li>
<li>($\overline{\boldsymbol{R}}=(\boldsymbol{r}_{,0}\ \boldsymbol{r}_{,1}\ \boldsymbol{r}_{,2}) = \begin{pmatrix}\boldsymbol{r}_{0,}\\ \boldsymbol{r}_{1,}\\ \boldsymbol{r}_{2,}\\ \end{pmatrix}$) =&gt; $\boldsymbol{X}^{-1}=\begin{pmatrix} \boldsymbol{r}_{0,} &amp; \boldsymbol{r}_{1,} &amp; \boldsymbol{r}_{2,} &amp; -\overline{\boldsymbol{R}}^\top \boldsymbol{t} \\ 0&amp;0&amp;0&amp;1 \end{pmatrix} $</li>
</ul>
<p>一个普遍的例子，计算摄像机的u，v，r坐标：</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/09/4.5.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter4"></figure><p>如图所示，设摄像机位置为$\boldsymbol{c}$，我们希望摄像机朝向（look at）目标$\boldsymbol{l}$，一个给定的摄像机向上的方向是$\boldsymbol{u'}$，我们希望计算一个由三个变量组成的向量${r,u,v}$</p>
<ul>
<li>step1:  $\boldsymbol{v}=(\boldsymbol{c-1})/|\boldsymbol{c-l}|$</li>
<li>step2:  $\boldsymbol{r} = -(\boldsymbol{v}\times\boldsymbol{u'})/|\boldsymbol{v}\times\boldsymbol{u'}|$，$\boldsymbol{r}$代表向右的向量</li>
<li>step3: 由于$\boldsymbol{u'}$不能保证准确指向上，所以最终的向上$\boldsymbol{u=v\times{r}}$</li>
</ul>
<p>我们构建一个摄像机变换矩阵$\boldsymbol{M}$，首先平移所有物体使摄像机至于原点，然后使$\boldsymbol{r}$和$(1,0,0)$重合，$\boldsymbol{u}$和$(0,0,1)$重合：</p>
<p>$$<br>
\boldsymbol{M}= \underbrace{\begin{pmatrix}<br>
r_x &amp; r_y &amp; r_z &amp; 0 \\<br>
u_x &amp; u_y &amp; u_z &amp; 0 \\<br>
v_x &amp; v_y &amp; v_z &amp; 0 \\<br>
0 &amp; 0 &amp; 0 &amp; 1 \\<br>
\end{pmatrix}}_{\text{基础变化 (change of basis)}}<br>
\underbrace{\begin{pmatrix}<br>
1 &amp; 0 &amp; 0 &amp; -t_x \\<br>
0 &amp; 1 &amp; 0 &amp; -t_y \\<br>
0 &amp; 0 &amp; 1 &amp; -t_z \\<br>
0 &amp; 0 &amp; 0 &amp; 1 \\<br>
\end{pmatrix}}_{\text{位移(translation)}}<br>
= \begin{pmatrix}<br>
r_x &amp; r_y &amp; r_z &amp; \boldsymbol{-t}\cdot\boldsymbol{r} \\<br>
u_x &amp; u_y &amp; u_z &amp; \boldsymbol{-t}\cdot\boldsymbol{u} \\<br>
v_x &amp; v_y &amp; v_z &amp; \boldsymbol{-t}\cdot\boldsymbol{v} \\<br>
0 &amp; 0 &amp; 0 &amp; 1 \\<br>
\end{pmatrix}<br>
$$</p>
<br>
<h4 id="417normaltransform">4.1.7 Normal Transform （法线变换）</h4>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/09/4.6.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter4"></figure><p>一个矩阵可以始终可以变换点线三角或者其他几何体，也可以沿着线段或者三角形表面变换切线向量（tangent vector），但是不总能用于变换表面法线（或者顶点光照法线）。正确的方法是使用伴随矩阵（matrix's adjoint）的转置(transpose)。另一种传统做法是使用顶点变换矩阵的逆转置矩阵。但是逆矩阵不一定存在且效率较低。</p>
<p>多数变换是仿射的，所以由齐次坐标传入的w分量的值不会发生改变，即他们不执行projection。所以只要计算伴随矩阵左上的3×3部分即可。</p>
<p>甚至有时伴随矩阵的计算都是不需要的。设变换矩阵由位移旋转和统一缩放（不形变）组成。位移不改变法线，缩放不影响法线方向，只改变长度，只剩旋转。旋转矩阵本身的转置就是它的逆。所以在这种情况下直接用两个转置（或两个逆）旋转矩阵的组合去计算即可。</p>
<p>重算法线也不是经常需要，如果没有缩放，只有旋转和位移的话；如果有统一缩放，我们可以用缩放因子直接去除法线。</p>
<br>
<h4 id="418computationofinverses">4.1.8 Computation of Inverses (逆的运算)</h4>
<ul>
<li>简单变换如$\boldsymbol{M=T(t)R}(\phi)$，它的逆是$\boldsymbol{M^{-1}}=\boldsymbol{R}(-\phi)\boldsymbol{T(-t)}$。</li>
<li>若矩阵是正交的，则$\boldsymbol{M^{-1}=M^\top}$，任意序列的旋转矩阵都是旋转矩阵，也是正交矩阵。</li>
<li>如果没有已知，则伴随矩阵的方法，克莱姆法则（Cramer's rule），分解法（LU decomposition）或者高斯消元法（Gaussian Elimination）可以用于计算逆矩阵。（前两种更推荐）。</li>
</ul>
<br>
<h3 id="42specialmatrixtransformsandoperations">4.2 Special Matrix Transforms and Operations（特殊的矩阵变换和计算）</h3>
<h4 id="421theeulertransform">4.2.1 The Euler Transform（欧拉变换）</h4>
<p>首先确定观察方向为-z轴方向，头顶方向为y轴方向，如图：</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/09/4.7.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter4"></figure><p>$$<br>
\boldsymbol{E}(h,p,r)=\boldsymbol{R}_z(r)\boldsymbol{R}_x(p)\boldsymbol{R}_y(h)<br>
$$</p>
<p>矩阵的排序可以有24中不同方式，上面这种更常用。h,p,r代表基于head（或yaw），pitch，roll的旋转量。有时候存在z-up或是y-up的分歧，3D打印工业，航海航空等可能认为z轴朝上，而媒体相关常常将y轴作为向上。本书使用y-up。</p>
<p>欧拉角的限制：两个欧拉角难以组合使用，万向锁问题。</p>
<br>
<h4 id="422extractingparametersfromtheeulertransform">4.2.2 Extracting Parameters from the Euler Transform（从欧拉变换中提取参数）</h4>
<p>一些情况下，我们希望在欧拉矩阵中提取h,p,r，在这里我们废弃了4×4矩阵是因为所有有效信息都包含在了左上的3×3矩阵中。</p>
<p>$$<br>
\boldsymbol{E}(h,p,r)=  \begin{pmatrix}<br>
e_{00} &amp; e_{01} &amp; e_{02} \\<br>
e_{10} &amp; e_{11} &amp; e_{12} \\<br>
e_{20} &amp; e_{21} &amp; e_{22}<br>
\end{pmatrix}<br>
=  \boldsymbol{R}_z(r)\boldsymbol{R}_x(p)\boldsymbol{R}_y(h)<br>
$$</p>
<p>展开后得到：</p>
<p>$$<br>
\boldsymbol{E}(h,p,r)=<br>
\begin{pmatrix}<br>
\cos{r}\cos{h}-\sin{r}\sin{p}\sin{h} &amp;<br>
-\sin{r}\cos{p} &amp;<br>
\cos{r}\sin{h}+\sin{r}\sin{p}\cos{h} \\<br>
\sin{r}\cos{h}+\cos{r}\sin{p}\sin{h} &amp;<br>
\cos{r}\cos{p} &amp;<br>
\sin{r}\sin{h}-\cos{r}\sin{p}\cos{h} \\<br>
-cos{p}\sin{h} &amp; \sin{p} &amp; \cos{p}\cos{h}<br>
\end{pmatrix}<br>
$$</p>
<p>得到：</p>
<p>$$<br>
h = atan2(-e_{20}, e_{22}) \\<br>
p = \arcsin(e_{21}) \\<br>
r = atan2(-e_{01}, e_{11})<br>
$$</p>
<p>如果$\cos{p}=0$ ，会产生万向锁问题，导致r和h绕同一个轴转动。万向锁指的是在进行旋转时，失去了一个方向的自由度。尽管如此还是被广泛使用，比如动画师喜欢用曲线编辑器调整角度如何随时间变化。</p>
<br>
<h4 id="423matrixdecomposition">4.2.3 Matrix Decomposition（矩阵分解）</h4>
<p>从串联的矩阵中检索各种变换的任务叫做矩阵分解。以下情况可能会用到：</p>
<ul>
<li>对物体提取缩放因子。</li>
<li>为一个特殊的系统找到一个变换，有些系统可能不支持专有的4×4矩阵。</li>
<li>判断一个模型是否只经历了刚体变换。</li>
<li>在只可以操作矩阵的时候在关键帧间插值</li>
<li>从一个旋转矩阵中剔除剪切。</li>
</ul>
<p>（这里的内容详见原书参考）</p>
<br>
<h4 id="424rotationaboutanarbitraryaxis">4.2.4 Rotation about an Arbitrary Axis（关于任意轴的旋转）</h4>
<p>创建一个变换使物体围绕一个归一化的向量$\boldsymbol{r}$旋转$\alpha$弧度。</p>
<p>首先我们先使用一个旋转矩阵$\boldsymbol{M}$把$\boldsymbol{r}$和x轴对齐，我们使用$\boldsymbol{M}^{-1}$退回这个操作。为计算$\boldsymbol{M}$，我们需要找到两个向量使对彼此以及对$\boldsymbol{r}$都正交且归一化。$\boldsymbol{t}=\boldsymbol{r}\times\boldsymbol{s}$。可以找到$\boldsymbol{r}$中绝对值比较小那个设为0，交换剩余两个值，然后把在前面的值设为负。数学描述为：</p>
<p>$$<br>
\overline{\boldsymbol{s}}=<br>
\begin{cases}<br>
(0,-r_z,r_y),<br>
if\<br>
\vert{r_x}\vert\le\vert{r_y}\vert \<br>
\ and \<br>
\vert{r_x}\vert\le\vert{r_y}\vert, \\<br>
(-r_z,0,r_y),<br>
if\<br>
\vert{r_y}\vert\le\vert{r_x}\vert \<br>
\ and \<br>
\vert{r_y}\vert\le\vert{r_z}\vert, \\<br>
(-r_y,r_x,0),<br>
if\<br>
\vert{r_z}\vert\le\vert{r_x}\vert \<br>
\ and \<br>
\vert{r_z}\vert\le\vert{r_y}\vert,<br>
\end{cases}<br>
\\<br>
\boldsymbol{s}=\overline{\boldsymbol{s}}/\vert\vert\overline{\boldsymbol{s}}\vert\vert,<br>
\\<br>
\boldsymbol{t} = \boldsymbol{r}\times\boldsymbol{s}<br>
$$</p>
<p>还有一些其他算法也可以进行此类计算，请查看原书资源部分。</p>
<p>用得到的三个向量创建矩阵$\boldsymbol{M}$:</p>
<p>$$<br>
\boldsymbol{M}=<br>
\begin{pmatrix}<br>
\boldsymbol{r}^\top\\<br>
\boldsymbol{s}^\top\\<br>
\boldsymbol{t}^\top\\<br>
\end{pmatrix}<br>
$$</p>
<p>得出最终矩阵：</p>
<p>$$<br>
\boldsymbol{X} = \boldsymbol{M}^\top\boldsymbol{R}_x(\alpha)\boldsymbol{M}<br>
$$</p>
<p>还有一个来自Goldman的矩阵方法：</p>
<p>$$<br>
\boldsymbol{R}=<br>
\begin{pmatrix}<br>
\cos\phi+(1-\cos\phi)r^2_x &amp;<br>
(1-\cos\phi)r_xr_y-r_z\sin\phi &amp;<br>
(1-\cos\phi)r_xr_z+r_y\sin\phi \\<br>
(1-\cos\phi)r_xr_y+r_z\sin\phi &amp;<br>
\cos\phi + (1-\cos\phi)r^2_y &amp;<br>
(1-\cos\phi)r_yr_z-r_x\sin\phi \\<br>
(1-\cos\phi)r_xr_z-r_y\sin\phi &amp;<br>
(1-\cos\phi)r_yr_z+r_x\sin\phi &amp;<br>
\cos\phi+(1-\cos\phi)r^2_z<br>
\end{pmatrix}<br>
$$</p>
<br>
<h3 id="43quaternions">4.3 Quaternions（四元数）</h3>
<p>四元数在很多方面都优越于欧拉角或矩阵，用于标识旋转或方向。任意三维方向可以表示为一个对特定轴的旋转。给定的轴或者角度可以简单的转换为四元数或者由四元数转换。四元数还可以用于插值（欧拉角不行）。</p>
<p>一个复数（complex number）有实部（real part）和虚部（imaginary part），类似的，四元数的前三个值和旋转轴密切相关，旋转角度影响四个部分。后面我们用$\hat{\boldsymbol{q}}$表示四元数。</p>
<h4 id="431mathematicalbackground">4.3.1 Mathematical Background（数学背景）</h4>
<p>定义：</p>
<p>$$<br>
\begin{align}<br>
&amp;\hat{\boldsymbol{q}}<br>
=(\boldsymbol{q}_v,q_w)=iq_x + jq_y + kq_z + q_w = \boldsymbol{q}_v+q_w,\\<br>
&amp;\boldsymbol{q}_v = iq_x + jq_y + kq_z = (q_x,q_y,q_z), \\<br>
&amp;i^2 = j^2 = k^2 = -1,jk=-kj=i,ki=-ik=j,ij=-ji=k<br>
\end{align}<br>
$$</p>
<p>$q_w$被称为四元数的实部，$\boldsymbol{q}_v, i, j,k$称为四元数的虚部。</p>
<p>根据定义，两个四元数$\hat{\boldsymbol{q}},\hat{\boldsymbol{r}}$ 的乘法，注意乘法中虚部是不满足交换律的：</p>
<p>$$<br>
\begin{align}<br>
\hat{\boldsymbol{q}}\hat{\boldsymbol{r}}<br>
=&amp;(iq_x + jq_y + kq_z + q_w)(ir_x + jr_y + kr_z + r_w) \\<br>
=&amp;\quad i(q_yr_z-q_zr_y+r_wq_x+q_wr_x) \\<br>
&amp;+ j(q_zr_x-q_xr_z+r_wq_y+q_wr_y) \\<br>
&amp;+ k(q_xr_y-q_yr_x+r_wq_z+q_wr_z) \\<br>
&amp;+ q_wr_w-q_xr_x-q_yr_y-q_zr_z \\<br>
=&amp;(\boldsymbol{q}_v\times\boldsymbol{r}_v + r_w\boldsymbol{q}_v + q_w\boldsymbol{r}_v, q_w r_w-\boldsymbol{q}_v\cdot\boldsymbol{r}_v)<br>
\end{align}<br>
$$</p>
<p>其他：</p>
<p>$$<br>
\begin{align}<br>
&amp;\text{加法(Addition):}<br>
&amp;&amp;\hat{\boldsymbol{q}}+\hat{\boldsymbol{r}}<br>
=(\boldsymbol{q_v}+\boldsymbol{r_v},q_w+r_w) \\<br>
&amp;\text{共轭(Conjugate):}<br>
&amp;&amp;\hat{\boldsymbol{q_v}}^* = (\boldsymbol{q_v},q_w)^* = (-\boldsymbol{q_v},q_w) \\<br>
&amp;\text{归一化:}<br>
&amp;&amp;n(\hat{\boldsymbol{q}})<br>
= \sqrt{\hat{\boldsymbol{q}}\hat{\boldsymbol{q}}^*}<br>
= \sqrt{\hat{\boldsymbol{q}}^*\hat{\boldsymbol{q}}}<br>
= \sqrt{\boldsymbol{q}_v\cdot\boldsymbol{q}_v + q_w^2}<br>
= \sqrt{q_x^2+q_y^2+q_z^2+q_w^2} \\<br>
&amp;\text{同一性(Identity，即没有旋转):}<br>
&amp;&amp;\hat{\boldsymbol{i}}=(\boldsymbol{0},1) \\<br>
&amp;\text{标量乘法(Scalar Multiplication):}<br>
&amp;&amp;s\hat{\boldsymbol{q}}<br>
= (\boldsymbol{0},s)(\boldsymbol{q}_v,q_w)<br>
= (s\boldsymbol{q}_v, sq_w)<br>
\end{align}<br>
$$</p>
<p>归一化公式简化后，虚部抵消了只剩下实部。以上公式还可以导出乘法的逆$\hat{\boldsymbol{q}}^{-1}$，即$\hat{\boldsymbol{q}}^{-1}\hat{\boldsymbol{q}}=\hat{\boldsymbol{q}}\hat{\boldsymbol{q}}^{-1} = 1$：</p>
<p>$$<br>
n(\hat{\boldsymbol{q}})^2=\hat{\boldsymbol{q}}\hat{\boldsymbol{q}}^*<br>
\Longleftrightarrow<br>
\frac{\hat{\boldsymbol{q}}\hat{\boldsymbol{q}}^*}{n(\hat{\boldsymbol{q}})^2} = 1 \\<br>
\Downarrow \\<br>
\hat{\boldsymbol{q}}^{-1}<br>
=\frac{1}{n(\hat{\boldsymbol{q}})^2}\hat{\boldsymbol{q}}^*<br>
$$</p>
<p>以下定理基于定义很容易证明：</p>
<p>$$<br>
\begin{align}<br>
&amp;共轭法则(Conjugate Rules)：<br>
&amp;&amp;(\hat{\boldsymbol{q}}^*)^* = \hat{\boldsymbol{q}} \\<br>
&amp;&amp;&amp; (\hat{\boldsymbol{q}}+\hat{\boldsymbol{r}})^*<br>
=\hat{\boldsymbol{q}}^*+\hat{\boldsymbol{r}}^* \\<br>
&amp;&amp;&amp; (\hat{\boldsymbol{q}}\hat{\boldsymbol{r}})^*<br>
=\hat{\boldsymbol{r}}^*\hat{\boldsymbol{q}}^* \\<br>
&amp;\text{归一法则（Norm Rules）：}\\<br>
&amp;&amp;&amp; n(\hat{\boldsymbol{q}}^*)=n(\hat{\boldsymbol{q}}) \\<br>
&amp;&amp;&amp; n(\hat{\boldsymbol{q}}\hat{\boldsymbol{r}})<br>
=n(\hat{\boldsymbol{q}})n(\hat{\boldsymbol{r}}) \\<br>
&amp;\text{乘法法则(Laws of Multiplication): }\\<br>
&amp;\text{线性关系（Linearity）：}\\<br>
&amp;&amp;&amp; \hat{\boldsymbol{p}}(s\hat{\boldsymbol{q}}+t\hat{\boldsymbol{r}})<br>
= s\hat{\boldsymbol{p}}\hat{\boldsymbol{q}}<br>
+t\hat{\boldsymbol{p}}\hat{\boldsymbol{r}} \\<br>
&amp;&amp;&amp; (s\hat{\boldsymbol{p}}+t\hat{\boldsymbol{q}})\hat{\boldsymbol{r}}<br>
= s\hat{\boldsymbol{p}}\hat{\boldsymbol{r}}<br>
+t\hat{\boldsymbol{q}}\hat{\boldsymbol{r}} \\<br>
&amp;\text{结合律（Associativity）：}\\<br>
&amp;&amp;&amp; \hat{\boldsymbol{p}}(\hat{\boldsymbol{q}}\hat{\boldsymbol{r}})<br>
= (\hat{\boldsymbol{p}}\hat{\boldsymbol{q}})\hat{\boldsymbol{r}}<br>
\end{align}<br>
$$</p>
<p>如果$n(\hat{\boldsymbol{q}})=1$，则$\hat{\boldsymbol{q}}$是一个单位四元数（unit quaternion），那么可以写成：</p>
<p>$$<br>
\hat{\boldsymbol{q}}<br>
=(\sin\phi\boldsymbol{u}_q,\cos\phi)<br>
=\sin\phi\boldsymbol{u}_q+\cos\phi<br>
$$</p>
<p>对于$\boldsymbol{u}_q$满足$\Vert\boldsymbol{u}_q\Vert=1$，由于：</p>
<p>$$<br>
n(\hat{\boldsymbol{q}})=n(\sin\phi\boldsymbol{u}_q,\cos\phi)<br>
=\sqrt{\sin^2\phi(\boldsymbol{u}_q\cdot\boldsymbol{u}_q)+\cos^2\phi}<br>
=\sqrt{\sin^2\phi+\cos^2\phi}<br>
=1<br>
$$</p>
<p>当且仅当$\boldsymbol{u}_q\cdot\boldsymbol{u}_q=1=\Vert\boldsymbol{u}_q\Vert^2$时成立。$\boldsymbol{u}_q$非常适合在一些情况下快速创建旋转或者定位。</p>
<p>一个2D单位向量可以写为$\cos\phi+i\sin\phi=e^{i\phi}$，四元数则是：</p>
<p>$$<br>
\hat{\boldsymbol{q}}=\sin\phi\boldsymbol{u}_q+\cos\phi=e^{\phi\boldsymbol{u}_q}<br>
$$</p>
<p>其他一些关于单位四元数的方法：</p>
<p>$$<br>
\begin{align}<br>
对数：&amp;\log(\hat{\boldsymbol{q}})=\log(e^{\phi\boldsymbol{u}_q})=\phi\boldsymbol{u}_q\\<br>
乘方：&amp;\hat{\boldsymbol{q}}^t<br>
=(\sin\phi\boldsymbol{u}_q+\cos\phi)^t<br>
=e^{\phi t\boldsymbol{u}_q}<br>
=\sin(\phi t)\boldsymbol{u}_q+\cos(\phi t)<br>
\end{align}<br>
$$</p>
<br>
<h4 id="432quaterniontransforms">4.3.2 Quaternion Transforms（四元数变换）</h4>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/09/4.9.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter4"></figure><p>上图显示了一个单位四元数$\hat{\boldsymbol{q}}=(\sin\phi\boldsymbol{u}_q,\cos\phi)$，绕着$\boldsymbol{u}_q$旋转$2\phi$弧度。</p>
<p>一个单位四元数可以代表任意三维旋转。</p>
<p>我们把一个点$\boldsymbol{p}=(p_x,p_y,p_z,p_w)^\top$放到四元数$\boldsymbol{p}$中，然后假设我们有一个单位四元数$\hat{\boldsymbol{q}}=(\sin\phi\boldsymbol{u}_q,\cos\phi)$，可以证明$\hat{\boldsymbol{q}}\hat{\boldsymbol{p}}\hat{\boldsymbol{q}}^{-1}$使得点$\boldsymbol{p}$围绕轴$\boldsymbol{u}_q$旋转了$2\phi$角度，且$\hat{\boldsymbol{q}}$是一个单位四元数，所以$\hat{\boldsymbol{q}}^{-1}=\hat{\boldsymbol{q}}^*$，任意$\hat{\boldsymbol{q}}$的非零实数倍都表示着相同的变换，也就是$\hat{\boldsymbol{q}}$和$-\hat{\boldsymbol{q}}$表示同一个变换，再进一步说，忽略轴，$\boldsymbol{u}_q$和实部$q_w$就组成了和原始四元数一样的旋转。还意味着从矩阵中提取四元数可以得到$\hat{\boldsymbol{q}}$或$-\hat{\boldsymbol{q}}$。</p>
<p>给定两个单位四元数$\hat{\boldsymbol{q}}$和$\hat{\boldsymbol{r}}$，对点$\hat{\boldsymbol{p}}$应用$\hat{\boldsymbol{q}}$再应用$\hat{\boldsymbol{r}}$，可以表示为：</p>
<p>$$<br>
\hat{\boldsymbol{r}}<br>
(\hat{\boldsymbol{q}}\hat{\boldsymbol{p}}\hat{\boldsymbol{q}}^*)<br>
\hat{\boldsymbol{r}}^*<br>
=(\hat{\boldsymbol{r}}\hat{\boldsymbol{q}})<br>
\hat{\boldsymbol{p}}(\hat{\boldsymbol{q}}\hat{\boldsymbol{r}})^*<br>
=\hat{\boldsymbol{c}}\hat{\boldsymbol{p}}\hat{\boldsymbol{c}}^*<br>
\ where \<br>
(\hat{\boldsymbol{c}}=\hat{\boldsymbol{r}}\hat{\boldsymbol{q}})<br>
$$</p>
<br>
<h5 id="matrixconversion">Matrix Conversion（矩阵的转换）</h5>
<p>由于实际使用中我们需要串联很多不同的矩阵，于是我们要把$\hat{\boldsymbol{q}}\hat{\boldsymbol{p}}\hat{\boldsymbol{q}}^{-1}$转换成一个矩阵。</p>
<p>$$<br>
\boldsymbol{M}^q =<br>
\begin{pmatrix}<br>
1-s(q_y^2+q_z^2) &amp; s(q_xq_y-q_wq_z) &amp; s(q_xq_z+q_wq_y) &amp; 0 \\<br>
s(q_xq_y+q_wq_z) &amp; 1-s(q_x^2+q_z^2) &amp; s(q_yq_z-q_wq_x) &amp; 0 \\<br>
s(q_xq_z-q_wq_y) &amp; s(q_yq_z+q_wq_x) &amp; 1-s(q_x^2+q_y^2) &amp; 0 \\<br>
0 &amp; 0 &amp; 0 &amp; 1<br>
\end{pmatrix}<br>
$$</p>
<p>其中标量$s=2/(n(\hat{\boldsymbol{q}}))^2$，对于单位四元数，则上述矩阵可以简化为：</p>
<p>$$<br>
\boldsymbol{M}^q =<br>
\begin{pmatrix}<br>
1-2(q_y^2+q_z^2) &amp; 2(q_xq_y-q_wq_z) &amp; 2(q_xq_z+q_wq_y) &amp; 0 \\<br>
2(q_xq_y+q_wq_z) &amp; 1-2(q_x^2+q_z^2) &amp; 2(q_yq_z-q_wq_x) &amp; 0 \\<br>
2(q_xq_z-q_wq_y) &amp; 2(q_yq_z+q_wq_x) &amp; 1-2(q_x^2+q_y^2) &amp; 0 \\<br>
0 &amp; 0 &amp; 0 &amp; 1<br>
\end{pmatrix}<br>
$$</p>
<p>四元数不涉及任何三角函数的计算，所以很高效。</p>
<p>从矩阵到单位四元数的反向转换会更复杂一些，此过程的关键是以下公式和矩阵公式的一些差异：</p>
<p>$$<br>
m_{21}^q-m_{12}^q=4q_wq_x \\<br>
m_{02}^q-m_{20}^q=4q_wq_y \\<br>
m_{10}^q-m_{01}^q=4q_wq_z<br>
$$</p>
<p>如果 $q_w$ 已知，则可通过上述方程求出四元数。<a href="https://baike.baidu.com/item/%E7%9F%A9%E9%98%B5%E7%9A%84%E8%BF%B9">矩阵的迹</a>(trace of matrix)计算如下：</p>
<p>$$<br>
tr(\boldsymbol{M}^q)<br>
=4-2s(q_x^2+q_y^2+q_z^2)<br>
=4(1-\frac{q_x^2+q_y^2+q_z^2}{q_x^2+q_y^2+q_z^2+q_w^2})<br>
=\frac{4q_w^2}{q_x^2+q_y^2+q_z^2+q_w^2}<br>
=\frac{4q_w^2}{(n(\hat{\boldsymbol{q}}))^2}<br>
$$</p>
<p>则：</p>
<p>$$<br>
\begin{align}<br>
&amp;q_w = \frac12\sqrt{tr(\boldsymbol{M}^q)},&amp;q_x=\frac{m_{21}^q-m_{12}^q}{4q_w} \\<br>
&amp;q_y=\frac{m_{02}^q-m_{20}^q}{4q_w},&amp;q_z=\frac{m_{10}^q-m_{01}^q}{4q_w}<br>
\end{align}<br>
$$</p>
<p>为了使得数值更稳定，应该避免使用过小的数值进行除法运算。因此首先设$t=q_w^2-q_x^2-q_y^2-q_z^2$，然后:</p>
<p>$$<br>
m_{00}=t+2q_x^2,\\<br>
m_{11}=t+2q_y^2,\\<br>
m_{22}=t+2q_z^2,\\<br>
u=m_{00}+m_{11}+m_{22}=t+2q_w^2<br>
$$</p>
<p>反过来说$m_{00,11,22}$中最大的值和u共同决定了$q_x,q_y,q_z,q_w$中哪个最大，如果$q_w$最大，则用更上面的公式计算，其他情况则要注意：</p>
<p>$$<br>
4q_x^2=+m_{00}-m_{11}-m_{22}+m_{33},\\<br>
4q_y^2=-m_{00}+m_{11}-m_{22}+m_{33},\\<br>
4q_z^2=-m_{00}-m_{11}+m_{22}+m_{33},\\<br>
4q_w^2=tr(\boldsymbol{M}^q)<br>
$$</p>
<p>可以用这种方式找到最大值，然后再用来当除数，去计算其他值。</p>
<br>
<h5 id="sphericallinearinterpolation">Spherical Linear Interpolation（球面线性插值）</h5>
<p>球面线性插值是一种计算，给定两个单位四元数$\hat{\boldsymbol{q}}$和$\hat{\boldsymbol{r}}$，以及参数$t\in[0,1]$，计算一个插值后的四元数。这对于动画很有用，对于摄像机的方向则不太适用，因为摄像机的向上方向会在插值过程中变得倾斜。代数表示为：</p>
<p>$$<br>
\hat{\boldsymbol{s}}(\hat{\boldsymbol{q}},\hat{\boldsymbol{r}},t)<br>
=(\hat{\boldsymbol{r}}\hat{\boldsymbol{q}}^{-1})^t\hat{\boldsymbol{q}}<br>
$$</p>
<p>对于软件，一般表示为<code>slerp</code>：</p>
<p>$$<br>
\hat{\boldsymbol{s}}(\hat{\boldsymbol{q}},\hat{\boldsymbol{r}},t)<br>
=slerp(\hat{\boldsymbol{q}},\hat{\boldsymbol{r}},t)<br>
=\frac{\sin(\phi(1-t))}{\sin\phi}\hat{\boldsymbol{q}}<br>
+\frac{\sin(\phi t)}{\sin\phi}\hat{\boldsymbol{r}}<br>
$$</p>
<p>欲求$\phi$，可以用$\cos\phi=q_xr_x+q_yr_y+q_zr_z+q_wr_w$来计算，对$t\in[0,1]$，<code>slerp</code>函数构成了从$\hat{\boldsymbol{q}}(t=0)$到$\hat{\boldsymbol{r}}(t=1)$的四维单位球体球面上的最短弧（当且仅当这里的两个四元数不完全反向）。这个弧在$\hat{\boldsymbol{q}}(t=0)$和$\hat{\boldsymbol{r}}(t=1)$以及球的原点构成的平面与球体相交形成的圆上，这个圆上面的一部分被称为大弧（a great arc）。</p>
<p>计算出的四元数以恒定速度绕轴旋转，像这样具有恒定速度和0加速度的曲线，被称作测地曲线（geodesic curve）。</p>
<p>由于连续的四元数变换会有一些抖动的问题，更好的插值方式可能是用到一些样条。我们再四元数$\hat{\boldsymbol{q}}<em>i$和$\hat{\boldsymbol{q}}</em>{i+1}$中间引入两个四元数$\hat{\boldsymbol{a}}<em>i$和$\hat{\boldsymbol{a}}</em>{i+1}$，可以通过这四个四元数定义一个球面三次插值。</p>
<p>$$<br>
\hat{\boldsymbol{a}}_i=<br>
\hat{\boldsymbol{q}}_i\exp<br>
\left[<br>
-\frac{\log(\hat{\boldsymbol{q}}_i^{-1}\hat{\boldsymbol{q}}_{i-1})<br>
+\log(\hat{\boldsymbol{q}}_i^{-1}\hat{\boldsymbol{q}}_{i+1})}<br>
{4}<br>
\right]<br>
$$</p>
<p>使用三次样条曲线，$\hat{\boldsymbol{q}}_i$和$\hat{\boldsymbol{a}}_i$用于球面插值四元数：</p>
<p>$$<br>
squad(<br>
\hat{\boldsymbol{q}}_i,<br>
\hat{\boldsymbol{q}}_{i+1},<br>
\hat{\boldsymbol{a}}_i,<br>
\hat{\boldsymbol{a}}_{i+1})<br>
=slerp(slerp(\hat{\boldsymbol{q}}_i,\hat{\boldsymbol{q}}_{i+1},t),<br>
slerp(\hat{\boldsymbol{a}}_i,\hat{\boldsymbol{a}}_{i+1},t),<br>
2t(1-t))<br>
$$</p>
<p><code>squad</code>函数通过重复使用<code>slerp</code>插值进行计算，插值会通过所有$\hat{\boldsymbol{q}}_{i...}$，但不会通过$\hat{\boldsymbol{a}}_{i...}$，它们是用来表示初始方向的切线方向的。</p>
<br>
<h5 id="rotationfromonevectortoanother">Rotation from One Vector to Another（从一个向量旋转到另一个）</h5>
<p>使向量$\boldsymbol{s}$指向另一个向量$\boldsymbol{t}$的运算，首先归一化两个向量。然后计算单位旋转轴$\boldsymbol{u}=(\boldsymbol{s}\times\boldsymbol{t})/\Vert\boldsymbol{s}\times\boldsymbol{t}\Vert$，然后$e=\boldsymbol{s}\cdot\boldsymbol{t}=\cos(2\phi),\Vert\boldsymbol{s}\times\boldsymbol{t}\Vert=\sin(2\phi)$其中$2\phi$是两个向量间的夹角。则表示从$\boldsymbol{s}$转向$\boldsymbol{t}$的四元数可以表示为$\hat{\boldsymbol{q}}=(\sin\phi\boldsymbol{u},\cos\phi)$。使用半角公式和三角恒等式简化后得到：</p>
<p>$$<br>
\hat{\boldsymbol{q}}=(\boldsymbol{q}_v,q_w)<br>
=\left(<br>
\frac{1}{\sqrt{2(1+e)}}(\boldsymbol{s}\times\boldsymbol{t}),<br>
\frac{\sqrt{2(1+e)}}{2}<br>
\right)<br>
$$</p>
<p>矩阵化的方式，注意它不需要计算三角函数：</p>
<p>$$<br>
\boldsymbol{R(s,t)}=<br>
\begin{pmatrix}<br>
e+hv_x^2 &amp; hv_xv_y-v_z &amp; hv_xv_z + v_y &amp; 0 \\<br>
hv_xv_y + v_z &amp; e+hv_y^2 &amp; hv_yv_z-v_x &amp; 0 \\<br>
hv_xv_z-v_y &amp; hv_yv_z+v_x &amp; e+hv_z^2 &amp; 0 \\<br>
0&amp;0&amp;0&amp;1<br>
\end{pmatrix} where \ \<br>
\begin{align}<br>
&amp; \boldsymbol{v} = \boldsymbol{s} \times \boldsymbol{t} \\<br>
&amp; e = \cos(2\phi)=\boldsymbol{s}\cdot\boldsymbol{t} \\<br>
&amp; h = \frac{1-\cos(2\phi)}{\sin^2(2\phi)}<br>
= \frac{1-e}{\boldsymbol{v}\cdot\boldsymbol{v}}<br>
= \frac{1}{1+e}<br>
\end{align}<br>
$$</p>
<p>若两个向量趋近平行，则我们可以返回单位矩阵；若两个向量相差180度，则可以围绕任意轴旋转$\pi$，该轴可以基于$\boldsymbol{s}$与任意不与$\boldsymbol{s}$平行的向量之间的叉积。<br>
<br></p>
<h3 id="44vertexblending">4.4 Vertex Blending（顶点绑定）</h3>
<p>假设一条胳膊由前臂和上臂组成，如果由两个部分组成，使用刚体变换模拟肘部旋转，则不太真实。顶点绑定（Vertex blending）用于解决此类问题。这种技术还有其他几种名字linear-blend skinning，enveloping或者skeleton-subspace deformation。</p>
<p>一种简单的形式前臂和上臂照原来形式变换，但是在衔接处使用弹性的“皮肤”连接，因此这个皮肤的一部分顶点将由前臂的矩阵变换，另一部分由上臂的矩阵变换。更进一步，我们可以让单个顶点接收若干矩阵的变换，所得到的位置加权混合在一起。这是通过对动画对象设置骨骼实现的。整个手臂可能都是这样的，所以在骨骼上的网格我们称作“皮肤”。</p>
<p>数学描述，若$p$是原始顶点，$\boldsymbol{u}(t)$是根据时间$t$变换后的坐标：</p>
<p>$$<br>
\boldsymbol{u}(t)<br>
=\sum\limits_{i=0}^{n-1}w_i\boldsymbol{B}_i(t)\boldsymbol{M}_i^{-1}\boldsymbol{p}<br>
\quad<br>
\text{where}<br>
\quad<br>
\sum\limits_{i=0}^{n-1}w_i=1,w_i\ge0<br>
$$</p>
<p>有n个骨骼影响着$\boldsymbol{p}$点，以世界坐标描述。$w_i$表示第$i$个骨骼对于点$p$的权重，矩阵$\boldsymbol{M}_i$把骨骼的初始坐标转换为世界坐标。通常骨骼在其坐标系的原点处具有控制关节（controlling joint）。例如前臂骨骼将肘关节移动至原点，旋转矩阵再将手臂的这个部分围绕关节旋转。$\boldsymbol{B}_i(t)$是随时间变化的第$i$个骨骼的世界变换，通常由一串矩阵构成，例如先前的骨骼变换层次结构以及局部动画矩阵。</p>
<p>每个骨骼在每一帧于自己的坐标系中将一个顶点进行变换，最终结果由计算出的点集插值而来。由于矩阵$\boldsymbol{M}_i$总是矩阵链接中的一部分，所以这里把它单提出来，也有时候它也会合并到$\boldsymbol{B}_i(t)$矩阵中。</p>
<p>由于权重都是非负的且总和为1，也就是顶点被变换到若干的部分再进行插值。所以计算出的结果点$\boldsymbol{u}$在固定时间点（$t$为定值）将在点集$\boldsymbol{B}_i(t)\boldsymbol{M}_i^{-1}\boldsymbol{p}，i=0...n-1$围成的凸多边形内。</p>
<p>顶点混合非常适合在GPU上面计算，网格中的顶点可以放置在静态缓存中一次性发送给GPU并反复利用。在每一帧中，只有骨骼矩阵发生了改变，然后由顶点着色器计算这些顶点。这样由CPU传输的数据量被降到最低，使GPU得以高效渲染网格。如果模型的整个骨矩阵可以一起使用，这是最简单的；否则必须拆分模型并复制一些骨骼。或者，骨骼变换可以存储在顶点访问的纹理中，这样可以避免达到寄存器存储限制。</p>
<p>在使用例如变形等其他混合算法时，可以使权重和大于1。</p>
<p>简单的顶点混合会发生不希望的折叠、扭曲和自交叉等问题，更好的方法是使用双四元数蒙皮。这种技术有助于保持原本皮肤的刚性，避免发生&quot;candy wrapper&quot;四肢扭曲。计算成本小于线性混合的1.5倍而且结果不错，使这项技术快速得以广泛使用。然而双四元数蒙皮可能导致膨胀问题，后Le和Hodgins 提出的旋转中心蒙皮成为更好的选择。这种方法依赖于局部变换是刚体的假设，并且顶点具有相似的权重和相似的变换。每个顶点的旋转中心被预计算，同时施加正交（刚体）约束（orthogonal/rigidbody constraint）以防止肘部塌陷（collapse）和糖果包裹物扭曲（candy wrapper）。该算法在GPU中于中心点执行线性混合蒙皮，再加一个四元数的混合步骤。</p>
<br>
<h3 id="45morphing">4.5 Morphing（变形）</h3>
<p>执行动画，从一个3D模型变成另一个3D模型时很有用。顶点涉及解决两个主要问题，顶点对应问题和插值问题。</p>
<p>给定两个可能具有不同拓扑，不同顶点数和不同网格连通性的任意模型，我们一般需要先设置这些顶点对应的关系。这是一个难题，在此领域已经有很多研究。</p>
<p>若已经存在一对一的顶点关系，则可以每个顶点做插值。为计算关于时间$t\in\left[t_0,t_1\right]$的变形顶点，我们首先要计算$s=(t-t_0)/(t_1-t_0)$，然后是线性顶点混合：</p>
<p>$$<br>
\boldsymbol{m}=(1-s)\boldsymbol{p}_0+s\boldsymbol{p}_1<br>
$$</p>
<p>这里$\boldsymbol{p}_0$和$\boldsymbol{p}_1$指的是相同点在不同时间$\boldsymbol{t}_0$和$\boldsymbol{t}_1$所对应的位置。</p>
<p>变形目标（Morph target）或者融合变形（blend shapes）可以为用户操作变形提供更直观的控制。</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/09/4.15.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter4"></figure><p>我们用$\cal{N}$表示一个中性的模型脸，然后有一个笑脸模型$\cal{P}_i,i\in[1,...,k]$（可以是多个），作为预处理，差异模型为：$\cal{D_i=P_i-N}$，原始模型从每个pose中被减去。</p>
<p>然后变形后的模型$\cal{M}$为：</p>
<p>$$<br>
\cal{M=N+\sum\limits_{i=1}^kw_iD_i}<br>
$$</p>
<p>我们可以根据权重$w_i$根据需要添加不同姿势的特征。</p>
<br>
<h3 id="46geometrycacheplayback">4.6 Geometry Cache Playback（几何缓存的播放）</h3>
<p>在一些镜头中，我们需要使用质量极高的动画。一种想法是将所有帧的所有顶点都存储在磁盘中，读取他们来更新网格。但是对于一个有30000点的简单模型来说，需要每秒50MB的速度。Gneiting提出了几种将内存成本降低到10％左右的方法。</p>
<p>对所有位置和纹理坐标使用16-bit整数存储。这一步压缩操作是有损的。然后进行空间和时间的预测并编码差异，对于空间压缩可以使用平行四边形预测(parallelogram prediction)。对于一个三角形网格，下个顶点的预测位置只是简单的在当前三角形周围的三角形所在平面中反射当前三角形，形成平行四边形。然后编码这个新位置的差异，通过良好的预测，大多数差异接近0，所以这是许多常用压缩方案的理想选择。与MPEG压缩类似，预测也在时间维度上执行。也就是说每n帧执行空间压缩。在两帧之间进行时间维度预测，如某顶点移动了从帧n-1到帧n的增量矢量，可能在n+1帧的时候移动相似的量。这些技术充分减少了存储，使该系统可以用于实时数据传输。</p>
<br>
<h3 id="47projections">4.7 Projections（投影）</h3>
<p>目前我们看到的变换中，$w$分量都不受影响，也就是在变换之后点和向量保留了他们的类型。此外矩阵的底始终为$(0,0,0,1)$。</p>
<p>投影矩阵则不同，底行包括矢量和操作点的数字，通常使用均匀化过程（homogenization process）。也就是说$w$分量通常不为1，需要除以$w$来获得非均匀点。</p>
<p>本节假设观察者沿着摄像机$-z$轴观察，$y$朝上，$x$轴朝右，这是一个右手坐标系。DirectX等使用左手系统。</p>
<h4 id="471orthographicprojection">4.7.1 Orthographic Projection（正交投影）</h4>
<p>正交投影后平行线保持平衡，观看场景时，对象保持相同大小。$\boldsymbol{P}_o$是一个正交投影矩阵，它使一个点的x,y分量保持不变，z分量设为0：</p>
<p>$$<br>
\boldsymbol{P}_o=\begin{pmatrix} 1&amp;0&amp;0&amp;0\\0&amp;1&amp;0&amp;0\\0&amp;0&amp;0&amp;0\\0&amp;0&amp;0&amp;1 \end{pmatrix}<br>
$$</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/09/4.17.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter4"></figure><p>效果如上图所示，$\boldsymbol{P}_o$是不可逆的因为$\vert\boldsymbol{P}_o\vert=0$。换句话说，从三维降到二维，无法恢复已删除的维度。使用这种正交投影存在的问题是他将无论正负的z值都投影到平面上，将z值（以及x，y值）限制为某个间隔通常很有用。</p>
<p>一个更常见的矩阵由6元祖组成：$(l,r,b,t,n,f)$，分别表示左右底顶近平面和远平面。此矩阵将这些平面形成的轴对齐包围盒（axis-aligned bounding box，AABB）缩放并转换为以原点为中心的轴对齐立方体（axis-aligned cube）。AABB中最小的角是$(l,b,n)$，最大的角是$(r,t,f)$，要认识到$n&gt;f$，因为在空间中俯瞰负z轴。常识认为far比near要大，所以可以让用户如此做，由计算时再翻转过来。</p>
<p>在OpenGL中的最小角和最大角分别是$(-1,-1,-1)$和$(1,1,1)$，而DirectX中是$(-1,-1,0)$和$(1,1,1)$。这个立方体称为标准视域体（canonical view volume）在体积内的坐标称为归一化设备坐标（normalized device coordinates）。转换过程如图所示：</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/09/4.18.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter4"></figure><p>转换为标准视域体后，要渲染的几何体的顶点要根据立方体进行裁剪。最终将在立方体映射到屏幕上以渲染在立方体内部的几何体，此正交变换如下：</p>
<p>$$<br>
\boldsymbol{P}_o=\boldsymbol{S(s)T(t)}=<br>
\begin{pmatrix}<br>
\frac{2}{r-l}&amp;0&amp;0&amp;0\\<br>
0&amp;\frac{2}{t-b}&amp;0&amp;0\\<br>
0&amp;0&amp;\frac{2}{f-n}&amp;0\\<br>
0&amp;0&amp;0&amp;1<br>
\end{pmatrix}<br>
\begin{pmatrix}<br>
1&amp;0&amp;0&amp;-\frac{l+r}{2}\\<br>
0&amp;1&amp;0&amp;-\frac{t+b}{2}\\<br>
0&amp;0&amp;1&amp;-\frac{f+n}{2}\\<br>
0&amp;0&amp;0&amp;1\\<br>
\end{pmatrix}<br>
=\begin{pmatrix}<br>
\frac{2}{r-l}&amp;0&amp;0&amp;-\frac{r+l}{r-l}\\<br>
0&amp;\frac{2}{t-b}&amp;0&amp;-\frac{t+b}{t-b}\\<br>
0&amp;0&amp;\frac{2}{f-n}&amp;-\frac{f+n}{f-n}\\<br>
0&amp;0&amp;0&amp;1<br>
\end{pmatrix}<br>
$$</p>
<p>由等式可以看出，该矩阵还可以写为一个平移矩阵$\boldsymbol{T(t)}$和一个缩放矩阵$\boldsymbol{S(s)}$的串联，其中$\boldsymbol{s}=(2/(r-l),2/(t-b),2/(f-n)),\boldsymbol{t}=(-(r+l)/2,-(t+b)/2,-(f+n)/2)$。矩阵是可逆的：$\boldsymbol{P}_s^{-1}=\boldsymbol{T(-t)S}((r-l)/2,(t-b)/2,(f-n)/2)$。</p>
<p>在计算机图形学中，在投影后最常使用左手坐标系（投影前是右手，OpenGL），即在视口（viewport）下，x轴向右，y轴向上，z轴向着视口远方。由于远值小于近值，正交投影将始终包含一个镜像变换。</p>
<p>为验证这一点我们使原始AABB和canonical view volume等大小，（此时的AABB坐标为$(l,b,n)=(-1,-1,1)and(r,t,f)=(1,1,-1)$）然后得到：</p>
<p>$$<br>
\boldsymbol{P}_o=\begin{pmatrix} 1&amp;0&amp;0&amp;0\\0&amp;1&amp;0&amp;0\\0&amp;0&amp;-1&amp;0\\0&amp;0&amp;0&amp;1 \end{pmatrix}<br>
$$</p>
<p>这是一个镜像矩阵。使右手坐标系转换到了左手坐标系。但在DirectX中有所不同，由于z轴的映射不同，我们可以在正交矩阵后增加一个平移矩阵和一个缩放矩阵：</p>
<p>$$<br>
\boldsymbol{M}_{st}=<br>
\begin{pmatrix}<br>
1&amp;0&amp;0&amp;0\\<br>
0&amp;1&amp;0&amp;0\\<br>
0&amp;0&amp;0.5&amp;0.5\\<br>
0&amp;0&amp;0&amp;1\\<br>
\end{pmatrix}<br>
$$</p>
<p>然后得到对于DirectX的正交矩阵为：</p>
<p>$$<br>
\boldsymbol{P}_{o[0,1]}=<br>
\begin{pmatrix}<br>
\frac{2}{r-l}&amp;0&amp;0&amp;-\frac{r+l}{r-l}\\<br>
0&amp;\frac{2}{t-b}&amp;0&amp;-\frac{t+b}{t-b}\\<br>
0&amp;0&amp;\frac{1}{f-n}&amp;-\frac{n}{f-n}\\<br>
0&amp;0&amp;0&amp;1<br>
\end{pmatrix}<br>
$$</p>
<p>它通常以转置的形式出现，因为DirectX使用row-major书写矩阵。</p>
<h4 id="472perspectiveprojection">4.7.2 Perspective Projection（透视投影）</h4>
<p>透视接近匹配我们感知世界的方式，近大远小。平行线在投影后通常不平衡，在极端情况下收敛到一点。</p>
<p>首先，我们将为投影到$z=-d，d&gt;0$的透视投影矩阵提供指导性推导。我们从世界空间派生，以简单理解世界到视图转换的过程。同样以OpenGL的形式推导。</p>
<p>假设摄像机位于坐标原点，我们要将一个点$\boldsymbol{p}$投影到平面$z=-d，d&gt;0$上，产生一个新的点$\boldsymbol{q}=(q_x,q_y,-d)$。如下图：</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/09/4.19.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter4"></figure><p>以图中的相似三角形进行推导：</p>
<p>$$<br>
\frac{q_x}{p_x}=\frac{-d}{p_z} \Longleftrightarrow q_x=-d\frac{p_x}{p_z},<br>
q_y=-d\frac{p_y}{p_z},q_z=-d \\<br>
\Downarrow \\<br>
\boldsymbol{P}_p=<br>
\begin{pmatrix}<br>
1&amp;0&amp;0&amp;0\\<br>
0&amp;1&amp;0&amp;0\\<br>
0&amp;0&amp;1&amp;0\\<br>
0&amp;0&amp;-\frac{1}{d}&amp;0<br>
\end{pmatrix}<br>
$$</p>
<p>我们用下列等式检验矩阵：</p>
<p>$$<br>
\boldsymbol{q}=\boldsymbol{P}_p\boldsymbol{p}<br>
=\begin{pmatrix}<br>
1&amp;0&amp;0&amp;0\\<br>
0&amp;1&amp;0&amp;0\\<br>
0&amp;0&amp;1&amp;0\\<br>
0&amp;0&amp;-\frac{1}{d}&amp;0<br>
\end{pmatrix}<br>
\begin{pmatrix}<br>
p_x\\<br>
p_y\\<br>
p_z\\<br>
1<br>
\end{pmatrix}<br>
=\begin{pmatrix}<br>
p_x\\<br>
p_y\\<br>
p_z\\<br>
-p_z/d<br>
\end{pmatrix}<br>
\Rightarrow<br>
\begin{pmatrix}<br>
-dp_x/p_z\\<br>
-dp_y/p_z\\<br>
-d\\<br>
1<br>
\end{pmatrix}<br>
$$</p>
<p>最后一步使向量除以w分量，使得$z$值为$-d$。</p>
<p>均匀化过程的一种几何解释是它将点 $(p_x, p_y, p_z)$ 投影到平面$w=1$上。</p>
<p>与正交变换一样，还存在透视变换，它并不投影到平面上（不可逆的），而是将是椎体转换为先前描述的canonical view volume。</p>
<p>这里假设视锥体（view frustum）从$z=n$开始到$z=f$结束，其中$0\gt n\gt f$。在$z=n$处的矩形具有最小角并处于$(l,b,n)$，在$(r,t,n)$具有最大角。如图：</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/09/4.20.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter4"></figure><p>参数$(l,r,b,t,n.f)$确定摄像机的视锥体。水平视场角（horizontal field of view）由左右平面（由$l,r$确定）之间的角度确定，垂直视场角（vertical field of view）由上下平面（由$t,b$确定）之间的角度确定。视场角（FOV，field of view）越大，摄像机视野越大，看到越多。不对称的截锥体（平截头体，frusta）可以由$(r\ne-l)or(t\ne-b)$创建，这种截锥体可以用于立体显示（stereo viewing）或虚拟现实（VR，virtual reality）。</p>
<p>FOV是提供场景感的重要因素。眼睛本身也具有物理性的FOV，他们的关系是：</p>
<p>$$<br>
\phi = 2\arctan(w/(2d))<br>
$$</p>
<p>其中$\phi$是FOV，$w$是垂直于视线的宽度，$d$是到物体的距离。如25寸显示器的宽约22寸，距离12英寸，水平视野为85度。在20英寸处，是58度。30英寸40度。这个公式还可以用摄像机镜头参数计算FOV。更小的FOV使透视效果更少，更宽的FOV会使对象发生扭曲（像广角相机镜头），尤其靠近屏幕边缘的物体会变得更大。但能够提供更多的关于场景的信息。</p>
<p>把视锥体变换到一个单位矩形中的透视变换矩阵为：</p>
<p>$$<br>
\boldsymbol{P}_p=<br>
\begin{pmatrix}<br>
\frac{2n}{r-l}&amp;0&amp;-\frac{r+l}{r-l}&amp;0\\<br>
0&amp;\frac{2n}{t-b}&amp;-\frac{t+b}{t-b}&amp;0\\<br>
0&amp;0&amp;\frac{f+n}{f-n}&amp;-\frac{2fn}{f-n}\\<br>
0&amp;0&amp;1&amp;0<br>
\end{pmatrix}<br>
$$</p>
<p>对一个点应用该变换后得到点$\boldsymbol{q}=(q_x,q_y,q_z,q_w)^\top$，$w$分量一般不为0且不为1，所以要除以该分量得到投影后的点 $\boldsymbol{p}$：</p>
<p>$$<br>
\boldsymbol{p}=(p_x/p_w,p_y/p_w,p_z/p_w,1)<br>
$$</p>
<p>矩阵$\boldsymbol{P}_p$中的$z=f$总是映射到+1，$z=n$总是映射到-1。远平面之外的物体将被裁剪，不会出现在场景中。透视投影可以处理远平面为无限远的情况，这时透视变换矩阵为：</p>
<p>$$<br>
\boldsymbol{P}_p=<br>
\begin{pmatrix}<br>
\frac{2n}{r-l}&amp;0&amp;-\frac{r+l}{r-l}&amp;0\\<br>
0&amp;\frac{2n}{t-b}&amp;-\frac{t+b}{t-b}&amp;0\\<br>
0&amp;0&amp;1&amp;-2n\\<br>
0&amp;0&amp;1&amp;0<br>
\end{pmatrix}<br>
$$</p>
<p>总结下，透视变换应用后，进行了剪切和均匀化（除以$w$分量），然后计算归一化设备坐标。</p>
<p>为了在OpenGL中使用透视变换，首先乘以$\boldsymbol{S}(1,1,-1,1)$，做镜像，这一步使得矩阵第三列颠倒正负。然后near和far的值以正值输入，$0\lt n'\lt f'$，就像它们呈现给用户的方式一样。它们仍表示着沿着-z轴也就是视野方向的距离：</p>
<p>$$<br>
\boldsymbol{P}_{OpenGL}=<br>
\begin{pmatrix}<br>
\frac{2n'}{r-l}&amp;0&amp;\frac{r+l}{r-l}&amp;0\\<br>
0&amp;\frac{2n'}{t-b}&amp;\frac{t+b}{t-b}&amp;0\\<br>
0&amp;0&amp;-\frac{f'+n'}{f'-n'}&amp;-\frac{2f'n'}{f'-n'}\\<br>
0&amp;0&amp;-1&amp;0<br>
\end{pmatrix}<br>
$$</p>
<p>还有一种简单的形式，FOV表示为$\phi$，屏幕长宽比为$a=w/h$：</p>
<p>$$<br>
\boldsymbol{P}_{OpenGL}=<br>
\begin{pmatrix}<br>
\frac{c}{a}&amp;0&amp;0&amp;0\\<br>
0&amp;c&amp;0&amp;0\\<br>
0&amp;0&amp;-\frac{f'+n'}{f'-n'}&amp;-\frac{2f'n'}{f'-n'}\\<br>
0&amp;0&amp;-1&amp;0<br>
\end{pmatrix}<br>
where \<br>
c=1.0/\tan(\phi/2)<br>
$$</p>
<p>这个矩阵就是老的OpenGL函数<code>gluPerspective()</code>所执行的内容，它属于OpenGL Utility Library（GLU）。</p>
<p>一些API（如DirectX）把近平面映射到$z=0$（OpenGL是-1），远平面映射到$z=1$，所以DirectX不需要做镜像，它使用左手坐标系，等式为：</p>
<p>$$<br>
\boldsymbol{P}_{p[0,1]}=<br>
\begin{pmatrix}<br>
\frac{2n'}{r-l}&amp;0&amp;-\frac{r+l}{r-l}&amp;0\\<br>
0&amp;\frac{2n'}{t-b}&amp;-\frac{t+b}{t-b}&amp;0\\<br>
0&amp;0&amp;\frac{f'}{f'-n'}&amp;-\frac{f'n'}{f'-n'}\\<br>
0&amp;0&amp;1&amp;0<br>
\end{pmatrix}<br>
where \<br>
c=1.0/\tan(\phi/2)<br>
$$</p>
<p>使用透视变换后，计算出的深度值不会随点的z值线性变化：</p>
<p>$$<br>
\boldsymbol{v}=\boldsymbol{P}\boldsymbol{p}=<br>
\begin{pmatrix}<br>
...\\<br>
...\\<br>
dp_z+e\\<br>
\pm{p_z}<br>
\end{pmatrix}<br>
$$</p>
<p>其中$d,e$的取值取决于使用哪个矩阵。例如我们使用OpenGL的矩阵时（第一个），$d=-(f'+n')/(f'-n'),e=-2f'n'/(f'-n')，v_w=-p_z$。（这里原书应该是有勘误的）。除完分量后得：</p>
<p>$$<br>
z_{NDC}=\frac{dp_z+e}{-p_z}=d-\frac ep_z<br>
$$</p>
<p>可以看出，$z_{NDC}$和输入的$p_z$成反比。</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/09/4.21.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter4"></figure><p>不同的near和far设置可以影响深度的精度，上图显示了改变近平面距离原点距离的效果。</p>
<p>有几种方法可以提高深度的精度。一种常见的我们称之为反向z（reversed z），是使用浮点数或整型数存储$1.0-z_{NDC}$：</p>
<figure class="kg-image-card"><img src="http://xx-ma.com/content/images/2018/09/4.22.png" class="kg-image" alt="Realtime Rendering 4th notes - chapter4"></figure><p>（后面介绍了很多优化的方法，有兴趣的朋友可以阅读原书）</p>
<h4 id="furtherreadingandresources">Further Reading and Resources</h4>
<ul>
<li><a href="http://immersivemath.com">immersive linear algebra 网站</a></li>
<li>Farin and Hansford’s The Geometry Toolbox</li>
<li>Lengyel’s Mathematics for 3D Game Programming and Computer Graphics</li>
<li>Hearn and Baker/Marschner and Shirley/Hughes et al.  的文章</li>
<li>Graphics Gems 系列</li>
<li>Golub and Van Loan’s Matrix Computations</li>
<li>SIGGRAPH paper</li>
</ul>
<p>...</p>
<p>(Chapter4 end.)</p>
]]></content:encoded></item><item><title><![CDATA[Realtime Rendering 4th notes - chapter3]]></title><description><![CDATA[实时渲染第四版翻译笔记第三章
realtime rendering 4th edition chapter 3 translation and note.
图形学 图形学基础]]></description><link>http://xx-ma.com/rtr4-3/</link><guid isPermaLink="false">5b98fd50a891d14fe3a9be3b</guid><category><![CDATA[notes]]></category><category><![CDATA[realtime-rendering]]></category><category><![CDATA[code]]></category><category><![CDATA[graphics]]></category><dc:creator><![CDATA[Marianax]]></dc:creator><pubDate>Wed, 12 Sep 2018 11:59:15 GMT</pubDate><media:content url="http://xx-ma.com/content/images/2018/09/nicola-gypsicola-73977-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<img src="http://xx-ma.com/content/images/2018/09/nicola-gypsicola-73977-unsplash.jpg" alt="Realtime Rendering 4th notes - chapter3"><p><a href="https://www.xx-ma.com/">(转载请注明出处，谢谢咯)</a></p>
<blockquote>
<p>&quot;The display is the computer.&quot; - Jen-Hsun Huang<br>
Photo by Nicola Gypsicola on Unsplash</p>
</blockquote>
<p>前面介绍了一些GPU的历史背景，延迟是什么以及shader core是一个独立运行的小处理器。</p>
<br>
<h3 id="31dataparallelarchitectures">3.1 Data-Parallel Architectures（并行数据结构）</h3>
<p>不同的处理器在为了避免卡顿延迟方面有着不同的设计策略。CPU被设计成尽快的处理大量数据和大型代码库。除很少的一些SIMD向量处理器外，CPU的处理器单元处理时都是连续的。为避免延迟，大多CPU芯片都配有高速缓存。CPU也用分支预测（branch prediction），指令重整（instruction reordering），寄存器重命名（register renaming）和缓存预取（instruction reordering）来减少延迟。</p>
<p>GPU由大量（上千）处理器构成，叫做shader cores。GPU是流处理器，大量相似数据大量平行的依次执行，这些调用彼此间独立，没有共享的可写入的内存。潜在的延迟（stall，或者说停滞）是，一个处理器可能需要等另一个处理器处理结束。</p>
<p>GPU被设计成大吞吐量的结构，由于每个处理器占用的面积小于CPU每个处理器占用的面积，所以相对的在处理更多逻辑和缓存操作的时候延迟更高。每个GPU线程（lane）不同于CPU线程（thread），携带了一些内存用来记录输入值等。用相同shader program的lane被编成组，NVIDIA称作<em>warps</em>, AMD称作<em>wavefronts</em>。</p>
<p>Occupancy：活动的warp数量与最大数量的比值。 GPU的硬件利用率，利用率越高不一定性能就越高，但如果利用率很小，性能肯定不会好。</p>
<p>另一个影响效率的因素：dynamic branching，条件语句或循环会导致线程分散（thread divergence），一些线程可能执行了条件或循环而另一些线程不用，那么这些不用执行的线程就要等待他们执行完。<br>
<img src="http://xx-ma.com/content/images/2018/09/3.1.png" alt="Realtime Rendering 4th notes - chapter3"><br>
<br></p>
<h3 id="32gpupipelineoverviewgpu">3.2 GPU Pipeline Overview（GPU管线概览）</h3>
<p>在这里讨论的是GPU的逻辑模型，也就是暴露给开发者的API。vertex shader 负责执行几何阶段的事情，完全可编程；tessellation阶段和geometry shader 都是可选的；裁切，三角形设置，三角形遍历被固定硬件执行；屏幕映射受窗口和viewport影响；合并阶段不可编程，但高度可配置，可以设置各种运算（处理了color, z-buffer, blend, stencil...）；pixel shader和合并阶段一起于pixel processing stage执行。<br>
<img src="http://xx-ma.com/content/images/2018/09/3.2.png" alt="Realtime Rendering 4th notes - chapter3"></p>
<h3 id="33theprogrammableshaderstageshader">3.3 The Programmable Shader Stage（可编程shader阶段）</h3>
<p>vertex，pixel，geometry，tessellation shaders 共用一个统一的编程模型，内部他们有相同的指令集架构（instruction set architecture，ISA）。拥有这样统一模型的处理器在DirectX中称为common-shader core，拥有这种core的GPU称作其拥有统一shader架构。DirectX的HLSL和OpenGL的GLSL都可以编译为IL，供硬件识别。IL由驱动转换为ISA。</p>
<p>基础数据类型为32位单精度浮点数，在现代GPU中，32位整型和64位浮点数也被支持。</p>
<p><img src="http://xx-ma.com/content/images/2018/09/3.3.png" alt="Realtime Rendering 4th notes - chapter3"></p>
<p>draw call 触发graphic API绘制图元，使图形处理管线开始工作并运行其shader程序。每个可编程shader阶段都接受两类参数，uniform inputs和varying inputs（常量和变量）。</p>
<p>常熟寄存器比给变量输入提供的寄存器要多得多。是因为每个顶点或像素的变量的输入和输出必须分开存储，存在一个上限，但是uniform输入存储一次会在整个draw call过程中使用。temporary register用来暂时存储。</p>
<p>flow control 指用分支指令改变代码执行的流向。shader支持两种flow control。<em>Static flow control</em>基于uniform输入，在draw call 过程中不会变化。主要好处是一个shader多处用。当所有调用都选取一个代码路径的时候，并不会发生线程发散；Dynamic flow control基于变量输入，意味着每个fragment可以执行不同的代码。但可能引起效率问题。<br>
<br></p>
<h3 id="34theevolutionofprogrammableshadingandapisshadingapi">3.4 The Evolution of Programmable Shading and APIs（可编程Shading和API进化史）</h3>
<p>（略）<br>
<br></p>
<h3 id="35thevertexshader">3.5 The Vertex Shader（顶点着色器）</h3>
<p>在此阶段前的处理，在DirectX中称为input assembler。</p>
<p>keywords：data representation，instancing</p>
<p><img src="http://xx-ma.com/content/images/2018/09/3.7-1.png" alt="Realtime Rendering 4th notes - chapter3"></p>
<p>一个三角形mesh被一组点所描述，于每个点来说，还有一些其他可选属性如颜色和贴图坐标。也有顶点法线，不过从数学上讲，直接使用三角形的表面法线用于shading更合理。渲染中，三角面更多的用来代表曲面，顶点法线就用于表示表面的朝向。Vertex Shader一般情况下把顶点从模型空间转换到齐次裁剪空间，最少输出顶点的位置。Vertex Shader不能创建或删除顶点，也不能将结果传递给其他顶点。</p>
<p>一些顶点shader的例子：生成物体，变形；蒙皮技术；Procedural deformations，旗子，布料，水等；粒子生成；镜头扭曲，热浪，水波纹，翻页等通过framebuffer作为贴图使用在基于屏幕的网格中，使用procedural deformation；地形高度；</p>
<p><img src="http://xx-ma.com/content/images/2018/09/3.8.png" alt="Realtime Rendering 4th notes - chapter3"><br>
<br></p>
<h3 id="36thetessellationstage">3.6 The Tessellation Stage（曲面细分阶段）</h3>
<p>渲染曲面用，在DirectX11中开始，OpenGl4.0和OpenGL ES 3.2也支持。曲面的描述通常比用三角形描述更紧凑。</p>
<p>好处就是节省内存，节省CPU到GPU的传输。模型通常可以用表面描述转换为三角形网格网格然后再被随意变形，或让复杂的shading运算减少延迟。</p>
<p>三个元素：</p>
<p>DirectX：hull shader，tessellator，domain shader。</p>
<p>OpenGL: tessellation control shader，primitive generator，tessellation evaluation shader。</p>
<p>hull shader的输入是一个特殊的patch（比如Bézier patch ），包含一些控制点，描述了一个细分曲面。它有两个方法，第一个告诉tessellator多少个三角形要以什么样的配置生成；第二个在每个控制点上执行过程。hull shader也可以增删控制点（可选）。他的书厨师控制点和控制细分的数据，传递给domain shader。</p>
<p>tessellator由管线自动执行，用于给domain shader增加顶点。hull shader告诉tessellator需要什么样的细分面，如三角形，四边形（quadrilateral）或者等值线（isoline）。isoline是一组线段，有时候用于毛发的渲染。另一组由hull shader发送的值是tessellation factors（OpenGL中称为tessellation levels）。有两种类别：内边缘和外边缘。这两个内部因素决定三角形或多边形内细分发生的数量。外部因素决定每个边分裂的数量。通过允许单独控制，我们可以在细分中匹配相邻曲面的边，以便不受内部细分的影响。每个顶点被赋予重心坐标，用来标定每个点的相对位置。</p>
<p>hull shader总是输出一个patch和一组控制点。但是它可以通过向tessellator发送0或者更小（非数字或NaN）的外部曲面细分级别来丢弃patch。否则曲面细分器生成网格并传递给domain shader。每次调用domain shader时，都会使用来自hull shader的曲面控制点计算每个顶点的输出值。domain shader 和 vertex shader 有相似的数据流模式，输入一个来自tessellator的顶点，输出一个相应顶点。然后形成三角形传递到管线的下一个环节。</p>
<p>传递到hull shader 的patch通常改变很小或者没有改动。hull shader也可以用patch间的估计距离或屏幕大小来动态计算tessellation factors，如地形渲染。或者hull shader可以简单的为每个patch传递一组由应用程序计算提供的固定值。</p>
<p>tessellator执行一个固定的功能：生成顶点-&gt;给他们定位-&gt;指定他们生成的三角形或直线。数据的扩大环节被设计在shader之外以保证效率。</p>
<p>domain shader采用为每个点生成的重心坐标，在patch的evaluation equation中生成坐标，法线，贴图坐标和一些其他需要的顶点信息。<br>
<br></p>
<h3 id="37thegeometryshader">3.7 The Geometry Shader（几何着色器）</h3>
<p>几何着色器可以把输入图元转换为其他图元，这是曲面细分阶段不能完成的。在DirectX10中于2006年末加入。需要Shader Model 4.0。同时OpenGL3.2和OpenGL ES 3.2也支持这种类型的shader。</p>
<p>输入是一个object和它关联的顶点。这个object通常由网格中的三角形，线段或点组成。被扩展的图元被几何着色器定义并处理。还可以传入三角形外的三个附加顶点，或邻近线段的两个顶点。如下图。</p>
<p><img src="http://xx-ma.com/content/images/2018/09/3.12.png" alt="Realtime Rendering 4th notes - chapter3"></p>
<p>在DirectX11 Shader Model 5.0中，可以传入最多带有32个控制点的patch。这也就是说，曲面细分阶段生成patch更有效。</p>
<p>几何着色器被设计为处理输入数据或者制作有限数量的复制。比如生成6个变换后的数据拷贝用于模拟cube map。也可以用于高效生成高质量的级联阴影贴图（cascaded shadow maps）。再比如从点数据生成可变大小的粒子，沿着轮廓挤出鳍片用于毛发渲染，在阴影算法中找出物体边缘。</p>
<p>DirectX11增加了几何着色器使用instancing（实例化）的能力，也就是重复使用同一数据集变得更高效。在OpenGL4.0中是使用调用计数指定的。几何着色器也可以输出至多4个流（streams）。一个stream可以继续通过管线的其他阶段。所有流都可以被传给stream output render targets。</p>
<p>几何着色器确保输入输出的顺序相同，但这会影响性能，因为如果多个shader core平行运行，结果必须被存储和排序。</p>
<p>发出draw call 后，只有光栅化，曲面细分阶段和几何着色器这三个地方在GPU渲染管线中被生成。在这三个中，几何着色器的行为是最不可预测的，又考虑到这个阶段完全可编程，涉及资源和内存，又因为它没有很好的契合GPU的优势。所以通常较少使用。在一些移动设备中是使用软件实现的，所以在这种环境下不推荐使用。</p>
<br>
<h4 id="371streamoutput">3.7.1 Stream Output（流输出）</h4>
<p>过去，数据通过渲染管线时获取不到中间结果。在Shader Model 4.0中引入了流的概念。顶点细分或者几何结束后都可以传递到流，然后一个额外的有序数组被传递到光栅化阶段。光栅化也可以完全关闭，渲染管线纯粹用作流处理器而不是处理图像。以这种方式处理的数据可以通过渲染管线发回，从而允许迭代处理。这种类型的处理在模拟流体或者粒子时十分有用。</p>
<p>流输出仅以浮点数的形式返回数据，因此它比较耗费内存。stream output 用于图元，而不是直接作用于顶点。如果网格沿管线发送，每个三角形生成它的3个输出顶点集合，以至于顶点的共享信息消失。为此，一般传递点的时候使用一个图元（点集）。</p>
<p>在OpenGL中stream output被称作transform feedback，是因为其大部分目标是把顶点变换然后进行更多处理。</p>
<br>
<h3 id="38thepixelshader">3.8 The Pixel Shader（像素着色器）</h3>
<p>这一阶段是可配置的，不可编程。对每个三角形遍历确认覆盖了哪些像素。光栅器也可以粗算每个像素被覆盖的程度。一个三角形中部分或完全重叠像素的部分称为片段（fragment）。</p>
<p>三角形顶点的值，包括z值，为每个像素跨三角形表面进行差值。这些值输入给pixel shader。在OpenGL中pixel shader被称作fragment shader。在本书中使用前者以保持一致性。点和线也会生成片段。</p>
<p>差值类型由pixel shader指定，通常我们使用透视校正差值（perspective-correct interpolation）。</p>
<p>在编程术语中，vertex shader的输出，三角形差值的结果等都高效的称为pixel shader的输入，在Shader Model 3.0后，fragment基于屏幕的坐标也成为一个扩展输入。还有作为flag标记的三角形面哪一面可见，在一个pass中渲染出三角形两个面不同材质时很重要。</p>
<p>一般情况下pixel shader 计算并输出片段的颜色，也可以产生透明度，修改深度值。在合并期间，这些值用来修改像素处的内容。模板缓冲（stencil buffer）通常不能修改，直接送到merge阶段。DirectX 11.3允许修改stencil。雾的计算和alpha测试在SM4.0从合并操作转变为pixel shader计算。</p>
<p>pixel shader可以遗弃输入放弃输出。例如Clip plane。</p>
<p>最初，pixel shader 只能输出到merge stage用于最终显示。随着时间推移，pixel shader可以执行的指令数大幅增加，使得MRT(Multiple Rendering Target)应运而生。不同于只把颜色和z-buffer作为结果发送，更多组值在每个片段被生成然后保存到不同的buffer中，每一个称作一个<em>render target</em>。render target具有相同的x，y维度，一些API可能支持不同尺寸。根据GPU render target的数量可能是4或者8个。</p>
<p>如单个渲染过程可以在一个目标中生成彩色图像，一个目标中生成目标标识符，在第三个中生成世界空间距离。这种能力也产生了一种不同类型的渲染管线，称作延迟着色（deferred shading），它让可见性和着色完成于单独的pass中。第一个pass存储每个像素位置和材质的数据，接下来的pass完成光照和其他effects。</p>
<p>pixel shader 的限制是不能读取相邻点的当前结果。用image processing可以解决这个问题。</p>
<p>一个例外，pixel shader 可以在计算梯度或者导数信息的时候间接的访问相邻点的片段信息。当一个pixel shader 访问梯度信息的时候，相邻fragment的差被返回。（这部分看得不是很明白，P51，第三段）</p>
<p>DirectX11引入了UAV（unordered access view），允许对任意位置的写入权限。在DirectX11.1中扩展到了除pixel shader 和 compute shader 的其他shader中。在OpenGL中称作（SSBO，shader storage buffer object）。像素着色器以任意顺序并行运行，此buffer在它们之间共享。</p>
<p>通常需要一些机制来避免race condition（data hazard），GPU用专门的atomic units（原子单位）来解决这个问题。shader在试图访问内存时会因为其他shader先stall。</p>
<p>在一般情况下，fragment的结果在merge阶段执行前被排序。DirectX11.3引入了ROVs(Rasterizer order views)，强制执行顺序，和UAVs很像，shader可以以相同方式读写，区别在于ROV确保数据的执行顺序。ROV使像素着色器在不需要合并阶段的情况下，就可以编写自己的混合方法。不过如果检测到无序访问，pixel shader可能要stall一下。</p>
<br>
<h3 id="39themergingstage">3.9 The Merging Stage（合并阶段）</h3>
<p>合并阶段将深度缓冲和颜色缓冲组合起来。对于不透明表面，用当前片段颜色替换之前保存的颜色，混合当前片段颜色和已存颜色的混合操作（Blending）常用于透明和合成。</p>
<p>为了避免合并时使之前的pixel计算白白浪费，很多GPU在像素着色器前执行合并测试。片段的深度z用于检测可见性，如果隐藏则直接被剔除，这称作early-z。pixel shader 可以改变片段的z值或直接丢弃片段。如果检测到pixel shader中有任意操作，则early-z通常被关闭或禁用，导致低效。DirectX 11和OpenGL 4.2允许像素着色器强制打开early-z测试。</p>
<p>合并阶段是固定功能阶段（三角形设置）和完全可编程阶段中间的地带。不可编程但高度可配置。颜色混合可以设置各种操作如颜色和alpha乘法，加减，最大值最小值以及按位逻辑操作。</p>
<p>DirectX10加入了可以用两个pixel shader和framebuffer 混合的能力，称为dual source-color blending（双原色混合），不能和MRT共同使用。MRT以另一种方式支持混合，在DirectX10.1中引入了在每个单独的buffer中使用不同混合操作的功能。</p>
<br>
<h3 id="310thecomputeshader">3.10 The Compute Shader（计算着色器）</h3>
<p>GPU不仅可以用于实现传统渲染管线，也可用于诸如神经网络深度学习等的计算。由DirectX11引入，在渲染管线中没有固定位置的shader。由图形API调用，和顶点像素等其他shader一起使用。它使用和其他shader一样的处理器池，且可以访问输入输出缓冲区。warp和线程在compute shader中更明显。它还有线程组的概念，由1-1024个线程组成，由x, y, z坐标指定，便于在着色器代码中使用。每个线程组的线程都共享一部分内存，在DirectX11中，有32KB大小。compute shader由线程组执行，以确保所有线程同时执行。</p>
<p>compute shader 的一个重要的优势是它可以访问GPU上生成的数据，由GPU向CPU发送数据会导致延迟。Post-processing是一种Compute shader的常见用法。compute shader也同样适用于：粒子系统，网格处理如面部动画（facial animation），culling（剔除），图像过滤（image filtering），改进深度精度，阴影，景深（depth of field）以及任何可以用GPU执行的任务，等等。</p>
<br>
<h3 id="furtherreadingandresources">Further Reading and Resources</h3>
<ul>
<li>Giesen’s tour of the graphics pipeline</li>
<li>OpenGL Superbible</li>
<li>OpenGL Programming Guide</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Realtime Rendering 4th notes - chapter2]]></title><description><![CDATA[实时渲染第四版翻译笔记第二章
realtime rendering 4th edition chapter 2 translation and note.
图形学 图形学基础]]></description><link>http://xx-ma.com/rtr4-2/</link><guid isPermaLink="false">5b8666fba891d14fe3a9be2d</guid><category><![CDATA[notes]]></category><category><![CDATA[realtime-rendering]]></category><category><![CDATA[code]]></category><category><![CDATA[graphics]]></category><dc:creator><![CDATA[Marianax]]></dc:creator><pubDate>Wed, 29 Aug 2018 09:29:09 GMT</pubDate><media:content url="http://xx-ma.com/content/images/2018/09/alan-parker-279021-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<img src="http://xx-ma.com/content/images/2018/09/alan-parker-279021-unsplash.jpg" alt="Realtime Rendering 4th notes - chapter2"><p><a href="https://www.xx-ma.com/">(转载请注明出处，谢谢咯)</a></p><blockquote>
<p>&quot;A chain is no stronger than its weakest link.&quot;  - Anonymous</p>
</blockquote>
<p>渲染管线的目的是要<strong>生成</strong>(generate)并<strong>渲染</strong>(render)一张2D图像(two-dimensional image)。其过程包括设置虚拟摄像机，光源信息，模型，位置，材质等等信息。整个渲染管线分为几个环节或者称为阶段（stage），分别完成不同的事情，每个环节生成的结果作为下一个环节的输入。</p>
<h3 id="21architecture">2.1 Architecture</h3>
<p>实时渲染管线被分为四个阶段：</p>
<p>（应用）application -&gt;（几何） geometry processing -&gt;（光栅化）rasterization -&gt;（像素）pixel processing</p>
<p><img src="http://xx-ma.com/content/images/2018/09/----_20180901165918.png" alt="Realtime Rendering 4th notes - chapter2"></p>
<p>application阶段由应用程序本身控制，运行于CPU，执行碰撞检测，动画，物理模拟等过程，然后来到geometry processing执行坐标变换，投影等操作，决定了那些东西要怎样在哪里被渲染（what，how，where），运行于GPU；然后进入光栅化阶段，光栅化接收由三个顶点描述的三角形信息，然后找到所有这个三角形包含的像素信息，之后进入像素处理阶段；在像素处理阶段执行了一个针对每个像素的操作，决定了它的颜色，也可能会做深度测试看它是否可见，也可能blending一些新计算的颜色覆盖旧的颜色。光栅化和像素阶段都执行于GPU。</p>
<h3 id="22applicationstage">2.2 Application Stage</h3>
<p>可以在应用阶段通过compute shader用GPU处理过程。在应用阶段结束后，传入几何阶段的，我们称为rendering primitives（图元），即点、线、三角。这个阶段并不分成更多的子阶段，为了提高效率，通常平行运算于多个CPU线程，这在CPU设计中通常被称为superscalar construction。在此阶段一般还会有碰撞检测，输入，优化算法等处理。</p>
<h3 id="23geometryprocessing">2.3 Geometry Processing</h3>
<p>在GPU中对每个三角形和每个顶点做处理，分为如下几个函数过程：</p>
<p>Vertex Shading -&gt; Projection -&gt; Clipping -&gt; Screen Mapping.</p>
<p><img src="http://xx-ma.com/content/images/2018/09/----_20180901170042.png" alt="Realtime Rendering 4th notes - chapter2"></p>
<h4 id="231vertexshading">2.3.1 Vertex Shading</h4>
<p>两个主要任务：处理顶点位置，处理其他程序员需要作为顶点信息输出的内容。</p>
<p>一般情况下可以存储顶点光照信息，这些信息之后会在三角形内部差值。随着现代GPU的不断升级，Shading的过程基本全部变成逐像素的，所以顶点shading的任务便更单一了，可能不会包含任何shading方程。</p>
<p>值得注意的是，第三版中单独把变换阶段独立出来，而在本版本中被包含到了Vertex Shading阶段。顶点从模型空间变换到世界空间，再变换到视图空间。顶点信息包含了位置，颜色，贴图坐标等，被一同输出到光栅化和像素阶段。视图变换后要进行投影变换和裁切，把结果变换到一个单位正方体内，称为canonical view volume。</p>
<p><img src="http://xx-ma.com/content/images/2018/09/----_20180901170102.png" alt="Realtime Rendering 4th notes - chapter2"></p>
<p>投影变换常用的两种：orthographic ，perspective；变换本身被描述为一个矩阵。点的z坐标信息并不存储于最终的图片信息中，而单独存储在z-buffer中。</p>
<h4 id="232optionalvertexprocessing">2.3.2 Optional Vertex Processing</h4>
<p>Vertex Shading结束后，有一些可选过程，例如：曲面细分(tessellation)，几何着色(geometry shading)和流输出(stream output)。这些过程的使用取决于GPU是否支持以及程序员是否需要。他们之间彼此独立。</p>
<p>通过曲面细分，一个曲面可以被细分为多个三角形面，又不会占用过多的系统资源。曲面细分包括一些子过程：hull shader -&gt; tessellator -&gt; domain shader，把简单的点集转换成更大规模的点集，并可以通过摄像机远近控制。</p>
<p>几何着色的出现比曲面细分要早，所以有更多的GPU支持。和曲面细分比较像的是它也接收若干种类的图元输入然后产生新的顶点。但是它更简单，限制更多。例如烟火爆炸可以通过点表示每个火球，通过几何着色渲染为一个面向屏幕的片。</p>
<p>stream output可以让GPU变成一个几何引擎，我们可以把点的数据输出到数组供CPU或GPU在后面使用。例如生成粒子。</p>
<h4 id="233clipping">2.3.3 Clipping</h4>
<p><img src="http://xx-ma.com/content/images/2018/09/----_20180901170136.png" alt="Realtime Rendering 4th notes - chapter2"></p>
<p>把在视野外的物体排除，视野边界上的进行裁切，然后再传入光栅化的过程。之所以在变换和投影后再进行裁切，是因为这样可以保证裁切问题的统一性。用户还可以在视锥裁切的6个平面外，自行增加裁切平面。</p>
<p>在裁切过程中使用4值齐次坐标，因为在透视空间内的三角形中，数值不会线性插值。最后执行透视除法（perspective division），使三角形的位置转换为三维空间的归一化设备坐标（normalized device coordinates）。然后把他们转换到屏幕坐标。</p>
<h4 id="234screenmapping">2.3.4 Screen Mapping</h4>
<p>裁切后的图元被发送到屏幕映射阶段，坐标被映射到窗口坐标中，z坐标也同样被映射到$(0, 1)$。然后映射后的值被传入到光栅化阶段。</p>
<p><img src="http://xx-ma.com/content/images/2018/09/----_20180901170153.png" alt="Realtime Rendering 4th notes - chapter2"></p>
<p>给定一行像素数组，应用笛卡尔坐标系，像素的最左侧为0.0，这在openGL和dx10以及以后的版本中广泛使用。像素的中间是0.5。d = floor(c), c = d + 0.5, d是整型index，c是像素的连续浮点值。但是上下边界的值对于opengl和directx略有不同。</p>
<h3 id="24rasterization">2.4 Rasterization</h3>
<p>两个子阶段：triangle setup，triangle traversal，光栅化也叫扫描转换（scan conversion），对每个像素给与颜色。</p>
<p><img src="http://xx-ma.com/content/images/2018/09/----_20180901170211.png" alt="Realtime Rendering 4th notes - chapter2"></p>
<p>即从屏幕空间到屏幕上像素的转换。主要是一个像素是不是在一个图元的内部或一个三角形的内部，而标准一般是程序员制定，比如判断像素的中心点是否在三角形内部。还有诸如supersampling(SSAA)和multisampling(MSAA)的抗锯齿技术。</p>
<h4 id="241trianglesetup">2.4.1 Triangle Setup</h4>
<p>在这个阶段计算三角形的数据，固定功能的硬件用来做这个任务。</p>
<h4 id="242triangletraversal">2.4.2 Triangle Traversal</h4>
<p>在这个阶段，像素是否在三角形内部得到检测，然后一个片段(fragment)由像素和三角形重叠的部分生成。寻找哪个样本或像素在三角形内部通常称为三角形遍历(Triangle Traversal)。</p>
<h3 id="25pixelprocessing">2.5 Pixel Processing</h3>
<p>两个子阶段：pixel shading，merging，逐像素操作在这里进行。</p>
<h4 id="251pixelshading">2.5.1 Pixel Shading</h4>
<p>所有逐像素操作再此执行，以之后的shading数据作为输入。结果是若干颜色输出到下一阶段。此阶段由程序员书写的过程执行。有很多技术在此阶段得到应用，比如贴图。</p>
<h4 id="252merging">2.5.2 Merging</h4>
<p>每个像素的信息都被存储在color buffer中，它是一个矩形数组，每个颜色由r，g，b构成。这个阶段也叫ROP(raster operations(pipeline)/render output unit)。使上一阶段的fragment color与当前buffer中的值混合，通过比对z-buffer判断可见性。其他的通道或者缓存有：alpha channel（透明通道），stencil buffer（记录图元位置，8-bit per pixel）。这些在管线末执行的方法被称为ROP或者blend operations。</p>
<p>framebuffer表示所有系统中的buffer。双缓冲技术（double buffering）保证了当一帧渲染结束后再被替换到屏幕上。</p>
<h3 id="26throughthepipeline">2.6 Through the Pipeline</h3>
<p>一个CAD的例子，贯穿始终。</p>
<h4 id="conclusion">Conclusion</h4>
<p>这并不是唯一的pipeline，还有离线渲染环境中的micropolygon管线，以及后来取而代之的光线追踪(ray tracing)和路径追踪(path tracing)等。固定管线在本书中已经不再涉及。</p>
<h4 id="furtherreadingandresources">Further Reading and Resources</h4>
<ul>
<li>A Trip Down the Graphics Pipeline</li>
<li>OpenGL Programming Guide (a.k.a. the &quot;Red Book&quot;)</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Realtime Rendering 4th notes - chapter1]]></title><description><![CDATA[实时渲染第四版翻译笔记第一章
realtime rendering 4th edition chapter 1 translation and note.
图形学 图形学基础]]></description><link>http://xx-ma.com/rtr4-1/</link><guid isPermaLink="false">5b8664d1a891d14fe3a9be21</guid><category><![CDATA[notes]]></category><category><![CDATA[graphics]]></category><category><![CDATA[realtime-rendering]]></category><category><![CDATA[code]]></category><dc:creator><![CDATA[Marianax]]></dc:creator><pubDate>Tue, 28 Aug 2018 14:03:34 GMT</pubDate><media:content url="http://xx-ma.com/content/images/2018/08/cover_fish3_jpg-969x1024-1.jpg" medium="image"/><content:encoded><![CDATA[<img src="http://xx-ma.com/content/images/2018/08/cover_fish3_jpg-969x1024-1.jpg" alt="Realtime Rendering 4th notes - chapter1"><p><a href="https://www.xx-ma.com/">(转载请注明出处，谢谢咯)</a></p>
<hr>
<blockquote>
<p>读到浅墨大大在知乎上的RTR3提炼时，时逢RTR4快要出版的时候，作为一本图形学必读书目，早就有从头到尾细读一遍的想法，不过一直苦于篇幅较长，没有动手。但第四版的推出，使我重拾动力，也为了其他觉得篇幅长或者英文不好的同学，决定通过笔记和翻译并存的形式，提炼知识点。也可以督促自己读完本书。这里感谢浅墨大大的工作给与我的启发，附上其知乎栏目地址：<br>
<a href="https://zhuanlan.zhihu.com/game-programming">https://zhuanlan.zhihu.com/game-programming</a><br>
删减内容主要包括历史性质主题，主要对焦技术相关内容，对于比较易懂或者资料比较多的部分精简程度较高。<br>
请遇到有内容不当或有误的地方，表述不够清晰的地方，都请及时告知，我将第一时间进行修改，感谢。</p>
</blockquote>
<h2 id="introduction">简介(Introduction)：</h2>
<p>fps和Hz的介绍，显卡的介绍等，略</p>
<hr>
<h2 id="11contentsoverview">1.1 目录的简介(Contents Overview):</h2>
<p>...</p>
<hr>
<h2 id="12notationanddefinitions">1.2 数学符号约定(Notation and Definitions):</h2>
<h3 id="121">1.2.1 数学符号</h3>
<p>L - 辐射亮度(radiance)，E - 辐射照度(irradiance)，$\sigma_s$ - 散射系数(scattering coefficient)</p>
<p>角度和标量取自实数集</p>
<p>矢量和点被标记为粗体的小写字母，也有时候会横着写：</p>
<p>$$<br>
\boldsymbol{v} =<br>
\begin{pmatrix}<br>
v_x \\<br>
v_y \\<br>
v_z \\<br>
\end{pmatrix}<br>
$$</p>
<h4 id="table11">table 1.1</h4>
<table>
<thead>
<tr>
<th>种类</th>
<th>记号</th>
<th>示例</th>
</tr>
</thead>
<tbody>
<tr>
<td>angle(角度)</td>
<td>小写希腊字母</td>
<td>$\alpha_i$,$\phi$,$\rho$,$\eta$,$\gamma_{242}$,$\theta$</td>
</tr>
<tr>
<td>scalar（标量）</td>
<td>小写斜体字母</td>
<td>$a$,$b$,$t$,$u_k$,$v$,$w_{ij}$</td>
</tr>
<tr>
<td>vector or point（点或矢量）</td>
<td>小写粗体</td>
<td>$\boldsymbol{a}$,$\boldsymbol{u}$,$\boldsymbol{v_s}$,$\boldsymbol{h}(\rho)$,$\boldsymbol{h}_z$</td>
</tr>
<tr>
<td>matrix（矩阵）</td>
<td>大写粗体</td>
<td>$\boldsymbol{T(t)}$,$\boldsymbol{X}$,$\boldsymbol{R}_x(\rho)$</td>
</tr>
<tr>
<td>plane（平面）</td>
<td>$\pi$: 一个矢量 和 一个标量，n是法线方向</td>
<td>$\pi:\boldsymbol{n}\cdot{\boldsymbol{x}}+d$<br>$\pi_1:\boldsymbol{n}_1\cdot{\boldsymbol{x}}+d_1$</td>
</tr>
<tr>
<td>triangle（三角）</td>
<td>$\triangle$ 3 点</td>
<td>$\triangle\boldsymbol{v_0}\boldsymbol{v_1}\boldsymbol{v_2}$, $\triangle\boldsymbol{c}\boldsymbol{b}\boldsymbol{a}$</td>
</tr>
<tr>
<td>line segment（线段）</td>
<td>2 点</td>
<td>$\boldsymbol{uv}$, $\boldsymbol{a_ib_j}$</td>
</tr>
<tr>
<td>geometric entity（几何体）</td>
<td>大写斜体，指代一个三维模型或一个object</td>
<td>$A_{OBB}, T, B_{AABB}$</td>
</tr>
</tbody>
</table>
<br>
<h4 id="table12">table 1.2</h4>
<table>
<thead>
<tr>
<th>符号</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>${}\dot{}$</td>
<td>dot product 点积</td>
</tr>
<tr>
<td>$ \times $</td>
<td>cross product 叉乘</td>
</tr>
<tr>
<td>$\boldsymbol{v}^\top$</td>
<td>transpose of the vector $\boldsymbol{v}$ 转置</td>
</tr>
<tr>
<td>$^\perp$</td>
<td>the unary, perp dot product operator 一元运算符，?垂直点乘（$\boldsymbol{a}^\perp \cdot \boldsymbol{b} =</td>
</tr>
<tr>
<td>$| \cdot |$</td>
<td>determinant of a matrix 矩阵的行列式</td>
</tr>
<tr>
<td>$| \cdot |$</td>
<td>absolute value of a scalar 标量的绝对值</td>
</tr>
<tr>
<td>$|| \cdot ||$</td>
<td>length (or norm) of argument 参数的长度或定值</td>
</tr>
<tr>
<td>$x^{+} $</td>
<td>clamping x to 0 (if x &gt;= 0 return x else return 0)</td>
</tr>
<tr>
<td>$x^\mp $</td>
<td>clamping x between 0 and 1 (if x &gt; 1 return 1 else if x &lt; 0 return 0 else return x)</td>
</tr>
<tr>
<td>$n$！</td>
<td>factorial 阶乘</td>
</tr>
<tr>
<td>$$\begin{pmatrix} n \\ k \\ \end{pmatrix}$$</td>
<td>binomial coefficients: $ \frac{n! }{k!(n - k)!} $</td>
</tr>
</tbody>
</table>
<br>
<h4 id="table13">table 1.3</h4>
<table>
<thead>
<tr>
<th></th>
<th>函数</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>1.</td>
<td>$atan2(y,x)$</td>
<td>两个参数的反正切函数，是一个C-math function，在后面经常用到，是数学反三角函数的扩展，区别为$-\frac{\pi}{2} &lt; arctan(x) &lt; \frac{\pi}{2}$ 但是 $0 \leq atan2(y,x) &lt; 2\pi$ , atan2(y,x) = atan(y/x) and $x\not= 0$</td>
</tr>
<tr>
<td>2.</td>
<td>$\log(n)$</td>
<td>n的自然对数</td>
</tr>
</tbody>
</table>
<p>坐标平面(coordinate/axis-aligned planes)，坐标轴(main axes/directions, $e_x=(1,0,0)^\top$,$e_y, e_z$)</p>
<p>颜色表示为一个向量，$r,g,b\in [0, 1]$</p>
<br>
<h4 id="122">1.2.2 几何定义</h4>
<p>scene，object..<br>
<br></p>
<h4 id="123shading">1.2.3 着色(Shading)</h4>
<br>
<h4 id="furtherreadingandresources">更多(further reading and resources:)</h4>
<p><a href="http://www.realtimerendering.com">realtimerendering.com</a></p>
<br>
<br>
<p>(chapter 1 end.)</p>
]]></content:encoded></item><item><title><![CDATA[This is a test page.]]></title><description><![CDATA[<p>什么情况</p>
<pre><code class="language-css">body {
    xxx : #000
}
</code></pre>
<p>$\sin()$</p>
<p>$$<br>
\sin(\mathbf{v})<br>
$$</p>
<table>
<thead>
<tr>
<th>Tables</th>
<th style="text-align:center">Are</th>
<th style="text-align:right">Cool</th>
</tr>
</thead>
<tbody>
<tr>
<td>col 1 is</td>
<td style="text-align:center">left-aligned</td>
<td style="text-align:right">$1600</td>
</tr>
<tr>
<td>col 2 is</td>
<td style="text-align:center">centered</td>
<td style="text-align:right">$12</td>
</tr>
<tr>
<td>col 3 is</td>
<td style="text-align:center">right-aligned</td>
<td style="text-align:right">$1</td>
</tr>
</tbody>
</table>]]></description><link>http://xx-ma.com/testpage/</link><guid isPermaLink="false">5b84b2baa4ac89478bd16651</guid><category><![CDATA[test]]></category><category><![CDATA[life]]></category><dc:creator><![CDATA[Marianax]]></dc:creator><pubDate>Tue, 28 Aug 2018 05:45:54 GMT</pubDate><media:content url="http://xx-ma.com/content/images/2018/08/ubuntu-book.jpg" medium="image"/><content:encoded><![CDATA[<img src="http://xx-ma.com/content/images/2018/08/ubuntu-book.jpg" alt="This is a test page."><p>什么情况</p>
<pre><code class="language-css">body {
    xxx : #000
}
</code></pre>
<p>$\sin()$</p>
<p>$$<br>
\sin(\mathbf{v})<br>
$$</p>
<table>
<thead>
<tr>
<th>Tables</th>
<th style="text-align:center">Are</th>
<th style="text-align:right">Cool</th>
</tr>
</thead>
<tbody>
<tr>
<td>col 1 is</td>
<td style="text-align:center">left-aligned</td>
<td style="text-align:right">$1600</td>
</tr>
<tr>
<td>col 2 is</td>
<td style="text-align:center">centered</td>
<td style="text-align:right">$12</td>
</tr>
<tr>
<td>col 3 is</td>
<td style="text-align:center">right-aligned</td>
<td style="text-align:right">$1</td>
</tr>
</tbody>
</table>
]]></content:encoded></item><item><title><![CDATA[Welcome to Ghost]]></title><description><![CDATA[Welcome, it's great to have you here.
We know that first impressions are important, so we've populated your new site with some initial getting started posts that will help you get familiar with everything in no time.]]></description><link>http://xx-ma.com/welcome/</link><guid isPermaLink="false">5b865fd8a891d14fe3a9bdfa</guid><category><![CDATA[Getting Started]]></category><dc:creator><![CDATA[Ghost]]></dc:creator><pubDate>Mon, 27 Aug 2018 02:11:00 GMT</pubDate><media:content url="https://casper.ghost.org/v2.0.0/images/welcome-to-ghost.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://casper.ghost.org/v2.0.0/images/welcome-to-ghost.jpg" alt="Welcome to Ghost"><p>👋 Welcome, it's great to have you here.</p><p>We know that first impressions are important, so we've populated your new site with some initial <strong>getting started</strong> posts that will help you get familiar with everything in no time. This is the first one!</p><p><strong>A few things you should know upfront</strong>:</p><ol><li>Ghost is designed for ambitious, professional publishers who want to actively build a business around their content. That's who it works best for. </li><li>The entire platform can be modified and customised to suit your needs. It's very powerful, but does require some knowledge of code. Ghost is not necessarily a good platform for beginners or people who just want a simple personal blog. </li><li>For the best experience we recommend downloading the <a href="https://ghost.org/downloads/">Ghost Desktop App</a> for your computer, which is the best way to access your Ghost site on a desktop device. </li></ol><p>Ghost is made by an independent non-profit organisation called the Ghost Foundation. We are 100% self funded by revenue from our <a href="https://ghost.org/pricing">Ghost(Pro)</a> service, and every penny we make is re-invested into funding further development of free, open source technology for modern publishing.</p><p>The version of Ghost you are looking at right now would not have been made possible without generous contributions from the open source <a href="https://github.com/TryGhost">community</a>.</p><h2 id="next-up-the-editor">Next up, the editor</h2><p>The main thing you'll want to read about next is probably: <a href="http://xx-ma.com/the-editor/">the Ghost editor</a>. This is where the good stuff happens.</p><blockquote><em>By the way, once you're done reading, you can simply delete the default <strong>Ghost</strong> user from your team to remove all of these introductory posts! </em></blockquote>]]></content:encoded></item><item><title><![CDATA[Creating a custom theme]]></title><description><![CDATA[Ghost comes with a beautiful default theme called Casper, which is designed to be a clean, readable publication layout and can be easily adapted for most purposes.]]></description><link>http://xx-ma.com/themes/</link><guid isPermaLink="false">5b865fd8a891d14fe3a9bdf4</guid><category><![CDATA[Getting Started]]></category><dc:creator><![CDATA[Ghost]]></dc:creator><pubDate>Sun, 26 Aug 2018 02:11:00 GMT</pubDate><media:content url="https://casper.ghost.org/v2.0.0/images/creating-a-custom-theme.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://casper.ghost.org/v2.0.0/images/creating-a-custom-theme.jpg" alt="Creating a custom theme"><p>Ghost comes with a beautiful default theme called Casper, which is designed to be a clean, readable publication layout and can be adapted for most purposes. However, Ghost can also be completely themed to suit your needs. Rather than just giving you a few basic settings which act as a poor proxy for code, we just let you write code.</p><p>There are a huge range of both free and premium pre-built themes which you can get from the <a href="http://marketplace.ghost.org">Ghost Theme Marketplace</a>, or you can create your own from scratch.</p><figure class="kg-image-card"><img src="https://casper.ghost.org/v1.0.0/images/marketplace.jpg" class="kg-image" alt="Creating a custom theme"><figcaption>Anyone can write a completely custom Ghost theme with some solid knowledge of HTML and CSS</figcaption></figure><p>Ghost themes are written with a templating language called handlebars, which has a set of dynamic helpers to insert your data into template files. For example: <code>{{author.name}}</code> outputs the name of the current author.</p><p>The best way to learn how to write your own Ghost theme is to have a look at <a href="https://github.com/TryGhost/Casper">the source code for Casper</a>, which is heavily commented and should give you a sense of how everything fits together.</p><ul><li><code>default.hbs</code> is the main template file, all contexts will load inside this file unless specifically told to use a different template.</li><li><code>post.hbs</code> is the file used in the context of viewing a post.</li><li><code>index.hbs</code> is the file used in the context of viewing the home page.</li><li>and so on</li></ul><p>We've got <a href="https://themes.ghost.org/v2.0.0/docs">full and extensive theme documentation</a> which outlines every template file, context and helper that you can use.</p><p>If you want to chat with other people making Ghost themes to get any advice or help, there's also a <strong>themes</strong> section on our <a href="https://forum.ghost.org/c/themes">public Ghost forum</a>.</p>]]></content:encoded></item><item><title><![CDATA[Apps & integrations]]></title><description><![CDATA[There are three primary ways to work with third-party services in Ghost: using Zapier, editing your theme, or using the Ghost API.]]></description><link>http://xx-ma.com/apps-integrations/</link><guid isPermaLink="false">5b865fd8a891d14fe3a9bdf5</guid><category><![CDATA[Getting Started]]></category><dc:creator><![CDATA[Ghost]]></dc:creator><pubDate>Sun, 26 Aug 2018 02:11:00 GMT</pubDate><media:content url="https://casper.ghost.org/v2.0.0/images/app-integrations.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://casper.ghost.org/v2.0.0/images/app-integrations.jpg" alt="Apps & integrations"><p>There are three primary ways to work with third-party services in Ghost: using Zapier, editing your theme, or using the Ghost API.</p><h1 id="zapier">Zapier</h1><p>You can connect your Ghost site to over 1,000 external services using the official integration with <a href="https://zapier.com">Zapier</a>.</p><p>Zapier sets up automations with Triggers and Actions, which allows you to create and customise a wide range of connected applications.</p><blockquote><strong>Example</strong>: When someone new subscribes to a newsletter on a Ghost site (Trigger) then the contact information is automatically pushed into MailChimp (Action).</blockquote><p><strong>Here are the most popular Ghost&lt;&gt;Zapier automation templates:</strong> </p><script src="https://zapier.com/apps/embed/widget.js?services=Ghost&container=true&limit=8"></script>
<h1 id="editing-your-theme">Editing your theme</h1><p>One of the biggest advantages of using Ghost over centralised platforms is that you have total control over the front end of your site. Either customise your existing theme, or create a new theme from scratch with our <a href="https://themes.ghost.org">Theme SDK</a>. </p><p>You can integrate <em>any</em> front end code into a Ghost theme without restriction, and it will work just fine. No restrictions!</p><p><strong>Here are some common examples</strong>:</p><ul><li>Include comments on a Ghost blog with <a href="https://help.ghost.org/article/15-disqus">Disqus</a> or <a href="https://help.ghost.org/article/35-discourse">Discourse</a></li><li>Implement <a href="https://help.ghost.org/article/89-mathjax">MathJAX</a> with a little bit of JavaScript</li><li>Add syntax highlighting to your code snippets using <a href="https://prismjs.com/">Prism.js</a></li><li>Integrate any dynamic forms from <a href="https://www.google.com/forms/">Google</a> or <a href="https://www.typeform.com/">Typeform</a> to capture data</li><li>Just about anything which uses JavaScript, APIs and Markup.</li></ul><h1 id="using-the-public-api">Using the Public API</h1><p>Ghost itself is driven by a set of core APIs, and so you can access the Public Ghost JSON API from external webpages or applications in order to pull data and display it in other places.</p><blockquote>The Ghost API is <a href="https://api.ghost.org">thoroughly documented</a> and straightforward to work with for developers of almost any level. </blockquote><p>Alright, the last post in our welcome-series! If you're curious about creating your own Ghost theme from scratch, here are <a href="http://xx-ma.com/themes/">some more details</a> on how that works.</p>]]></content:encoded></item><item><title><![CDATA[Organising your content]]></title><description><![CDATA[Ghost has a flexible organisational taxonomy called tags which can be used to configure your site structure using dynamic routing.]]></description><link>http://xx-ma.com/organising-content/</link><guid isPermaLink="false">5b865fd8a891d14fe3a9bdf6</guid><category><![CDATA[Getting Started]]></category><dc:creator><![CDATA[Ghost]]></dc:creator><pubDate>Sun, 26 Aug 2018 02:11:00 GMT</pubDate><media:content url="https://casper.ghost.org/v2.0.0/images/organising-your-content.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://casper.ghost.org/v2.0.0/images/organising-your-content.jpg" alt="Organising your content"><p>Ghost has a flexible organisational taxonomy called<strong> tags</strong> which can be used to configure your site structure using <strong>dynamic routing</strong>. </p><h1 id="basic-tagging">Basic Tagging</h1><p>You can think of tags like Gmail labels. By tagging posts with one or more keyword, you can organise articles into buckets of related content.</p><p>When you create content for your publication you can assign tags to help differentiate between categories of content. </p><p>For example you may tag some content with  News and other content with Podcast, which would create two distinct categories of content listed on <code>/tag/news/</code> and <code>/tag/weather/</code>, respectively.</p><p>If you tag a post with both <code>News</code> <em>and</em> <code>Weather</code> - then it appears in both sections. Tag archives are like dedicated home-pages for each category of content that you have. They have their own pages, their own RSS feeds, and can support their own cover images and meta data.</p><h1 id="the-primary-tag">The primary tag</h1><p>Inside the Ghost editor, you can drag and drop tags into a specific order. The first tag in the list is always given the most importance, and some themes will only display the primary tag (the first tag in the list) by default. </p><blockquote><em><strong>News</strong>, Technology, Startup</em></blockquote><p>So you can add the most important tag which you want to show up in your theme, but also add related tags which are less important.</p><h1 id="private-tags">Private tags</h1><p>Sometimes you may want to assign a post a specific tag, but you don't necessarily want that tag appearing in the theme or creating an archive page. In Ghost, hashtags are private and can be used for special styling.</p><p>For example, if you sometimes publish posts with video content - you might want your theme to adapt and get rid of the sidebar for these posts, to give more space for an embedded video to fill the screen. In this case, you could use private tags to tell your theme what to do.</p><blockquote><em><strong>News</strong>, #video</em></blockquote><p>Here, the theme would assign the post publicly displayed tags of News - but it would also keep a private record of the post being tagged with #video. In your theme, you could then look for private tags conditionally and give them special formatting. </p><blockquote><em>You can find documentation for theme development techniques like this and many more over on Ghost's extensive <a href="https://themes.ghost.org/v2.0.0/docs">theme documentation</a>.</em></blockquote><h1 id="dynamic-routing">Dynamic Routing</h1><p>Dynamic routing gives you the ultimate freedom to build a custom publication to suit your needs. Routes are rules that map URL patterns to your content and templates. </p><p>For example, you may not want content tagged with <code>News</code> to exist on: <code>example.com/tag/news</code>. Instead, you want it to exist on <code>example.com/news</code> . </p><p>In this case you can use dynamic routes to create customised collections of content on your site. It's also possible to use multiple templates in your theme to render each content type differently.</p><p>There are lots of use cases for dynamic routing with Ghost, here are a few common examples: </p><ul><li>Setting a custom home page with its own template</li><li>Having separate content hubs for blog and podcast, that render differently, and have custom RSS feeds to support two types of content</li><li>Creating a founders column as a unique view, by filtering content created by specific authors</li><li>Including dates in permalinks for your posts</li><li>Setting posts to have a URL relative to their primary tag like <code>example.com/europe/story-title/</code><br></li></ul><blockquote><em>Dynamic routing can be configured in Ghost using <a href="http://yaml.org/spec/1.2/spec.html" rel="noreferrer nofollow noopener">YAML</a> files. Read our dynamic routing <a href="https://docs.ghost.org/docs/dynamic-routing">documentation</a> for further details.</em></blockquote><p>You can further customise your site using <a href="http://xx-ma.com/apps-integrations/">Apps &amp; Integrations</a>.</p>]]></content:encoded></item></channel></rss>