Files
Trinittu/dynamic-sky/scripts/cloud_system_2d.gd
Overside srl 65bb24ff6a fix sky
2026-02-16 07:19:27 +01:00

306 lines
9.6 KiB
GDScript

@tool
class_name CloudSystem2D
extends Node3D
signal cloud_coverage_changed(coverage: float)
const MAX_LAYERS := 4
@export_group("Cloud Layers")
@export var layer_configs: Array[CloudLayerConfig] = []
@export var global_coverage: float = 0.5:
set(value):
global_coverage = clampf(value, 0.0, 1.0)
_update_coverage()
cloud_coverage_changed.emit(global_coverage)
@export_group("Evolution")
@export var evolution_enabled: bool = true
@export var evolution_speed: float = 0.01
@export var evolution_scale: float = 1.0
@export var evolution_seed: int = 0
@export_group("Wind Override")
@export var use_global_wind: bool = true
@export var global_wind_direction: Vector2 = Vector2(1.0, 0.0)
@export var global_wind_speed: float = 1.0
@export_group("Lighting")
@export var sun_direction: Vector3 = Vector3(0.0, 1.0, 0.0)
@export var sun_color: Color = Color.WHITE
@export var sun_intensity: float = 1.0
@export var moon_direction: Vector3 = Vector3(0.0, -1.0, 0.0)
@export var moon_color: Color = Color(0.7, 0.8, 1.0)
@export var moon_intensity: float = 0.3
@export_group("Performance")
@export var update_rate: float = 60.0
var _cloud_materials: Array[ShaderMaterial] = []
var _cloud_meshes: Array[MeshInstance3D] = []
var _time_accumulator: float = 0.0
var _evolution_offset: float = 0.0
var _update_timer: float = 0.0
func _ready() -> void:
if layer_configs.is_empty():
_initialize_default_layers()
_create_cloud_meshes()
_update_all_layers()
func _process(delta: float) -> void:
_update_timer += delta
var update_interval := 1.0 / update_rate
if _update_timer >= update_interval:
_update_timer = 0.0
_time_accumulator += delta * update_rate * update_interval
if evolution_enabled:
_evolution_offset += delta * evolution_speed
_update_flow(delta)
_update_lighting()
func _initialize_default_layers() -> void:
layer_configs.clear()
var low_layer := CloudLayerConfig.new()
low_layer.layer_type = CloudLayerConfig.LayerType.LOW
low_layer.opacity = 0.9
low_layer.flow_speed = 0.03
low_layer.parallax_depth = 0.3
low_layer.tiling = Vector2(2.0, 2.0)
layer_configs.append(low_layer)
var mid_layer := CloudLayerConfig.new()
mid_layer.layer_type = CloudLayerConfig.LayerType.MID
mid_layer.opacity = 0.7
mid_layer.flow_speed = 0.02
mid_layer.parallax_depth = 0.6
mid_layer.tiling = Vector2(3.0, 3.0)
layer_configs.append(mid_layer)
var high_layer := CloudLayerConfig.new()
high_layer.layer_type = CloudLayerConfig.LayerType.HIGH
high_layer.opacity = 0.5
high_layer.flow_speed = 0.01
high_layer.parallax_depth = 0.9
high_layer.tiling = Vector2(4.0, 4.0)
layer_configs.append(high_layer)
var wisps_layer := CloudLayerConfig.new()
wisps_layer.layer_type = CloudLayerConfig.LayerType.WISPS
wisps_layer.opacity = 0.3
wisps_layer.flow_speed = 0.015
wisps_layer.parallax_depth = 1.0
wisps_layer.tiling = Vector2(6.0, 6.0)
layer_configs.append(wisps_layer)
func _create_cloud_meshes() -> void:
for mesh in _cloud_meshes:
if is_instance_valid(mesh):
mesh.queue_free()
_cloud_meshes.clear()
_cloud_materials.clear()
for i in layer_configs.size():
var config := layer_configs[i]
if not config.enabled:
continue
var mesh_instance := MeshInstance3D.new()
mesh_instance.name = "CloudLayer_%d" % i
var quad := QuadMesh.new()
quad.size = Vector2(1000.0, 1000.0)
quad.orientation = PlaneMesh.FACE_Y
mesh_instance.mesh = quad
var material := _create_cloud_material(config)
mesh_instance.material_override = material
var height := config.get_layer_height() * 100.0 + 50.0
mesh_instance.position.y = height
add_child(mesh_instance)
_cloud_meshes.append(mesh_instance)
_cloud_materials.append(material)
func _create_cloud_material(config: CloudLayerConfig) -> ShaderMaterial:
var material := ShaderMaterial.new()
material.shader = _get_cloud_shader()
material.set_shader_parameter("tiling", config.tiling)
material.set_shader_parameter("opacity", config.opacity * global_coverage)
material.set_shader_parameter("color_tint", config.color_tint)
material.set_shader_parameter("shadow_color", config.shadow_color)
material.set_shader_parameter("flow_direction", config.flow_direction)
material.set_shader_parameter("flow_speed", config.flow_speed)
material.set_shader_parameter("light_influence", config.light_influence)
material.set_shader_parameter("rim_glow_intensity", config.rim_glow_intensity)
if config.texture:
material.set_shader_parameter("cloud_texture", config.texture)
else:
var procedural_tex := ProceduralCloudTexture.create_default_cloud_texture(config.layer_type)
material.set_shader_parameter("cloud_texture", procedural_tex)
return material
func _get_cloud_shader() -> Shader:
var shader := Shader.new()
shader.code = """
shader_type spatial;
render_mode unshaded, depth_draw_never, cull_disabled;
uniform sampler2D cloud_texture : source_color, filter_linear_mipmap, repeat_enable;
uniform vec2 tiling = vec2(4.0, 4.0);
uniform float opacity = 0.8;
uniform vec4 color_tint : source_color = vec4(1.0);
uniform vec4 shadow_color : source_color = vec4(0.6, 0.6, 0.7, 1.0);
uniform vec2 flow_direction = vec2(1.0, 0.0);
uniform float flow_speed = 0.02;
uniform float flow_offset = 0.0;
uniform vec3 light_direction = vec3(0.0, 1.0, 0.0);
uniform vec4 light_color : source_color = vec4(1.0);
uniform float light_intensity = 1.0;
uniform float light_influence = 1.0;
uniform float rim_glow_intensity = 0.3;
uniform float evolution_offset = 0.0;
uniform float coverage = 1.0;
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 += flow_direction * flow_offset;
vec2 distorted_uv = uv + vec2(sin(uv.y * 10.0 + evolution_offset) * 0.01, cos(uv.x * 10.0 + evolution_offset) * 0.01);
vec4 cloud_sample = texture(cloud_texture, distorted_uv);
float cloud_alpha = cloud_sample.r * opacity * coverage;
float light_dot = dot(normalize(light_direction), vec3(0.0, 1.0, 0.0));
float light_factor = mix(0.5, 1.0, (light_dot + 1.0) * 0.5) * light_influence;
vec3 lit_color = mix(shadow_color.rgb, color_tint.rgb, light_factor);
lit_color *= light_color.rgb * light_intensity;
float rim = pow(1.0 - cloud_sample.r, 2.0) * rim_glow_intensity;
lit_color += light_color.rgb * rim;
ALBEDO = lit_color;
ALPHA = cloud_alpha;
}
"""
return shader
func _update_flow(delta: float) -> void:
for i in _cloud_materials.size():
if i >= layer_configs.size():
continue
var config := layer_configs[i]
var material := _cloud_materials[i]
var wind_dir := global_wind_direction if use_global_wind else config.flow_direction
var wind_spd := global_wind_speed if use_global_wind else 1.0
var current_offset: Variant = material.get_shader_parameter("flow_offset")
var flow_offset: float = current_offset if current_offset != null else 0.0
flow_offset += config.flow_speed * wind_spd * delta
material.set_shader_parameter("flow_offset", flow_offset)
material.set_shader_parameter("flow_direction", wind_dir)
material.set_shader_parameter("evolution_offset", _evolution_offset)
func _update_lighting() -> void:
for material in _cloud_materials:
material.set_shader_parameter("light_direction", sun_direction)
material.set_shader_parameter("light_color", sun_color)
material.set_shader_parameter("light_intensity", sun_intensity)
func _update_coverage() -> void:
for i in _cloud_materials.size():
if i >= layer_configs.size():
continue
var config := layer_configs[i]
_cloud_materials[i].set_shader_parameter("coverage", global_coverage)
_cloud_materials[i].set_shader_parameter("opacity", config.opacity)
func _update_all_layers() -> void:
for i in _cloud_materials.size():
if i >= layer_configs.size():
continue
var config := layer_configs[i]
var material := _cloud_materials[i]
material.set_shader_parameter("tiling", config.tiling)
material.set_shader_parameter("opacity", config.opacity)
material.set_shader_parameter("color_tint", config.color_tint)
material.set_shader_parameter("shadow_color", config.shadow_color)
material.set_shader_parameter("flow_direction", config.flow_direction)
material.set_shader_parameter("flow_speed", config.flow_speed)
material.set_shader_parameter("light_influence", config.light_influence)
material.set_shader_parameter("rim_glow_intensity", config.rim_glow_intensity)
material.set_shader_parameter("coverage", global_coverage)
func set_layer_enabled(layer_index: int, enabled: bool) -> void:
if layer_index < 0 or layer_index >= _cloud_meshes.size():
return
_cloud_meshes[layer_index].visible = enabled
func set_layer_opacity(layer_index: int, opacity: float) -> void:
if layer_index < 0 or layer_index >= _cloud_materials.size():
return
if layer_index < layer_configs.size():
layer_configs[layer_index].opacity = opacity
_cloud_materials[layer_index].set_shader_parameter("opacity", opacity)
func set_layer_color(layer_index: int, color: Color) -> void:
if layer_index < 0 or layer_index >= _cloud_materials.size():
return
if layer_index < layer_configs.size():
layer_configs[layer_index].color_tint = color
_cloud_materials[layer_index].set_shader_parameter("color_tint", color)
func set_global_wind(direction: Vector2, speed: float) -> void:
global_wind_direction = direction.normalized()
global_wind_speed = speed
func set_sun_lighting(direction: Vector3, color: Color, intensity: float) -> void:
sun_direction = direction.normalized()
sun_color = color
sun_intensity = intensity
_update_lighting()
func set_moon_lighting(direction: Vector3, color: Color, intensity: float) -> void:
moon_direction = direction.normalized()
moon_color = color
moon_intensity = intensity
func rebuild_layers() -> void:
_create_cloud_meshes()
_update_all_layers()