当前位置:   article > 正文

使用曲面细分渲染毛发_渲染曲面细分毛发

渲染曲面细分毛发

使用曲面细分渲染毛发

original link

几周前,我无意间找到了一篇让很有趣的论文,主要讲述的是使用曲面细分技术去渲染头发,并且是在Direct3D 11类型的GPU上。这是我多年前在曲面细分技术上的实践,那时候我也才刚接触D3D11,当时就在尝试基于曲面细分技术的毛发渲染。那时候,我各种收集和挖掘有关资料,同时也决定将它发布在博客中,说不定会有人对它感兴趣。

在开始之前,我会先简短的对曲面细分进行一个总结,当然了,如果你很了解这部分知识,你可以直接跳过,请随意。

曲面细分 101

曲面细分(tessellation)是Direct3D11和OpenGL4.x的图形API中介绍的新特性。简单的说,曲面细分就是通过某种再分割的形式增加网格的几何体,它可以提高网格的多边形分辨率,可以产生更平滑或者说更多(几何学上的)细节的表面。因为这个技术完全由GPU完成,所以,这既省内存又省带宽,节约内存是因为内存中无需很多数据,节约带宽是因为在GPU中获得和处理的顶点较少。

曲面细分的工作伴随着由控制点定义的块,多数情况下,块是网格三角形,而控制点就是顶点。

当我们在曲面细分网格时,我们必须要知道每条边的细分数量,这个我们称之为"细分因子"。细分因子的数量在1-64之间,具体多少得取决于块的形状,如果块是个三角形,举个例子来说,外边缘会有3或者4个细分因子,而"内边"缘会有1个细分因子。通常,外边缘容易被直观显示,可以将之看作是细分之后的顶点个数(上图中,右边三角形外边缘的细分因子是4,3,3)

细分器支持3种图元类型,三角形,四边形以及直线

另外,我们可以使用多种分割策略进行细分操作,比如,整数,以2为底的幂函数(pow2),分数。

如果认真研究过D3D11的渲染管线,你就可以知道曲面细分是2种全新的shader类型的联合体:Hull着色器,Domain着色器以及位于它们之间的固定处理单元

实际上,Hull着色器是由两个着色器组成,一个是控制点着色器(Control Point Shader),另一个是固定块着色器(Patch Constant Shader),虽说,解释这两个着色器就并非这篇文章的主旨,但在讲述毛发渲染之前我已然做好了失去大部分读者的准备。控制点着色器对每个控制点(可以看成顶点)运行一次,同时也可以访问块(可以看成三角形)上的其它控制点;固定块着色器对每个块运行一次,它会输出细分因子,细分处理单元会根据这个细分因子决定图元的再分割程度

细分处理单元正如上述提到的固定处理单元一样,其目的是在常规标准化的图元(四边形,三角形以及直线)上生成新的点,同时输出它们的UVW坐标

有意思的是,细分处理单元并没有控制点/顶点/块的概念,因为它的操作对象是图元。

最后说说Domain着色器,Domain着色器接受控制点着色器的输出以及细分处理单元生成的新顶点,然后,再通过插值去生成新的图元。另外,如果我们想要比如使用高度图去表现顶点位移,那么就可以在这个阶段进行处理。

使用细分曲面渲染毛发

回到渲染毛发上,理论上讲很简单:

  • 构建细分处理单元产生使用直线图元的顶点
  • 在domain着色器中对数据进行插值处理,然后生成新的顶点
  • 使用几何着色器生成新的基于三角形的发丝网格

直线图元是种特殊的细分图元,它会返回2D数据,并且UV限制在0-1中,这对理解毛发渲染很关键,因为我们可以把UV取值范围看作是线的数量,而把其它部分看作是线中的段的数量

细分处理单元最多可以输出64条"线",每根线可以分成64段

发丝的生成发生在domain着色器中,那这里,可以访问到发丝网格几何体(三角形和顶点)的原数据,我们可以使用插值处理每根头发丝。要做到这一点,在CPU计算阶段,我使用了一个随机的重心坐标数组,并且将它们作为domain着色器的输入。如果带宽是瓶颈的话,那么此时你就可以在domain着色器中计算坐标。接着,我将细分处理单元提供的线的数量去索引重心坐标数组,然后去找新发丝所在位置。对于段的数量呢,我会用来向上延伸发丝,在这个例子中,每根毛发被分为4段

在对发丝顶点进行插值运算时,可以有两个做法,其一就是使用原始三角形顶点坐标对发丝的基顶点做一次插值(结果会得到三角面上的一个顶点),然后朝着法线方向向上延伸

对于短发(青草)的简单模拟,这是快速且容易的方法,但是在某些情况下,这样的简单模拟会有问题,比如模拟有很多段的长头发,又或者对更复杂情况的模拟/碰撞。另外,独立模拟每个发丝特别消耗资源

另一个做法就是通过使用"主"发丝顶点对每个发丝顶点进行插值然后生成新的发丝顶点(再次使用重心插值)

这种做法的好处就是:举个例子来说,我们可以在CPU或者compute 着色器中对主发丝进行模拟/碰撞检测,然后,再对‘已经"模拟好的"主发丝进行插值,从而得到新的发丝,这样可以明显降低开销

在这个例子中,我为每个三角形顶点创建了一个主发丝(顶点集合),然后再通过在CPU阶段定义的结构缓冲传递到domain着色器。如此一来,就不在需要基三角形顶点,也不需要除了会使用细分因子构建细分处理单元之外就别无他用的hull着色器。这个例子中还会检测基三角形的法线,同时,会剔除背对相机的部分。细分因子如果被设置为0,那么细分处理单元将不会生成任何新的顶点,这样的做法可以避免生成发丝的背面,不过请记住,即便基表面不可见,发丝仍旧可能可见,所以,在剔除时我们应该尽量保守点

我的所有数据都存储在结构缓冲中,但是我仍然需要做某些渲染层面的事情以便可以激活细分处理器,所以,我创建了有着一个顶点的顶点缓冲(坐标无所谓)以及有着很多三角行索引的索引缓冲。

前边提到过,细分处理器可以输出每个三角形最多64根线(发丝),这意味着,如果我们需要输出每个三角形更多的发丝,我们就必须处理更多的头发渲染过程(或者有一个非常稠密的网格)。在这里,我计算出了头发的强度值(每单位区域发丝的数量)以及根据区域指定每个三角形的发丝数量。如果一个三角形需要多于64根发丝才能被渲染,那么也需要更多的渲染步骤。

坦白讲,对于类似短毛发的渲染,我根本就没有使用主发丝,因为这不需要复杂的模拟,但是无论无何我相尝试一些这种方式。

domain着色器输出的发丝,实际上就是线,因此,很难让毛发产生体积,所以几何体着色器就被用于将直线放大为合适的三角形。

最后,为了让毛发看起来更加真实,我使用了各种不同参数的高光和一盏边缘光。

下边是通过修改毛发各方面比如长度,宽度和强度等对毛发渲染的结果:视频连接

我意识到由于毛发几何体(细丝)的本质,渲染结果很糟糕(没有几何体的抗锯齿),特别是毛发有动画时尤为糟糕:

添加了适度的MSAA(x4)数量以提升效果:

MASSx8的话效果提升了很多,但是x4就也就差不多足够了

我没有做屏幕空间的抗锯齿,不过,我觉得它对画质会有很大的影响(在完全没有使用几何抗锯齿的情况下)

即使使用几何抗锯齿,在与摄像机的距离发生改变时,仍然有会看到有发丝断裂,尤其是细发丝。为了解决这个问题,我尝试使用过 Emil Persson的Phone Wire AA方法,这个方法可以将"金属丝"几何体的宽度限制到最小值,如果实际宽度更小,那么会根据差值将其淡出。这个方法完美适用于"金属丝"类型的几何体,按道理对毛发也该有效才对。我仍然保留宽度最小的想法,意味这会让毛发在外观上有全面的提升

没使用Phone Wire AA方法的情况:

使用了Phone Wire AA:

我增加了毛发的长度以便可以更突出是否使用Phone Wire AA方法的差异,虽然这在静态图片中很难看得出

另外,同样的毛发渲染方法,我只是稍加改动,然后用于在别的例子中渲染草坪,最终很完美的呈现:

如果你有心尝试,你可以在这里找到VS2010的关于渲染毛发的项目工程,这个工程使用了Hieroglyph SDK以及FBX SDK 2013.3.

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号