├── README.md ├── GrassMesh.res ├── project.godot ├── Character.gd └── grass_shader.gdshader /README.md: -------------------------------------------------------------------------------- 1 | CC0 MultiMesh Grass Shader for Godot 2 | -------------------------------------------------------------------------------- /GrassMesh.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Malidos/Grass-Shader-Example/HEAD/GrassMesh.res -------------------------------------------------------------------------------- /project.godot: -------------------------------------------------------------------------------- 1 | ; Engine configuration file. 2 | ; It's best edited using the editor UI and not directly, 3 | ; since the parameters that go here are not all obvious. 4 | ; 5 | ; Format: 6 | ; [section] ; section goes between [] 7 | ; param=value ; assign values to parameters 8 | 9 | config_version=5 10 | 11 | [application] 12 | 13 | config/name="GrassExample" 14 | run/main_scene="res://ExampleScene.tscn" 15 | config/features=PackedStringArray("4.2", "Forward Plus") 16 | -------------------------------------------------------------------------------- /Character.gd: -------------------------------------------------------------------------------- 1 | extends CharacterBody3D 2 | 3 | 4 | const SPEED = 5.0 5 | const JUMP_VELOCITY = 4.5 6 | 7 | # Get the gravity from the project settings to be synced with RigidBody nodes. 8 | var gravity = ProjectSettings.get_setting("physics/3d/default_gravity") 9 | 10 | 11 | func _physics_process(delta): 12 | # Add the gravity. 13 | if not is_on_floor(): 14 | velocity.y -= gravity * delta 15 | 16 | # Handle jump. 17 | if Input.is_action_just_pressed("ui_accept") and is_on_floor(): 18 | velocity.y = JUMP_VELOCITY 19 | 20 | # Get the input direction and handle the movement/deceleration. 21 | # As good practice, you should replace UI actions with custom gameplay actions. 22 | var input_dir = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down") 23 | var direction = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized() 24 | if direction: 25 | velocity.x = direction.x * SPEED 26 | velocity.z = direction.z * SPEED 27 | else: 28 | velocity.x = move_toward(velocity.x, 0, SPEED) 29 | velocity.z = move_toward(velocity.z, 0, SPEED) 30 | move_and_slide() 31 | 32 | # Push the Character Position to the Shader 33 | var grass_node = find_parent("RootNode").get_child(3) 34 | grass_node.set_deferred("instance_shader_parameters/player_position", position + Vector3(0, -0.1, 0)) 35 | -------------------------------------------------------------------------------- /grass_shader.gdshader: -------------------------------------------------------------------------------- 1 | shader_type spatial; 2 | render_mode cull_disabled, diffuse_toon, specular_schlick_ggx; 3 | // Nice Shader by @_Malido ^^ 4 | 5 | uniform vec3 top_color: source_color; 6 | uniform vec3 bottom_color: source_color; 7 | uniform float ambient_occlusion_factor: hint_range(0.0, 1.0, 0.01) = 0.3; 8 | uniform float specular_strength: hint_range(0.0, 1.0, 0.01) = 0.4; 9 | uniform float player_displacement_strength: hint_range(0.0, 1.0, 0.01) = 0.4; 10 | uniform float player_displacement_size: hint_range(0.0, 2.0, 0.01) = 1.0; 11 | 12 | uniform vec3 wind_direction; // Use a negative y component to give it an extra touch (For displacement effect and noise scroll direction) 13 | uniform float wind_strength: hint_range(0.0, 1.0, 0.01) = 0.3; 14 | uniform sampler2D wind_noise; // Periln FBM Noise looks Best 15 | uniform float wind_noise_size: hint_range(0.0, 1.0, 0.001) = 0.05; // high values dont work well 16 | uniform float wind_noise_speed: hint_range(0.0, 1.0, 0.001) = 0.1; 17 | 18 | // Instance the Player Position through a GDScript in the _physics_process 19 | instance uniform vec3 player_position; 20 | 21 | void vertex() { 22 | vec3 world_position = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz; 23 | 24 | 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; 25 | vec3 wind_vector = (vec4(wind_texture * normalize(wind_direction) * wind_strength, 0.0) * MODEL_MATRIX).xyz; 26 | 27 | float player_height = smoothstep(1.0, 0.0, length(player_position.y - world_position.y + 0.3)); 28 | vec3 push_direction = vec3(world_position - player_position) * vec3(1 , -0.3 ,1); 29 | float player_position_factor = smoothstep(player_displacement_size, 0.0, length(push_direction)); 30 | 31 | vec3 player_push_vector = (vec4(normalize(push_direction), 0.0) * MODEL_MATRIX).xyz; 32 | 33 | // Apply Player Position displacement 34 | VERTEX += player_push_vector * (1.0 - UV.y) * player_position_factor * player_displacement_strength * player_height; 35 | // Apply Wind displacement linearly 36 | VERTEX += wind_vector * (1.0 - UV.y) * (1.0 - player_position_factor * 0.7); 37 | 38 | // A new normal correction, which aligns the normals of the mesh facing upwards no matter the original direction. 39 | NORMAL = vec3(0.0, 1.0, 0.0); 40 | } 41 | 42 | void fragment() { 43 | vec3 color = mix(bottom_color, top_color, 1.0 - UV.y); 44 | 45 | // Add fake ambient occlusion by darkening the base of the mesh 46 | float ao_fallof = pow(UV.y, 5.0); 47 | vec3 ao_color = bottom_color * (1.0 - ambient_occlusion_factor); 48 | 49 | ALBEDO = mix(color, ao_color, ao_fallof); 50 | ROUGHNESS = 0.4; 51 | 52 | // Increase the Specular with Grass Height 53 | SPECULAR *= (1.0 - UV.y) * specular_strength; 54 | 55 | // Just removing some funny shading 56 | if (!FRONT_FACING) { 57 | NORMAL = -NORMAL; 58 | } 59 | } --------------------------------------------------------------------------------