飘渺九月 发表于 2021-2-19 09:36

Unity草地交互的实现

上一篇文章(草地的顶点动画实现风吹草动)中我们通过模型的顶点动画来模拟了风吹草动的效果。今天我们在给他加上一个交互的效果,就是有角色或者其他物体在草地上走过时,草地会向周围散开。我们先看下草不动的时候向周围散开的样子。
其实实现方法也很简单,只要向Shader传递一下物体的坐标信息,然后在shader中用草的顶点坐标减去传递进来的物体坐标就可以得到草每个顶点散开的方向了。然后再通过一个半径的范围值来控制交互影响的范围,当然还是要通过UV的V方向的值来控制根部不动。
v2f vert (appdata v)
            {
                v2f o;
                float4 worldPos = mul(unity_ObjectToWorld, v.vertex);
                //得到物体和草模型顶点之间的距离
                float dis = distance(_PlayerPos, worldPos);
                //通过影响范围,还有强度和UV的值来控制草的弯曲强度
                float pushDown = saturate((1 - dis + _PushRadius) * v.uv.y * _Strength);
                //计算出每个顶点散开的方向并做归一化处理
                float3 direction = normalize(worldPos.xyz - _PlayerPos.xyz);
                //减弱一些y轴向上的影响,否则很多草顶点会穿过地面
                direction.y *= 0.5;
                worldPos.xyz += direction * pushDown;
                o.pos = mul(UNITY_MATRIX_VP, worldPos);
                o.uv = v.uv;
                return o;
            }是不是很简单,接下来我们再把这个加在之前的风吹草动上面就行了。shader代码如下:
Shader "Custom/GrassVertexAniInteractive"
{
    Properties
    {
      _MainTex ("Texture", 2D) = "white" {}
      _Noise("Noise", 2D) = "black" {}
      _WindControl("WindControl(x:XSpeed y:YSpeed z:ZSpeed w:windMagnitude)",vector) = (1,1,1,0.5)
      //前面几个分量表示在各个轴向上自身摆动的速度, w表示摆动的强度
      _WaveControl("WaveControl(x:XSpeed y:YSpeed z:ZSpeed w:worldSize)",vector) = (1,0,1,1)
      //前面几个分量表示在各个轴向上风浪的速度, w用来模拟地图的大小,值越小草摆动的越凌乱,越大摆动的越整体
      //_PlayerPos("PlayerPos", vector) = (0,0,0,0)
      //物体的位置坐标,需要在运行时通过C#代码传入,所以这里注释掉,把这个参数作为全局控制的参数
      _Strength("Strength", float) = 1
      //草地弯曲的强度
      _PushRadius("PushRadius", float) = 1
      //交互的范围
    }
    SubShader
    {
      Tags { "RenderType"="Opaque" }
      LOD 100

      Pass
      {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_instancing

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 pos : SV_POSITION;
            };

            sampler2D _MainTex;
            sampler2D _Noise;
            half4 _WindControl;
            half4 _WaveControl;

            float4 _PlayerPos;
            half _Strength;
            half _PushRadius;

            v2f vert (appdata v)
            {
                v2f o;
                UNITY_SETUP_INSTANCE_ID(v);
                //草地自身风吹草动的计算
                float4 worldPos = mul(unity_ObjectToWorld, v.vertex);
                float2 samplePos = worldPos.xz / _WaveControl.w;
                samplePos += _Time.x * -_WaveControl.xz;
                fixed waveSample = tex2Dlod(_Noise, float4(samplePos, 0, 0)).r;
                worldPos.x += sin(waveSample * _WindControl.x) * _WaveControl.x * _WindControl.w * v.uv.y;
                worldPos.z += sin(waveSample * _WindControl.z) * _WaveControl.z * _WindControl.w * v.uv.y;
                //草地交互的计算
                float dis = distance(_PlayerPos, worldPos);
                float pushDown = saturate((1 - dis + _PushRadius) * v.uv.y * _Strength);
                float3 direction = normalize(worldPos.xyz - _PlayerPos.xyz);
                direction.y *= 0.5;
                worldPos.xyz += direction * pushDown;

                o.pos = mul(UNITY_MATRIX_VP, worldPos);
                o.uv = v.uv;

                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
      }
    }
}在C# 代码中我们只要在Update里面把物体的位置传递给Shader 的_PlayerPos参数就行了
void Update()
    {
      playerPos = transform.position;
      Shader.SetGlobalVector("_PlayerPos" , playerPos);
    }最后如果是大面积的草地顶点数量很多的时候,可以写两份Shader,一个是带交互的,一个是不带交互的。然后把草地分块再加上Trigger,默认都使用不带交互的Shader,当角色或物体进入到某块草地的Trigger时动态把这块草地的Shader替换成带交互的,这样Trigger以外的顶点都不会参与计算了。

杨柳657 发表于 2021-2-19 09:45

如果是多个角色都需要扰动草的话,,有什么方法可以一次计算出来吗

井底燕雀傥 发表于 2021-2-19 09:53

那就要把多个角色的position通过数组传递给shader,然后在shader里面把草地交互计算那一段放在for循环里面来处理

芊芊551 发表于 2021-2-19 09:58

如果使用了gpu instancing 是否还有效? 比如用drawmeshinstanceIndirected

李志敏 发表于 2021-2-19 10:07

也是可以的,如果用DrawMeshInstanceIndirected的话需要配合ComputeBuffer和ComputeShader

馥琳 发表于 2021-2-19 10:16

offset写到纹理里

jimmy肖明 发表于 2021-2-19 10:20

塞尔达就是怎么实现的[惊喜]

简单350 发表于 2021-2-19 10:29

请问当我把草放大后,物体对草的影响程度就会非常低有什么解决方案么?

123456833 发表于 2021-2-19 10:33

把相应的影响范围和弯曲强度的值加大

李志敏 发表于 2021-2-19 10:40

调大之后感觉更多是鱼眼镜头效果儿不是草被压倒
页: [1] 2
查看完整版本: Unity草地交互的实现