边学边翻译,督促自己不能落下,同时也留点记忆
当试图复制现实世界的外观时,人们很快就会意识到几乎没有任何表面是没有特征的。木头和谷物一起生长;皮肤随着皱纹生长;布显示其编织结构;而油漆显示的是刷或滚筒的痕迹。即使是光滑的塑料也有凸起的模压,而光滑的金属则显示出加工过程的痕迹。曾经毫无特色的材料很快就会被标记、凹痕、污渍、划痕、指纹和污垢所覆盖。 在计算机图形学中,我们把所有这些现象归为“空间变化的表面属性”——不同地方的表面属性不同,但并不真正以有意义的方式改变表面的形状。为了实现这些效果,各种建模和渲染系统都提供了一些纹理映射的方法:使用图像(称为纹理映射、纹理图像或仅仅是纹理)来存储您想要在表面上进行的细节,然后用数学方法将图像“映射”到表面上。 事实证明,一旦存在将图像映射到表面的机制,就有许多不太明显的方法可以使用它,而不仅仅是介绍表面细节的基本目的。纹理可以用来制造阴影和反射,提供照明,甚至定义表面形状。在复杂的交互程序中,纹理被用来存储各种各样的数据,甚至与图片没有任何关系! 本章讨论使用纹理来表示表面细节、阴影和反射。虽然基本思想很简单,但有几个实际问题使纹理的使用变得复杂。首先,纹理很容易变形,设计将纹理映射到表面的功能是具有挑战性的。此外,纹理映射是一个重采样过程,就像缩放图像一样,正如我们在第10章中看到的,重采样很容易引入混叠工件。纹理映射和动画一起使用很容易产生真正戏剧性的混叠,而纹理映射系统的复杂性很大程度上是由用于驯服这些工件的反混叠措施造成的。
首先,让我们考虑纹理映射的一个简单应用程序。我们有一个木地板的场景,我们希望地板的漫反射颜色由显示木地板木纹的图像控制。无论我们是使用光线跟踪还是光栅化,计算光线-表面交点或光栅化器生成的片段的颜色的着色代码需要知道在着色点的纹理的颜色,以便在第5章的Lambertian着色模型中使用它作为漫反射颜色。 为了得到这个颜色,着色器执行一个纹理查找:它在纹理图像的坐标系中找出与阴影点对应的位置,并读出图像中该点的颜色,从而得到纹理样本。然后在阴影中使用该颜色,由于纹理查找发生在纹理中每个看到地板的像素的不同位置,不同颜色的模式显示在图像中。代码可能如下所示
Color texture_lookup(Texture t, float u, float v) {
int i = round(u * t.width() - 0.5)
int j = round(v * t.height() - 0.5)
return t.get_pixel(i,j)
}
Color shade_surface_point(Surface s, Point p, Texture t) {
Vector normal = s.get_normal(p)
(u,v) = s.get_texcoord(p)
Color diffuse_color = texture_lookup(u,v)
// compute shading using diffuse_color and normal
// return shading result
}
在这段代码中,着色器询问表面在纹理中的哪个位置,并且我们想要使用纹理着色的每个表面都需要能够回答这个查询。这就引出了纹理映射的第一个关键元素:我们需要一个从表面映射到纹理的函数,我们可以很容易地计算每个像素。这是纹理坐标函数(图11.1),我们说它将纹理坐标分配到表面上的每个点。数学上,它是从表面S到纹理T域的映射: $$ \phi :S ->T \ :(x,y,z)->(u,v). $$ 集合T,通常被称为“纹理空间”,通常只是一个包含图像的矩形;通常使用单位正方形(u, v) E[0,1]2(在本书中,我们将使用名称u和u作为两个纹理坐标)。在很多方面,它类似于第8章中讨论的视觉投影,在本章中称为n,它将场景中表面上的点映射到图像中的点;两者都是3d -到2d的映射,都是渲染所需要的,一个是知道从哪里获得纹理值,一个是知道在图像中的哪里放置阴影结果。但也有一些重要的区别:n几乎总是透视或正投影,而o可以有多种形式;而且一个图像只有一个观看投影,而场景中的每个物体可能都有一个完全独立的纹理坐标函数。 当我们的目标是将纹理放到表面上时,o是一个从表面到纹理的映射,这似乎令人惊讶,但这是我们需要的函数。 对于木地板的例子,如果地板恰好在恒定的z上,并与r轴和y轴对齐,我们就可以使用映射 $$ u = ax; v =by, $$ 对于一些适当选择的比例因子a和b,将纹理坐标(u, v)分配到点(T, y, z)层,然后使用纹理像素或texel的值,最接近(u, v)的纹理值作为(x, y)处的纹理值。通过这种方式,我们渲染了图11.2中的图像。 这是非常有限的,但是:如果房间的模型与r轴和y轴成一定的角度,或者如果我们想在弯曲的椅背上使用木材纹理,该怎么办呢?我们需要一些更好的方法来计算表面上点的纹理坐标。 另一个由最简单的纹理映射形式产生的问题是通过从非常掠掠的角度将高对比度纹理渲染到低分辨率图像而得到的显著说明。图11.3显示了一个更大的平面纹理使用相同的方法,但具有高对比度的网格模式和面向地平线的视图。你可以看到它包含混叠工件(前景的楼梯,远处的波浪和闪闪发光的图案),类似于在图像重采样(第10章)中没有使用适当的过滤器时出现的东西。虽然这需要一个极端的情况下,使这些工件在印刷在一本书的微小静态图像中如此明显,但在动画中,这些模式四处移动,即使它们非常微妙,也会非常分散注意力。 现在我们已经看到了基本纹理映射中的两个主要问题
- 定义纹理坐标函数,和
- 查找纹理值,而不引入太多的混叠。
这两个问题是所有类型的纹理映射应用的基础,将在11.2节和11.3节中讨论。一旦你理解了它们和它们的一些解决方案,你就理解了纹理映射。剩下的就是如何为各种不同的目的应用基本的纹理机制,这将在第11.4节中讨论。
设计好纹理坐标函数φ是获得良好纹理映射效果的关键要求。你可以认为这是决定你将如何变形一个平面,矩形图像,使它符合你想要绘制的3D表面。或者,您取一个表面,轻轻地把它压平,不让它起皱、撕裂或折叠,使它平躺在图像上。有时候,这很简单:也许3D表面已经是一个平坦的矩形了!在其他情况下,这是非常棘手的:3D形状可能非常复杂,就像角色的表面 定义纹理坐标函数的问题对计算机图形学来说并不新鲜。正是如此,制图师在设计时也面临着同样的问题覆盖地球表面大片区域的地图:从弯曲的地球到平面地图的映射不可避免地会造成区域、角度和/或距离的扭曲,这很容易使地图产生误导。在过去的几个世纪里,许多地图投影被提出,所有的都平衡了相同的竞争问题——在覆盖一个连续的大块区域的同时最小化各种失真——这是在纹理映射中面临的问题。 在某些应用程序中(一些例子见11.2.1节),使用特定的映射是有明确的理由的。但在大多数情况下,设计纹理坐标映射是一项微妙的任务,需要平衡相互竞争的关注点,熟练的建模人员要为此付出相当大的努力。“UV映射”或“表面参数化”是你可能遇到的纹理坐标函数的其他名称。 你可以用你能想到的任何方式定义φ。但有几个相互矛盾的目标需要考虑:
- 双射性。在大多数情况下,你希望φ是双射的(参见2.1.1节),这样表面上的每个点映射到纹理空间中的不同点。如果多个点映射到相同的纹理空间点,纹理中一个点的值将影响表面上的多个点。如果你想在一个表面上重复一个纹理(想想有重复图案的墙纸或地毯),有意地引入从表面点到纹理点的多对一映射是有意义的,但你不希望这种情况偶然发生。
- 变形大小。纹理的比例应该在表面上近似恒定。也就是说,表面上任何距离相同的紧密点应该映射到纹理中距离相同的点。就φ函数而言,φ的导数的大小变化不宜太大。
- 形状畸变。质地不能太扭曲。也就是说,画在表面上的一个小圆应该映射到纹理空间中一个合理的圆形形状,而不是一个极度挤压或拉长的形状。对于φ,不同方向上φ的导数不能相差太大。
- 连续性。不应该有太多的接缝:表面上的相邻点应该映射到纹理中的相邻点。也就是说,φ应该是连续的或有尽可能少的不连续。在大多数情况下,一些不连续是不可避免的,我们想把它们放在不显眼的位置。
你由参数方程(章节2.7.8)定义的曲面有一个内置的纹理坐标函数选择:简单地对函数求逆,它定义了表面,并使用表面的两个参数作为纹理坐标。这些纹理坐标可能有或可能没有理想的属性,这取决于表面,但它们确实提供了映射。 你但是对于隐式定义的表面,或者只是由三角形网格定义的表面,我们需要一些其他的方法来定义纹理坐标,而不依赖于现有的参数化。一般来说,定义纹理坐标的两种方法是:从曲面点的空间坐标几何上计算纹理坐标,或者对于网格曲面,在顶点上存储纹理坐标的值,并在曲面上插值它们。让我们逐一看看这些选项
几何决定的纹理坐标用于简单的形状或特殊情况,作为快速解决方案,或作为设计手工调整纹理坐标映射的起点。 我们将通过将图11.4中的测试图像映射到表面来演示各种纹理坐标函数。图像中的数字可以让您从渲染的图像中读出近似的(u, v)坐标,而网格则可以让您看到映射的失真程度。
也许,从3D到2D最简单的映射是平行投影——与正字法观看使用的映射相同(图11.5)。我们已经开发的用于查看的机制(章节8.1)可以直接用于定义纹理坐标:就像正投影查看归结为乘以一个矩阵并丢弃z分量一样,通过平面投影生成纹理坐标可以通过简单的矩阵乘法完成: $$ \phi(x,y,z) = (u,v) ~~~~~~ where\begin{bmatrix}u \ v \ * \ 1\ \end{bmatrix} = M_t\begin{bmatrix}x \ y \ z \ 1\ \end{bmatrix}$$ 其中纹理矩阵Mt代表一个仿射变换,星号表示我们不关心第三个坐标是什么。 这对于大部分平坦的表面非常有效,表面法线没有太多变化,通过取平均法线可以找到一个良好的投影方向。然而,对于任何一种闭合形状,平面投影都可以不是单射的:前面和后面的点会映射到纹理空间中的同一个点(图11.6) 通过简单地用透视投影代替正投影,我们得到了投影纹理坐标(图11.7) $$ \phi(x,y,z) = (\hat u /w,\hat v/w) ~~~~~~ where\begin{bmatrix}\hat u \ \hat v \ * \ w\ \end{bmatrix} = P_t\begin{bmatrix}x \ y \ z \ 1\ \end{bmatrix}$$ 现在,4×4矩阵Pt表示投影(不一定是仿射)变换——也就是说,最后一行可能不是投影纹理坐标在阴影映射技术中很重要,在第11节中讨论过。
对于球体,经度/纬度参数化是常见的和广泛使用的。它在两极附近有很多失真,这可能会导致困难,但它确实覆盖了整个球体,只有沿着一条纬度线的不连续 形状大致为球形的曲面可以使用纹理坐标函数参数化,该函数使用径向投影将曲面上的一点映射到球面上的一点:从球面的中心取一条线,穿过曲面上的点,然后找到与球面的交点。这个交点的球坐标是你在表面上开始的点的纹理坐标 另一种说法是,你用球坐标(ρ, θ, φ)表示曲面点,然后放弃ρ坐标,将θ和φ分别映射到[0,1]的范围内。公式依赖于球坐标约定;使用2.7.8节的约定 $$ \phi(x,y,z) = ([\pi+ atan2(y,x)]/{2\pi},[\pi - acos(z/||x||)]/\pi ) $$ 如果从中心点可以看到整个曲面,那么除了极点以外,球面坐标地图在任何地方都是双射的。它继承了与球体上经纬度图相同的在两极附近的扭曲。图11.8显示了一个对象,球面坐标为其提供了合适的纹理坐标函数
柱面坐标对于柱状而非球形的物体,从轴向外投射到圆柱体上可能比从一点投射到球体上效果更好(图11.9)。类似于球面投影,这相当于转换到柱坐标,并丢弃半径: $$ \phi(x,y,z) = (\frac{1}{2\pi}[\pi+ atan2(y,x)]/{2\pi},\frac{1}{2}[1+z]) $$ 使用球坐标来参数化一个球形或类球形的形状会导致形状和极点附近的区域高度失真,这通常会导致可见的工件,揭示出有两个特殊的点,在那里纹理出现了问题。一个流行的替代方案是更加统一,但代价是有更多的不连续。这个想法是投影到一个立方体上,而不是一个球体,然后使用六个独立的正方形纹理为立方体的六个面。六个正方形纹理的集合称为立方体贴图。这就导致了所有c的不连续 算cubemap纹理坐标也比球坐标便宜,因为投影到平面上只需要划分—本质上与透视投影相同。例如,对于投影到正z面上的点: $$ (x,y,z)\to (\frac{x}{z},\frac{y}{z}) $$ 立方体图的一个令人困惑的方面是建立如何在六个面上定义u和v方向的惯例。任何惯例都可以,但是所选择的惯例会影响纹理的内容,所以标准化是很重要的 。cubemap通常用于从立方体内部观察的纹理(参见11.4.5节中的环境映射),通常的约定有u轴和u轴的方向,所以从内部观察时u是顺时针方向。OpenGL使用的约定是: $$ \phi{-x}(x,y,z)=\frac{1}{2}[1+(+z,-y)/|x|], \ \phi{+x}(x,y,z)=\frac{1}{2}[1+(-z,-y)/|x|], \ \phi{+y}(x,y,z)=\frac{1}{2}[1+(+x,-z)/|y|], \ \phi{-y}(x,y,z)=\frac{1}{2}[1+(+x,+z)/|y|], \ \phi{+z}(x,y,z)=\frac{1}{2}[1+(-x,-y)/|z|], \ \phi{-z}(x,y,z)=\frac{1}{2}[1+(+x,-y)/|z|], \ $$ 下标表示每个投影对应于立方体的哪个面。例如,对于z = +1时投影到立方体表面的点,用φ -z表示。你可以通过观察绝对值最大的坐标来判断一个点投影到哪个面:例如,如果|a > |y和| > z),则这个点投影到+r面或-r面,这取决于z的符号。 与立方体映射一起使用的纹理有六个正方形块。(参见图11.10)。通常它们被打包在一个图像中存储,就像立方体被打开一样。
为了更好的控制一个关于三角形表面的纹理坐标函数,你需要显试的在每个顶点存储纹理坐标,用重心插值(9.1.2)。它的工作原理与你在网格上定义的其他平滑变化的数量完全相同:颜色,法线,甚至3D位置本身。 让我们看一个单三角形的例子。图11.11显示了一个三角形纹理映射到现在熟悉的测试模式的一部分。通过观察呈现在渲染三角形上的模式,您可以推断出三个顶点的纹理坐标是(0.2,0.2)、(0.8,0.2)和(0.2,0.8),因为它们是纹理中出现在三角形三个角上的点。 就像上一节中几何形状确定的映射一样,我们通过从表面到纹理域的映射来控制纹理在表面上的位置 在图11.12中,我们展示了一种在整个网格上可视化纹理坐标的常用方法:简单地在纹理空间中绘制三角形,顶点定位为他们的纹理坐标。这个可视化显示了纹理的哪些部分被哪个三角形使用,它是一个方便的工具,用于计算纹理坐标和调试各种类型的纹理映射代码。 由顶点纹理坐标定义的纹理坐标映射的质量取决于分配给顶点的坐标,也就是说,网格如何在纹理空间中布局。无论分配什么坐标,只要网格中的三角形共享顶点(章节12.1),纹理坐标映射总是连续的,因为相邻的三角形在共享边缘上的纹理坐标是一致的。但上面描述的其他可取的品质并不是自动产生的。注入性意味着三角形在纹理空间中不会重叠——如果重叠,则意味着纹理中的某个点将出现在表面的多个地方。 当纹理空间中三角形的面积与它们在三维中的面积成比例时,尺寸失真较低。例如,如果用连续纹理坐标函数映射一个角色的脸,通常会以鼻子被挤压到纹理空间中一个相对较小的区域结束,如图11.13所示。虽然鼻子上的三角形比脸颊上的小,但尺寸的比例在纹理空间上更极端。结果是鼻子上的纹理被放大了,因为小面积的纹理必须覆盖大面积的表面。类似地,对比额头和太阳穴,三角形在3D中大小相似,但太阳穴周围的三角形在纹理空间中更大,导致纹理在那里看起来更小。 同样,当三角形的形状在3D和纹理空间中相似时,形状失真较低。脸的例子有相当低的形状失真,但是,例如,图11.15中的球体在极点附近有非常大的形状失真。
允许纹理坐标超出纹理图像的边界通常是有用的。有时,这是一个细节:纹理坐标计算中的舍入误差可能会导致恰好落在纹理边界上的顶点略微偏外,在这种情况下,纹理映射机制应该不会失败。但它也可以成为建模工具。 如果一个纹理只应该覆盖表面的一部分,但纹理坐标已经设置好,将整个表面映射到单位正方形,一个选项是准备一个纹理图像,内容在一个小的区域内,大部分是空白的。但这可能需要一个非常高分辨率的纹理图像来获得相关区域的足够细节。另一个选择是缩放所有的纹理坐标它们覆盖了更大的范围- [- 4.5,5.5]×[- 4.5, 5.5],例如,在表面中心放置十分之一大小的单元正方形 对于这样的情况,纹理图像覆盖的单位方形区域之外的纹理查找应该返回恒定的背景色。一种方法是设置一个背景颜色,由单位方块外部的纹理查找返回。如果纹理图像已经有一个恒定的背景颜色(例如,白色背景上的logo),另一种在平面上自动扩展这个背景的方法是安排在单位方格之外的查找,以返回边缘上最近点的纹理图像的颜色,通过夹紧u和v坐标来实现。 有时,我们想要一个重复的图案,如棋盘、瓷砖地板或砖墙。如果模式在矩形网格上重复,那么创建包含相同数据的多个副本的图像将是浪费的。相反,我们可以使用环绕索引来处理纹理图像外部的纹理查找——当查找点离开纹理图像的右边缘时,它会环绕到左边缘。使用像素坐标上的整数余数操作可以非常简单地处理这个问题
Color texture_lookup_wrap(Texture t, float u, float v) {
int i = round(u * t.width() - 0.5)
int j = round(v * t.height() - 0.5)
return t.get_pixel(i % t.width(), j % t.height())
}
Color texture_lookup_wrap(Texture t, float u, float v) {
int i = round(u * t.width() - 0.5)
int j = round(v * t.height() - 0.5)
return t.get_pixel(max(0, min(i, t.width()-1)),
(max(0, min(j, t.height()-1))))
}
在这两种处理越界查找方法之间的选择是通过从一个列表中选择一个包装模式来指定的,该列表包括平铺、夹紧以及通常是这两种方法的组合或变体。使用包装模式,我们可以自由地将纹理视为一个函数,它为无限的2D平面中的任何点返回颜色(图11.14)。当我们使用图像指定纹理时,这些模式描述了如何使用有限的图像数据来定义这个函数。在第11.5节中,我们将看到程序纹理可以自然地跨越一个无限的平面,因为它们不受有限的图像数据的限制。因为两者在逻辑上都是无限的,这两种类型的纹理是可以互换的。 当调整纹理的比例和位置时,通过应用矩阵反式来避免实际改变生成纹理坐标的函数,或存储在网格顶点的纹理坐标值,是很方便的 生成纹理坐标,然后使用它们来采样纹理: $$ \phi (x) = M_T \phi_{model}(x) $$ model是模型提供的纹理坐标函数,M是一个3 × 3矩阵,表示使用齐次坐标的2D纹理坐标的仿射或射影变换。这样的转换有时仅限于缩放和/或转换,大多数使用纹理映射的渲染器都支持这种转换。
虽然低失真和连续性是纹理坐标函数中很好的属性,但不连续常常是不可避免的。对于任何闭合的3D曲面,拓扑学的一个基本结果是,不存在将整个曲面映射为纹理图像的连续的双目标函数。有些东西必须放弃,通过引入接缝——在纹理坐标突然变化的表面上的曲线——我们可以在其他地方获得低失真。上面讨论的许多由几何决定的映射已经包含了接缝:在球面和柱坐标中,接缝是atan计算的角度从n绕到-T的位置,而在立方体映射中,接缝是沿着立方体边缘的,其中映射在六个正方形纹理之间切换。 对于插值的纹理坐标,接缝需要特别考虑,因为它们不是自然发生的。我们之前观察到插值的纹理坐标在共享顶点网格上是自动连续的——纹理坐标的共享保证了这一点。但这意味着,如果一个三角形跨越了一个接缝,其中一些顶点在一侧,另一些顶点在另一侧,插值机制将乐意提供一个连续的映射,但它可能会高度扭曲或折叠,从而不是单射的。图11.15说明了用球坐标映射的球体上的这个问题。例如,在地球的底部有一个三角形,它的一个顶点在新西兰南岛的顶端,另一个顶点在北岛东北约400公里的太平洋上。在这些点之间飞行的明智的飞行员会飞越新西兰,但是路径从东经167°(1 167)开始,到西经179°(即经度-179)结束,所以线性插值选择了在途中穿过南美洲的路线。 这将导致整个地图的向后拷贝被压缩成穿过子午线180度的三角形带!解决方案是用181°E的等效经度标记第二个顶点,但这只会把问题推到下一个三角形。 创建一个干净的过渡的唯一方法是避免在接缝处共享纹理坐标:穿过新西兰的三角形需要插值到经度+181,而太平洋上的下一个三角形需要继续从经度-179开始。为此,我们复制了接缝处的顶点:对于每个顶点,我们添加了一个具有相同经度的第二个顶点,其经度相差360°,并且接缝相对边的三角形使用了不同的顶点。这个解决方案如图11.15的右半部分所示,其中纹理空间最左边和最右边的顶点是重复的,具有相同的3D位置。
纹理用于各种渲染系统,虽然基本原理是相同的,但对于射线跟踪和光栅化系统,细节是不同的。 纹理坐标是渲染模型的一部分,场景描述需要包含足够的信息来定义它们是什么。大多数情况下,这意味着将纹理坐标存储为将与纹理一起使用的所有三角形网格的每个顶点属性。如果渲染系统直接支持几何基元而不是网格,这些基元通常具有预定义的纹理坐标(例如,球体上的经纬度坐标),可能为每种基元类型提供了映射方案的选择 在射线跟踪渲染器中,每一种支持射线相交的曲面都必须不仅能够计算交点和曲面法线,而且能够计算交点的纹理坐标。像关于交集的其他信息一样,纹理坐标可以存储在一个命中记录中)。在用三角形网格表示的几何图形的常见情况下,射线-三角形交点代码将从存储在顶点的纹理坐标通过重心插值计算纹理坐标,而对于其他类型的几何图形,则计算纹理坐标。 在基于栅格化的系统中,三角形通常是唯一受支持的几何类型,因此所有曲面都必须转换为这种形式。纹理坐标可以通过模型读取(常见情况),或者对于代码生成的三角形网格,可以在创建网格时计算和存储它们。另外,对于可以从其他顶点数据计算的纹理坐标(例如,纹理坐标是从3D位置计算的),纹理坐标也可以在顶点着色器中计算并传递给光栅化器。纹理坐标被光栅化器插值,这样每次片段着色器的调用都有适合其片段的纹理坐标
纹理映射的第二个基本问题是抗锯齿。渲染一个图像纹理是一个采样过程:将纹理映射到表面,然后将表面投影到图像中,生成一个跨图像平面的2D函数,我们在像素处采样。正如我们在第10章中看到的,当图像包含细节或尖锐的边缘时,使用点采样将产生混叠工件——因为纹理的整个要点是引入细节,它们成为我们在图中看到的混叠问题的主要来源。 就像线或三角形的反锯齿光栅化(第9.3节)、反锯齿射线跟踪或下采样图像(第10.4节)一样,解决方案是使每个像素不是点采样,而是图像的面积平均,在与像素大小相似的区域上。使用与反锯齿栅格化和光线追踪相同的超采样方法,只要有足够多的样本,就可以在不改变纹理映射机制的情况下获得优秀的结果:一个像素区域内的许多样本将落在纹理映射的不同位置,并将计算得到的阴影结果平均起来然而,对于详细的纹理,它需要非常多的样本才能得到好的结果,这是缓慢的。在纹理表面存在纹理的情况下,有效计算纹理平均面积是纹理抗锯齿的第一个关键问题。 纹理图像通常是由光栅图像定义的,因此也有一个重构问题需要考虑,就像上采样图像(10.4)。对于纹理的解决方案是一样的:使用一个重建过滤器来插值texels之间。 我们将在接下来的部分中对每个主题进行扩展。
使纹理抗锯齿比其他类型的抗锯齿更复杂的是渲染图像和纹理之间的关系是不断变化的。每个像素值都应该计算为属于图像中像素的区域的平均颜色,在通常情况下,像素正在观察一个单一的表面,这对应于对表面上的一个区域的平均。如果表面颜色来自一个纹理,这反过来相当于纹理的对应部分的平均,称为像素的纹理空间足迹。图11.16说明了正方形区域(可能是低分辨率图像中的像素区域)的足迹如何映射到地板纹理空间中非常不同大小和形状的区域。 回想一下在渲染纹理时涉及的三个空间:投影π将3D点映射到图像中,纹理坐标函数φ将3D点映射到纹理空间中。要处理像素足迹,我们需要理解这两个映射的组成:首先遵循π向后从图像到表面,然后遵循φ向前。这篇文章ψ = φ◦π−1是决定像素足迹的因素:一个像素的足迹是在映射ψ下该像素的图像的平方面积的图像。 纹理抗锯齿的核心问题是的平均值的计算像素足迹上的纹理。要做到这一点可能是一项相当复杂的工作:对于一个具有复杂表面形状的遥远物体,足迹可能是一个覆盖大面积的复杂形状,也可能是纹理空间中几个不相连的区域。但在典型情况下,像素落在平滑的表面区域,该区域被映射到纹理中的单个区域。 因为y既包含从图像到表面的映射,也包含从表面到纹理的映射,所以足迹的大小和形状取决于观看情况和纹理坐标函数。当表面离相机越近时,像素足迹越小;当同一个表面移动得越远,足迹就越大。当以斜角观察表面时,一个像素在表面上的足迹被拉长,这通常意味着它在纹理空间中也会被拉长。即使是一个固定的视图,纹理坐标函数也会导致足迹的变化:如果它扭曲了区域,足迹的大小将会变化,如果它扭曲了形状,它们可以被拉长,甚至对于表面的正面视图。 然而,要找到计算反锯齿查找的有效算法,就需要一些实质性的近似。当一个函数是光滑的,线性近似通常是有用的。在纹理反锯齿的情况下,这意味着近似地将图像空间到纹理空间的映射y作为从2D到2D的线性映射: $$ φ(x) = ψ(xo) + J(x - Xo), $$ 其中2乘2矩阵J是t的导数的近似,它有四个分量,如果我们把像空间的位置表示为x = (r, y)和纹理空间位置为u = (u, u),则 的 $$ J = $$ 其中四阶导数描述了当我们改变r和y时,图像中某个点(r, y)处的纹理点(u, u)是如何变化的。 这种近似的几何解释(图11.17)是,它表示图像中以x为中心的单位大小的正方形像素区域将近似映射到纹理空间中以(x)为中心的平行四边形,其边缘平行于 你的蔬菜 导数矩阵J很有用,因为它讲述了整个图像(近似的)纹理空间足迹的变化过程。大小较大的导数表示较大的纹理空间足迹,导数向量u和u之间的关系表示形状。当它们正交且长度相同时,足迹是正方形的,当它们变得倾斜和/或长度非常不同时,足迹就会变长。 我们现在已经达到了通常被认为是“正确答案”的问题的形式:在特定图像空间位置的过滤纹理样本应该是纹理映射在由纹理坐标导数在该点定义的平行四边形足迹上的平均值。这已经包含了一些假设,即从图像到纹理是光滑的,但它是足够准确的优秀的图像质量。然而,这个平行四边形的面积平均值已经太昂贵,无法精确计算,所以使用了各种近似方法。纹理抗锯齿的方法在近似查找时所做的速度/质量权衡上有所不同。我们将在以下部分讨论这些内容。
当足迹小于texel时,我们将放大纹理,因为它被映射到图像中。这种情况类似于图像的上采样,主要考虑的是在纹理之间进行插值,以生成纹理网格不明显的平滑图像。就像在图像上采样一样,这个平滑过程是由一个重构滤波器定义的,它被用于计算纹理空间中任意位置的纹理样本。(参见图11.18)。 这些考虑因素与图像重采样几乎相同,但有一个重要的区别。在图像重采样中,任务是在规则网格上计算输出样本,而这种规则在可分离重构滤波器的情况下实现了重要的优化。在纹理过滤中,查找的模式是不规则的,样本必须单独计算。这意味着大的,高质量的重建滤波器是非常昂贵的使用,和因此,通常用于纹理的最高质量过滤器是双线性插值。 双线性插值纹理样本的计算与双线性插值上采样图像中的一个像素的计算是相同的。首先,我们用(实值)texel坐标表示纹理空间样本点,然后读取相邻四个texel的值并对其进行平均。纹理通常在单位正方形上参数化,texel的位置与任何图像中的像素相同,在u方向上间隔为1/nu,在v中间隔为1/nv, texel(0,0)位于距离边缘半texel处,以实现对称。(见第十章)
Color tex_sample_bilinear(Texture t, float u, float v) {
u_p = u * t.width - 0.5
v_p = v * t.height - 0.5
iu0 = floor(u_p); iu1 = iu0 + 1
iv0 = floor(v_p); iv1 = iv0 + 1
a_u = (iu1 - u_p); b_u = 1 - a_u
a_v = (iv1 - v_p); b_v = 1 - a_v
return a_u * a_v * t[iu0][iv0] + a_u * b_v * t[iu0][iv1] +
b_u * a_v * t[iu1][iv0] + b_u * b_v * t[iu1][iv1]
}
在许多系统中,这个操作成为一个重要的性能瓶颈,主要是因为从纹理数据中获取四个纹理值所涉及的内存延迟。纹理样本点的模式是不规则的,因为从图像到纹理空间的映射是任意的,但通常是连贯的,因为附近的图像点倾向于映射到可能读取相同纹理的附近纹理点。出于这个原因,高性能系统有专门的硬件用于纹理采样,处理插值和管理最近使用的纹理数据的缓存,以最小化从存储纹理数据的内存中获取缓慢数据的数量。 在阅读了第10章之后,你可能会抱怨线性插值对于一些苛刻的应用程序来说可能不是一个足够平滑的重构。然而,通过使用更好的过滤器将纹理重采样到一个更高的分辨率,它总是可以足够好,这样纹理就足够光滑,双线性插值工作得很好。
做好插值工作只适用于纹理被放大的情况:像素占用比texels的间距小。当一个像素足迹覆盖许多像素时,需要良好的抗锯齿计算多个texels的平均值,以平滑信号,以便安全采样。 一种非常精确的计算纹理平均值的方法是找到纹理中的所有纹理并将它们相加。然而,当占用空间很大时,这可能会非常昂贵——仅一次查找就需要读取数千个texel。一个更好的方法是预计算和存储纹理在不同大小和位置的不同区域的平均值。 这个想法的一个非常流行的版本被称为“MIP映射”或简称为mipmapping。mipmap是包含相同图像但分辨率越来越低的纹理序列。原始的、全分辨率的纹理图像被称为mipmap的基本级别,或第0级,第1级是通过获取该图像并在每个维度上对其进行2倍的下采样而生成的,结果是图像具有四分之一的texels。粗略地说,这个图像中的texels是0级图像中2 × 2 texels大小的正方形区域的平均值。 这个过程可以根据需要继续定义任意多的mipmap级别:第k级的图像通过对第k−1级的图像下采样2来计算。在k级的texel对应于原始纹理中测量2k * 2k texel的正方形区域。例如,从1024 × 1024的纹理图像开始,我们可以生成一个具有11个级别的mipmap:级别0是1024 × 1024;第一个层次是512 × 512,以此类推,直到关卡10(游戏邦注:即只有一个纹理)。这种以一系列越来越低的采样率表示相同内容的图像结构被称为图像金字塔,基于将所有较小的图像堆叠在原始图像之上的视觉隐喻
有了mipmap或图像金字塔,可以比单独访问多个texels更有效地进行纹理过滤。当我们需要一个大面积的平均纹理值时,我们只需使用来自mipmap更高级别的值,这些值已经是图像大片区域的平均值。最简单和最快的方法是从mipmap中查找单个值,选择级别,以便该级别上的texels覆盖的大小与像素占用的总体大小大致相同。当然,像素足迹在形状上可能与texel所表示的(总是方形的)区域有很大的不同,我们可以预期这会产生一些工件. 暂且不考虑当像素足迹具有拉长形状时该怎么办的问题,假设足迹是宽度为D的正方形,度量就全分辨率纹理中的纹理而言。mipmap的哪个级别适合采样?由于第k级的texels覆盖了2k宽度的正方形,因此选择k似乎是合适的: $$ 2^k \approx D $$ 设k = log2D。当然,这在大多数情况下会给出非整数值的k,我们只存储整数值级别的mipmap图像。两种可能的解决方案是只查找最接近k的整数的值(有效,但在级别之间的突变处产生接缝)或查找最接近k的两个整数的值并线性插值值(工作量是两倍,但更流畅) 在我们真正写下mipmap采样算法之前,我们必须决定当足迹不是正方形时,我们将如何选择“宽度”D。有些方法可能是使用面积的平方根,或者找到占地面积的最长轴,称之为宽度。一个很容易计算的实用折衷方法是使用最长边的长度: $$ D = max\left|||u_x|,|||U_y|\right. $$
Color mipmap_sample_trilinear(Texture mip[], float u, float v,
matrix J) {
D = max_column_norm(J)
k = log2(D)
k0 = floor(k); k1 = k0 + 1
a = k1 - k; b = 1 - a
c0 = tex_sample_bilinear(mip[k0], u, v)
c1 = tex_sample_bilinear(mip[k1], u, v)
return a * c0 + b * c1
}
基本的mipmapping在去除混叠方面做得很好,但因为它不能处理拉长的或各向异性的像素足迹,所以当以掠射角度观察表面时,它的性能不佳。这是最常见的大平面,代表一个表面的观众站在上面。地面上较远的点以非常陡峭的角度观察,导致非常各向异性的脚印,mipmapping与更大的正方形区域近似。生成的图像在水平方向上会出现模糊
mipmap可以与多次查找一起使用,以更好地近似拉长的占用空间。的想法是基于最短的轴来选择mipmap级别而不是最大的,然后将沿着长轴间隔的几个查找的平均值加在一起。(参见图11.19)。
一旦你理解了为表面定义纹理坐标的想法和查找纹理值的机制,这个机制就有很多用途。在本节中,我们将研究纹理映射中一些最重要的技术,但纹理是一种非常通用的工具,应用程序只受程序员能够想到的限制。
纹理映射最基本的用途是通过使用于着色计算的漫反射颜色(无论是在射线跟踪器中还是在片段着色器中)依赖于从纹理中查找的值来引入颜色的变化。有纹理的漫反射组件可用于粘贴贴花、油漆装饰或在表面上打印文本,它还可以模拟材料颜色的变化,例如,木材或石头。 不过,没有什么能限制我们只改变漫反射的颜色。任何其他参数,如镜面反射率或镜面粗糙度,也可以被纹理化。例如,一个粘有透明包装胶带的纸板箱,它可能有相同的漫反射颜色,但在胶带的地方比其他地方更亮,有更高的镜面反射率和更低的粗糙度。在许多情况下,不同参数的映射是相关的:例如,一个印有标识的光滑的白色陶瓷杯,在印刷的地方可能既粗糙又暗(图11.20),用金属墨水印刷书名的书可能会在漫反射色、镜面色和粗糙度上同时发生变化。
另一个对着色很重要的量是表面法线。通过插值法线(第9.2节),我们知道底纹法线不必与下垫面的几何法线相同。法线映射利用了这一事实,使阴影法线依赖于从纹理映射中读取的值。最简单的方法是将法线存储在纹理中,在每个被解释的texel中存储三个数字,而不是作为一种颜色的三个分量,作为法向量的3D坐标 但是,在使用法线映射之前,我们需要知道从映射中读取的法线是用什么坐标系表示的。将法线直接存储在对象空间中,在用于表示表面几何本身的相同坐标系中,这是最简单的:从地图中读取的法线可以以与表面本身报告的法线完全相同的方式使用:在大多数情况下,它将需要转换到世界空间中进行照明计算,就像几何体中附带的法线一样。 然而,存储在对象空间中的法线映射本质上与曲面几何相关联——即使法线映射没有效果,为了用几何法线再现结果,法线映射的内容必须跟踪曲面的方向。此外,如果曲面将要变形,因此几何法线发生了变化,那么对象空间法线映射就不能再使用了,因为它会一直提供相同的阴影法线。 解决方法是为附着在曲面上的法线定义一个坐标系。这样的坐标系可以根据曲面的切空间来定义(参见2.7节):选择一对切向量,用它们来定义一个标准正交基(参见2.4.5节)。纹理坐标函数本身提供了一种选择一对切向量的有用方法:使用与常数u和v的直线相切的方向。这些切线通常不是正交的,但我们可以使用第2.4.7节中的步骤来“平方”标准正交基,或者可以使用表面法线和一个切向量来定义它 当法线在这个基础上表示时,它们的变化就小得多;因为它们大多指向光滑表面的法线方向附近,所以它们将靠近法线映射中的向量(0,0,1)T。 法线映射从何而来?通常它们是从一个更详细的模型中计算出来的,光滑的表面是一个近似的模型;其他时候,它们可以直接从真实的表面测量。它们也可以作为建模过程的一部分编写;在这种情况下,最好使用凹凸贴图来间接指定法线。其思想是,凹凸映射是一个高度字段:一个函数,它给出了光滑表面之上细节表面的局部高度。在数值高的地方(地图看起来很亮的地方,如果你以图像的形式显示它),表面凸出在外面 从凹凸贴图导出法向贴图很简单:法向贴图(在切线框中表示)是凹凸贴图的导数 图11.21显示了纹理贴图被用来创建木纹颜色,并模拟增加的表面粗糙度,由于饰面浸透到更多孔的木材部分,与凹凸贴图一起创建不完美的饰面和板之间的缝隙,以制作一个真实的木地板
法线贴图的一个问题是,它们实际上根本不会改变表面;它们只是一个阴影技巧。当法线映射的几何图形在3D中产生明显的效果时,这一点就变得很明显了。在静态图像中,要注意的第一个问题通常是物体的轮廓保持平滑,尽管在内部出现了肿块。在动画中,视差的缺乏会泄露出凸起的信息,不管这些凸起多么令人信服,它们实际上只是“画”在表面上。 纹理不仅可以用于着色,还可以用于改变几何形状。位移映射是这种思想的最简单版本之一。 这个概念与凹凸贴图相同:一个标量(单通道)贴图,给出“平均地形”之上的高度。但效果是不同的。在使用光滑几何图形时,位移图实际上改变了表面,沿着光滑表面的法线移动每个点到一个新的位置,而不是从高度图中获得阴影法线。每一种情况的法线大致相同,但表面不同 实现位移映射最常见的方法是用大量小三角形对光滑表面进行镶嵌,然后使用位移映射置换生成网格的顶点。在图形管道中,这可以在顶点阶段使用纹理查找来完成,对于地形尤其方便
阴影是一个场景中物体关系的重要线索,正如我们所见,它们很容易包含在光线追踪图像中。然而,如何在栅格化渲染图中获得阴影并不明显,因为表面被认为是孤立的,一次一个。阴影映射是一种使用纹理映射机制从点光源获得阴影的技术 阴影图的概念是表示被点光源照亮的空间的体积。想想聚光灯或投影仪之类的光源,它从一个点向有限范围的方向发射光。的体积是被照亮的——你会看到光在你手上的集合,如果你把它放在那里——是把光源连接到最近的线段的并集沿着离开该点的每条射线的表面点。 有趣的是,这个体积与位于与光源相同位置的透视相机可见的体积相同:当且仅当从光源位置可见时,一个点被光源照亮。 在这种情况下,我们需要评估场景中点的可见性:对于可见性,我们需要知道一个片段对相机是否可见,知道是否要在图像中绘制它;对于阴影,我们需要知道一个碎片是否对光源可见,知道它是否被光源照亮。(参见图11.22)。 在这两种情况下,解决方案是相同的:一个深度图,告诉沿着一束射线到最近表面的距离。在可见性的情况下,这是zbuffer(章节9.2.3),而对于阴影情况,它被称为阴影映射。在这两种情况下,通过比较新碎片的深度与存储在地图中的深度来评估可见性,如果该表面的深度大于最近的可见表面的深度,则该表面将从投影点(遮挡或阴影)隐藏起来。不同之处在于,z缓冲区用于跟踪目前为止看到的最近的表面,并在渲染过程中更新,而阴影映射则告诉整个场景中到最近的表面的距离。 阴影贴图是提前在单独的渲染通道中计算的:像往常一样简单地栅格化整个场景,并保留结果的深度贴图(不需要费心计算像素值)。然后,有了阴影映射,您执行一个普通的渲染过程,当您需要知道一个片段对源是否可见时,您在阴影映射中投影它的位置(使用最初用于渲染阴影映射的同一透视投影),并将查找的值dmap与d到的实际距离进行比较源。如果距离相同,碎片的点被照亮;如果d>dmap,这意味着有一个不同的表面更接近源,所以它是阴影 “如果距离相同”这句话应该会在你的脑海中引发一些危险信号:因为所有涉及的量都是精度有限的近似值,我们不能期望它们完全相同。对于可见点,d≈dmap但有时d会大一点,有时会小一点。因此,需要一个公差:如果d−dmap <,则认为一个点被照亮。这种容忍度被称为阴影偏差. 当在阴影地图中查找时,在地图中记录的深度值之间插入没有多大意义。这可能会导致在光滑区域获得更精确的深度(需要更少的阴影偏差),但在阴影边界附近会导致更大的问题,在那里深度值会突然变化。因此,阴影映射中的纹理查找是使用最近邻重构完成的。为了减少混叠,可以使用多个样本,对1或0阴影结果(而不是深度)进行平均;这就是所谓的百分比接近过滤
就像纹理可以方便地将细节引入到表面的阴影中,而不必向模型添加更多的细节一样,纹理也可以用于将细节引入到照明中,而不必为复杂的光源几何建模。当光线来自与视野中物体大小相比较远的地方时,场景中点与点之间的照度变化很小.假设照明只依赖于你看的方向,并且场景中的所有点都是相同的,然后使用环境图来表示照明对方向的依赖,这很方便。 环境图的思想是定义在方向上的函数3D是单位球体上的一个函数,所以它可以用纹理映射来表示,就像我们表示球形物体上的颜色变化一样。而不是计算纹理坐标从一个表面点的三维坐标,我们使用完全相同的公式计算纹理坐标从单位向量的三维坐标代表我们想要知道的照度方向。 环境地图最简单的应用是在射线追踪器中为不击中任何物体的射线提供颜色
trace_ray(ray, scene) {
if (surface = scene.intersect(ray)) {
trace_ray(ray, scene) {
if (surface = scene.intersect(ray))
u, v = spheremap_coords(r.direction)
return texture_lookup(scene.env_map, u, v
}
随着光线追踪器的改变,反射其他场景物体的闪亮物体现在也会反射背景环境 在栅格化上下文中,通过在阴影计算中添加镜像反射,可以实现类似的效果,它的计算方法与射线跟踪器相同,但只是直接在环境地图中查找,而不考虑场景中的其他对象
shade_fragment(view_dir, normal) {
out_color = diffuse_shading(k_d, normal)
out_color += specular_shading(k_s, view_dir, normal)
u, v = spheremap_coords(reflect(view_dir, normal))
out_color += k_m * texture_lookup(environment_map, u, v)
}
这种技术称为反射映射 更高级的环境映射计算环境映射的所有照明,而不仅仅是镜面反射。这是环境照明,可以用蒙特卡洛积分在射线示踪器中计算,也可以用点源集合近似环境并计算许多阴影图的光栅化计算 环境映射可以存储在任何用于映射球体的坐标中。球形(经纬度)坐标是一个流行的选择,尽管在极点压缩纹理浪费纹理分辨率,并可能在极点创建工件。立方体映射是一种更有效的选择,在交互应用程序中广泛使用(图11.23)。
在前几章中,我们使用cr作为物体上一点的漫反射系数。对于没有纯色的对象,我们可以用一个函数cr(p)来替换它,该函数将3D点映射到RGB颜色(Peachey, 1985;佩林,1985)。这个函数可能只是返回包含p的物体的反射率。但是对于有纹理的物体,我们应该期望cr(p)随着p在表面上移动而变化 定义从3D表面映射到2D纹理域的纹理映射函数的另一种方法是创建一个定义RGB值的3D纹理三维空间中的每一个点。我们只将它称为曲面上的点p,但通常为所有3D点定义它比在任意曲面上定义一个潜在的奇怪的2D点子集更容易。3D纹理映射的好处是很容易定义映射函数,因为表面已经嵌入到3D空间中,从3D到纹理空间的映射没有失真。这种策略显然适用于从固体介质“雕刻”出来的表面,例如大理石雕塑 3D纹理的缺点是将它们存储为3D光栅图像或卷将消耗大量内存。因此,3D纹理坐标最常用于程序纹理,其中纹理值是通过数学过程计算出来的,而不是通过从纹理图像中查找它们。在本节中,我们将介绍几个用于定义过程纹理的基本工具。这些也可以用于定义2D程序纹理,尽管在2D中更常用的是使用光栅纹理图像。
制作条纹纹理的方法有很多。假设我们有两种颜色c0和c1,我们想用它们来制作条纹颜色。我们需要一个振荡函数来在两种颜色之间切换。一个简单的是sin:
RGB stripe( point p )
if (sin(xp) > 0) then
return c0
else
return c1
我们也可以让条纹的宽度w可控:
RGB stripe( point p, real w)
if (sin(πxp/w) > 0) then
return c0
else
return c
如果我们想在条纹颜色之间平滑地插值,我们可以使用参数t来线性地改变颜色
RGB stripe( point p, real w )
t = (1 + sin(πpx/w))/2
return (1 − t)c0 + tc1
这三种可能性如图11.24所示.
虽然规则的纹理,如条纹通常是有用的,我们希望能够使“斑驳的”纹理,如我们看到的鸟蛋。这通常是通过使用一种“固体噪声”来实现的,通常被称为柏林噪声,以其发明者的名字命名,他因其在电影工业中的影响而获得了技术奥斯卡奖(柏林,1985) 通过为每个点调用随机数来获得嘈杂的外观是不合适的,因为这就像电视静态中的“白噪音”。我们想使它更流畅,而不失去随机质量。一种可能是模糊白噪声,但目前还没有实际的实现。另一种可能是在每个晶格点上都有一个随机数,然后在晶格节点之间插入这些随机数作为新点;这只是一个在上一节中描述的带有随机数的3D纹理数组。这种技术使晶格过于明显。柏林用了各种技巧来改进这种基本的晶格技术所以晶格不是那么明显。 这导致了一组相当巴洛克风格的步骤,但从本质上来说,线性插值一个随机值的3D数组只有三个变化。第一个改变是使用Hermite插值来避免马赫带,就像don一样与普通纹理。第二个变化是使用随机向量而不是值,通过点积得到随机数;这通过将局部极小值和极大值移出网格顶点,使得底层网格结构在视觉上不那么明显。第三个变化是使用1D数组和哈希来创建随机向量的虚拟3D数组。这增加了计算量,降低了内存使用量。 这是他的基本方法 $$ n(x,y,z) = \sum_{i=\lfloor x \rfloor}^{\lfloor x \rfloor+1} \sum_{j=\lfloor y \rfloor}^{\lfloor y \rfloor+1} \sum_{k=\lfloor z \rfloor}^{\lfloor z \rfloor+1} \Omega_{ijk}(x-i,y-j,z-k), $$ (x,y,z) 是X的笛卡尔坐标,并且 $$ \Omega_{ijk}(u,v,w) = w(u)w(v)w(w)(\Gamma{ijk}\cdot(u,v,w)) $$ w(t) 是一个三次加权函数: $$ w(t) =\begin{cases} 2|t|^3-3|t|^2+1 & if|t|<1,\ 0 &otherwise. \end{cases} $$
最后的$\Gamma_{ijk}$是一个点(x, y, z) =(i, j, k)的随机的单位向量,因为我们想要任何潜在的ijk,所以我们使用伪随机表:
$$
\Gamma_{ijk}=G(\phi(i+\phi(j+\phi(k)))),
$$
其中G是一个预先计算的n个随机单位向量的数组,而φ(i) =P[i mod n]其中P是一个长度为n的数组,包含整数0到n−1的排列。在实践中,Perlin报告n = 256效果很好。选择一个随机单位向量(vx, vy, vz).
$$
v_x = 2\varepsilon -1,\
v_y = 2\varepsilon^{'} -1,\
v_z = 2\varepsilon^{''} -1,
$$
许多自然纹理在同一纹理中包含各种不同的特征大小。Perlin使用了伪分形“湍流”函数: 如图11.27所示,这有效地重复添加了噪声函数的缩放副本。 湍流可以用来扭曲条纹函数:
RGB turbstripe( point p, double w )
double t = (1 + sin(k1zp + turbulence(k2p))/w)/2
return t ∗ s0 + (1 − t) ∗ s1
k1和k2的不同值被用来生成图11.28
- 如何在光线追踪中实现位移映射? 没有理想的方法可以做到这一点。生成所有三角形并在必要时缓存几何图形将防止内存过载(Pharr & Hanrahan, 1996;Pharr, Kolb, Gershbein, & Hanrahan, 1997)。当位移函数受到限制时,尝试直接与位移曲面相交是可能的(Patterson,Hoggar & Logie, 1991;海德里奇和塞德尔,1998年;史密茨、雪莉和斯塔克,2000年)
- 为什么我的图像纹理看起来不真实? 人类善于发现表面的小瑕疵。几何缺陷通常在使用纹理贴图的计算机生成图像中是不存在的,所以它们看起来“太平滑了”
透视校正纹理的讨论是基于快速阴影和使用纹理映射的照明效果(Segal, Korobkin, van Widenfelt,Foran, & Haeberli, 1992)和3D游戏引擎设计(Eberly, 2000)。
- 找到几种使用表面和固体技术实现无限2D棋盘的方法。哪个是最好的呢?
- 使用蛮力代数验证式(9.4)是一个有效的等式?
- 你如何通过使用z缓冲区深度和矩阵变换实现实体纹理?
- 将mipmap样本三线性函数扩展为单个函数