序
Hello大家好我是来自引擎部ta组的小圆,这次为大家介绍多层采样的冰面制作的分享。这个效果的特点在于:使用视差+多层采样实现了表面的凹凸起伏和具有深度的裂纹效果。
本次分享的包含:
- Substance Designer 制作冰面材质
- Unity下的shader实现
这篇分享的实现方案非实际项目中落地方案,性能、效果并不是最优解,但希望能为大家带来一些启发。
==========================================================
Substance Designer 制作冰面材质
首先我们来看Substance Designer下的冰面材质制作。
我们的目标是:
1. 得到冰面的表面材质,包括常规的法线、粗糙度、颜色等
2. 得到冰面裂纹的alpha图,方便之后的效果制作
3. 在Substance Designer里,我们用Cell4作为起点。Cell4 + Edge Detect可以让我们获得一个基础的网状结构。
Note:Cell2和Cell4 + Edge Detect的结果几乎一样,为什么不直接使用Cell2?因为Cell2的细线在经过Warp等节点后,边缘会无法识别,导致无法使用Flood Fill等节点、难以进行后续的流程。
在Cell4和Edge Detect中间,我们可以插入一些Direction Warp节点,让网格的硬边产生扭曲。扭曲网格我们可以分低频(对网格进行大轮廓的扭曲)和高频(细小的弯折)来进行,让网格看起来更自然
这一步结束时候的网格轮廓
现在这个基础轮廓看起来还闲的很单调,我们尝试使用两组这样的纹理混合在一起,让一些网格的内部生成更细小的裂纹。我们可以在计算Edge Detect之前,使用Histogram Select节点,对比度拉倒最高,来筛选出一部分块,混合另外复制出来的一组裂纹。我们可以分别调整两组裂纹的参数,让两组裂纹的混合结果看起来更自然。
下图是这一部分的混合结果
有了基础的轮廓,我们在后面接一个Flood Fill节点,Flood Fill节点可以根据输入贴图的轮廓,为输入的贴图的每一块内容生成独立的UV、随机颜色等内容。我们现在Flood Fill后接一个Flood Fill to Random Gradient,为每一个单独的黑白渐变,把角度随机值拉满,我们就可以得到一个类似冰面向随机方向翘起的效果。
为了让每一块的高度有一定差异,我们可以使用Flood Fill to Random Grayscale,和random gradient相加,得到我们冰面的高度图的基础。
Note:为了避免相乘之后出现死黑、死白的情况,我们在混合前可以对混合对象使用Level进行调整。在这里我将两张输入都将其颜色范围从0-1调整到了0-0.5,以避免相加后出现大于1的情况
此时我们放大看我们目前得到的高度图,我们可以看到块和块之间有很宽的边,这在最后呈现在渲染上会出现沟壑。
为了避免这种黑边,我们可以使用两个角度互相垂直的Multi Dir. Warp,混合模式使用max。将我们原始的网格线框稍微模糊后反相,可以作为intensity输入。只需要给Multi Dir. Warp一个很小的值,就可以弥合大部分的裂纹。
这是使用Multi Dir. Warp之后的效果。
接下来的这一串看起来挺多其实挺简单:先使用了FXAA对我们上一部的贴图进行了简单的过滤,然后依次混合了:
Gaussion Sopt 2 + level,给冰面增加一些小的凹坑。
两组不同频率的Perlin Noise,给冰面增加一些柔和的起伏。
Clouds 1,给冰面增加一点点细密的凹凸。
Grunge Scratches Fine + FXAA + Level + transform 2D,给冰面增加一些细小的划痕。
这样我们就得到了我们最终的高度图。其中一些小的划痕在高度图上几乎不可见,这是正常的。
我们给高度图直接连一个Normal节点,可以直接获得法线贴图。
粗糙度的制作也是从我们的基础冰面网格开始,使用Multi Dir. Warp消除黑边,然后混合两种灰度,作为冰表面、和冰缝隙的粗糙度区分。
然后,将之前混合给高度图的凹痕、划痕依次混合给粗糙度。
最后,我们可以使用Hipass Grayscale对高度图进行滤波,保留冰裂纹边缘的高对比信息,用一张0.5灰度颜色与其进行max模式混合(只保留大于0.5的部分),再用这张图和我们上一步做出的roughness进行overlay混合,来增强冰面较高边缘处的粗糙度。
至此我们得到了冰面的Roughness。表面纹理差不多就这些了(AO也可以用高度图得出)。
最后我这里使用Megascans上下载的雪地材质做了一下高度混合,来丰富表面材质的效果。Height Blend节点可以根据高度混合两种不同的材质,输出混合后的高度和混合蒙版,再用混合蒙版混合其他贴图。
然后我们需要冰面的Alpha图:因为冰是透明物体,所以相对于Albedo,我们更需要的是一张冰面裂纹的蒙版图,这张图需要记录冰面的裂纹走向和深浅、冰封的气泡、以及因为压力或气体等原因产生的类似雾的效果。
我们看看这张图的制作。
首先,我们来处理冰最主体的裂纹结构,当前的裂纹看起来还是稍显死板,我们可以使用Slope Blur和Direction Blur来“破”开一些边缘,让裂纹看起来更“生动”。
然后,冰可能会在裂开的边缘因为受到压力产生一些裂纹。Grunge Leaky Paint这个噪声看起来就有点“形似”,我们使用cartesian to polar节点将其以极坐标的形式映射。再使用Flood Fill Mapper,为每一个裂纹块内都填充一个这样的噪声。
但当前的噪声过于均匀,我们只希望噪声出现在冰块的边缘,我们既需要计算一个遮罩。Flood Fill除了连接Flood Fill专有的一系列节点之外,也可以直接拿来计算。Flood Fill节点的xy通道是每一个单独块的UV,我们可以用Pixel Processor节点来计算每个点到uv中心的距离。然后混合一个Perlin Noise和随机灰度,让蒙版更自然。
Pixel Processor里面的内容非常简单,就是计算每个像素到(0.5,0.5)的距离。
接下来我们就可以混合我们的蒙版和噪音,并用一个Multi Dir. Warp和Cloud噪音为它添加更多细节。
目前效果看起来是这样的:
直接混合我们的主体裂纹结构:
接下来我们给裂纹周围再生成一些“影分身”。来模拟主裂纹周围的小裂纹。
我们可以使用Direction Warp来让主裂纹产生一些扭曲。
同一个方向连续扭曲四次,并将他们混合在一起。这里混合的时候可以让扭曲更厉害、偏移更远的裂纹更淡一些。最后加一个Slop Blur增加细节。
目前纹理的局部效果:
重复三次,每一组偏移到不同的方向。并分块混合在一起
对其中一部分块稍作模糊,然后和我们之前所做的纹理混合在一起。然后反相。
使用Transformation 2d节点,对裂纹做一些旋转变换,然后混合在一起,作为冰里隐含的裂纹(裂了,但是没有断开)。
当前的纹理效果
为裂纹增加一些气泡和小点。在通过Pow节点调整一下整体亮度
这就是最后的Alpha图的效果。
最后我们打包并输出了这三张图。做为我们的后续工作的基础。
Rgh_Alpha_Mask_Noise这张图的A通道我塞了一张Noise进去。
==========================================================
Unity Shader部分
Shader这里主要分为表面的视差遮蔽映射、和裂纹的视差偏移计算。
先说表面的视差遮蔽计算:
来一张来自Learn OpenGL的经典示意图。
视差遮蔽计算基本逻辑是:将视线向量由世界空间转换到切线空间,在切线空间下,从表面开始,每沿视线前进方向移动一小段距离(也就是偏移UV坐标),就对当前位置的高度图进行采样,检查当前射线位置是否和当前高度图相交。如果相交,则通过当前位置的高度图高度、和上一步位置的高度图高度,插值计算两步之间的UV坐标,则为最终UV坐标。
Talk is cheap.
直接放代码吧
这里大部分应该比较好理解,其中我困惑过的部分有两点:
一是为什么要viewDir_TS / viewDir_TS.z。
这里画一张图:我们可以看到,如果深度图的范围没有进过缩放,那就是0-1,但是归一化的-viewDir的向量长度为1,如果-viewDir不和表面垂直(这是绝大多数的情况),那么viewDir.z的分量就是一个不定值,我们不好确定需要走多少步,viewDir才能和底面相交。
所以我们用ViewDir.z来除ViewDir,将ViewDir进行缩放,我们就可以确定,只要沿-ViewDir的方向步进进过缩放后的ViewDir的长度,就一定可以步进完整个贴图的深度范围。
第二个让我有点困惑的点是,如果高度有偏移应该怎么办?
最开始我的想法是,如果以原始的表面UV进行高度图采样的时候,发现已经和高度图相交的话,就需要反方向步进。但这样以来一部分沿-ViewDir方向步进、一部分ViewDir方向步进,处理起来就有点复杂了。
比较好的做法是,在步进前直接以用Offset的值,来对表面进行“偏移”,使用偏移后的UV和高度作为我们步进的起点,这样就可以只沿一个方向步进,减少复杂度。
有了视差遮蔽后,我们的冰面看起来有了起伏,但UV坐标的偏移并不能带来投影,所以我们还需要单独计算投影。
投影也是在视差遮蔽环节计算的。我们还是先放码。
计算投影的思路是:我们通过上一步的时差计算已经知道了经过视差偏移后的UV,自然也就能使用偏移后的UV采样高度图,得到当前像素的高度。然后我们从当前UV位置、深度出发,沿着light Dir步进,如果和高度图相交,则为实心投影。
如果我们的Light Dir始终都没有和高度图相交,则取步进过程中离高度图最近的一段距离,进行缩放和偏移,做为半影。
增加投影之后效果好了一丢丢,但还差的远。下面我们做冰面的裂纹。
冰面的裂纹本质上也是视差计算,但是冰面的裂纹我们不需要考虑视线和高度图相交的问题,我们只需要沿折射方向不断偏移UV、采样、做颜色的映射就可以了。
其中比较可说的有三点:
1- 折射的计算,我们可以用严谨的斯涅耳定理来计算折射角度,但比较麻烦,一个折射的近似方案是:refraction = - normalize(ViewDir + Normal);
2- 如果严格沿着折射方向步进、采样,得到的裂纹会非常死板,可以使用一张噪声图,对UV进行偏移,来扭曲每一层的裂纹,看起来更自然。
3- 可以使用SampleLevel()来进行采样,在裂纹更深的地方使用更高的mip值,这样可以让较深的裂纹有柔和的边缘。
下边就是裂纹部分的代码。
加上裂纹,效果看起来就好多了。
这里我对冰面部分的光照做了一点魔改,是一个简陋、不物理准确、但看上去还行的改动。
因为冰是透明物体,所以他的背光面不应该是黑的,所以将NdotL的负值部分做了反转,然后做了一点偏移。
如果觉得自己电脑性能实在是过剩,还可以再为裂纹单独采样一张噪声(这实在是太奢侈了……),来为冰面裂纹增添一些“纹理”。
效果上看好了那么一点。但是性能就……
End
以上就是使用多层采样+视差实现冰面效果的全部内容了。
实际上因为贴图采样的开销比较大,一般近距离的冰面裂纹效果并不会用多层采样来实现,视差在手游中也并不常见。但在较远距离下,表现冰面的“厚度感”还是比较合适的。
前段时间的战神诸神黄昏的冰面看起来也非常漂亮,但战神诸神黄昏里的冰面似乎是使用插片的方式来做了冰的裂纹,也是做冰面裂纹很好的办法(尤其要求裂纹很深、纹理很清晰的情况下)。
欢迎加入我们!
感兴趣的同学可以投递简历至:CYouEngine@cyou-inc.com