[Shader] Moving Trees Grass in Wind Outside of Terrain

Today I put together a shader that will make grass + trees move as if in the wind, without having anything to do with the terrain. It’s base is the Transparent Cutout Diffuse shader. I figured I’d share it with you guys since I spent weeks looking for a good shader like this with no success, so I made it myself.

Here is a video of the shader in action, in scene view applied to the leaf material of this tree:
http://gyazo.com/0507d1048ead73b6b1cb0c3c6b7a96da.mp4

The wind is customizable on each material so you can have the desired amount of movement.

There are TWO versions of the shader. The 1st one is similar to the Transparent Cutout Diffuse shader. the 2nd one has Illumination. It gives it a different look, some may prefer the second one for their tree leaves.

Here is the 1st One:

Shader "Transparent/Cutout/Diffuse Shake" {

Properties {
	_Color ("Main Color", Color) = (1,1,1,1)
	_MainTex ("Base (RGB) Trans (A)", 2D) = "white" {}
	_Cutoff ("Alpha cutoff", Range(0,1)) = 0.5
	_ShakeDisplacement ("Displacement", Range (0, 1.0)) = 1.0
	_ShakeTime ("Shake Time", Range (0, 1.0)) = 1.0
	_ShakeWindspeed ("Shake Windspeed", Range (0, 1.0)) = 1.0
	_ShakeBending ("Shake Bending", Range (0, 1.0)) = 1.0
}

SubShader {
	Tags {"Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="TransparentCutout"}
	LOD 200
	
CGPROGRAM 
#pragma target 3.0
#pragma surface surf Lambert alphatest:_Cutoff vertex:vert addshadow

sampler2D _MainTex;
fixed4 _Color;
float _ShakeDisplacement;
float _ShakeTime;
float _ShakeWindspeed;
float _ShakeBending;

struct Input {
	float2 uv_MainTex;
};

void FastSinCos (float4 val, out float4 s, out float4 c) {
	val = val * 6.408849 - 3.1415927;
	float4 r5 = val * val;
	float4 r6 = r5 * r5;
	float4 r7 = r6 * r5;
	float4 r8 = r6 * r5;
	float4 r1 = r5 * val;
	float4 r2 = r1 * r5;
	float4 r3 = r2 * r5;
	float4 sin7 = {1, -0.16161616, 0.0083333, -0.00019841} ;
	float4 cos8  = {-0.5, 0.041666666, -0.0013888889, 0.000024801587} ;
	s =  val + r1 * sin7.y + r2 * sin7.z + r3 * sin7.w;
	c = 1 + r5 * cos8.x + r6 * cos8.y + r7 * cos8.z + r8 * cos8.w;
}


void vert (inout appdata_full v) {
	
	float factor = (1 - _ShakeDisplacement -  v.color.r) * 0.5;
		
	const float _WindSpeed  = (_ShakeWindspeed  +  v.color.g );		
	const float _WaveScale = _ShakeDisplacement;
	
	const float4 _waveXSize = float4(0.048, 0.06, 0.24, 0.096);
	const float4 _waveZSize = float4 (0.024, .08, 0.08, 0.2);
	const float4 waveSpeed = float4 (1.2, 2, 1.6, 4.8);

	float4 _waveXmove = float4(0.024, 0.04, -0.12, 0.096);
	float4 _waveZmove = float4 (0.006, .02, -0.02, 0.1);
    
	float4 waves;
	waves = v.vertex.x * _waveXSize;
	waves += v.vertex.z * _waveZSize;

	waves += _Time.x * (1 - _ShakeTime * 2 - v.color.b ) * waveSpeed *_WindSpeed;

	float4 s, c;
	waves = frac (waves);
	FastSinCos (waves, s,c);

	float waveAmount = v.texcoord.y * (v.color.a + _ShakeBending);
	s *= waveAmount;

	s *= normalize (waveSpeed);

	s = s * s;
	float fade = dot (s, 1.3);
	s = s * s;
	float3 waveMove = float3 (0,0,0);
	waveMove.x = dot (s, _waveXmove);
	waveMove.z = dot (s, _waveZmove);
	v.vertex.xz -= mul ((float3x3)_World2Object, waveMove).xz;
	
}

void surf (Input IN, inout SurfaceOutput o) {
	fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
	o.Albedo = c.rgb;
	o.Alpha = c.a;
}
ENDCG
}

Fallback "Transparent/Cutout/VertexLit"
}

2nd one with Illumination:

Shader "Transparent/Cutout/Self Illum Diffuse Shake" {

Properties {
	_Color ("Main Color", Color) = (1,1,1,1)
	_MainTex ("Base (RGB) Trans (A)", 2D) = "white" {}
	_Illum ("Illumin (A)", 2D) = "white" {}
	_Cutoff ("Alpha cutoff", Range(0,1)) = 0.5
	_ShakeDisplacement ("Displacement", Range (0, 1.0)) = 1.0
	_ShakeTime ("Shake Time", Range (0, 1.0)) = 1.0
	_ShakeWindspeed ("Shake Windspeed", Range (0, 1.0)) = 1.0
	_ShakeBending ("Shake Bending", Range (0, 1.0)) = 1.0
	
}

SubShader {
	Tags {"Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="TransparentCutout"}
	LOD 200
	
CGPROGRAM 
#pragma target 3.0
#pragma surface surf Lambert alphatest:_Cutoff vertex:vert addshadow

sampler2D _MainTex;
sampler2D _Illum;
fixed4 _Color;
float _ShakeDisplacement;
float _ShakeTime;
float _ShakeWindspeed;
float _ShakeBending;

struct Input {
	float2 uv_MainTex;
	float2 uv_Illum;
};

void FastSinCos (float4 val, out float4 s, out float4 c) {
	val = val * 6.408849 - 3.1415927;
	float4 r5 = val * val;
	float4 r6 = r5 * r5;
	float4 r7 = r6 * r5;
	float4 r8 = r6 * r5;
	float4 r1 = r5 * val;
	float4 r2 = r1 * r5;
	float4 r3 = r2 * r5;
	float4 sin7 = {1, -0.16161616, 0.0083333, -0.00019841} ;
	float4 cos8  = {-0.5, 0.041666666, -0.0013888889, 0.000024801587} ;
	s =  val + r1 * sin7.y + r2 * sin7.z + r3 * sin7.w;
	c = 1 + r5 * cos8.x + r6 * cos8.y + r7 * cos8.z + r8 * cos8.w;
}

void vert (inout appdata_full v) {
	
	float factor = (1 - _ShakeDisplacement -  v.color.r) * 0.5;
		
	const float _WindSpeed  = (_ShakeWindspeed  +  v.color.g );		
	const float _WaveScale = _ShakeDisplacement;
	
	const float4 _waveXSize = float4(0.048, 0.06, 0.24, 0.096);
	const float4 _waveZSize = float4 (0.024, .08, 0.08, 0.2);
	const float4 waveSpeed = float4 (1.2, 2, 1.6, 4.8);

	float4 _waveXmove = float4(0.024, 0.04, -0.12, 0.096);
	float4 _waveZmove = float4 (0.006, .02, -0.02, 0.1);
    
	float4 waves;
	waves = v.vertex.x * _waveXSize;
	waves += v.vertex.z * _waveZSize;

	waves += _Time.x * (1 - _ShakeTime * 2 - v.color.b ) * waveSpeed *_WindSpeed;

	float4 s, c;
	waves = frac (waves);
	FastSinCos (waves, s,c);

	float waveAmount = v.texcoord.y * (v.color.a + _ShakeBending);
	s *= waveAmount;

	s *= normalize (waveSpeed);

	s = s * s;
	float fade = dot (s, 1.3);
	s = s * s;
	float3 waveMove = float3 (0,0,0);
	waveMove.x = dot (s, _waveXmove);
	waveMove.z = dot (s, _waveZmove);
	v.vertex.xz -= mul ((float3x3)_World2Object, waveMove).xz;
	
}

void surf (Input IN, inout SurfaceOutput o) {
	fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
	o.Albedo = c.rgb;
	o.Emission = c.rgb * tex2D(_Illum, IN.uv_Illum).a;
	o.Alpha = c.a;
}
ENDCG
}

Fallback "Transparent/Cutout/VertexLit"
}
25 Likes

Nice! Can you give the details on how the vertex colors affect wind? Just looking through the code, it looks like alpha affects shakebending and b is waves and g affects windspeed impact?
Any clarification on that would be handy :slight_smile:

bump!!!

Thx for sharing this shaders!
1611427--97886--$Diffuse Shake.png
Im using them in this way)

Any idea how to lower the distance between shadow and bush?

Yes. This is all about shadow offset parameter in the light, you are using. Try to tweak it.

Thanks @GambinoInd ! This works great! I saw a youtube video of someone doing something like this with Strumpy, but I like the simplicity of this (plus I think Strumpy is not updated for 4.3… don’t quote me on that - I need to check further).
Anyway, you saved me a lot of time trying to cobble together one of these myself, so thanks again!1668106--104493--grassImage.png
Grass test image.

I hope it’s ok to bring up an old thread like this, but i need to solve an issue regarding this shader.
I was trying to find a way to animate the foliage in a 2D game i’m making. So i came across this shader solution and managed to make it work nicely on the 2D sprites i have.
The thing is, after making it work, i assembled a tree i’ve painted for the background of the scenery. The tree is made up of 8 sprites in a spritesheet. 1 sprite its the trunk and the other 7 are groups of leaves that together makes the treetop. Theses 7 other sprites are assigned a new material with the shake shader. The issue is, 3 of them flow and shake realy nice, but the other 4 are almost stopped. Even if i crank up the ShakeBending those 4 sprites movement is almost imperceptible. This results in a treetop wich some areas move a lot and some barely move at all. Not a nice result.
I tried to look around the code to understand it better, but i’m kinda newb in the “shader business”. I was hoping someone could kindly help me out on this matter.
Thanks for the time.

Thank you, it’s very usefull.

I add your code to the built-in Tree Soft Occlusion Leaves to have leaves movement for imported terrain tree

Shader "Nature/Tree Soft Occlusion Leaves" {
    Properties {
        _Color ("Main Color", Color) = (1,1,1,1)
        _MainTex ("Main Texture", 2D) = "white" {  }
        _Cutoff ("Alpha cutoff", Range(0.25,0.9)) = 0.5
        _BaseLight ("Base Light", Range(0, 1)) = 0.35
        _AO ("Amb. Occlusion", Range(0, 10)) = 2.4
        _Occlusion ("Dir Occlusion", Range(0, 20)) = 7.5
        _ShakeDisplacement ("Displacement", Range (0, 1.0)) = 1.0
        _ShakeTime ("Shake Time", Range (0, 1.0)) = 1.0
        _ShakeWindspeed ("Shake Windspeed", Range (0, 1.0)) = 1.0
        _ShakeBending ("Shake Bending", Range (0, 1.0)) = 1.0

        // These are here only to provide default values
        _Scale ("Scale", Vector) = (1,1,1,1)
        _SquashAmount ("Squash", Float) = 1
    }
  
    SubShader {
        Tags {
            "Queue" = "Transparent-99"
            "IgnoreProjector"="True"
            "RenderType" = "TreeTransparentCutout"
        }
        Cull Off
        ColorMask RGB
      
        Pass {
            Lighting On
      
            CGPROGRAM
            #pragma vertex leavesCustom
            #pragma fragment frag
            #pragma glsl_no_auto_normalization
            #include "SH_Vertex.cginc"
          
            float _ShakeDisplacement;
            float _ShakeTime;
            float _ShakeWindspeed;
            float _ShakeBending;

            sampler2D _MainTex;
            fixed _Cutoff;


    void FastSinCos2 (float4 val, out float4 s, out float4 c) {
        val = val * 6.408849 - 3.1415927;
        float4 r5 = val * val;
        float4 r6 = r5 * r5;
        float4 r7 = r6 * r5;
        float4 r8 = r6 * r5;
        float4 r1 = r5 * val;
        float4 r2 = r1 * r5;
        float4 r3 = r2 * r5;
        float4 sin7 = {1, -0.16161616, 0.0083333, -0.00019841} ;
        float4 cos8  = {-0.5, 0.041666666, -0.0013888889, 0.000024801587} ;
        s =  val + r1 * sin7.y + r2 * sin7.z + r3 * sin7.w;
        c = 1 + r5 * cos8.x + r6 * cos8.y + r7 * cos8.z + r8 * cos8.w;
    }

    v2f leavesCustom(appdata_tree v)
    {  
        float factor = (1 - _ShakeDisplacement -  v.color.r) * 0.5;
     
        const float _WindSpeed  = (_ShakeWindspeed  +  v.color.g );  
        const float _WaveScale = _ShakeDisplacement;
 
        const float4 _waveXSize = float4(0.048, 0.06, 0.24, 0.096);
        const float4 _waveZSize = float4 (0.024, .08, 0.08, 0.2);
        const float4 waveSpeed = float4 (1.2, 2, 1.6, 4.8);
        float4 _waveXmove = float4(0.024, 0.04, -0.12, 0.096);
        float4 _waveZmove = float4 (0.006, .02, -0.02, 0.1);
 
        float4 waves;
        waves = v.vertex.x * _waveXSize;
        waves += v.vertex.z * _waveZSize;
        waves += _Time.x * (1 - _ShakeTime * 2 - v.color.b ) * waveSpeed *_WindSpeed;
        float4 s, c;
        waves = frac (waves);
        FastSinCos2 (waves, s,c);
        float waveAmount = v.texcoord.y * (v.color.a + _ShakeBending);
        s *= waveAmount;
        s *= normalize (waveSpeed);
        s = s * s;
        float fade = dot (s, 1.3);
        s = s * s;
        float3 waveMove = float3 (0,0,0);
        waveMove.x = dot (s, _waveXmove);
        waveMove.z = dot (s, _waveZmove);
        v.vertex.xz -= mul ((float3x3)_World2Object, waveMove).xz;
      
        v2f o;

        float3 viewpos = mul(UNITY_MATRIX_MV, v.vertex);
        o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
        o.uv = v.texcoord;
  
        TerrainAnimateTree(v.vertex, v.color.w);
      
        float4 lightDir = 0;
        float4 lightColor = 0;
        lightDir.w = _AO;

        float4 light = UNITY_LIGHTMODEL_AMBIENT;

        for (int i = 0; i < 4; i++) {
            float atten = 1.0;
            #ifdef USE_CUSTOM_LIGHT_DIR
                lightDir.xyz = _TerrainTreeLightDirections[i];
                lightColor = _TerrainTreeLightColors[i];
            #else
                    float3 toLight = unity_LightPosition[i].xyz - viewpos.xyz * unity_LightPosition[i].w;
                    toLight.z *= -1.0;
                    lightDir.xyz = mul( (float3x3)_CameraToWorld, normalize(toLight) );
                    float lengthSq = dot(toLight, toLight);
                    atten = 1.0 / (1.0 + lengthSq * unity_LightAtten[i].z);
              
                    lightColor.rgb = unity_LightColor[i].rgb;
            #endif

            lightDir.xyz *= _Occlusion;
            float occ =  dot (v.tangent, lightDir);
            occ = max(0, occ);
            occ += _BaseLight;
            light += lightColor * (occ * atten);
        }

        o.color = light * _Color;
        o.color.a = 0.5 * _HalfOverCutoff;

        return o;
    }
      

    fixed4 frag(v2f input) : SV_Target
    {
        fixed4 c = tex2D( _MainTex, input.uv.xy);
        c.rgb *= 2.0f * input.color.rgb;
              
        clip (c.a - _Cutoff);
              
        return c;
    }
    ENDCG
}
      
        Pass {
            Name "ShadowCaster"
            Tags { "LightMode" = "ShadowCaster" }
          
            Fog {Mode Off}
            ZWrite On ZTest LEqual Cull Off
            Offset 1, 1
  
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma glsl_no_auto_normalization
            #pragma multi_compile_shadowcaster
            #include "UnityCG.cginc"
            #include "TerrainEngine.cginc"
          
            struct v2f {
                V2F_SHADOW_CASTER;
                float2 uv : TEXCOORD1;
            };
          
            struct appdata {
                float4 vertex : POSITION;
                fixed4 color : COLOR;
                float4 texcoord : TEXCOORD0;
            };
            v2f vert( appdata v )
            {
                v2f o;
                TerrainAnimateTree(v.vertex, v.color.w);
                TRANSFER_SHADOW_CASTER(o)
                o.uv = v.texcoord;
                return o;
            }
          
            sampler2D _MainTex;
            fixed _Cutoff;
                  
            float4 frag( v2f i ) : SV_Target
            {
                fixed4 texcol = tex2D( _MainTex, i.uv );
                clip( texcol.a - _Cutoff );
                SHADOW_CASTER_FRAGMENT(i)
            }
            ENDCG  
        }
    }
  
    // This subshader is never actually used, but is only kept so
    // that the tree mesh still assumes that normals are needed
    // at build time (due to Lighting On in the pass). The subshader
    // above does not actually use normals, so they are stripped out.
    // We want to keep normals for backwards compatibility with Unity 4.2
    // and earlier.
    SubShader {
        Tags {
            "Queue" = "Transparent-99"
            "IgnoreProjector"="True"
            "RenderType" = "TransparentCutout"
        }
        Cull Off
        ColorMask RGB
        Pass {
            Tags { "LightMode" = "Vertex" }
            AlphaTest GEqual [_Cutoff]
            Lighting On
            Material {
                Diffuse [_Color]
                Ambient [_Color]
            }
            SetTexture [_MainTex] { combine primary * texture DOUBLE, texture }
        }      
    }

    Dependency "BillboardShader" = "Hidden/Nature/Tree Soft Occlusion Leaves Rendertex"
    Fallback Off
}

Why define your own sincos function? I doubt that whatever implementation you have there will outperform the built-in sincos(), which could even be engineered into the hardware.

Many thanks Gambinolnd!

Can you give an answer to this? Looks like the sader works without painting any vertex-colors though… Can you explain how does the shader work in regard to the vertex colors?

Thanks again!

To be honest I kind of just pieced together scripts that i found to produce this. I’m not great with shaders. Can’t answer your question haha

Well, since I discovered how does the wind works with “detail mesh” along with the default hidden/grass shader, I ended up using “detail mesh” in the terrain. Good thing is that, if you use terrain “detail mesh” as grass you can still benefit from the detail distance fade out, which is very nice and optimizes your scene.

Here’s the way:

  • Create whatever you like in your 3d app, say 10 X-shaped patches of grass scattered around
  • Still in the 3d app, paint the mesh using grayscale in the color vertex alpha (vertex paint)
  • Import the mesh to unity
  • In your unity terrain object, set the imported mesh as “detail mesh” and use “grass” as render mode.
  • Now your mesh bends with the terrain wind properties

Detail meshes can’t have edge turbulence (like tree/leaves or tree/fronds) by default, so it can only be used with meshes that you want to be bending without shaking (grass-like).

Using this shader with my own grass placement system it works super fast and was very easy to setup. I modified the shader to fade based on camera distance. Thank you so much for giving this shader to the community!!

do you still have that modified shader?

Yes I’m not home at the moment but it’s exactly he same shader except o.Alpha is detemined by the camera distance. I’ll look it up tonight and give you the code, but it’s a simple modification from the above shader.

awesome man no rush and thanks alot :slight_smile:

Hi! Can you help me on how to use it? I created a Shader archive and put the code in there, then in the materials of my tree i set to this custom one, but doesn’t work :frowning:

Hey thanks for the shader code. Exactly what I was looking for. Just need to randomize the movement a little bit and it will be perfect.

Hey, thanks for the great solution! Just wondering how to randomize the movement; belamessex have you managed to do it and if you have, could you share it please?

Excellent!

I have no experience with shaders. I’m hoping to tweak this shader so foliage is double-sided and the shake / warping responds appropriately to directional and spherical wind.

Any ideas on where to start?