unity透明通道加颜色_Unity-雪地效果的实现

9350ed4fe31842d1f1dc4f6c8dd3f55e.png
499716dcee16501b0dd7298ba11fffa4.png
最终效果https://www.zhihu.com/video/1217959676639330304

本文使用unity实现雪地效果,主要参考了github上两个已有的demo,将其结合并并增加了一些自己东西。

本文主要讲雪地效果,简单说就是雪地踩脚印的实现,并非雪面的渲染。然后因为是一段时间之前写的,细节部分有些遗忘,所以争取主要把原理讲清楚,不会涉及太多代码细节。可以下载github的demo参考更改。

如果有任何错误,欢迎指正,谢谢!~

参考demo

Demo1: https://github.com/thnewlands/unity-deformablesnow

f20e76b62776db65612db2c2321fc0f9.png

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

96f3e6c1904344a2d06e2d58abb3b0f6.png

雪地效果最基本原理

a8aa1c2234f2ebbf1a4ecd0f0cdd6d9e.png

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

本文实现流程

1464c4694f9b9b32f770083e3efa33d2.png

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

9b7ed69d44092f89a7ded6e8b20a0d84.png
初始地形为平面

7ed8dda08ec7bdd3db622d2f68b5024b.png
初始地形为山丘
c6ba0829fddc3e756ca9731c2fd55bc2.png
初始地形为山丘最终效果https://www.zhihu.com/video/1217959942839558144

另外一个步骤则是根据计算后的深度值图,进行两次轮廓线检测,采用的是最基本简单的sobel算子,然后用g和b通道分别存储其颜色。这样做的目的是,可以根据最后的深度图的不同通道,将雪地分为基本雪面(无颜色值)、凸起部分(g通道)、侧边(b通道)、凹陷底部(a通道)四个部分,如图。只需要在tessellation的过程中,将g通道的部分向上位移顶点,与a通道操作相反,则可以形成踩雪路径左右两边的凸起部分。

然后根据不同的通道使用不同的diffuse、normal等贴图渲染即可得到不同的雪面渲染效果。在这之前可以适当的将深度图进行模糊处理,使边界柔和。同时在雪面渲染地shader中使用lerp函数对贴图进行插值。结果如下图:

8e90d636c359093bb8e82bc55d7370ec.png
不同通道颜色的深度值对应的雪面贴图

实现细节

相机部分

387beeb2c03201ecffcd4c86592b2d3c.png

这是下方放置的正交相机的脚本的截图,是获得物体深度的相机,另外一个获得初始地形深度的相机大致相同,现在介绍其主要部分。代码如下:

/// <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中保持形成轨迹。

雪面渲染材质

39c820c91430ee4765b779bcba1a892b.png

bb46f4d3e4d05bc28d94c124878f11c3.png

其中主要的参数有:

  • 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

eed011109e80330284f72601605d0846.png

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

b1b35f936c58b5902fff4d76c259743a.png

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

三向贴图

4ee13ba461396bf7be9565e99b1383fd.png
没有使用三项贴图

2447c5318cfe2fe931a697e0af770d39.png
使用了三向贴图

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

fixed4 

241dcf28f453c6fd1739b07b3765c608.png

最后的最后,本文是参考了两个demo融合更改的雪面效果,如果有错误,希望大家指正,谢谢~