From d73fc766987680aff0d72e3fe23c4b0d69f065b0 Mon Sep 17 00:00:00 2001 From: Voyager Date: Sun, 9 Aug 2026 12:44:45 -0400 Subject: [PATCH] Add wind motion to foliage --- world/materials/foliage_wind.gdshader | 44 +++++++++++++ world/materials/foliage_wind.gdshader.uid | 1 + world/regions/starter_island_region.gd | 79 +++++++++++++++++++++++ 3 files changed, 124 insertions(+) create mode 100644 world/materials/foliage_wind.gdshader create mode 100644 world/materials/foliage_wind.gdshader.uid diff --git a/world/materials/foliage_wind.gdshader b/world/materials/foliage_wind.gdshader new file mode 100644 index 0000000..a1220e7 --- /dev/null +++ b/world/materials/foliage_wind.gdshader @@ -0,0 +1,44 @@ +shader_type spatial; +render_mode cull_back, depth_draw_opaque; + +uniform vec4 albedo_color : source_color = vec4(1.0); +uniform float material_roughness = 1.0; +uniform float material_metallic = 0.0; +uniform float local_min_y = 0.0; +uniform float local_height = 1.0; +uniform float local_wind_strength = 0.08; +uniform float wind_speed = 0.9; +uniform float wind_phase = 0.0; + + +void vertex() { + float height_ratio = clamp( + (VERTEX.y - local_min_y) / max(local_height, 0.001), + 0.0, + 1.0 + ); + float bend_mask = smoothstep(0.08, 1.0, height_ratio); + vec3 world_position = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz; + float broad_wave = sin( + TIME * wind_speed + + dot(world_position.xz, vec2(0.23, 0.17)) + + wind_phase + ); + float leaf_flutter = sin( + TIME * wind_speed * 2.17 + + dot(world_position.xz, vec2(-0.41, 0.37)) + + wind_phase * 1.7 + ); + float displacement = ( + broad_wave * 0.72 + leaf_flutter * 0.28 + ) * local_wind_strength * bend_mask; + VERTEX.x += displacement; + VERTEX.z += displacement * 0.42; +} + + +void fragment() { + ALBEDO = albedo_color.rgb; + ROUGHNESS = material_roughness; + METALLIC = material_metallic; +} diff --git a/world/materials/foliage_wind.gdshader.uid b/world/materials/foliage_wind.gdshader.uid new file mode 100644 index 0000000..e5162a5 --- /dev/null +++ b/world/materials/foliage_wind.gdshader.uid @@ -0,0 +1 @@ +uid://c3q7l8m9n2v4x diff --git a/world/regions/starter_island_region.gd b/world/regions/starter_island_region.gd index 01e244f..2cbc24f 100644 --- a/world/regions/starter_island_region.gd +++ b/world/regions/starter_island_region.gd @@ -5,6 +5,14 @@ extends WorldRegion const FishingShopInteractionType = preload( "res://world/fishing_shop_interaction.gd" ) +const FOLIAGE_WIND_SHADER: Shader = preload( + "res://world/materials/foliage_wind.gdshader" +) +const FOLIAGE_MATERIAL_NAMES: Array[StringName] = [ + &"leaf", + &"leaf_light", + &"leaf_dark", +] @export_group("Owned Nodes") @export_node_path("MeshInstance3D") @@ -25,10 +33,14 @@ var fishing_shop_path: NodePath = ( ) @export_group("Terrain Collision") @export var rebuild_terrain_collision_on_ready: bool = false +@export_group("Foliage Wind") +@export_range(0.0, 0.4, 0.005) var foliage_wind_strength: float = 0.12 +@export_range(0.0, 4.0, 0.05) var foliage_wind_speed: float = 0.9 func _ready() -> void: if rebuild_terrain_collision_on_ready or not has_terrain_collision(): _build_terrain_collision() + _apply_foliage_wind() if Engine.is_editor_hint(): update_configuration_warnings() @@ -107,6 +119,73 @@ func _collect_terrain_mesh_instances(root: Node) -> Array[MeshInstance3D]: return mesh_instances +func _apply_foliage_wind() -> void: + var terrain_root: Node = get_node_or_null(terrain_visual_root_path) + if terrain_root == null: + return + for mesh_instance: MeshInstance3D in _collect_terrain_mesh_instances( + terrain_root + ): + _apply_foliage_wind_to_mesh(mesh_instance) + + +func _apply_foliage_wind_to_mesh(mesh_instance: MeshInstance3D) -> void: + if mesh_instance.mesh == null: + return + var bounds: AABB = mesh_instance.get_aabb() + var global_scale: Vector3 = mesh_instance.global_basis.get_scale().abs() + var horizontal_scale: float = maxf( + (global_scale.x + global_scale.z) * 0.5, + 0.001, + ) + var local_strength: float = foliage_wind_strength / horizontal_scale + var phase: float = fposmod( + mesh_instance.global_position.x * 0.73 + + mesh_instance.global_position.z * 0.41, + TAU, + ) + for surface_index: int in mesh_instance.mesh.get_surface_count(): + var source_material: Material = mesh_instance.get_active_material( + surface_index + ) + if not source_material is StandardMaterial3D: + continue + if not FOLIAGE_MATERIAL_NAMES.has( + StringName(source_material.resource_name) + ): + continue + var source_standard := source_material as StandardMaterial3D + var wind_material := ShaderMaterial.new() + wind_material.shader = FOLIAGE_WIND_SHADER + wind_material.set_shader_parameter( + "albedo_color", + source_standard.albedo_color, + ) + wind_material.set_shader_parameter( + "material_roughness", + source_standard.roughness, + ) + wind_material.set_shader_parameter( + "material_metallic", + source_standard.metallic, + ) + wind_material.set_shader_parameter("local_min_y", bounds.position.y) + wind_material.set_shader_parameter( + "local_height", + maxf(bounds.size.y, 0.001), + ) + wind_material.set_shader_parameter( + "local_wind_strength", + local_strength, + ) + wind_material.set_shader_parameter("wind_speed", foliage_wind_speed) + wind_material.set_shader_parameter("wind_phase", phase) + mesh_instance.set_surface_override_material( + surface_index, + wind_material, + ) + + func _get_configuration_warnings() -> PackedStringArray: var warnings := PackedStringArray() var visual_mesh := get_node_or_null(visual_mesh_path) as MeshInstance3D