内托体头 发表于 2021-2-18 09:05

Unity Shader 基于顶点偏移的云海

先来一个效果图,看起来其实有点卡通。
以前我曾经写过一个利用视差映射实现的云海,效果不错,但是在平视的时候就露馅了,因为毕竟只是个片而已。利用模型顶点便宜就是来解决这个问题。
方法是看的Roman Papush大神的一个视频,过程还是很简单的,链接在这,不过需要科学上网。他是用shadergraph连出来的,这里我们还是用代码生写好了。
https://www.youtube.com/watch?v=Y7r5n5TsX_E
首先,我们需要一个合适的模型,上图展示了模型的细化过程,这种布线方式更加均匀,不论从哪个方向拉伸顶点,都不会有太明显的变形。你可以试试用标准横平竖直的布线,差别很明显。按这种方式细化下去,最后我选择了两个级别,一个24000面的 一个90000多面的,最后我会都放进测试工程里,大家可以对比看看,有点差距,但不是那么明显,手机上用一个两万多的面足够了。
接下来进入shader。
原视频中用了shadergraph自带的noise生成,这里我自己找了一张图,其实并不是那么合适,大家可以自己再找找有没有更合适的,原则上要足够平滑,云嘛,总是很丝滑的,如果图片黑白变化太大,你会发现,云波动的时候会有抖的现象,我这个图就有一点。
            v2f vert (appdata v)
            {
                v2f o;
                //用两个方向获取噪波图的运动
                o.CloudUV01 = v.uv + _Time.x* _Speed;
                o.CloudUV02 = v.uv - _Time.x* _Speed;
                o.noisePos.x = tex2Dlod(_NoiseTex, float4(o.CloudUV01, 0, 0)).r;
                o.noisePos.y = tex2Dlod(_NoiseTex, float4(o.CloudUV02, 0, 0)).r;
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);

                o.vertex = UnityObjectToClipPos(v.vertex);
                //让云的Y轴扰动起来
                o.vertex.y += o.noisePos.x* o.noisePos.y * _Height ;
                //以中心开始 将圆变成一个碗的形状,因为边缘太低,可能会露出一些不必要的场景。
                o.vertex.y -= pow(distance(float2(0, 0), o.worldPos.xz) / _DcurvatureRadius, 3);

                o.screenPos = ComputeScreenPos(o.vertex);


                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }这里是顶点函数,一开始我们先采样了两遍云的贴图,运动方向相反,然后乘到一起,目的就是增加随机感,做过海水扰动的应该都能明白,然后把这个扰动乘以一个可调节的参数再给到模型的Y轴上,这样云的模型就动起来了,非常简单对吧。
接下来的Y轴又加了一个算法,就是利用平面顶点的距离来抬高Y轴,这样可以让云的模型变成一个碗状,如下图
图上弯曲的有点过分,演示而已,目的就在于可以隐藏一下边界,否则天际线看起来过于平并且过于低,具体高度可以根据自己场景需求来调节。
            fixed4 frag(v2f i) : SV_Target
            {
                //采样摄像机深度图
                float4 depthSample = SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, (i.screenPos));
                float depth = LinearEyeDepth(depthSample);
                float borderLine = saturate((depth - i.screenPos.w)/2 - _DepthBiasFactor);
                //获取遮罩图
               float mask = saturate(i.noisePos.x * i.noisePos.y);
                //利用噪波图来融合顶底两个颜色
                float4 col = ( _TopColor*mask) +(_BottomColor *(1-mask));
                //设置与物体交接的地方透明
                col.a = borderLine;
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }上面是片元函数,一开始我们先采样了一下相机的深度图,用来检测是不是与模型有交叉,从而实现边界透明的效果。跟海水的岸边,浪花是同样的方法。
嗯,看上去顺眼多了。
接下来我又重新获取了噪波图的高度(也就是亮度),把它当作遮罩融合了两个高低的颜色,来做最终输出。下面是我用两个极端颜色来展示的。
这样基本就大功告成了,如果想要效果更好一些,可以再加一张不同纹理的燥波图,不同大小的纹理叠加起来效果会更好,这里我就不展示了。


工程里高模和低模都有,大家可以自己看看差别。
http://zhstatic.zhihu.com/assets/zhihu-components/file-icon/zhimg_answer_editor_file_other.svgMeshCloud.unitypackage
4M
· 百度网盘

塞翁364 发表于 2021-2-18 09:13

效果不错

123456790 发表于 2021-2-18 09:14

令人眼馋的技术 先收藏了

123456868 发表于 2021-2-18 09:17

20000多面...20个面都行吧,ndc 弄个environment map, 来几个quad照相机周围,fragment shader动一动
页: [1]
查看完整版本: Unity Shader 基于顶点偏移的云海