《铁匠》中的皱纹图

在策划 《铁匠》 短片时,我们从未真正将自定义皮肤着色器列为优先事项,因此它没有任何实际机会被选为一项任务。然而,我们仍然想看看是否可以做一些简单的事情来为挑战者的表情增添一点活力。经过快速的头脑风暴后,我们决定尝试在项目中添加混合形状驱动的皱纹图。
为了给表情添加细节和深度,我们决定,如果让皱纹同时影响法线和遮挡,标准着色器将为我们带来最大的收益。我们还希望找到一种方法来限制某些表情对面部特定部位的影响。
使用 Wrinkle Maps 驱动程序
我们创建了一个组件,允许动画师定义皱纹层,网格中每个混合形状一层。图层定义包含纹理映射和强度修改器,以及一组与脸部部分遮罩纹理匹配的遮罩权重。使用遮罩权重,特定的皱纹层可以影响被遮罩的脸部部分的一到四个,每个部分都有不同的影响。

由于我们希望能够在任何给定时间混合多达 4 种不同的表情,因此仅混合一项就需要 11 个纹理采样器并启用所有功能(两个基本纹理、八个细节纹理和一个遮罩纹理)。唯一现实的选择是在屏幕外的预渲染过程中合成混合皱纹图。我们发现 ARGB2101010 渲染纹理格式对我们来说是完美的,因为它允许我们将法线打包到两个 10 位通道中,剩下的一个接收遮挡。每一帧,皱纹图组件都会找到四个最有影响力的混合形状,并相应地分配层渲染权重。

一旦我们在屏幕空间中组合了所有的皱纹数据,剩下要做的就是重定向我们用于脸部渲染的标准着色器中的法线和遮挡数据输入。实际上,这仅意味着在表面着色器主函数中添加几行代码。
// Sample occlusion and normals from screen-space buffer when wrinkle maps are active
#ifdef WRINKLE_MAPS
float3 normalOcclusion =
tex2D(_NormalAndOcclusion, IN.screenPos.xy / IN.screenPos.w).rgb;
o.Occlusion = normalOcclusion.r;
#ifdef _NORMALMAP
o.Normal.xy = normalOcclusion.gb * 2.f - 1.f;
o.Normal.z = sqrt(saturate(1.f - dot(o.Normal.xy, o.Normal.xy)));
#endif
#endif最终结果
将基本头部与全重夸张的愤怒混合形状进行比较,可以说明混合皱纹图添加的额外细节:


我们还添加了各种调试输出模式,让我们能够轻松地看到完全混合的遮挡和法线贴图。这些对于弄清楚哪个组件对最终结果有何贡献非常有用。

我们将此功能分解为一个示例项目,您可以 从 Asset Store 获取。它基本上只是挑战者的头部,带有我们在 《铁匠》中使用的几个表情,但应该可以作为在您自己的项目中运行此系统的一个有用起点。
