Stylized Multimesh Grass Shader

Another grass shader with wind animation and player interaction using simple parameters.
Now supports day and night cycle!

 

Set up

  • Create a MultiMeshInstance with your grass mesh.
  • Apply the spatial shader as a material override.
  • Set colors and other parameters
  • Set the wind noise to a Perlin FBM FastNoise

If you want to use textured grass remove the Albedo code in the Fragment Shader and replace it with your Texture.

To set the player position for the shader edit the instance parameter through a script.

func _physics_process(delta):

$MultiMeshInstance3D.set(“instance_shader_parameters/player_position”, player_position)

 

My Parameters
– Top Color: 6ab144
– Bottom Color: 2d5518
– Ambient Occlusion Factor: 0.5
– Specular Strength: 0.4
– Player Displacement Strength: 0.5
– Player Displacement Size: 0.85
– Wind Direction: (0.4, -0.3, 0.81) -> It must not be Zero at all times! Else the Grass will disappear 
– Wind Strength: 0.4
– Wind Noise Size: 0.06
– Wind Noise Speed: 0.14
– Wind Noise: Seamless Perlin FBM Noise
Gain: 0.5, Lacunarity: 1.5, Octaves: 3

Shader code
shader_type spatial;
render_mode cull_disabled, diffuse_toon, specular_schlick_ggx;
// Nice Shader by @_Malido ^^

uniform vec3 top_color: source_color;
uniform vec3 bottom_color: source_color;
uniform float ambient_occlusion_factor: hint_range(0.0, 1.0, 0.01) = 0.3;
uniform float specular_strength: hint_range(0.0, 1.0, 0.01) = 0.4;
uniform float player_displacement_strength: hint_range(0.0, 1.0, 0.01) = 0.4;
uniform float player_displacement_size: hint_range(0.0, 2.0, 0.01) = 1.0;

uniform vec3 wind_direction; // Use a negative y component to give it an extra touch (For displacement effect and noise scroll direction)
uniform float wind_strength: hint_range(0.0, 1.0, 0.01) = 0.3;
uniform sampler2D wind_noise; // Periln FBM Noise looks Best
uniform float wind_noise_size: hint_range(0.0, 1.0, 0.001) = 0.05; // high values dont work well
uniform float wind_noise_speed: hint_range(0.0, 1.0, 0.001) = 0.1;

// Instance the Player Position through a GDScript in the _physics_process
instance uniform vec3 player_position;

void vertex() {
	vec3 world_position = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
	
	vec3 wind_texture = texture(wind_noise, world_position.xz * wind_noise_size + normalize(-wind_direction.xz) * (TIME + UV.y / 2.5) * wind_noise_speed).rgb;
	vec3 wind_vector = (vec4(wind_texture * normalize(wind_direction) * wind_strength, 0.0) * MODEL_MATRIX).xyz;
	
	float player_height = smoothstep(1.0, 0.0, length(player_position.y - world_position.y + 0.3));
	vec3 push_direction = vec3(world_position - player_position) * vec3(1 , -0.3 ,1);
	float player_position_factor = smoothstep(player_displacement_size, 0.0, length(push_direction));
	
	vec3 player_push_vector = (vec4(normalize(push_direction), 0.0) * MODEL_MATRIX).xyz;
	
	// Apply Player Position displacement
	VERTEX += player_push_vector * (1.0 - UV.y) * player_position_factor * player_displacement_strength * player_height;
	// Apply Wind displacement linearly
	VERTEX += wind_vector * (1.0 - UV.y) * (1.0 - player_position_factor * 0.7);
	
	// A new normal correction, which aligns the normals of the mesh facing upwards no matter the original direction.
	NORMAL = vec3(0.0, 1.0, 0.0);
}

void fragment() {
	vec3 color = mix(bottom_color, top_color, 1.0 - UV.y);
	
	// Add fake ambient occlusion by darkening the base of the mesh
	float ao_fallof = pow(UV.y, 5.0);
	vec3 ao_color = bottom_color * (1.0 - ambient_occlusion_factor);
	
	ALBEDO = mix(color, ao_color, ao_fallof);
	ROUGHNESS = 0.4;
	
	// Increase the Specular with Grass Height
	SPECULAR *= (1.0 - UV.y) * specular_strength;
	
	// Just removing some funny shading
	if (!FRONT_FACING) {
		NORMAL = -NORMAL;
	}
}
Tags
3d, ambient occlusion, displacement, grass, Interaction, stylized, sway, updated, wind
The shader code and all code snippets in this post are under CC0 license and can be used freely without the author's permission. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

More from Malido

Related shaders

guest

7 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
bujupah
bujupah
1 year ago

Can you please share an example? I am bit confused on how to set this up!

Instructions
1 year ago

This is so cool, thanks for sharing!

Kieran
Kieran
1 year ago

This looks amazing!

Last edited 1 year ago by Kieran
cnidus
cnidus
9 months ago

newbie here.. how would I go about reducing the size of the whole thing? I tried changing the InstanceCount on the Multimesh (in the example) and it didn’t work 🙁

zpecc
zpecc
2 months ago
Reply to  cnidus

Changing the InstanceCount directly is currently quite buggy in Godot (as of 4.5). If you have the example project open, what you need to do is change the size of the Ground mesh (which is just a normal MeshInstance3D).
Then select the MultiMesh node and click the button above the game view that says “MultiMesh”, and then “Populate Surface”.
Choose the Ground node as “Target Surface”, and the GrassSourceMesh node as “SourceMesh”, then choose how many grass objects you want under “Amount” and hit “Populate”.

(I realize this comment is half a year old, but if anyone else is struggling I thought I’d answer)

zpecc
zpecc
2 months ago

Looks great! Even though you clearly point this out in your description I’d like to repeat that the grass will not show at all if Wind Direction is set to zero. Confused me quite a bit for 15 minutes or so… (I’m terrible at paying attention to details…)