查看: 143|回复: 3

[随心记] Unity Shader 基于顶点偏移的云海

[复制链接]

8

主题

2

听众

56

积分

问题学生

Rank: 1

升级   28%

发表于 2021-2-18 09:05 |显示全部楼层
先来一个效果图,看起来其实有点卡通。
以前我曾经写过一个利用视差映射实现的云海,效果不错,但是在平视的时候就露馅了,因为毕竟只是个片而已。利用模型顶点便宜就是来解决这个问题。
方法是看的Roman Papush大神的一个视频,过程还是很简单的,链接在这,不过需要科学上网。他是用shadergraph连出来的,这里我们还是用代码生写好了。
https://www.youtube.com/watch?v=Y7r5n5TsX_E
首先,我们需要一个合适的模型,上图展示了模型的细化过程,这种布线方式更加均匀,不论从哪个方向拉伸顶点,都不会有太明显的变形。你可以试试用标准横平竖直的布线,差别很明显。按这种方式细化下去,最后我选择了两个级别,一个24000面的 一个90000多面的,最后我会都放进测试工程里,大家可以对比看看,有点差距,但不是那么明显,手机上用一个两万多的面足够了。
接下来进入shader。
原视频中用了shadergraph自带的noise生成,这里我自己找了一张图,其实并不是那么合适,大家可以自己再找找有没有更合适的,原则上要足够平滑,云嘛,总是很丝滑的,如果图片黑白变化太大,你会发现,云波动的时候会有抖的现象,我这个图就有一点。
  1.             v2f vert (appdata v)
  2.             {
  3.                 v2f o;
  4.                 //用两个方向获取噪波图的运动
  5.                 o.CloudUV01 = v.uv + _Time.x* _Speed;
  6.                 o.CloudUV02 = v.uv - _Time.x* _Speed;
  7.                 o.noisePos.x = tex2Dlod(_NoiseTex, float4(o.CloudUV01, 0, 0)).r;
  8.                 o.noisePos.y = tex2Dlod(_NoiseTex, float4(o.CloudUV02, 0, 0)).r;
  9.                 o.worldPos = mul(unity_ObjectToWorld, v.vertex);
  10.                 o.vertex = UnityObjectToClipPos(v.vertex);
  11.                 //让云的Y轴扰动起来
  12.                 o.vertex.y += o.noisePos.x* o.noisePos.y * _Height ;
  13.                 //以中心开始 将圆变成一个碗的形状,因为边缘太低,可能会露出一些不必要的场景。
  14.                 o.vertex.y -= pow(distance(float2(0, 0), o.worldPos.xz) / _DcurvatureRadius, 3);
  15.                 o.screenPos = ComputeScreenPos(o.vertex);
  16.                 UNITY_TRANSFER_FOG(o,o.vertex);
  17.                 return o;
  18.             }
复制代码
这里是顶点函数,一开始我们先采样了两遍云的贴图,运动方向相反,然后乘到一起,目的就是增加随机感,做过海水扰动的应该都能明白,然后把这个扰动乘以一个可调节的参数再给到模型的Y轴上,这样云的模型就动起来了,非常简单对吧。
接下来的Y轴又加了一个算法,就是利用平面顶点的距离来抬高Y轴,这样可以让云的模型变成一个碗状,如下图
图上弯曲的有点过分,演示而已,目的就在于可以隐藏一下边界,否则天际线看起来过于平并且过于低,具体高度可以根据自己场景需求来调节。
  1.             fixed4 frag(v2f i) : SV_Target
  2.             {
  3.                 //采样摄像机深度图
  4.                 float4 depthSample = SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, (i.screenPos));
  5.                 float depth = LinearEyeDepth(depthSample);
  6.                 float borderLine = saturate((depth - i.screenPos.w)/2 - _DepthBiasFactor);
  7.                 //获取遮罩图
  8.                float mask = saturate(i.noisePos.x * i.noisePos.y);
  9.                 //利用噪波图来融合顶底两个颜色
  10.                 float4 col = ( _TopColor*mask) +(_BottomColor *(1-mask));
  11.                 //设置与物体交接的地方透明
  12.                 col.a = borderLine;
  13.                 // apply fog
  14.                 UNITY_APPLY_FOG(i.fogCoord, col);
  15.                 return col;
  16.             }
复制代码
上面是片元函数,一开始我们先采样了一下相机的深度图,用来检测是不是与模型有交叉,从而实现边界透明的效果。跟海水的岸边,浪花是同样的方法。
嗯,看上去顺眼多了。
接下来我又重新获取了噪波图的高度(也就是亮度),把它当作遮罩融合了两个高低的颜色,来做最终输出。下面是我用两个极端颜色来展示的。
这样基本就大功告成了,如果想要效果更好一些,可以再加一张不同纹理的燥波图,不同大小的纹理叠加起来效果会更好,这里我就不展示了。


工程里高模和低模都有,大家可以自己看看差别。
MeshCloud.unitypackage
4M
· 百度网盘

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

楼主热帖

9

主题

3

听众

68

积分

问题学生

Rank: 1

升级   34%

发表于 2021-2-18 09:13 |显示全部楼层
效果不错
回复

使用道具 举报

4

主题

3

听众

56

积分

问题学生

Rank: 1

升级   28%

发表于 2021-2-18 09:14 |显示全部楼层
令人眼馋的技术 先收藏了
回复

使用道具 举报

8

主题

2

听众

59

积分

问题学生

Rank: 1

升级   29.5%

发表于 2021-2-18 09:17 |显示全部楼层
20000多面...20个面都行吧,ndc 弄个environment map, 来几个quad照相机周围,fragment shader动一动
回复

使用道具 举报

温馨提示:求助请到“Unity技术讨论”版块中发帖,便于集中解决!
您需要登录后才可以回帖 登录 | 立即注册

Unity游戏引擎开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2021-3-7 07:18 , Processed in 0.104073 second(s), 36 queries .