纳鲁演示(2004年)的头发动画和渲染

2021-04-26 10:32:56

GPU宝石2现在可以在网上提供。您可以购买这本书的美妙印刷版,以及该系列中的其他人,享有30%的折扣提供信息和Addison-Wesley。 在Web和下载时可用CD内容,包括演示和内容。我们在开发NALU演示的单一最大技术挑战6800推出是实时发射的渲染。以前的nvidia演示人物黎明和黄昏有头发,但它很短,黑暗和静态。对于纳鲁来说,我们旨在实现渲染长,金发,流动的头发的目标。在本章中,我们描述了我们使用实时实现这一目标的技术。它们包括用于模拟发型的系统和#39; S移动,遮蔽算法来计算毛发自阴影,以及模拟通过单独的头发股线的光散射的反射率模型。当组合时,这些元素实际产生了极其现实的头发图像。图23-1显示了一个例子。

在NALU' S头发阴影的后台,有一个系统产生头发几何形状,并控制每个帧的动态和碰撞。它基本上分为两部分:几何生成和动态/碰撞计算。

头发由4,095个单独的毛发制成,使用线基元呈现。我们使用了123,000个顶点,只需用于发型渲染。通过动力学和碰撞检测发送所有这些顶点将是过度速度的,所以我们使用控制毛:纳鲁' S发型可以通过较小的数百头发来描述和控制,即使渲染需要数千个。所有昂贵的动态计算只应用于这些控制毛发。

我们没有时间,没有工具可以用手为控制头发结构进行动画。即使我们有工具,手也很多控制毛发很难。需要许多微妙的次要动作让头发看起来很可信。见图23-2。物理上基于动画帮助了很多。

当然,当介绍程序动画时,对发毛运动的一些控制丢失(在我们的情况下,这意味着失去了90%的控制)。碰撞检测和响应可以引入不希望的毛发行为,以妨碍创造性的动画。我们的动画师对了解动态的内部工作并创造了一些解决方法。在工程方面增加了一些额外的工具,以获得对动态的10%的人体控制。

控制头发结构用于粗略地描述发型。我们"生长"来自玛雅的专用几何形状的控制毛,代表"头皮" (在渲染时间看不可见)。控制头发从头皮的每个顶点沿着正常的情况生长,如图23-3所示。增长是100%的程序。

一旦我们有一组控制毛发,我们将它们进行物理,动态和碰撞计算,以便程序上动画头发。在这个演示中,运动几乎完全依赖于动态。虽然这种系统看起来很有吸引力,但具有人类可控系统更为希望。当我们想戏剧戏剧性,我们需要能够"假"或" fix"头发行为。此外,我们的毛发动态不足以在某些情况下像真正的头发一样逼真。具有更好的手动控制将使我们能够使运动更加令人信服。

因为头发完全动态并改变每个框架,我们需要重建最终渲染头发设置每个帧。图23-4显示了数据如何通过该过程移动。动画控制毛发被转换为Bezier曲线并镶嵌以获得光滑的线路。然后插值平滑线以增加头发密度。将该组插入的头发发送到发动机以进行最终帧渲染。我们使用动态顶点缓冲区来保存顶点数据。

头发曲面细分过程包括通过添加顶点平滑每个控制头发。我们从7到36顶点增加超过五倍。见图23-5和23-6。

计算新顶点'位置,我们通过计算它们的切线(对于每个帧)并使用它们来计算Bezier控制点来将控制毛转换为Bezier曲线。从Bezier曲线来看,我们计算引入的额外顶点的位置,以平滑控制毛发。

平滑的控制毛发将通过插值来复制,以创建密集的头发,准备最终渲染。

使用ScalP网格拓扑创建内插头发。见图23-7。每个三角形的四肢有三个平滑的控制毛发(以绿色显示)。我们希望用毛发填充三角形表面的内部,因此我们内插的控制毛三胞胎的坐标,以产生新的平滑头发(以灰色显示)。光滑的控制毛发和平滑的内插毛具有相同数量的顶点。

要填满头发的每个三角形,我们使用重心坐标来创建新的,内插,毛发。例如,基于三个重心系数(B A,B B,B C)来计算内插发Y(在图23-7中),其中B A + B B + B C = 1:

通过在[0..1]中产生两个随机值,如果它们的总和大于1,则计算1减去它们的较大,并且将第三个作为其他两个设置为使它们总和为1,我们可以确定位置它增长了所需的头发密度。

NALU' S头发动态基于粒子系统,如图23-3B所示。每个未接人的控制毛发顶点被动画为粒子。颗粒均匀地沿头发间隔开。当我们远离头骨时,控制头发中的细分会更大。这允许具有更长的头发,而不添加太多顶点。

对于这个项目,我们选择使用Verlet集成来计算粒子'运动,因为它往往比欧拉集成更稳定,而且比跳动-Kutta集成更简单。解释超出了本章的范围,但如果您想了解更多有关在游戏中使用Verlet集成的方法,请参阅Jakobsen 2003。

虽然颗粒在四处移动时,控制毛的长度必须保持恒定以避免拉伸。为了使这种情况发生,我们在控制头发内使用粒子之间的约束。如果它们彼此过于靠近,则约束使粒子变得困扰,或者如果颗粒相距太远,则它们会收缩段。当然,当我们提取一个粒子时,相邻段的长度变为无效,因此需要迭代地应用修改。经过几次迭代,系统会聚朝向所需的结果。见图23-8。

我们尝试了许多碰撞检测技术。我们需要保持简单快速的过程。对于这个演示,领域的钻井座钻井井是井,而且最容易实施。见图23-9。

起初,解决方案没有按计划起作用,因为一些毛发段大于碰撞球。因为我们正在碰撞"一个反对领域的点,"没有什么能阻止头发与球体相交的两个肢体。

为了防止这种情况发生,我们介绍了一个"珍珠配置"在控制束碰撞数据中,如图23-10所示。而不是用点碰撞,每个球体会碰撞与粒子上的另一个球体碰撞。

我们还试图检测段和球体之间的碰撞。它'不是很困难,但它与使用"珍珠没有那么快。"我们的边缘碰撞检测合理良好,但它具有稳定性问题,可能是由于我们的碰撞反应代码。无论如何,我们有一些足够好的东西的目的。

美人鱼的原始概念包括长且柔软的鳍,我们认为这是一个特征的关键部分。将固体翅片构建并皮肤呈现给骨架。然而,在动画过程中,在玛雅,鳍刚刚遵循身体,看起来很僵硬。你可以看到我们在图23-11中的意思。

幸运的是,我们的毛发动态代码也可用于执行布模拟。因此,在实时发动机中,我们计算布仿真并将布料的结果混合到皮肤几何形状。涂上一个体重图以定义要混合的物理量。我们应用的物理越多,豆瓣越柔软。相反,混合更多的剥皮导致更刚性的运动。我们希望鳍的底部比提示更加严格,而重量映射允许我们做到这一点,因为我们可以完全绘制物理学与皮肤的比例,我们想要每种布顶。见图23-12。最终,这种组合使我们能够产生柔软和现实的翅片,如图23-13所示。

遮光发的问题可以分为两部分:局部反射模型的头发和毛发之间自阴影的方法。

对于我们当地的反射模型,我们选择了Marschner等人的模型。我们选择了这种模式,因为它是一种全面,物理基于的头发反射的代表。

在I [ - / 2,3]和I [0,2]是极坐标中的输入方向,而O [ - / 2, / 2]和O [0,2]是光方向的极性坐标。 [1]功能s是一种完整的头发纤维如何散发和反射光的描述;如果我们可以评估此​​功能,那么我们可以计算表面的阴影以进行任何光位置。

因为S是昂贵的评估,我们想避免在每个像素处计算它。一个可能的解决方案是将s存储在查找表中并在运行时从中读取。然后,此查找表可以在纹理中编码,允许我们从像素着色器访问它。

不幸的是,函数S有四个参数,GPU没有对四维纹理的本机支持。这意味着我们需要某种方案来编码二维纹理中的四维函数。幸运的是,如果我们仔细执行我们的表查找,我们只能使用少量的二维地图来编码完整的四维功能。

Marschner模型将每个头发纤维视为半透明圆柱,并考虑光可能穿过头发的可能路径。考虑三种类型的路径,每个路径在路径符号中以不同方式标记。路径符号表示光的每个路径作为字符串,每个路径表示光线和表面之间的相互作用类型。 r路径表示光从头发纤维表面朝向观察者反弹的光。 TT路径代表折射到头发中并朝观察者折射的光。 TRT路径代表折射到头发纤维中的光,反射内表面,并再次折射到观察者。在每种情况下," r"表示光反射,和#34; t"表示通过表面透射(或折射)的光线。

Marschner等人。 (2003)表明,这些路径中的每一个有助于头发反射率的不同和视觉上的方面,允许与先前的头发反射模型不可能实现一定程度的现实主义。图23-14显示了三种反射率路径,这些路径为头发的出现贡献。

每个术语S P可以进一步被考虑为两个功能的乘积。功能M P描述了角度对反射率的影响。另一个函数n p朝着方向捕获反射。如果我们假设一个完美的圆形发纤维,那么我们就可以在较小的角度方面编写m和n。通过定义次要角度d =½(i - o)和d = i-o,可以写入前述等式的每个术语作为:

S p = m p(i,o)x n p(d,d)p = r,tt,ttt。

在此表单中,我们可以看到M和N都是只有两个参数的功能。这意味着我们可以为每个功能计算查找表,并以二维纹理编码它们。二维纹理是理想的,因为GPU针对二维纹理进行了优化。我们还可以使用GPU' S插值和MIPMapping功能来消除着色器别名。 [2]

虽然我们存储了六个功能,但其中许多只是单个通道,并且可以存储在相同的纹理中。 M r,m tt和m trt每个只有一个通道,因此它们将它们一起装入第一个查找纹理。 n R是单个通道,但是n tt和n trt都是每个三个通道。我们将n tt和n r存储在第二个查找纹理中。为了提高性能并降低演示中的纹理用法,我们缩短了M TT(i,O)= M TRT(i,O)的假设。这使我们可以在相同的纹理中存储N TT和N TRT,并将纹理的数量从三到两个剪切。见图23-15和23-16。

尽管模型以角度表示,但是从向量计算这些角度将需要逆三角函数。这些都很昂贵,如果可能,我们希望避免它们。我们计算SINES而不是将I和O传递到第一次查找

然后,我们可以用罪的函数表达m,在着色器中保存一些数学。

有一点工作,我们也可以计算cos d。我们首先将垂直于头发的眼睛和光矢量突出如下:

唯一剩下的角度是d。为此,我们注意到D是I和O的函数。因为我们已经使用了I和O索引的查找表,所以我们可以将额外的通道添加到存储d的查找表。

在我们的实现中,查找表在CPU上计算。我们使用了128 x 128纹理,具有8位按组分量格式。 8位格式要求我们将值缩放到范围内[0..1]。因此,我们必须在着色器中添加一个额外的比例因子,以平衡这些术语的相对强度。如果需要更准确,我们可以跳过此步骤并使用16位浮点纹理来存储查找表。我们发现这是我们的目的不必要的。

为了实际计算这些查找表中的一个,我们必须编写一个程序来评估函数m和n。这些函数太复杂于此; Marschner等人。 2003提供了完整的描述以及推导。

//在顶点着色器:sinthetai = dot(light,tangent)sinthetao = dot(眼睛,切线)lightperp = light - sinthetai *切线eyeperp =眼睛 - sinthetao *切线; Cosphid = Dot(eyeperp,Lightperp)*(Dot(eyeperp,eyeperp)*点(Lightperp,Lightperp))^ - 0.5 //在片段着色器中:( MR,MTT,MTRT,COSthetad)= Lookup1(Costhetai,Costhetao) (NTT,NR)= LOOKUP2(COSPID,COSTHETAD)NTRT = LOOKUP3(CASPID,COSTHETAD)S = MR * NR + MTT * NTT + MTRT * NTRT

注意,在查找表中编码的反射率模型存在许多其他参数。这些包括亮点的宽度和强度以及头发折射的颜色和指数等。在我们的实现中,我们允许在运行时更改这些参数,我们会在飞行中重新计算查找表。

虽然我们使用该发射模型来渲染表示为线条条的头发,但是可以将其延伸到表示为固体几何形状的头发。我们可以使用表面的主要切线之一而不是使用线条的切线。最后,我们必须考虑到表面的自动阻塞。这是通过乘以额外的术语(包裹+点(n,l))/(1 +包装)来完成的,其中点(n,l)是正常光矢量和包裹之间的点产品是零和一个之间的值控制允许照明围绕模型包裹多远。这是一种简单的近似,用于通过头发模拟光线(绿色2004)。

实时应用中的阴影通常用两种方法之一计算:模板暗影卷或阴影贴图。不幸的是,这些技术都不适用于在头发上计算阴影。头发中使用的几何形状的纯粹量将使模板暗影卷难以处理,阴影图在高度详细的几何形状上表现出严重的锯齿,如头发。

相反,我们使用了一种专门用于渲染头发阴影的阴影渲染方法。透明度阴影贴图扩展阴影映射以处理体积对象和抗锯齿(Kim和Neumann 2001)。透明度暗影地图最初是在GeForce 2级硬件上实现的,其中可编程性的灵活性比当前GPU上的可用性更长。原始实现在大数据集上没有实现实时性能,并且需要在CPU上运行大量算法。 GeForce 6系列硬件具有足够的可编程性,我们可以在GPU上执行大部分算法。在这样做时,我们即使对于大型头发数据集而达到实时性能。

我们使用透明度暗影图,而不是使用传统阴影贴图等离散测试,而不是使用诸如传统阴影贴图。这意味着,而不是简单的闭塞的二进制测试,我们需要测量在给定像素上渗透到深度z(在光空间中)的光百分比。这由以下公式给出:

T(x,y,z)是在像素位置(x,y)处的透过深度z的光的分数,称为透明度厚度,并且R(x,y,z)是消光系数,其描述了百分比在点处(x,y,z)处的单位距离被每单位距离吸收的光。值k是选择的常数,使得当= 1,t约为0(在数值精度内)。这允许我们忽略范围之外的值[0..1]。

不透明度阴影贴图的想法是在离散的z值z 0上计算。 。 z n-1。然后,我们通过以下两个最接近值之间的插值来确定in-westals值,如下所示:

这是一个合理的近似,因为Sigma是z的严格增加的函数。我们将n达到16,z 0是光空间中的头发的近平面,z 15是光空间中的头发的远平面。其他平面均匀分开,使Z i = Z 0 + I DZ,其中DZ =(Z 15 - Z 0)/ 16。请注意,因为R是头发卷的外部0,(x,y,z 0)= 0,所以我们只需要在z = z 1.处存储1.。 。 Z 15。

Kim和Neumann(2001)指出,可以使用在图形硬件上使用添加剂混合来计算积分Sigma。我们的实现还使用硬件混合,但它还使用着色器来减少CPU所完成的工作量。

天真的方法是在16个纹理中存储(x,y,z i)。这需要我们渲染16次以生成完整的透明度阴影贴图。我们的第一个观察是存储只需要1个通道,因此我们可以将最多4个Σ值打包成单个4通道纹理并同时呈现给所有的所有频体。使用此方案,我们可以减少16到4的渲染的数量。

我们可以做得好比4渲染通过,但是,如果我们使用多个渲染目标(MRT)。 MRT是一个功能,允许我们同时渲染最多4个不同的纹理。如果我们使用MRT,我们可以同时呈现为4个独立的4通道纹理,允许我们在仅单个渲染通行证中呈现给所有16个通道。

现在,如果我们只是b

......