@tool class_name ShadowProxySystem extends Node3D @export_group("Shadow Settings") @export var enabled: bool = true: set(value): enabled = value if _shadow_mesh: _shadow_mesh.visible = enabled @export var shadow_texture: Texture2D: set(value): shadow_texture = value _update_shadow_texture() @export var shadow_opacity: float = 0.3: set(value): shadow_opacity = clampf(value, 0.0, 1.0) _update_shadow_material() @export var shadow_color: Color = Color(0.0, 0.0, 0.1): set(value): shadow_color = value _update_shadow_material() @export_group("Movement") @export var flow_direction: Vector2 = Vector2(1.0, 0.0) @export var flow_speed: float = 5.0 @export var sync_with_clouds: bool = true @export_group("Projection") @export var projection_size: float = 500.0: set(value): projection_size = value _update_projection_size() @export var projection_height: float = 0.1 @export var tiling: Vector2 = Vector2(2.0, 2.0): set(value): tiling = value _update_shadow_material() @export_group("Blur") @export var blur_enabled: bool = true @export var blur_amount: float = 0.5: set(value): blur_amount = clampf(value, 0.0, 1.0) _update_shadow_material() @export_group("Performance") @export var follow_camera: bool = true @export var update_distance: float = 50.0 var _shadow_mesh: MeshInstance3D var _shadow_material: ShaderMaterial var _uv_offset: Vector2 = Vector2.ZERO var _last_camera_position: Vector3 = Vector3.ZERO func _ready() -> void: _create_shadow_mesh() _create_shadow_material() _update_shadow_material() func _process(delta: float) -> void: if not enabled: return _update_flow(delta) if follow_camera: _follow_camera() func _create_shadow_mesh() -> void: if _shadow_mesh: _shadow_mesh.queue_free() _shadow_mesh = MeshInstance3D.new() _shadow_mesh.name = "CloudShadowMesh" var plane := PlaneMesh.new() plane.size = Vector2(projection_size, projection_size) _shadow_mesh.mesh = plane _shadow_mesh.position.y = projection_height _shadow_mesh.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF add_child(_shadow_mesh) func _create_shadow_material() -> void: _shadow_material = ShaderMaterial.new() _shadow_material.shader = _get_shadow_shader() if _shadow_mesh: _shadow_mesh.material_override = _shadow_material func _get_shadow_shader() -> Shader: var shader := Shader.new() shader.code = """ shader_type spatial; render_mode unshaded, depth_draw_opaque, cull_disabled, shadows_disabled; uniform sampler2D shadow_texture : source_color, filter_linear_mipmap, repeat_enable; uniform vec4 shadow_color : source_color = vec4(0.0, 0.0, 0.1, 1.0); uniform float opacity = 0.3; uniform vec2 tiling = vec2(2.0, 2.0); uniform vec2 uv_offset = vec2(0.0, 0.0); uniform float blur_amount = 0.5; uniform float edge_fade = 0.1; varying vec2 world_uv; void vertex() { world_uv = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xz; } void fragment() { vec2 uv = world_uv * tiling * 0.001 + uv_offset; vec4 shadow_sample; if (blur_amount > 0.01) { float blur = blur_amount * 0.01; shadow_sample = texture(shadow_texture, uv) * 0.25; shadow_sample += texture(shadow_texture, uv + vec2(blur, 0.0)) * 0.125; shadow_sample += texture(shadow_texture, uv - vec2(blur, 0.0)) * 0.125; shadow_sample += texture(shadow_texture, uv + vec2(0.0, blur)) * 0.125; shadow_sample += texture(shadow_texture, uv - vec2(0.0, blur)) * 0.125; shadow_sample += texture(shadow_texture, uv + vec2(blur, blur)) * 0.0625; shadow_sample += texture(shadow_texture, uv - vec2(blur, blur)) * 0.0625; shadow_sample += texture(shadow_texture, uv + vec2(blur, -blur)) * 0.0625; shadow_sample += texture(shadow_texture, uv + vec2(-blur, blur)) * 0.0625; } else { shadow_sample = texture(shadow_texture, uv); } float shadow_value = shadow_sample.r; vec2 local_uv = fract(world_uv * 0.001); float edge = smoothstep(0.0, edge_fade, local_uv.x); edge *= smoothstep(0.0, edge_fade, local_uv.y); edge *= smoothstep(0.0, edge_fade, 1.0 - local_uv.x); edge *= smoothstep(0.0, edge_fade, 1.0 - local_uv.y); ALBEDO = shadow_color.rgb; ALPHA = shadow_value * opacity * edge; } """ return shader func _update_shadow_material() -> void: if _shadow_material == null: return _shadow_material.set_shader_parameter("shadow_color", shadow_color) _shadow_material.set_shader_parameter("opacity", shadow_opacity) _shadow_material.set_shader_parameter("tiling", tiling) _shadow_material.set_shader_parameter("blur_amount", blur_amount if blur_enabled else 0.0) func _update_shadow_texture() -> void: if _shadow_material and shadow_texture: _shadow_material.set_shader_parameter("shadow_texture", shadow_texture) func _update_projection_size() -> void: if _shadow_mesh and _shadow_mesh.mesh is PlaneMesh: var plane := _shadow_mesh.mesh as PlaneMesh plane.size = Vector2(projection_size, projection_size) func _update_flow(delta: float) -> void: _uv_offset += flow_direction * flow_speed * delta * 0.001 _uv_offset.x = fmod(_uv_offset.x, 1.0) _uv_offset.y = fmod(_uv_offset.y, 1.0) if _shadow_material: _shadow_material.set_shader_parameter("uv_offset", _uv_offset) func _follow_camera() -> void: var camera := get_viewport().get_camera_3d() if camera == null: return var camera_pos := camera.global_position var distance := camera_pos.distance_to(_last_camera_position) if distance >= update_distance: _last_camera_position = camera_pos global_position.x = camera_pos.x global_position.z = camera_pos.z func set_flow(direction: Vector2, speed: float) -> void: flow_direction = direction.normalized() flow_speed = speed func set_shadow_appearance(color: Color, opacity_value: float, blur: float) -> void: shadow_color = color shadow_opacity = opacity_value blur_amount = blur _update_shadow_material() func sync_with_cloud_system(cloud_system: CloudSystem2D) -> void: if sync_with_clouds and cloud_system: flow_direction = cloud_system.global_wind_direction flow_speed = cloud_system.global_wind_speed * 5.0 func set_cloud_coverage(coverage: float) -> void: shadow_opacity = coverage * 0.5 _update_shadow_material()