

本文使用unity实现雪地效果,主要参考了github上两个已有的demo,将其结合并并增加了一些自己东西。
本文主要讲雪地效果,简单说就是雪地踩脚印的实现,并非雪面的渲染。然后因为是一段时间之前写的,细节部分有些遗忘,所以争取主要把原理讲清楚,不会涉及太多代码细节。可以下载github的demo参考更改。
如果有任何错误,欢迎指正,谢谢!~
参考demo
Demo1: https://github.com/thnewlands/unity-deformablesnow

Demo2: https://github.com/TheBeach54/SnowSimulation

雪地效果最基本原理

雪地效果的根本就是要踩出脚印。而实现这个的原理分两步:第一步,在雪地plane的下方放置一个正交相机,从下往上拍摄,如下图。目的是实时获取上方物体生成一张深度图;第二步,根据上一步生成深度图,将雪地plane的顶点向下方位移,使用tessellation技术。参考Demo1就是根据这一基本原理实现。理解上不难哈。
本文实现流程

本文采用的方法比上文说的多两个步骤。一个是参考了demo2的方法,具体则是在下方放置两个摄像机,一个相机在第一帧的时候记录下初始地面深度,因为初始地面可以是地形,本身就有一定高度。另外一个相机记录实时拍摄的物体深度。这样根据两个深度就能在任何地形上计算出最后在真正的踩雪地方,将其结果保存在r通道内。当然需要两个正交相机参数完全相同,具体计算方式后面会说。结果如下图和视频:



另外一个步骤则是根据计算后的深度值图,进行两次轮廓线检测,采用的是最基本简单的sobel算子,然后用g和b通道分别存储其颜色。这样做的目的是,可以根据最后的深度图的不同通道,将雪地分为基本雪面(无颜色值)、凸起部分(g通道)、侧边(b通道)、凹陷底部(a通道)四个部分,如图。只需要在tessellation的过程中,将g通道的部分向上位移顶点,与a通道操作相反,则可以形成踩雪路径左右两边的凸起部分。
然后根据不同的通道使用不同的diffuse、normal等贴图渲染即可得到不同的雪面渲染效果。在这之前可以适当的将深度图进行模糊处理,使边界柔和。同时在雪面渲染地shader中使用lerp函数对贴图进行插值。结果如下图:

实现细节
相机部分

这是下方放置的正交相机的脚本的截图,是获得物体深度的相机,另外一个获得初始地形深度的相机大致相同,现在介绍其主要部分。代码如下:
/// <summary>
首先,下方相机获取深度图,这一步通过SetReplacementShader替换上方物体的材质为SnowDepthShader获得,另外一个相机。SnowDepthShader中最基本的一个步骤就是讲fragment shader中的颜色值替换为o.rgb = float3(1 - i.depth, 0,0);这样就能使得相机获取深度图。
然后则是传给CamRecieveMat,该材质的shader就是用来处理接受到的ObjDepthTex(物体深度图)和FloorDepthTex(初始地形深度图,另外一个相机生成)。然后根据这两张图生成一张计算后的深度值图。计算代码如下:
float 其中floor是初始地形的深度值,current.x是当前拍摄到的物体深度值,_SnowMaxHeight似是设定的雪面高度,_SnowFarPlane是相机的远平面距离,最终将计算出的深度值重新赋值给current.x。这样生成的计算后的深度值图,就能避免原始物体深度图中,在雪面以上的深度值被提取。
最后则是在OnRenderImage中进行两次后处理,分别用EdgeDetectMat和EdgeDetectMatOut,也就是两次sobel算子的轮廓线检测,分别存储到g和b通道内部。
另外一个重点则是我们使用了两张RT分辨存储深度值,然后在Update中交替传给各种mat。这样做主要是因为,一张RT存储当前帧的深度图,另外一张RT
存储上一帧的深度图,这样就能让深度图在CamRecieveMat中保持形成轨迹。
雪面渲染材质


其中主要的参数有:
- Edge Length: Tessellation的程度
- Displacement Textur: 初始时让雪面适当位移的噪声贴图
- Imprint Texture: Camera脚本传递进来的最后的深度图
- Top Material Settings: 基本雪面(无颜色值部分)的雪面渲染
- Bottom Material Settings: 凹陷雪面(r通道部分)的雪面渲染
- Upper Material Settings: 凸起部分(g通道部分)的雪面渲染
- Middle Material Settings: 侧边雪面(b通道部分)的雪面渲染
然后还有一些关于个部分雪面渲染的程度等设定不赘述。
对于凸起部分的雪面贴图仅采用了不同的diffuse贴图和法线贴图,并附加不同的颜色值和法线程度。
Tessellation

根据上文所获得的最终深度图,直接使用unity提供的tessellation技术(不赘述),在disp函数里将顶点的y方向进行相对应的位移。不过需要注意的是需要重新计算法线方向,否则阴影会有问题。

另外还有一个trick,可以将只需要顶点位移的地方tessellation的值提高。但是这样做需要在剩余没有脚印的雪面部分变化不大,没有明显的位移,否则在不同tessellation的接缝处会产生裂痕。所以实际上最后我也没有用,只是一个参考。
三向贴图


最后的一个处理是在雪面的渲染部分,地形变形导致了贴图的拉伸,因此采用三向贴图的算法进行贴图(具体原理连接https://blog.csdn.net/liu_if_else/article/details/73833656)。左图是普通uv贴图,靠近雪面边沿的地方贴图会有明显的拉伸。右图是三项贴图处理过后,贴图拉伸变形效果消除。只需要针对所有的贴图处理一下即可,代码如下:
fixed4 
最后的最后,本文是参考了两个demo融合更改的雪面效果,如果有错误,希望大家指正,谢谢~