【Siggraph 2011】Real-time Texture Quilting

人之为人的最光辉之处,就是他永远身处某种局限性之中,却依旧能做出奋力向前的姿势。
——张定浩《爱欲与哀矜》

今天来学习一项比较老的技术,来自于Siggraph 2011年的分享。

Slide 1
Slide 2

地形如果使用同一种材质会存在大量的tiling pattern,这是游戏开发中比较常见的问题。

Slide 3

由于一系列的原因(内存消耗等),在游戏制作中常常使用小尺寸的贴图tiling的方式来实现模型表面效果的表达。

Slide 4

这里的splat指的是从原始贴图上复制过来的一小块区域,其实这里想表达的是tiling的做法。

Slide 5

这里的splat对应的是顶点上的数据,上图展示了三个顶点对应的splat效果,每个像素(或面片)都同时被三个splat(顶点一一对应)所影响,如果每个像素对应的splat数目更少会更好,因为这就意味着采样数会降低。每个像素的采样点数固定下来比随着像素而变化要好一些,shader执行效率会更高。

Slide 6

在PS中,每个像素需要计算出每个splat的边界从而知道该采哪个位置的贴图,为了使得采样结果能够无缝衔接,之类需要做一些处理,而经调查发现,虽然有较多的方案在应对这个问题,但是没有一个是single pass可以完成的。

Slide 7

这里解释的是如左图所示的多张本身不相衔接的贴图在拼合的时候,要怎么消除中间的裂缝。采用的做法是通过一些clonepaint的方法来实现,具体细节没有介绍,不过简单思考应该方案还是挺多的,比如将上贴图在下边缘处取一个范围做翻转当做其向下的延伸(从而保证在原贴图边界处是无缝的),之后将下贴图的上边缘做同样对称的处理,之后在这一段过渡范围内将两者的混合权重从0过渡到1。

当然,这里只是一个示例,具体的解决方案不是重点。

Slide 8

这里给出一个三角形,假设三个顶点上的splat中心点都在顶点上,边缘则对应于三角形的边界,希望给出一个算法,使得三角形内部像素对三个splat采样的结果是无缝的。

假设对于每个splat而言,其中心处的采样权重为1,边缘处为0,那么最直观的方式就是将这个权重编码在顶点上(如对于A顶点对应的Splat而言,在顶点上给定一个参数,在A顶点上设置数值为1,其他顶点设置数值为0,经过硬件光栅化插值后,每个像素就自然得到了对应的权重)。

下面三图给出了只考虑单个splat采样结果的表现:

Slide 9
Slide 10
Slide 11
Slide 12

为了给美术同学提供更高的控制力度,这里使用了一张weightmap(右图),每个顶点中存储的权重还需要再跟这个weightmap采样结果相乘来得到最终的权重。

Slide 13

这是经过前面混合算法后得到的3splat混合结果,已经看不到明显的缝隙了。

Slide 14

这里是三张splat以及对应的权重(下面的红蓝绿三色代表)

Slide 15

这里给出的图是最终混合结果,跟前面的一页slide来回切换可以看出每个splat的贡献,需要注意的是,这里的混合权重不是线性的。

Slide 16

下面我们来看下这个算法怎么应用到整体的mesh(而非triangle)上。

Slide 17

这是应用到一个整个mesh上的结果,可以看到,虽然我们只使用了简单几张低分辨率贴图,最终得到的却是一张高分辨率的mesh效果展示图,其中还能依稀看出重复的纹理,但是由于不是周期性重复的,因此效果要自然很多。

Slide 18

下面来介绍一下具体的原理,为了方便描述,这里先用1D而非2D来进行陈述,前面说过,每个splat的混合权重取决于两个方面,一方面是顶点上的混合权重,另一方面是weightmap上的权重。

这里描述的是顶点上的混合权重的走势,上图左侧代表的是某个顶点,橙色表示这个顶点对应的splat的权重,右侧代表的则是另一个顶点,可以看到,随着X轴的增加,代表着我们从某个顶点平滑移动到另一个顶点,在这个过程中,前面顶点的权重逐渐下降,后面顶点的权重则逐渐增加。

Slide 19

在添加上weightmap的加持之后,两个顶点上对应的splat的权重就如上图所示。

不同splat的在权重加持下要如何混合?最简单的方法就是在所有权重中选取最大的那个来使用,不过这种做法在效果上存在较多的突变,不够连续,在实际情况中,我们更倾向于一种较为平滑的,美术同学控制力度更大的方式。

Slide 20

这里是对每个位置的权重值进行偏移,偏移的值则是当前位置所有权重值中最大的那一个:

float offset = max(weight1, weight2); 
float newweight1 = weight1 - offset; 
float newweight2 = weight2 - offset;
Slide 21

之后再基于美术同学设定的一个过渡范围:transition range。对此前处于此范围内的结果进行一次放大,并剔除掉小于0的部分:

newweight1 += transitionRange;
newweight2 += transitionRange;
clamp(newweight1);
clamp(newweight2);
// need to multiply original weight, don't know why
newweight1 *= weight1;
newweight2 *= weight2;

在上面的公式中,我们看到,通过transitionRange的控制,相当于对处于这个范围内的数值进行了放大,这个范围越小,weight的变化就越剧烈,混合效果突变的概率越高。

Slide 22

随着transitionRange的增加,出现波动的区域开始变宽,这就意味着逐渐存在混合的范围开始增加,同时也意味着splat数目开始增加(因为范围越大,能够框进来的区间就越大,其他低权splat被纳入混合的概率就增加),而更多的splat导致的结果就是混合效果开始变得模糊,不再收敛到某个清晰的splat贴图上。

Slide 23
Slide 24
Slide 25

代码的逻辑在前面基本上介绍过了,不过这里有一点需要补充一下,在上面代码中也看到,在clamp之后还乘上了原始的权重值fitnessVec(顶点权重与weightmap权重相乘后的权重),而我们前面展示的效果其实是没有乘上这个数值展示的效果,如果没有这个乘法,那么整体效果来看,将有大部分区域权重值为0或者1(这种数值的区间大小取决于transitionRange的大小,Range越小区间越大),这样导致的结果是否会有什么异常呢?(理论推测,如果不乘这个数值,可能会导致突变较为剧烈)。

这里的公式给出了完整的逻辑,可以支持三个splat的混合。

Slide 26

这里是最终的效果图,如果不采用这里的程序来进行处理,而是用手工处理的话,那么其中需要大量的调教来避免缝隙,工作量就会比较大。

Slide 27

这里是细节图。

Slide 28

再来看一下更近距离的效果图。

Slide 29

Quilting技术的另一个用处就是用来消除tiling的pattern,上图展示了这个效果的成果,可以看到每块砖的纹理都不相同。

Slide 30

而这里将splat所应用的mesh的线框显示出来,通过线框可以知道splat的尺寸与transition发生的位置。

Slide 31

这里展示了其他基于两个贴图混合得到的效果图,如上图中,左上角与右上角的两张原始贴图经过混合,得到了下方的效果,虽然这个效果不如美术同学手绘质量高,但是好处在于不需要额外的处理,即可适应任意拓扑结构、任意曲线的模型,而不会存在缝隙或者扭曲。

Slide 32

这是底部mesh的线框效果,这个示例有如下的一些难处理的点:

  1. splat图是带有光照效果的,因此在旋转时会存在约束,否则效果会显得异常
  2. mesh的面片是规则的,这就加重了tiling pattern的概率。
Slide 33

这个方法还支持提供一张自定义的过渡贴图,如上图左侧三张小图的中间小图。

Slide 34

这里是底部mesh的线框图。

Slide 35

将一个定制的transition splat贴图用在任意的3D模型上意味着我们可以将一张指定的转角贴图用在一个模型的各个角落上。

上图展示了这个用法的效果,到目前为止,还没有提及transition splat是如何映射到模型上的,实际上这里的shader代码跟之前的一样,不过区别在于数据是如何构造的。

这里需要换一种视角来看,之前的方案讨论的是每个顶点有一个splat,基于不同的权重混合得到结果,这里则换成了一个三角面片的效果可以由三张相互独立的splat贴图混合输出。(并没有get到转换视角的意义)

在带有转角的模型上,比如上图中的室内,墙体跟地板应该要使用不同的材质,如果使用相同的材质,在拉伸的时候会导致效果异常,而夹角大于90度又会加剧(这里应该指的是planar mapping算法,基于某个世界坐标系的轴进行映射的方法)。

在计算的时候,需要知道什么位置是转角,这里采用的方法是添加一张alpha贴图来进行标注。

上面的模型中,用来实现转角效果的splat图只包含了两个edge splat,没有corner splat,这是因为有edge splat就已经可以得到平滑效果了,这也就意味着不需要专门为不同夹角或者不同划分(如将180度四分还是五分)的corner添加额外的splat。

Slide 36

下面来介绍其中的一些实现细节。

对于每个三角形,我们有三张splat需要采样,因此需要为每个splat指定一个单独的UV Channel,而这里存在一个问题:加入ABC、ACD组成了两个三角形,其中B顶点使用了某个UV Channel来表示对B顶点对应的Splat采样的UV值,那么如果要想保证AC这条边上的顶点同时被这两个三角形共用(而不拆出新的顶点),就使得D不能复用B的UV Channel,因为如果复用,就会导致经过光栅化插值后得到的UV值,在AC处存在冲突(即从B这一边开始插值,与从D这一边开始插值,得到的结果可能不一样,当然,如果保证在AC上插值得到的结果永远是0,说不定还能匹配上)。

而UV Channel是有限的,不能为每个顶点分配一个单独的Channel,也就意味着,我们需要对顶点进行拆分,而拆分导致的问题是VB尺寸会是三倍之大,且在VS中经历Transform之后的顶点缓存命中率将永远是0。

Slide 37

这里的做法如上图所示,对于一个三角形而言,我们提供一个顶点数据,在其他两个顶点上则是全零。在经过光栅化插值后,三角形内的像素就能够得到上图下面的一行所给的数值,如果我们只是想要还原上面顶点的数据,只需要对这一行数值除以最后的k即可。

这种做法的好处在于:

  1. 剩余两个顶点的数值为0,因此作用共边来使用的话不会存在数据冲突
  2. 可以为需要的顶点指定一个顶点属性,其他两个顶点的属性值为0来实现不同效果,而不需要存储UV坐标来进行贴图读取(取决于贴图是否是简单色块贴图)

不过这里需要担心的是万一k=0(像素中心正好落在底边上),这个除法就会存在问题,不过理论上只要我们使用的是浮点坐标,那么这种可能性基本可以忽略,而且在底边上的权重为0,即使出了问题也可以被掩盖。

Slide 38

可以看到,经过上述方法,可以在每个三角形中输出对应的主控顶点的属性值(用颜色表示),这个属性值可以用作这个三角形的某个常量。

Slide 39

将上述方法应用到三张splat上,我们只需要额外增加两行顶点数据即可。

Slide 40

使用上面的方法,就能够实现顶点的复用,这里的问题在于,我们要如何将顶点属性写入到顶点中,以实现每个三角形的三个顶点中的数据符合上面的规范呢?

这里采用的是贪心算法,即从一个三角形开始,分配三个不同的属性数据到三个顶点不同的属性行上,之后从这个三角形往外延伸,碰到新的三角形,就根据已分配的顶点属性(比如已经分配了红绿),将剩余的属性(蓝)分配到新的顶点上,循环往复,得到最终的结果如上图最下面的小图所示。

这种方法在上面的规整mesh上得到了完美的效果,即每个三角形的三个顶点属性都符合规范,但是在于一些复杂mesh上,可能得不到这样漂亮的结果,但是即使如此,也可能只是几个三角面片存在问题而已,不会有太大影响。

Slide 41

这里是复杂模型的表现,可以看到在部分区域结果存在一些问题。

Slide 42

这里给出一个在负责模型上使用两个splat得到的效果,一个splat用于模型表面,另一个用于边缘,可以看到没有周期性的重复效果,下面来看一下具体细节:

Slide 43
Slide 44

可以看到,效果确实很好。

Slide 45

这里是在实际游戏中使用得到的效果图(不知道这里说的是蘑菇,还是地表)。

Slide 46
Slide 47

前面介绍的技术阐释了,通过为不同的顶点指定不同的splat(mesh中多个顶点会共用slpat,不需要每个顶点的splat都是不同的)的方法从而实现以极低的贴图内存占用达到高精度的贴图效果。

这里的一个问题是,哪个顶点该使用哪张splat,每个顶点使用的splat是否需要旋转或缩放呢?

一个可行的方法是手动摆放,但这样工时消耗太高了,另一个方法就是程序化计算,而程序化的算法存在的问题在于:

  1. 美术同学有新的工具的学习成本
  2. 美术同学的新需求需要程序同学参与进行迭代
  3. 在最终的场景效果出现问题时,不知道该如何调整原始的splat贴图来消除这个问题

这里给出的方法是:

  1. 设定一个标准,对于每个splat(或顶点),使用重复的贴图
  2. 之后使用“block-out”材质来覆盖模型,这里的“block-out”指的是没有filleting、没有重叠、没有过渡,大概意思是将模型划分成一个个的splat小块,每个小块对应一个贴图。
  3. 对这个模型进行UV Map映射,目标是使得得到的UV Map的texel尺寸跟splat中的texel尺寸保持一致
  4. 最终得到的这个模型就可以用作splat生成代码中所使用的source模型

这一页描述了这个方法是如何使用的(然而并没有看明白),将这个图片跟Slide 34的图片进行比对。注意在这个mesh中,我们有两个材质,这两个材质之间的效果转换是硬切的,且每个贴图都是在一大片区域中重复使用的,这个重复效果在褐色的贴图应用区域较为明显(虽然没有重复)。

这个方法相对于新的程序化工具而言,主要的优点是,美术同学只需要使用他们所熟知的工具:tiled贴图以及一个简单的UV映射模型。

splat方案不会对模型进行改动(增减面等),因此美术同学对最终的模型几何拓扑结构有着完全的控制。

这里还有一些问题没有介绍到:

  • 如何创建与维护splat的一些布局信息,比如splat的混合柔软度等
  • 如何实现对类似slide33/34中的自定义过渡贴图
  • 如何在转角处支持splat

这些问题需要TA以及一些跟项目相关的工具才能覆盖到(大意就是这里不讲了。。。太模糊了)

Slide 48

前面说到的fitness map(weightmap,权重贴图)并不是手动绘制的,而是基于color map转换得到的,具体来说就是基于某个公式完成颜色到灰度值的转换,其中越接近白色,灰度值就越大,在混合的时候权重就越高,因此,基于这种方式得到的混合结果,在混合区域中颜色会更亮一点,可能会使得结果存在一些瑕疵。

一个解决方案是对于我们转换后的灰度值应用上图所示的曲线。这个曲线之所以被选中是因为经过这个处理之后,我们的fitness map就相当于移除了亮色或者暗色的矫正作用,因此最终的混合区域就不再有明显的偏向。

本文中的效果都是经过这个处理的,shader代码中会读取原始的color贴图,将之转换成灰度值,之后经过一个高通处理(是上图所示的处理吗?),最终通过下面公式(公式的作用是什么?移除高亮度信息?)计算出最终的fitness

总结

本文描述了如何通过一些小贴图(splat)经过一些混合计算来实现大贴图的效果,以保证大的贴图效果中不会存在裂缝、重复纹理等问题。

splat与顶点的映射关系通过顶点属性来实现,每个顶点都对应于同一个splat,而这个splat被这个顶点所参与的所有面片共享。

这里面也有存在如下的一些问题没有回答清楚,希望后面有其他的资料可以补充一下这些问题的答案:

  • 小贴图要如何设计?是否任意的贴图都可以?

Slide 4中说明splat是从一张大的原始贴图中剪切出来的局部贴图,听起来只需要splat具有代表性即可

  • 小贴图的种类数目是否有要求,对于每个顶点应该分配哪种贴图是否有约束,还是随机分配都可以?

splat的数目文中给出的示例最少两张(否则混合效果会存在tiling?),最多的话应该是没有限制的,但是考虑到性能,需要有所约束;
splat与顶点的对应关系,如果不考虑edge等情况,可以不做约束,实际使用时应该需要根据对应区域的纹理效果来进行指定(如前面案例中土壤地表与沥青地表之间应该要使用不同的splat,在二者衔接区域,还需要指定过渡splat)

  • 小贴图分配给对应的顶点,此顶点用来采样的UV数值要如何设计,是默认从0,0开始采样,还是基于世界坐标进行变换?
  • 小贴图的朝向与采样UV是如何设计的?

Slide 29中描述说是完全按照planar projection来模仿,其中为了避免重复效果过于明显,一张splat在UV方向采用分别随机0.5或1/3的倍数的方式应用在不同的砖块上,因此总共有6种纹理效果,也就是说UV应该不是默认值,但是具体要如何计算,还是没有给出,可以通过公式随意计算(比如基于世界坐标转换,就是其中一种)


planar projection,即平面投影,将3D物体投影到2D平面的一种方法,3D上的每个点与投影中心点相连得到的线段延长后与2D平面的交点即为投影点,当投影中心点距离投影平面距离有限,就为透视投影,否则就为正交投影

  • 小贴图的覆盖范围要如何确定?

Slide 8说了splat的中心点落在对应的顶点上,这里是字面意思吗?可以理解成这个地方的UV是(0.5, 0.5)
每个splat的覆盖范围如何计算,如果过小,那面片中超出覆盖范围的部分是否就直接tiling了?根据Slide 8的描述与Slide 9的示例,其覆盖范围需要正好(其实是至少,不过考虑到覆盖范围越大,精度越低,因此正好的话效果是最佳的)覆盖到顶点对应的另一条边,通过对权重进行调整使得到对边位置处,此splat的权重已经接近于0

  • fitness map是从color读取的,那么color就是splat吗?从color到灰度的转换采用哪个公式,是直接从颜色转亮度的那个吗?

根据Slide 12的描述,color就是splat对应的原始颜色贴图,fitness map就是基于这张贴图计算得到的权重贴图;不过这里也说了,fitness map是额外的一张贴图,那么为什么不在运行时基于color map计算得到fitness map呢?一个考虑是这个计算过程消耗有点高,至少要高于采样的消耗

  • 顶点权重是如何设计的?是默认采自己的splat用1,其他splat用0?

Slide 18确认了此疑问

  • 过渡splat在使用时的一些细节是怎样的,比如过渡区域的三角形,哪个顶点使用过渡splat,哪个顶点不用?
  • 顶点与splat的映射关系是如何指定的?

美术同学在建模软件中通过设定顶点属性实现吗?
Slide 40通过程序化的方法来指定不同顶点对应的UV Channel,但是没有说明这个UV采样的贴图是否有所不同,或许这里如果不需要考虑edge情况,只需要准备三张splat,之后完全交给程序即可?

  • 假如某个面片覆盖范围过大(地表),此时单个splat(在特定的分辨率下)只能覆盖一小部分区域,整体效果会存在异常吗?即通过tiling来进行采样,在算法作用下tiling问题是否会被消解?

参考

[1]. Real-time image quilting: Arbitrary material blends, invisible seams, and no repeats

你可能感兴趣的:(【Siggraph 2011】Real-time Texture Quilting)