RTR_Chapter_6 下

程序化纹理

  上文所提到的方法,都是给定一个纹理空间中的坐标位置,然后在图像中进行查找,从而获得纹理值。还有一种方法是对函数进行求值,然后作为对应位置上的纹理值,这就是程序化纹理(procedural texture)。

  在过去,程序化纹理通常运用于离线渲染中,在实时渲染中更加常见的则是图像纹理,这是因为现代GPU中的图像纹理硬件是非常高效的,可以在一秒钟内执行数十亿次的纹理访问操作。然而,目前的GPU架构正在向着更低的计算成本,以及更昂贵(相对)的存储访问发展,也就是说,存储访问和带宽限制越来越成为GPU的性能瓶颈。这些趋势使得程序化纹理在实时渲染中得到了更加广泛的应用。

  考虑到体积纹理的高昂存储成本,因此程序化的体积纹理,是过程化生成中一个特别有吸引力的应用。这样的纹理可以通过很多技术进行合成,其中最为常见的一种方法是,使用一个或者多个噪声函数来生成纹理值,如图6.24所示。这些噪声函数通常都是以连续2次幂的频率进行采样的,这被称为octave(八度)。每个octave都有一个权重,这个权重通常会随着频率的增加而下降,而这些加权样本的总和被称为湍流(turbulence)函数。

在这里插入图片描述

图6.24 两个使用的实时程序化纹理生成的体积纹理。左边的大理石是使用光线步进(ray marching)渲染的半透明体积纹理。右边的物体是使用复杂的程序化木材着色器生成的图像,并将其在“真实”环境(背景)中进行了渲染。

  由于计算噪声函数的成本较大,因此三维数组中的点通常都是预先计算好的,并使用这些点进行插值从而获得相应的纹理值。有许多方法可以通过使用颜色缓冲混合,来快速生成这些三维数组。Perlin提出了一种快速且实用的方法,来对这个噪声函数进行采样,并展示了一些实际用途。Olano提供了一种可以允许在存储纹理和执行计算之间进行权衡的噪声生成算法。McEwan等人实现了在着色器中计算经典噪声(classic noise)和单纯形噪声(simplex noise)的方法,该方法无需任何查找,并提供了完整的源代码。Parberry 使用动态规划,来将计算分摊到若干个像素上,从而加快了噪声的计算速度。Green 给出了一种更高质量的方法,它更适用于接近交互式帧率的应用程序,因为它在一次查找中使用了50个像素着色器指令。Perlin提出的原始噪声函数可以被进一步改进,Cook和DeRose 提出了另一种表示方法,称为小波噪声(wavelet noise),它避免了锯齿问题,同时只略微增加了计算成本。Liu等人通过使用各种噪声函数,来模拟不同的木材纹理和表面抛光效果。

  其他的程序化方法也是可行的。例如:通过测量从每个位置到一组“特征点”(分散在空间中)的距离,来构建蜂窝状纹理(cellular texture)。还可以用不同的方法对这些生成的最近距离进行映射,例如:改变颜色或者着色法线,从而生成看起来像细胞、石板、蜥蜴皮肤以及其他自然图案的纹理。Griffiths 讨论了如何高效地在GPU上找到最近邻居,并生成蜂窝状纹理的方法。

  另一种类型的程序化纹理是物理模拟或者其他交互过程的结果,例如水面波纹或者扩展裂缝。在这种情况下,程序化纹理可以根据动态条件的不同,有效地生产无限种变化。

  当生成一个程序化的二维纹理时,参数化问题(UV)可能要比创建纹理更加困难,因为在传统的纹理制作过程中,可以对UV拉伸或者UV接缝进行手动调整,而程序化纹理则不行。一种解决方案是直接在表面上合成纹理,从而完全避免参数化的问题;在复杂表面上执行这种操作,在技术上是具有挑战性的,这也是一个十分活跃的研究领域,详情请参阅Wei等人有关该领域的综述。

  对程序化纹理的抗锯齿处理,要比图像纹理的抗锯齿既困难和又容易。一方面,类似mipmap这样的预计算方法是不可行的,这会给程序员带来极大的负担。另一方面,在生成程序化纹理的时候,实际上已经知道了有关于纹理内容的“内部信息”,因此可以对其进行调整,从而避免锯齿现象。这对于叠加了多个噪声函数的程序化纹理尤其正确,因为每个噪声函数的频率都是已知的,因此可以主动丢弃那些会导致锯齿的频率,这实际上会使得计算成本变得更低。对于其他类型的程序化纹理而言,有着各种各样的抗锯齿技术。Dorn等人对过去的工作进行了讨论总结,并提出了一些重新构造纹理函数的过程,以避免过高的频率,即有限频宽(band-limited)的频率。

纹理动画

  应用于表面上的图像纹理也不一定是静态的。例如:视频源(video source)可以用作一种随帧变化的特殊纹理。

  纹理坐标也不一定是静态的。无论是在网格数据本身对纹理坐标进行修改,还是通过顶点着色器或者像素着色器中的函数,来对纹理坐标进行修改,应用程序设计人员都可以显式地改变帧与帧之间的纹理坐标。想象一下,现在有了一个已经建模好的瀑布模型,并且它已经被一个图像纹理化了,使得它看起来很像瀑布。假设纹理坐标 v v v是水流的方向,为了让水流动起来,必须从每一帧的坐标 v v v中减去一定的数值。纹理坐标的减法操作会使得纹理本身看起来正在向前移动。

  可以通过对纹理坐标应用变换矩阵来生成更加精细的效果。除了平移之外,它还允许其他的线性变换操作,例如缩放、旋转和剪切,图像扭曲(image warping)和变形转换(morphing transforms),以及广义投影等。通过在CPU或者着色器中应用变换函数,可以生成更复杂的效果。

  通过使用纹理混合(texture blending)技术,还可以实现其他的动画效果。例如:从一个大理石纹理出发,将其渐变为一个肉质纹理,从而使得雕像看起来像是活过来一样。

材质映射

  纹理的一个常见用途是对材质属性进行修改,从而影响着色方程的计算结果。现实世界中的物体通常都会具有不同的表面材质属性,为了模拟这样的物体,像素着色器可以从纹理中读取纹理值,并在计算着色方程之前,使用它们来修改材质的参数。纹理最常修改的参数就是表面是颜色,这种纹理通常被称为反照率颜色贴图(albedo color map)或者漫反射颜色贴图(diffuse color map)。但是,理论上任何参数都可以被纹理进行修改,例如:替换、相乘或者以其他方式等。例如在图6.25中的表面,应用了三种不同的纹理来替换常量值。

在这里插入图片描述

图6.25 金属砖块和灰浆材质。右边的纹理分别代表了表面颜色、粗糙度(越亮越粗糙)和凹凸高度(越亮起伏程度越大)。

  材质的纹理还有更加广泛的用途。纹理还可以用来控制像素着色器本身的流程以及函数功能,而不是简单地修改着色方程中的参数。具有不同着色方程和参数的多个材质,可以同时被应用于一个表面上,通过使用一个特殊纹理,来指定表面的哪些区域具有哪些材质,从而在每个区域上执行不同的着色代码。例如:在具有生锈区域的金属表面上,可以使用一个特殊贴图来指示生锈的位置,根据纹理查找的结果,来决定是执行生锈着色器还是执行闪亮的金属着色器。

  着色模型中的一些输入参数(例如表面颜色),与着色器的最终颜色输出具有线性关系。包含类似输入参数的纹理可以使用标准技术来进行过滤,从而避免瑕疵和锯齿。对于包含非线性着色输入参数的纹理而言,例如粗糙度或者凹凸贴图,则需要更多的注意和处理,来避免锯齿和走样的出现。使用将着色方程考虑在内的滤波技术,可以改善这类纹理的结果。

Alpha映射

  alpha值可以用于alpha混合或者alpha测试,它们能够实现许多效果,例如树叶渲染、爆炸效果和远处物体等。本小节将讨论如何使用带有alpha通道的纹理,以及这些方法的各种限制和对应的解决方案。

  一种与纹理相关的效果是贴花(decal)。例如:假设想在茶壶上放置一张花的图片,我们并不需要花的整个画面,只需要花所在的部分即可;对于那些不包含花的纹素,可以将它的alpha值设为0,使这些部分变得透明,这样它就没有任何效果了。因此,通过正确设置贴花纹理的alpha值,可以使用贴花纹理来替换对应的底层表面,或者将二者混合。通常,可以使用一个clamp转换函数与透明边框,将贴花的单个副本(相对于重复纹理)应用到表面上。图6.26展示了贴花实现的示意图。

在这里插入图片描述

图6.26 实现贴花的一种方法。首先使用场景来渲染一个帧缓冲,然后再渲染一个box,对于box内的所有顶点,贴花纹理会被投影到帧缓冲上。box中最左侧的纹素是完全透明的,因此它不会影响帧缓冲;box中的黄色纹素是不可见的,因为它会被投影到表面的隐藏部分。

  alpha值的另一个类似应用是制作镂空(cutout)效果。假设制作了一个灌木的贴花图像,并将其应用到场景中的一个矩形上。这个原理和贴花是相同的,不同之处在于,这个灌木图像并不是和底层表面对应的,而是会绘制在它背后的任何几何图形上。通过这种方式,可以仅仅使用单个矩形,便能够渲染出具有复杂轮廓的物体。

在这里插入图片描述

图6.27 左边是灌木纹理贴图及其对应的1 bit alpha通道贴图。右边是灌木贴图渲染在单个矩形上的效果;通过添加旋转90度的第二个矩形副本,可以创建一个廉价的三维灌木丛。

  在这个灌木的例子中,如果观察者围绕它进行旋转,这个视觉错觉就会露陷,因为这个灌木矩形实际上并没有厚度。一种解决方案是将这个灌木矩形复制一份,并沿着树干旋转90度,使得两个灌木矩形相互垂直。这两个矩形构成了一个廉价的三维灌木效果,它有时会被称为“交叉树”,从地面上进行观察的时候,这种视错觉是相当有效的,如图6.27所示。Pelzer 讨论了一个类似的配置方法,他使用了三个镂空纹理来表示草。在章节13中,将讨论一种称为广告牌(billboard)的方法,它仅使用单个矩形便可以实现这种渲染效果。如果观众移动到地面以上,那么这种视错觉也会露陷,因为在灌木上面可以看到两个明显切口,如图6.28所示。为了解决这个问题,可以使用不同的方式来添加更多的镂空纹理(例如切片、分支、层等)。

在这里插入图片描述

图6.28 从离开地面一点的地方来观察这个“交叉树”灌木,这种视错觉就消失了。

  将alpha贴图和纹理动画结合在一起,可以产生令人信服的特殊效果,例如闪烁的火炬,植物生长,爆炸和大气效果等。

  使用alpha贴图来渲染物体有几个可选的方式。alpha混合(章节5.5)允许透明度值为小数,从而实现了物体边缘的抗锯齿,以及部分透明物体的渲染。但是alpha混合需要在渲染不透明三角形之后,再去渲染透明的三角形,并且需要严格按照从后向前的顺序进行渲染。一个无法实现的例子是刚才提到的简单交叉树,它包含了两个镂空纹理,在这个例子中,没有任何一个渲染顺序是正确的,因为两个四边形相互交叉,每个四边形都位于另一个四边形的前面。而且,即使在理论上可以对其进行排序并得到正确的渲染顺序,这样做通常也是十分低效的,例如:一块土地上可能会有成千上万的草叶,这些草叶都是用镂空纹理进行表示的。每个网格物体都由多个单独的叶片所组成,因此显式地对每个叶片进行排序是非常不切实际的。

  在渲染的时候,有几种不同的方法可以改善这个问题。一种是使用alpha测试,即在像素着色器中丢弃alpha值低于给定阈值的片元,其做法如下:

i f ( t e x t u r e . a < a l p h a T h r e s h o l d ) d i s c a r d (6.9) \mathrm{if (texture.a < alphaThreshold) \quad discard} \tag{6.9} if(texture.a<alphaThreshold)discard(6.9)

  其中 t e x t u r e . a \mathrm{texture.a} texture.a代表了从纹理中检索到的对应alpha值,参数 a l p h a T h r e s h o l d \mathrm{alphaThreshold} alphaThreshold是用户提供的阈值,它将决定哪些片元会被丢弃。这个二元的可见性测试允许以任意顺序来渲染三角形,因为透明的片元都会被丢弃。通常希望对alpha值为 0.0 0.0 0.0的任何片元都执行这个操作,将完全透明的片元直接丢弃还有额外的好处,它节省了后续着色器处理和合并的计算成本,同时还可以避免将z-buffer中的像素错误地标记为可见。而对于镂空纹理而言,通常会将这个阈值设置为大于 0.0 0.0 0.0的值,比如 0.5 0.5 0.5或者更高。或者再进一步,直接忽略alpha值,不使用它们进行混合;这样做可以避免乱序所带来的瑕疵,但是结果的质量会很低,因为只有两个级别的透明度(完全不透明和完全透明)是可用的。另一种解决方案是对每个模型执行两个pass:第一个pass用于渲染实体(不透明)的镂空样本,同时写入z-buffer;另一个pass则用于渲染半透明的样本,此时并不会写入z-buffer。

  alpha测试还有另外两个问题,即过度放大和过度缩小,当alpha测试与mipmap一起使用的时候,如果不进行特殊处理,那么效果会很差,如图6.29顶部所示,树木的叶子会比预期变得更加透明。这可以用这样一个例子来进行解释:假设现在有一个具有四个alpha值的一维纹理,即 ( 0.0 , 1.0 , 1.0 , 0.0 ) (0.0,1.0,1.0,0.0) (0.0,1.0,1.0,0.0);而在平均之后,下一个mipmap层级所对应的纹素值会变为 ( 0.5 , 0.5 ) (0.5,0.5) (0.5,0.5),最高层级mipmap所对应的纹素值为 ( 0.5 ) (0.5) (0.5)。现在,假设现在使用 α t = 0.75 \alpha_t = 0.75 αt=0.75来作为alpha测试的阈值。当访问第0级的mipmap时,4个纹素中的2个可以通过alpha测试,不会被丢弃。但是,当访问剩下两个mipmap层级时,由于 0.5 < 0.75 0.5 < 0.75 0.5<0.75,因此所有纹素都会被丢弃。图6.30展示了另一个例子。

在这里插入图片描述

图6.29 上图:alpha测试与mipmap没有进行任何的修正。下图:根据覆盖率重新调整alpha值的alpha测试结果。

  Castano 提出了一个作用于mipmap创建过程中的简单解决方案,它工作得很好。对于第 k k k个mipmap层级,覆盖范围 c k c_k ck的定义如下:

c k = 1 n k ∑ i ( α ( k , i ) > α t ) (6.10) c_{k}=\frac{1}{n_{k}} \sum_{i}\left(\alpha(k, i)>\alpha_{t}\right) \tag{6.10} ck=nk1i(α(k,i)>αt)(6.10)

  其中 n k n_k nk为第 k k k级mipmap中的纹素个数, α ( k , i ) \alpha(k, i) α(k,i)为第 k k k级mipmap在像素 i i i处的alpha值, α t \alpha_{t} αt为方程6.9中用户提供的alpha阈值。这里假设 α ( k , i ) > α t \alpha(k, i)>\alpha_{t} α(k,i)>αt的结果为1或者0。需要注意的是, k = 0 k=0 k=0代表了最低层级的mipmap,即原始图像。对于每个mipmap层级,都会找到一个新的mipmap阈值 α k \alpha_{k} αk,而不是一直使用原始阈值 α t \alpha_{t} αt,这样做可以使得 c k c_{k} ck等于 c 0 c_{0} c0(或者尽可能得接近),这个过程这可以使用二分查找来实现。最后,使用 α t / α k \alpha_{t} / \alpha_{k} αt/αk来对第 k k k级mipmap中所有纹素的alpha值进行缩放,如图6.29的底部所示,NVIDIA的纹理工具也支持这个方法。Golus 给出了一个该方法的变体,该方法并没有对mipmap进行修改,而是随着mipmap层级的增加,在着色器中对alpha进行放大。

在这里插入图片描述

图6.30 第一行是叶子图案不同mipmap层级的结果,更高的mipmap层级放大了叶子的可见性。在第二行中,mipmap进行了阈值为0.5的alpha测试处理,这个过程展示了随着物体与相机距离的增加,其在屏幕上占据像素数量越来越少

  Wyman和McGuire提出了一种不同的解决方案,其中方程6.9中的代码在理论上被替换为:

i f ( t e x t u r e . a < r a n d o m ( ) ) d i s c a r d (6.11) \mathrm{ if (texture.a < random()) \quad discard } \tag{6.11} if(texture.a<random())discard(6.11)

  随机函数在 [ 0 , 1 ] [0,1] [0,1]中返回一个均匀的值(0-1均匀分布),这意味着平均而言,这个操作会产生正确的结果。例如:如果检索到的纹理alpha值为 0.3 0.3 0.3,那么意味着该片元将会有30%的概率被丢弃。这是随机透明的一种形式,每个像素仅包含一个样本。在实践中,会将这个随机函数替换为哈希函数,从而避免时空上(temporal,spatial)的高频噪声:

float  hash ⁡ 2 D ( x , y ) { return ⁡ fract ⁡ ( 1.0 e 4 ∗ sin ⁡ ( 17.0 ∗ x + 0.1 ∗ y ) ∗ ( 0.1 + abs ⁡ ( sin ⁡ ( 13.0 ∗ y + x ) ) ) ) ; } (6.12) \begin{aligned} \text { float } & \operatorname{hash} 2 D(x, y)\{ \\&\operatorname{return} \enspace \operatorname{fract}(1.0 e 4 * \sin (17.0 * x+0.1 * y) \enspace * \\& (0.1+\operatorname{abs}(\sin (13.0 * y+x)))) ; \}\end{aligned} \tag{6.12}  float hash2D(x,y){returnfract(1.0e4sin(17.0x+0.1y)(0.1+abs(sin(13.0y+x))));}(6.12)

  通过对上述函数进行嵌套调用,可以创建一个三维哈希,它返回一个位于 [ 0 , 1 ) [0,1) [0,1)内的数字,即:

f l o a t h a s h 3 D ( x , y , z ) { r e t u r n h a s h 2 D ( h a s h 2 D ( x , y ) , z ) ; } \mathrm{ float \enspace hash3D(x,y,z)\enspace \{ \enspace return \enspace hash2D(hash2D(x,y),z); \} } floathash3D(x,y,z){returnhash2D(hash2D(x,y),z);}

  哈希函数的输入是,物体局部空间坐标除以该坐标相对于屏幕空间( x x x方向和 y y y方向)的最大导数,然后再对其进行clamp操作。需要进一步注意来获得在 z z z方向上的稳定性,该方法最好与时域抗锯齿技术相结合。这个方法随着距离的增加而渐隐,在近距离时根本不会看到任何随机效果。该方法的优点在于,每个片元的结果在平均上都是正确的。Castano的方法则是为每个mipmap层级都创建一个修正过的阈值 α k \alpha_{k} αk;然而,这个值可能会随着mipmap层级的不同而发生变化,这可能会降低最终的质量,并需要艺术家进行手动调整。

  alpha测试会在放大情况下显示出波纹瑕疵(ripple artifact),这可以通过将alpha贴图预先计算为一个距离场来避免。

  Alpha To Coverage,以及类似的透明度自适应抗锯齿(transparency adaptive antialiasing),这些方法会将片元的透明度值转换为像素内覆盖的样本数。这个想法类似于章节5中所介绍的点阵剔除半透明方法(screen-door transparency),只不过这里的方法作用于亚像素级别。假设每个像素有四个样本位置,现在有一个片元覆盖了一个像素,但是由于镂空纹理的影响,其透明度为25%(即75%不透明)。Alpha To Coverage模式使得该片元变得完全不透明,尽管它只覆盖了四个样本中的三个。这个模式对镂空纹理而言十分有用,例如重叠的草叶。由于绘制的每个样本都是完全不透明的,因此最近的叶子会将以相同的方式,在其边缘处遮挡后面的物体。由于此时关闭了alpha混合,因此不需要进行排序,就可以正确地混合半透明的边缘像素。

  Alpha To Coverage对于alpha测试的抗锯齿而言是非常好的,但是在alpha混合的时候,可能会出现瑕疵。例如:两个具有相同alpha覆盖百分比的alpha混合片元,将会使用相同的亚像素模式,这意味着一个片元将会完全覆盖另一个片元,而不是与之混合。Golus讨论了使用着色器指令$\text {fwidth()} $来对边缘进行锐化处理,如图6.31所示。

在这里插入图片描述

图6.31 使用不同技术进行渲染的叶子纹理,在叶子的边缘部分使用了alpha覆盖。从左到右分别是:alpha测试,alpha混合,Alpha To Coverage,Alpha To Coverage+边缘锐化。

  对于任何alpha映射的使用,理解双线性插值对颜色值的影响原理是很重要的。想象两个相邻的纹素: r g b α = ( 255 , 0 , 0 , 255 ) rgbα =(255,0,0,255) rgbα=(255,0,0,255)代表了不透明红色,其相邻的 r g b α = ( 0 , 0 , 0 , 2 ) rgbα =(0,0,0,2) rgbα=(0,0,0,2)代表了几乎完全透明的黑色。那么两个纹素中间位置上的 r g b α rgbα rgbα值是多少呢?最简单的插值方法可以获得 ( 127 , 0 , 0 , 128 ) (127,0,0,128) (127,0,0,128),这个 r g b rgb rgb值是一个“较暗”的红色。然而,这个结果实际上并没有变暗,它是一个预乘了alpha的全红色。如果要对alpha进行插值的话,为了得到正确的插值结果,需要确保被插值的颜色在插值之前就已经预乘了alpha。例如:假设刚才那个几乎透明的邻居纹素被设置为 r g b α = ( 0 , 255 , 0 , 2 ) rgbα =(0,255,0,2) rgbα=(0,255,0,2),即几乎完全透明的绿色。这个颜色没有预乘alpha,在插值时得到的结果是 ( 127 , 127 , 0 , 128 ) (127,127,0,128) (127,127,0,128),这个几乎完全透明的绿色会把结果变成(预乘了alpha)黄色。这个相邻纹素的alpha预乘版本是 ( 0 , 2 , 0 , 2 ) (0,2,0,2) (0,2,0,2),它会给出正确的预乘颜色结果,即 ( 127 , 1 , 0 , 128 ) (127,1,0,128) (127,1,0,128)。这个结果才是有意义的,因为生成的颜色以红色为主,还有一抹很难察觉的绿色。

  如果忽略双线性插值给出的预乘结果,则会导致贴花物体和镂空物体周围出现黑色的边缘。在上文的例子中,插值产生的“暗”红色结果会被渲染管线的后续阶段视为未相乘alpha的颜色,这会使得边缘变成黑色。即使使用了alpha测试,也会看到这种效果。最好的策略就是在进行双线性插值之前,就进行alpha预乘。WebGL API支持这个功能,因为合成对于网页而言十分重要。然而,双线性插值通常是由GPU执行的,而在执行这个操作之前,着色器并不能对纹素值进行操作。图片也无法以PNG等文件格式进行预乘,因为这样做会失去色彩精度。这两个因素结合在一起,会导致在使用alpha映射时,产生默认的黑色边缘。一个常见的解决方法是对镂空纹理进行预处理,将透明的“黑色”纹素涂上来自附近不透明纹素的颜色。所有透明区域通常都需要使用这种方式来重新进行绘制,这个过程可以手动完成或者自动完成,这样mipmap的各个层级也可以避免边缘问题。同样值得注意的是,在使用alpha值来生成mipmap的时候,也应当使用alpha预乘的颜色值。

凹凸映射

  本小节介绍了一大类小尺度细节(small-scale detail)的表现技术,我们统称为凹凸映射(bump mapping)。所有的这些方法,通常都是通过逐像素地修改着色例程来实现的;它们在没有添加任何额外几何形状的前提下,提供了比纹理映射更加立体的外观。

  物体表面上的细节可以分为三种尺度:覆盖大量像素的宏观特征(macro-feature)、覆盖几个像素的细观特征(meso-feature,或者叫做介观)以及尺寸远大小于一个像素的微观特征(micro-feature)。这些类别并不是完全固定的,因为在动画或者交互的过程中,观察者可能会在许多不同的尺度上来观察同一个物体。

  宏观几何(macrogeometry)由顶点、三角形或者其他几何图元构成。当我们创建一个三维角色时,四肢和头部通常都是在宏观尺度上进行建模的。而微观几何则会被封装在着色模型中,通常在像素着色器中进行实现,并使用纹理贴图作为着色方程的输入参数。着色模型可以用来模拟表面微观几何与光线之间的相互作用,例如:表面有光泽的物体在微观上是光滑的,而漫反射的表面在微观上则是粗糙的。角色的皮肤和衣服看起来具有不同的材质,这是因为它们使用了不同的着色器,或者至少在这些着色器中使用了不同的参数。

  细观几何(meso-geometry,介观几何)描述了宏观尺度和微观尺度之间的一切特征。它包含的细节过于复杂,以致于无法使用单个三角形来进行有效地渲染;但是这个尺度对于观察者而言已经足够大了,可以在几个像素上分辨出表面曲率的细微变化。例如:角色脸上的皱纹、肌肉组织的细节、衣服上的褶皱和接缝,都属于细观范畴。一类统称为凹凸映射技术的方法,通常用于建模这样的细观尺度,它们会在像素级别上调整着色参数,使得观众能感知到基础几何形状之外的微小扰动,同时使得基础几何形状保持平坦。不同类型凹凸映射技术的主要区别在于它们表示细节特征的具体方式。细观几何的变化因素包括现实主义水平和细节特征的复杂程度,例如:数字艺术家通常会在模型中雕刻细节,然后使用软件将这些几何元素转换为一个或多个纹理贴图,例如凹凸贴图以及可能的缝隙暗化(crevice-darkening)纹理。

  Blinn在1978年提出了在纹理中编码细观尺度细节的想法。他观察到:如果在着色过程中使用稍微扰动的表面法线来代替真实的表面法线,这样表面看起来就像是具有了小尺度细节一样。他将描述表面法线扰动的数据存储在数组中。

  其中最关键的想法是,不使用纹理贴图来改变光照方程中的颜色参数,而是使用纹理贴图来修改表面的法线数据。表面原本的几何法线保持不变;只是修改了光照方程中所使用的法线;这个操作并没有物理意义上的等效操作,虽然对表面法线进行一些修改,但是表面本身在几何意义上仍然是保持光滑的。就像是每个顶点上都有一个法线会给人一种错觉,即三角形之间的表面全都是光滑的;逐像素地修改法线,会改变对三角形表面本身的感知,但是并不会真正地修改这个三角形的几何形状。

  对于凹凸映射而言,表面法线必须相对于某个参照系改变自身的方向。为了实现这个操作,每个顶点都会存储一个切线坐标系(tangent frame),它也称为切线空间基底(tangent-space basis)。这个参考系用于将光线转换到表面着色点的局部空间中(反之亦可),从而计算扰动法线的效果。对于一个应用了法线贴图的多边形表面,除了顶点法线之外,还存储了所谓的切线向量(tangent vector)和副切线向量(bitangent vector)。其中副切线有时也被错误地称为副法线(binormal vector)。

  切线向量和副切线向量代表了物体空间坐标系中,法线贴图本身的坐标轴,因为这里的目标是将光线转换到相对于法线贴图的局部空间中,详见图6.32。

在这里插入图片描述

图6.32 上图展示了两个球面化的三角形,并展示了其切线坐标系。诸如球体和圆环面这样的形状,有一个很自然的切线空间基底,正如圆环面上的经纬度线所展示的那样。

  这三个向量,法向量 n \mathbf{n} n,切线向量 t \mathbf{t} t,和副切线向量 b \mathbf{b} b,构成一个基底矩阵:

( t x t y t z 0 b x b y b z 0 n x n y n z 0 0 0 0 1 ) (6.13) \left(\begin{array}{cccc}t_{x} & t_{y} & t_{z} & 0 \\ b_{x} & b_{y} & b_{z} & 0 \\ n_{x} & n_{y} & n_{z} & 0 \\ 0 & 0 & 0 & 1\end{array}\right) \tag{6.13} txbxnx0tybyny0tzbznz00001 (6.13)

  这个矩阵有时会缩写为 T B N TBN TBN,它用于将光线方向(对于给定顶点)从世界空间转换到切线空间中。这些向量并不需要严格的相互垂直,因为法线贴图本身就可能会被拉伸扭曲,以适应表面。但是使用非正交基会引入纹理的偏移,这也意味着可能需要更多的存储空间,同时还可能对性能产生影响,例如无法通过简单的转置操作来求取逆矩阵。一种节省存储空间的方法是,只存储顶点的切线和副切线,然后使用它们的叉乘来计算法线;然而,这种方法只有在 T B N TBN TBN矩阵的手性(handedness)总是相同的情况下才有效。但是实践中的很多模型都是对称的,例如:飞机、人体、文件柜以及其他对称物体等。由于纹理会占据大量的存储空间,为了降低对称模型的纹理存储空间,因此它们通常会被镜像到对称模型上;即物体上的纹理只有一半会被存储下来,然后再使用纹理映射,将其应用在模型对称的两侧上。在这种情况下,切空间的手性在对称的两侧是不同的,不满足手性相同的假设。当然还可以在每个顶点上存储额外的bit信息来指示手性,这样也可以避免存储法线。如果存在这个额外的手性bit的话,它会被用来修正切线和副切线的叉乘结果(取反),从而生成正确的法线。如果切线坐标系是正交的,也可以将基底存储为四元数,这样既提高了存储效率,又可以节省每个像素上的一些计算量;这样做可能会稍微损失一些质量,尽管在实践中这种质量损失是很难察觉到的。

  这种切线空间的思想对于其他算法而言也很重要。虽然许多着色方程只依赖于表面的法线方向,但是拉丝铝或者丝绒等材质,也需要知道观察者与光线相对于表面的相对方向,而切线坐标系则可以用来定义表面上的材质朝向。Lengyel 和Mittring 的文章对这一领域进行了广泛的介绍。Schuler提出了一种在像素着色器中,动态计算切线空间基底的方法,从而无需逐顶点地存储预先计算好的切线坐标系。Mikkelsen 对该技术进行了改进,并推导出了一种不需要任何参数化的方法,而是使用表面位置的导数和高度场的导数来计算扰动法线。然而,与使用标准的切线空间映射相比,这些技术可能会导致更少的显示细节,并且可能会产生与美术工作流相关的问题。

Blinn 方法

  Blinn原始的凹凸映射方法是,在纹理的每个纹素上存储两个带符号的值, b u b_u bu b v b_v bv。这两个值代表了沿图像 u u u轴和 v v v轴法线的改变量。也就是说,这个纹理值(通常是双线性插值而来的)用于对两个垂直于法线的向量进行缩放,然后将这两个向量加到法线上,从而来改变法线的方向。 b u b_u bu b v b_v bv这两个值描述了表面在该点处的朝向,如图6.33所示。这种类型的凹凸贴图纹理通常被称为偏移矢量凹凸贴图(offset vector bump map)或者偏移贴图(offset map)。

在这里插入图片描述

图6.33 左图中,在凹凸纹理中检索到了对应的值( b u , b v b_u, b_v bu,bv),法线 n \mathbf{n} n会在 u u u v v v两个方向上被这个值进行修改,最终得到 n ′ \mathbf{n}^{\prime} n(这个新法线是非归一化的)。右图展示了高度场及其对着色法线的影响,这些法线可以在不同高度之间进行插值,从而获得更加平滑的效果。

  另一种用于表示凸起的方法是,使用高度场来修改表面法线的方向。每个单色的纹理值都代表了一个高度:在纹理中,白色代表较高的区域,黑色则代表较低的区域(反之亦然),如图6.34所示。这是首次创建或者扫描凹凸贴图时所用的格式,它也是由Blinn在1978年提出的。这个由纹理值定义的高度场,用于推导出 u u u v v v的带符号值,类似于第一种方法中使用的值。具体的推导方式是通过相邻列之间的差来得到 u u u的斜率,以及相邻行之间的差来得到 v v v的斜率。一种变体是使用Sobel滤波器,它会为直接相邻的纹素赋予更大的权重。

在这里插入图片描述

图6.34 波浪高度场凹凸图像及其在球体上的应用效果。

法线映射

  凹凸贴图的一个常用方法是直接存储法线贴图(normal map),其算法和结果与Blinn的方法在数学上是等价的;二者的不同之处在于存储格式以及像素着色器中的计算操作。

  法线贴图将 ( x , y , z ) (x, y, z) (x,y,z)映射到 [ − 1 , 1 ] [−1,1] [1,1]中,例如:对于8 bit纹理而言, x x x轴上的值 0 0 0表示 − 1.0 −1.0 1.0,值 255 255 255则表示 1.0 1.0 1.0。图6.35给出了一个例子: [ 128 , 128 , 255 ] [128,128,255] [128,128,255]是一个浅蓝色,具有该颜色值的平面,其法线为 [ 0 , 0 , 1 ] [0,0,1] [0,0,1]

在这里插入图片描述

图6.35 使用法线贴图进行凹凸映射。图像的每个颜色通道,实际上都是表面法线坐标的一个分量。红色通道代表了法线在 x x x方向上的偏移;法线贴图的颜色越红,代表了修正后的法线越指向右。绿色通道是 y y y方向上的偏移,蓝色通道是 z z z方向上的偏移。右边是使用法线贴图生成的图像,请注意立方体顶部的扁平外观。

  这种法线贴图的表示方法,最初是作为世界空间法线贴图引入的,现在在实践中已经很少使用了。对于这种类型的映射而言,扰动方法是直截了当的:在每个像素位置上,从法线贴图中直接检索对应的法线,并使用这个法线方向和光线方向来计算表面上该位置的着色结果。法线贴图也可以在物体的局部坐标系中进行定义,这样模型在旋转之后,法线仍然是有效的。然而,世界空间和模型空间的表示方法,都将法线贴图与特定方向的特定几何形状相绑定,这大大限制了法线贴图的可复用性。

  相反,扰动法线通常会在切线空间中进行存储和检索,即相对于表面本身的空间。这允许表面发生形变,以及在最大程度上保证了法线贴图的可复用性。切线空间中的法线映射也可以很好地进行压缩,因为 z z z分量(与未受干扰的表面法线相对齐的分量)的符号通常可以假设为正。

  法线映射可以很好地增强物体表面的真实感,如图6.36所示。

在这里插入图片描述

图6.36 一个在游戏场景中使用法线贴图进行凹凸映射的例子。左上角:没有应用右侧的两个法线贴图。左下角:应用了法线贴图。右边:法线贴图。

  与颜色纹理的过滤相比,对法线贴图的过滤是一个困难的问题。一般来说,法线与着色结果之间的关系并不是线性的,因此标准的滤波方法可能会产生令人讨厌的瑕疵。想象正看着由闪闪发光的白色大理石砌成的楼梯。在某些观察角度下,楼梯的顶部或者侧面会反射出明亮的高光。然而,楼梯的平均法线是45度角,当我们从较远处观察这个楼梯时,它的高光将会出现在完全不同的观察角度下,与原来的楼梯截然相反。当带有锐利高光的凹凸贴图没有被正确的过滤时,可能会出现一些分散观察者注意力的闪光效果,这些高光部分的出现和消失,取决于光线镜面反射的着色点法线落在哪里。

  Lambertian材质表面是一种特殊情况,法线贴图对着色结果的影响几乎是线性的;Lambertian的着色操作几乎完全是一个点乘,这是一个线性运算。取一组法线的平均值,并对平均后法线进行点乘运算;与取单个法线的点乘结果,然后再进行平均是一样的,其数学形式如下:

l ⋅ ( ∑ j = 1 n n j n ) = ∑ j = 1 n ( l ⋅ n j ) n . (6.14) \mathbf{l} \cdot\left(\frac{\sum_{j=1}^{n} \mathbf{n}_{j}}{n}\right)=\frac{\sum_{j=1}^{n}\left(\mathbf{l} \cdot \mathbf{n}_{j}\right)}{n}. \tag{6.14} l(nj=1nnj)=nj=1n(lnj).(6.14)

  请注意,这个平均法线向量在使用之前并没有进行归一化处理。方程6.14表明,标准的滤波操作和mipmap几乎可以对Lambertian表面产生正确的结果;但是这个结果实际上并不是完全正确的,因为Lambertian着色方程其实并不是一个简单的点乘,而是一个clamp过的点乘,即 max ⁡ ( l ⋅ n , 0 ) \max (\mathbf{l} \cdot \mathbf{n}, 0) max(ln,0),这个clamp操作使得点积不再线性。如果光线以掠射角度打到表面上,这会使得表面变得过暗,但是在实践中,这通常并不会令人反感。还需要注意的是,一些通常用于法线贴图的纹理压缩方法(例如从另外两个分量中重建 z z z分量)并不支持非单位长度的法线,因此使用非归一化的法线贴图可能会造成压缩困难。

  在非Lambertian表面的情况下,需要将着色方程的输入参数作为一个整体来进行过滤,而不是单独对法线贴图进行过滤,这样可能会产生更好的效果。

  最后,还可以从高度贴图 h ( x , y ) h(x, y) h(x,y)中推导出法线贴图,具体的做法如下:首先,使用中心差分来计算 x x x y y y方向上梯度的近似值:

h x ( x , y ) = h ( x + 1 , y ) − h ( x − 1 , y ) 2 , h y ( x , y ) = h ( x , y + 1 ) − h ( x , y − 1 ) 2 . (6.15) h_{x}(x, y)=\frac{h(x+1, y)-h(x-1, y)}{2}, \\[2mm] h_{y}(x, y)=\frac{h(x, y+1)-h(x, y-1)}{2}. \tag{6.15} hx(x,y)=2h(x+1,y)h(x1,y),hy(x,y)=2h(x,y+1)h(x,y1).(6.15)

  则在纹素 ( x , y ) (x, y) (x,y)处的非归一化法线为:

n ( x , y ) = ( − h x ( x , y ) , − h y ( x , y ) , 1 ) (6.16) \mathbf{n}(x, y)=\left(-h_{x}(x, y),-h_{y}(x, y), 1\right) \tag{6.16} n(x,y)=(hx(x,y),hy(x,y),1)(6.16)

  在进行实际计算的时候需要格外注意纹理的边界。

  地平线映射(horizon mapping)可以用来进一步增强法线贴图,它可以让凸起的部分在其表面上投射阴影。这是通过预先计算额外的纹理来实现的,每个纹理都与表面平面的方向相关联,在每个纹素中存储该方向上的地平线角度。

视察映射

  凹凸映射和法线映射存在的一个问题是,表面上凹凸的位置永远不会随着视角的变化而变化,也不会发生互相遮挡的情况。例如:如果以一个掠射视角,沿着真正的砖墙进行观察,从某个角度开始就看不到砖与砖之间的砂浆了,这是因为砂浆相对于砖块而言是凹下去的。墙壁的凹凸贴图永远无法表现这种类型的遮挡关系,因为它仅仅是改变了表面的法线。想要实现这种表面自遮挡效果,最好是让这些凸起对表面上每个像素渲染的位置产生实际影响。

  视差映射(parallax mapping)的想法是由Kaneko 于2001年提出的,并由Welsh 进行了改进和推广。视差(parallax)这个概念指的是,当观察者的位置发送移动时,物体的位置也会相对发生移动;当观察者移动时,凸起部分应当看起来具有高度感。视差映射的核心思想是通过检查可见物体的高度,对像素中应当看到的内容进行有根据的猜测。

在这里插入图片描述

图6.37 左边是视差映射的目标:根据观察向量穿过高度场的位置,找到表面上被观察到的实际位置。视差映射通过获取矩形位置上的高度,并使用它来找到新的位置 p a d j \mathbf{p}_{adj} padj来对 p i d e a l \mathbf{p}_{ideal} pideal进行一阶近似。

  对于视差映射而言,表面的凸起会被存储在一个高度场纹理中。当观察表面上的某个像素时,将会检索该位置对应的高度场值,并将其用于对纹理坐标进行移动,从而对表面上的不同部分进行检索。具体移动多少距离,取决于检索到的高度值以及眼睛到表面上该点的角度,这个过程如图6.37所示。这个高度场值要么存储在一个单独的纹理中,要么被打包在其他纹理未使用的颜色通道或者alpha通道中(在打包不相关纹理的时候必须十分小心,因为这可能会对压缩质量产生负面影响)。在用于移动坐标之前,高度场的值还会被缩放和偏移:缩放的大小取决于高度场在地表之上(或者地表之下)延伸的高度;偏移取决于不发生变化的“海平面”高度。给定纹理坐标位置 p \mathbf{p} p,调整后的高度场高度 h h h,以及归一化的观察向量 v \mathbf{v} v,并且高度值为 v z v_z vz,水平分量为 v x y \mathbf{v}_{xy} vxy,那么经视差调整后的新纹理坐标 p a d j \mathbf{p}_{adj} padj为:

p a d j = p + h ⋅ v x y v z (6.17) \mathbf{p}_{\mathrm{adj}}=\mathbf{p}+\frac{h \cdot \mathbf{v}_{x y}}{v_{z}} \tag{6.17} padj=p+vzhvxy(6.17)

  请注意,与大多数着色方程不同,这里执行计算的空间坐标系是很重要的,因为观察向量需要位于切线空间中。

  虽然这只是一个很简单的近似,但是如果凸起高度变化得相对缓慢时,这种移动近似在实践中的效果相当好。相邻纹素大概率具有相同的高度,因此使用原始位置的高度来对新位置的高度进行估计,这个想法是有一定道理的。然而,这种方法在掠射视角下会失效,因为当观察向量近乎平行于表面时,一个很小的高度变化,可能就会导致一个相当大的纹理坐标偏移,此时的近似会失效,因为检索到的新位置与原始表面位置的高度相关性很小,或者是根本没有高度相关性。

  为了改善这个问题,Welsh 引入了偏移限制(offset limit)的概念,这样做的目的是对移动量进行限制,使其永远不会大于检索到的高度值,其数学形式为:

p adj  ′ = p + h ⋅ v x y (6.18) \mathbf{p}_{\text {adj }}^{\prime}=\mathbf{p}+h \cdot \mathbf{v}_{x y} \tag{6.18} padj =p+hvxy(6.18)

  请注意,这个方程的计算速度要比原来的快。这种方法的几何解释是:该位置的高度定义了一个半径,超过这个半径位置就无法进行移动,如图6.38所示。

在这里插入图片描述

图6.38 在视差偏移限制中,偏移量的最大值就是原始位置的高度,例如左图中的虚线圆弧。左图中的灰色偏移代表了原始的偏移结果,而黑色偏移代表了限制偏移后的结果。右图则是使用这种技术渲染出来的砖墙。

  在垂直于表面的观察角度上,由于 v z v_z vz(观察向量的 z z z分量)接近于1,因此这个方程几乎与原始方程完全相同。而在掠射观察角度上,偏移量的影响是有限的。从视觉上看,这减小了在掠射观察角度下的凹凸程度,但是相比于对视差纹理进行随机采样(偏移量很大,无法进行控制)要好得多。随着观察视角的变化,还会存在纹理游动(texture swimming)的问题;对于立体渲染而言,观察者会同时感知两个视点,这要求必须提供一致的深度暗示。即使有着这些缺点,但是具有偏移限制的视差映射,只需要花费一些额外的像素着色器指令即可实现,并且相较于基本的法线映射而言,它可以大幅改善图像的质量。Shishkovtsov 通过在凹凸贴图的法线方向上,对估计位置进行移动,从而改善视差遮挡的阴影效果。

视察遮挡映射

  凹凸映射并不会基于高度场来对纹理坐标进行修改,它仅仅是改变了该位置上的着色法线。视差映射提供了一个简单的近似高度场效果,它假设一个像素的高度与其邻居的高度大致相同,但是这个假设很快就会被打破。视差映射所产生的凸起并不会相互遮挡,也不会产生阴影。真正想要的是在像素处可见的内容,即观察向量第一次与高度场相交的地方。

  为了能够更好地解决这个问题,一些研究人员提出沿着观察向量进行光线步进(ray marching),直到找到与表面高度场的第一个交点(或者近似交点)。这项工作可以在像素着色器中完成,可以通过访问纹理来检索到所需的高度数据。将这些方法的研究归类为视差映射技术的一个子集,这些技术以这样或者那样的方式,来利用光线步进解决问题。

在这里插入图片描述

<a name="图6.39“>图6.39 绿色的观察射线被投影到表面上,并以一定的间隔进行采样(紫色点),在每个采样点上,会检索对应位置的高度值。该算法的目标是,找到观察射线与近似弯曲高度场(黑色线段)的第一个交点。

  这类算法被称为视差遮挡映射(parallax occlusion mapping,POM)或者浮雕映射(relief mapping)等。其关键思想是:首先沿着观察方向的投影向量,对固定数量的高度场纹理样本进行测试。在掠射观察角度下,通常会生成数量更多的样本,以便不会遗漏这个最近交点。会对沿着光线方向的每个三维样本位置进行检索,并将其转换到纹理空间中进行处理,从而确定该位置是高于该点的高度场,还是低于该点的高度场。一旦找到了一个低于高度场的样本,就使用这个低于高度场的样本,与前一个高于高度差的样本,来找到一个交点位置,如图6.39所示。然后使用额外的法线贴图、颜色贴图以及任何其他纹理,来对该位置进行着色。使用多层高度场,可以用于生产悬垂结构(overhang)、独立的重叠表面和双面浮雕等效果。高度场追踪方法也可以用来让凹凸不平的表面在自身上投射阴影,包括硬阴影和软阴影,如图6.40所示。

在这里插入图片描述

图6.40 没有使用光线步进的视差映射(左)与使用光线步进的视差映射(右)之间的对比。在没有使用光线步进的时候,立方体的顶部会变得很平坦(因为顶部处于掠射观察视角)。使用光线行进也可以产生自阴影效果。

  关于这个话题,有着大量的相关文献,虽然所有的这些方法都是沿着一条光线进行步进的,但是它们之间也有一些不同之处。有些方法可以直接使用简单的纹理来检索高度数据,但是也可以使用更加高级的数据结构,以及更加高级的求根方法(寻找交点)。有些技术还可能涉及到着色器丢弃像素或者写入深度缓冲区的操作,这些额外的操作可能会对性能产生影响。下面对大量方法进行了总结,但是记住,随着GPU的不断发展,最好的方法也会不断发展。这种“最佳”方法取决于生成的效果,以及光线步进过程中所需的步进次数。

  确定两个均匀间隔样本之间的交点,实际上是一个求根问题。实际上,在实践中,高度场更多地会被视为深度场,每个纹素的矩形平面代表了改表面的最大高度;这样平面上的初始点会位于高度场之上。在找到了位于高度场表面上方的最后一个点,以及位于表面下方的第一点之后,Tatarchuk 使用了一次迭代的割线法(secant method)来找到近似解。Policarpo等人在这两个点之间,使用二分查找(binary search)来寻找最近的交点。Risser等人使用迭代的割线法来加快收敛速度,这里涉及一个权衡问题:固定间隔的采样可以通过并行完成;迭代方法虽然在整体上会进行较少的纹理访问操作,但是它在执行的过程中必须等待结果的返回,并执行效率较低的依赖纹理读取。暴力求解方法(brute-force methods)总体上表现良好。

  对于高度场进行足够多的采样是很重要的。McGuire和McGuire 建议对mipmap查找进行偏移处理,并使用各向异性的mipmap来确保对高频高度场(例如尖刺或者头发)的正确采样。也可以用比法线贴图更高的分辨率来存储高度场纹理。一些渲染系统甚至不会去存储法线贴图,它们更喜欢使用交叉滤波器(cross filter),来从高度场中动态生成法线信息,方程16.1展示了这个方法。

  还有一种提高性能和采样精度的方法,即不以固定的间隔来对高度场进行初始采样,而是尝试跳过中间的空白区域,即在光线步进的过程中,动态调整下一次步进的步长。Donnelly 将高度场预处理成一组体素,在每个体素中存储它距离高度场表面的距离,在这种方式下,光线在步进过程中,可以快速跳过中间的空白区域,其代价是增加了每个高度场的存储空间。Wang等人使用了五维位移映射(five-dimensional displacement mapping)的方案,来记录从各个方向和各个位置到地面的距离,这种方案支持更加复杂的表面、自阴影以及其他效果,其代价是内存开销会很大。Mehra和Kumar 使用了定向距离图(directional distance map)来达到类似的目的。Dummer 提出了锥形步进映射(cone step mapping)的思想,Policarpo和Oliveira 对其进行了改进。这里的核心思想是,为每个高度场位置存储一个圆锥半径(cone radius),这个半径定义了在射线方向上,与高度场最多只有一个交点的区间。尽管这个方法需要依赖纹理读取,但是它允许沿着射线方向进行快速跳跃,并且不会错过任何可能的交点;这种方法的另一个缺点是,它需要进行预计算来生成锥形步进图,这使得该方法无法用于动态变化的高度场。Schroders和Gulik 提出了四叉树浮雕映射(quadtree relief mapping)方法,这是一种在遍历过程中跳过空间的分层方法。Tevs等人使用了“最大化mipmap”来支持动态步长,同时最小化了预计算成本,这里的“最大化mipmap”是指,在生成mipmap的时候,并不是取上一级像素颜色的平均值,而是取其中的最大值来作为下一级mipmap的值(译者注:这种结构类似于Hi-z)。Drobot 还使用了存储在mipmap中的类四叉树结构,来遍历进行加速,并提出了一种在不同高度场之间进行混合的方法,它允许一种地形类型转换为另一种地形类型。

在这里插入图片描述

图6.41 法线映射和浮雕映射。法线映射不会产生自遮挡现象;浮雕映射在具有重复纹理的轮廓边缘上会出现问题,因为纹素矩形更像是高度场的视图,而不是真正的边界定义。

  上述所有方法都存在一个问题,即这种视差错觉会在物体的轮廓边缘处消失,转而会显示出原始表面的平滑轮廓,如图6.41所示。这个问题的关键在于,被渲染的三角形仅仅定义了哪些像素应当由像素着色器进行计算,而不是表面上的实际位置,因此位于三角形之外的像素都不会受到视差映射的影响。此外,对于曲面而言,视察映射的轮廓问题将会变得更加复杂。Oliveira和Policarpo 描述并开发了一种使用二次轮廓近似技术的方法。Jeschke等人和Dachsbacher等人对之前的工作进行了回顾,并给出了一种更加通用且健壮的方法,能够正确处理轮廓和曲面等情况。Hirche 首先提出了将网格中的每个三角形向外挤压形成棱柱(prism)的想法,然后渲染这个棱柱,计算所有可能会出现高度场中的像素。这种类型的方法称为外壳映射(shell mapping),因为向外扩展的网格在原始模型上,形成一个单独的外壳。尽管计算成本很高,但是通过在与光线相交时保持棱柱的非线性特性,可以实现无瑕疵的高度场渲染。图6.42展示了这种令人印象深刻的技术案例。

在这里插入图片描述

图6.42视差遮挡映射,又名浮雕映射,将其应用在石径上,可以使得石头看起来更加真实。图中的石径地面实际上是一组简单的三角形,外加一个高度场。

纹理光源

  纹理还可以用于为光源添加更加丰富的视觉表现,并且允许光源具有非常复杂的强度分布函数或者聚光灯函数。对于所有光线都限制在一个圆锥(cone),或者一个截锥体(frustum)内的光源而言,投影纹理(projective texture)可以用来调节光线的强度。这样就可以实现有形状的聚光灯、有图案的光源、甚至是“幻灯片投影仪”效果(如图6.43所示)。这些光源通常被称为gobo或者cookie,这是在专业剧院和电影照明中用于描述cutou镂空(cutout)的术语。

在这里插入图片描述

图6.43 投影纹理光源。纹理被投影到茶壶和地面上,并用于控制投影截锥体内的光线分布(位于截锥体外的光线,其强度为0)。

  对于不局限于截锥体范围,而是照亮所有方向的光源而言,可以使用立方体贴图来调节它的强度,而不是像其他光源一样使用二维的投影纹理。一维纹理可用于定义任意的距离衰减函数;将它与二维角度衰减贴图结合,可以实现复杂的体积照明模式。一种更加普遍的做法是,使用三维(体积)纹理来控制光线的衰减,这种方法可以实现包括光束(light beam)在内的任意体积效果。与其他所有的体积纹理一样,这种技术方案也是内存密集型的。如果光源的体积效果沿三个轴对称的话,那么可以将数据在每个八个象限中进行镜像,从而将内存占用减少到原来的八分之一。

  任何类型的光源都可以添加纹理,从而实现额外的视觉效果。纹理光源允许艺术家们,通过对纹理进行编辑和修改,从而很轻松地控制照明效果。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/439509.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

序列化与反序列化基础及反序列化漏洞(附案例)

参考文章&#xff1a; [web安全原理]PHP反序列化漏洞 - 笑花大王 - 博客园 (cnblogs.com) 一、概念 为了能有效的存储数据而不丢失数据的类型和内容&#xff0c;经常需要通过序列化对数据进行处理&#xff0c;将数据进行序列化后&#xff0c;会生成一个字符串&#xff0c;字符…

使用TM1618控制LED了解P-MOS和N-MOS的开漏输出的不同

数据手册上的截取内容 手册中推荐的共阴/阳极电路 可以发现GRID总接LED的负极&#xff0c;SEG引脚接的是LED 正极 分析输出的MOS管类型可以很好的知道原因 图片来源 通过都是开漏输出可以看出&#xff0c;引脚引出的内部电路是不同的。P-mos引出的是漏极&#xff0c;导通时…

Redission · 可重入锁(Reentrant Lock)

前言 Redisson是一个强大的分布式Java对象和服务库&#xff0c;专为简化在分布式环境中的Java开发而设计。通过Redisson&#xff0c;开发人员可以轻松地在分布式系统中共享数据、实现分布式锁、创建分布式对象&#xff0c;并处理各种分布式场景的挑战。 Redisson的设计灵感来…

软考鸭微信小程序:助力软考备考的便捷工具

一、软考鸭微信小程序的功能 “软考鸭”微信小程序是一款针对软考考生的备考辅助工具&#xff0c;提供了丰富的备考资源和功能&#xff0c;帮助考生提高备考效率&#xff0c;顺利通过考试。其主要功能包括&#xff1a; 历年试题库&#xff1a;小程序内集成了历年软考试题&…

国内旅游:现状与未来趋势分析

在当今社会快速发展的背景下&#xff0c;国内旅游更是呈现出蓬勃的发展态势。中国&#xff0c;这片拥有悠久历史、灿烂文化和壮丽山河的广袤土地&#xff0c;为国内旅游的兴起与发展提供了得天独厚的条件。 本报告将借助 DataEase 强大的数据可视化分析能力&#xff0c;深入剖…

Java.数据结构.HashMap

目录 1基本概念 2数据结构 3常用操作 3.1 put(K key, V value)&#xff1a;插入键值对。 3.2 get(Object key)&#xff1a;根据键获取值。 3.3 remove(Object key)&#xff1a;移除键值对。 3.4 containsKey(Object key)&#xff1a;判断Map中是否包含指定的键。 3.5 c…

Android Camera2 与 Camera API技术探究和RAW数据采集

Android Camera2 Android Camera2 是 Android 系统中用于相机操作的一套高级应用程序接口&#xff08;API&#xff09;&#xff0c;它取代了之前的 Camera API。以下是关于 Android Camera2 的一些主要信息&#xff1a; 主要特点&#xff1a; 强大的控制能力&#xff1a;提供…

神秘的二叉树

一.什么是树 都说艺术来源于生活&#xff0c;技术同样也是来源于生活。什么是树&#xff0c;它是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树&#xff0c;也就是说…

小程序 uniapp+Android+hbuilderx体育场地预约管理系统的设计与实现

目录 项目介绍支持以下技术栈&#xff1a;具体实现截图HBuilderXuniappmysql数据库与主流编程语言java类核心代码部分展示登录的业务流程的顺序是&#xff1a;数据库设计性能分析操作可行性技术可行性系统安全性数据完整性软件测试详细视频演示源码获取方式 项目介绍 用户 注册…

VUE2常见问题以及解决方案汇总(不断更新中)

解决vue项目中 el-table 的 row-click 事件与行内点击事件冲突&#xff0c;点击事件不生效&#xff08;表格行点击事件和行内元素点击事件冲突&#xff09;需要阻止事件冒泡 问题描述 1.点击列的编辑按钮&#xff0c;会触发按钮本身事件&#xff0c;同时会触发行点击事件 2.点…

Kotlin 处理字符串和正则表达式(二十一)

导读大纲 1.1 处理字符串和正则表达式1.1.1 分割字符串1.1.2 正则表达式和三引号字符串1.1.3 多行三引号字符串IntelliJ IDEA 和 Android Studio 中三重引号字符串内部的语法高亮显示 1.1 处理字符串和正则表达式 Kotlin 字符串与 Java 字符串完全相同 可以将 Kotlin 代码中创建…

R包的安装、加载以及如何查看帮助文档

0x01 如何安装R包 一、通过R 内置函数安装&#xff08;常用&#xff09; 1.安装CRAN的R包 install.packages()是一个用于安装 R 包的重要函数。 语法&#xff1a;install.packages(pkgs, repos getOption("repos"),...) 其中&#xff1a; pkgs&#xff1a;要安…

问题-python-运行报错-SyntaxError: Non-UTF-8 code starting with ‘\xd5‘ in file 汉字编码问题

​ 编码: 把字符转换成字节序列的过程。因为计算机只能处 理二进制数据&#xff0c;所以不能直接处理文本&#xff0c;需要先把文本转换为二进制数据。 解码: 把二进制数据转换成字符的过程。把接收到的数据转换成程序中使用的编码方式。 ​ 这个报错原因就是编码和解码没达成…

【C++ STL】手撕vector,深入理解vector的底层

vector的模拟实现 前言一.默认成员函数1.1常用的构造函数1.1.1默认构造函数1.1.2 n个 val值的构造函数1.1.3 迭代器区间构造1.1.4 initializer_list 的构造 1.2析构函数1.3拷贝构造函数1.4赋值运算符重载 二.元素的插入,删除,查找操作2.1 operator[]重载函数2.2 push_back函数:…

[已解决] Install PyTorch 报错 —— OpenOccupancy 配环境

目录 关于 常见的初始化报错 环境推荐 torch, torchvision & torchaudio cudatoolkit 本地pip安装方法 关于 OpenOccupancy: 语义占用感知对于自动驾驶至关重要&#xff0c;因为自动驾驶汽车需要对3D城市结构进行细粒度感知。然而&#xff0c;现有的相关基准在城市场…

TriLite完成A轮扩展融资:加速AR微型投影仪技术创新与市场拓展

近日,全球领先的AR微型投影仪开发商TriLite宣布成功完成A轮扩展融资,将A轮融资总额提升至超过2000万欧元。这一轮融资不仅彰显了资本市场对TriLite技术实力和市场潜力的高度认可,更为其后续在AR微型投影仪领域的技术研发、产品迭代以及市场拓展提供了坚实的资金保障。以下是…

大厂笔试现已经禁用本地IDE怎么看

如果我说本来面试做题这种事情就是反人类你相信吗&#xff1f; 这个罪恶的源头就是 Google&#xff0c;说是为了选择高素质的计算机编程水平的人才&#xff0c;然后把面试就变成了考试&#xff0c;最大的受益者当然是印度人了。 当把一个考察过程变成标准化的考试过程&#x…

【AI知识点】置信区间(Confidence Interval)

置信区间&#xff08;Confidence Interval, CI&#xff09; 是统计学中用于估计总体参数的范围。它给出了一个区间&#xff0c;并且这个区间包含总体参数的概率等于某个指定的置信水平&#xff08;通常是 90%、95% 或 99%&#xff09;。与点估计不同&#xff0c;置信区间通过区…

Unity Input System自动生成配置

参考视频 创建及配置新输入系统 New Input System&#xff5c;Unity2022.2 最新教程《勇士传说》入门到进阶&#xff5c;4K_哔哩哔哩_bilibili ProjectSettings设置 Unity编辑器菜单栏选择Edit->Project Settings->Player->Other Settings,将Api Compatibility Level…

OpenAI 开发者大会!实时语音功能有API了,GPT-4o支持多模态微调,上下文cache功能上线

家人们&#xff01;十一假期第1天&#xff0c; OpenAI一年一度的开发者大会又来了惹&#xff01;今年的开发者大会分成三部分分别在美国、英国、新加坡三个地点举办&#xff0c;刚刚结束的是第一场。 去年的OpenAI开发者大会公布了GPT-4 Turbo和GPTs&#xff0c;今年没有大更新…