## Cloud shadow layer — ground-level plane that receives 3D noise textures
## and cloud params from SkyDome so shadows match volumetric clouds exactly.
class_name ShadowLayer
extends CloudLayer
const SHADOW_SHADER = preload("res://addons/sky3d_extended/shaders/cloud_shadow_ground.gdshader")
enum ShadowMode { ANALYTICAL, DECAL, BOTH, NONE }
var _mesh: MeshInstance3D
var _material: ShaderMaterial
var shadow_mode: ShadowMode = ShadowMode.ANALYTICAL
var shadow_darkness: float = 0.7
func _init() -> void:
name = "Shadows"
func get_params() -> Array[Dictionary]:
return [
{"name": "shadow_darkness", "min": 0.0, "max": 1.0, "step": 0.01, "value": shadow_darkness},
]
func set_param(param_name: String, value: float) -> void:
match param_name:
"shadow_darkness":
shadow_darkness = value
if _material:
_material.set_shader_parameter("shadow_darkness", value)
func get_param(param_name: String) -> float:
match param_name:
"shadow_darkness": return shadow_darkness
return 0.0
func create(parent: Node3D) -> void:
_mesh = MeshInstance3D.new()
_mesh.name = "CloudShadowPlane"
var plane := PlaneMesh.new()
plane.size = Vector2(10000, 10000)
plane.subdivide_width = 4
plane.subdivide_depth = 4
_mesh.mesh = plane
_mesh.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
_material = ShaderMaterial.new()
_material.shader = SHADOW_SHADER
_material.render_priority = -3
_mesh.material_override = _material
_mesh.position.y = 1.0
_mesh.visible = shadow_mode in [ShadowMode.DECAL, ShadowMode.BOTH]
parent.add_child(_mesh)
_material.set_shader_parameter("shadow_darkness", shadow_darkness)
func destroy() -> void:
if is_instance_valid(_mesh):
_mesh.queue_free()
_mesh = null
_material = null
func update(shared: CloudSharedState, _delta: float) -> void:
if not _mesh:
return
_mesh.visible = shadow_mode in [ShadowMode.DECAL, ShadowMode.BOTH]
# Follow camera horizontally at ground level
_mesh.global_position = Vector3(
shared.camera_pos.x,
1.0,
shared.camera_pos.z
)
if _material:
_material.set_shader_parameter("sun_direction", shared.sun_direction)
func update_lighting(_shared: CloudSharedState) -> void:
pass
func set_visible(value: bool) -> void:
if _mesh:
_mesh.visible = value and shadow_mode in [ShadowMode.DECAL, ShadowMode.BOTH]
func set_shadow_mode(mode: ShadowMode) -> void:
shadow_mode = mode
if _mesh:
_mesh.visible = shadow_mode in [ShadowMode.DECAL, ShadowMode.BOTH]
## Return the ShaderMaterial so SkyDome can push noise/cloud params directly.
func get_material() -> ShaderMaterial:
return _material
## Get params dict for analytical shadow uniforms on other materials.
func get_cloud_shadow_params(wind_offset: Vector2, weather: float) -> Dictionary:
return {
"cloud_shadow_altitude": (1500.0 + 3500.0) * 0.5,
"cloud_shadow_scale": 1.0 / 10000.0,
"cloud_shadow_wind_offset": wind_offset,
"cloud_shadow_coverage": lerpf(0.25, 0.95, weather),
"cloud_shadow_darkness": shadow_darkness,
}
## Apply cloud shadow uniforms to a ShaderMaterial.
func apply_cloud_shadow_params(mat: ShaderMaterial, wind_offset: Vector2, weather: float) -> void:
var params := get_cloud_shadow_params(wind_offset, weather)
for key in params:
mat.set_shader_parameter(key, params[key])