update snow management

This commit is contained in:
2026-07-08 10:14:29 +02:00
parent 5341fdd3f5
commit e86da0c2bc
4 changed files with 128 additions and 16 deletions

View File

@@ -4,6 +4,15 @@ extends Node3D
const NOISE_TEXTURE: Texture2D = preload("res://core/daynight/noise.tres")
const WEATHER_SHADER: Material = preload("res://core/daynight/weather_overlay.tres")
const DYNAMIC_ENVIRONMENT_UPDATES_PER_FRAME: int = 32 #how many update of the environment (apply materials) will be done per frame
const FORCE_WEATHER_MATERIAL_PATH_PARTS: PackedStringArray = [
"/chunk/material/field.tres",
"/chunk/material/grassflat_chunk.tres",
"/chunk/material/path_chunk.tres",
"/chunk/material/railway_chunk.tres",
"/chunk/material/terrain.tres",
"/chunk/prop/house/house.tres",
"/chunk/prop/house/house_emissiv.tres",
]
@export var environment_config: EnvironmentConfig
@@ -157,9 +166,17 @@ func _restore_keyboard_action_events() -> void:
_blocked_keyboard_events_by_action.clear()
func _on_tree_node_added(node: Node) -> void:
if node.is_in_group("wind_node") or node.is_in_group("weather_node") or node.is_in_group("weather_vegetables_node"):
if _should_queue_dynamic_environment_node(node):
_queue_dynamic_environment_node(node)
func _should_queue_dynamic_environment_node(node: Node) -> bool:
if node.is_in_group("wind_node"):
return true
if node.is_in_group("weather_vegetables_node"):
return true
return node is Node3D
func _queue_dynamic_environment_node(node: Node) -> void:
if not is_instance_valid(node):
return
@@ -205,7 +222,7 @@ func _apply_dynamic_environment_materials(node: Node) -> void:
if node.is_in_group("wind_node"):
_apply_wind_noise_to_node(node, NOISE_TEXTURE)
if node.is_in_group("weather_node") or node.is_in_group("weather_vegetables_node"):
if node is Node3D:
_apply_weather_overlay_to_node(node, _get_weather_overlay_material(node))
if _should_ignore_weather_overlay(node):
@@ -231,11 +248,9 @@ func _apply_wind_noise_to_node(node: Node, noise_tex: Texture2D) -> void:
_apply_wind_noise_to_node(child, noise_tex)
func ApplyWeatherShaderToMaterials():
for node in get_tree().get_nodes_in_group("weather_node"):
_apply_weather_overlay_to_node(node, _get_weather_overlay_material(node))
for node in get_tree().get_nodes_in_group("weather_vegetables_node"):
_apply_weather_overlay_to_node(node, _get_weather_overlay_material(node))
var current_scene := get_tree().current_scene
if current_scene != null:
_apply_weather_overlay_to_node(current_scene, _get_weather_overlay_material(current_scene))
func _get_weather_overlay_material(node: Node) -> Material:
if not node.is_in_group("weather_overlay_no_noise"):
@@ -255,13 +270,21 @@ func _apply_weather_overlay_to_node(node: Node, material: Material) -> void:
return
if node is GeometryInstance3D:
if _should_clear_weather_overlay(node) or _geometry_uses_alpha_texture(node):
if _geometry_should_never_receive_weather_overlay(node):
node.material_overlay = null
elif _should_clear_weather_overlay(node) and not _geometry_forces_weather_overlay(node):
node.material_overlay = null
elif _geometry_uses_alpha_texture(node) or _geometry_should_skip_weather_overlay(node):
node.material_overlay = null
else:
node.material_overlay = material
var child_material := _get_weather_overlay_material(node)
if child_material == WEATHER_SHADER and material == weather_shader_no_noise:
child_material = material
for child in node.get_children():
_apply_weather_overlay_to_node(child, material)
_apply_weather_overlay_to_node(child, child_material)
func _clear_weather_overlay_from_node(node: Node) -> void:
if node is GeometryInstance3D:
@@ -289,11 +312,99 @@ func _geometry_uses_alpha_texture(node: GeometryInstance3D) -> bool:
return false
func _geometry_should_never_receive_weather_overlay(node: GeometryInstance3D) -> bool:
if node is GPUParticles3D:
return true
var node_name := String(node.name).to_lower()
return node_name.contains("cloud") or node_name.contains("fog") or node_name.contains("godray")
func _geometry_should_skip_weather_overlay(node: GeometryInstance3D) -> bool:
if _geometry_forces_weather_overlay(node):
return false
if _node_name_indicates_water(node):
return true
var has_material: bool = false
var all_materials_ignored: bool = true
var material_override := node.material_override
if material_override != null:
has_material = true
all_materials_ignored = all_materials_ignored and _material_ignores_weather_overlay(material_override)
if node is MeshInstance3D:
for surface_index in node.get_surface_override_material_count():
var surface_material: Material = node.get_surface_override_material(surface_index)
if surface_material == null:
continue
has_material = true
all_materials_ignored = all_materials_ignored and _material_ignores_weather_overlay(surface_material)
if node.mesh:
for surface_index in node.mesh.get_surface_count():
var mesh_material: Material = node.mesh.surface_get_material(surface_index)
if mesh_material == null:
continue
has_material = true
all_materials_ignored = all_materials_ignored and _material_ignores_weather_overlay(mesh_material)
return has_material and all_materials_ignored
func _geometry_forces_weather_overlay(node: GeometryInstance3D) -> bool:
if _material_forces_weather_overlay(node.material_override):
return true
if node is MeshInstance3D:
for surface_index in node.get_surface_override_material_count():
if _material_forces_weather_overlay(node.get_surface_override_material(surface_index)):
return true
if node.mesh:
for surface_index in node.mesh.get_surface_count():
if _material_forces_weather_overlay(node.mesh.surface_get_material(surface_index)):
return true
return false
func _material_forces_weather_overlay(material: Material) -> bool:
if material == null:
return false
var resource_path := material.resource_path.to_lower()
for path_part in FORCE_WEATHER_MATERIAL_PATH_PARTS:
if resource_path.contains(path_part):
return true
return false
func _node_name_indicates_water(node: Node) -> bool:
var node_name := String(node.name).to_lower()
return node_name.begins_with("water") or node_name.contains("/water")
func _material_ignores_weather_overlay(material: Material) -> bool:
if material == null:
return false
var resource_path := material.resource_path.to_lower()
if resource_path.contains("/water") or resource_path.contains("water_"):
return true
if resource_path.contains("/cloud/") or resource_path.contains("cloud"):
return true
var base_material := material as BaseMaterial3D
if base_material != null:
return base_material.transparency != BaseMaterial3D.TRANSPARENCY_DISABLED or base_material.albedo_color.a < 0.99
return false
func _shader_material_uses_alpha_texture(material: ShaderMaterial) -> bool:
if material == null or material.shader == null:
return false
return material.shader.code.find("alpha_texture") != -1
var shader_code := material.shader.code
return shader_code.find("alpha_texture") != -1 or shader_code.find("ALPHA") != -1 or shader_code.find("opacity") != -1
func _should_ignore_weather_overlay(node: Node) -> bool:
return node.is_in_group("weather_overlay_ignore")

View File

@@ -13,8 +13,9 @@ global uniform float global_snow_melt_speed = 0.1;
global uniform float global_snow_amount = 0.0;
global uniform vec4 global_snow_color;
global uniform float global_rain_intensity;
const float LEAVES_FULL_SNOW_START = 0.58;
const float LEAVES_FULL_SNOW_END = 0.86;
const float LEAVES_FULL_SNOW_START = 0.96;
const float LEAVES_FULL_SNOW_END = 1.0;
const float LEAVES_TOP_SNOW_COVERAGE = 0.75;
uniform sampler2D wind_noise : filter_linear_mipmap;
uniform bool billboard_enabled = true;
@@ -110,7 +111,7 @@ void fragment() {
float snow_amount = pow(get_snow_progress(), 0.55);
float top_mask = 1.0 - shifted_uv.y;
float snow_mask = smoothstep(1.0 - snow_amount * 1.35, 1.08 - snow_amount * 1.35, top_mask) * snow_visibility;
float snow_mask = smoothstep(1.0 - snow_amount * LEAVES_TOP_SNOW_COVERAGE, 1.08 - snow_amount * LEAVES_TOP_SNOW_COVERAGE, top_mask) * snow_visibility;
snow_mask *= step(0.01, snow_amount);
float full_snow_mask = smoothstep(LEAVES_FULL_SNOW_START, LEAVES_FULL_SNOW_END, snow_amount) * clamp(snow_visibility, 0.0, 1.0);
float final_snow_mask = max(snow_mask, full_snow_mask);

View File

@@ -17,8 +17,8 @@ global uniform float global_snow_cap_flatness_end = 0.96;
global uniform float global_snow_cap_noise_scale = 0.22;
global uniform float global_snow_cap_noise_strength = 0.18;
const float SNOW_VISUAL_RESPONSE = 0.55;
const float FULL_SNOW_VISUAL_START = 0.82;
const float FULL_SNOW_VISUAL_END = 0.98;
const float FULL_SNOW_VISUAL_START = 0.58;
const float FULL_SNOW_VISUAL_END = 0.86;
uniform bool snow_noise_enabled = true;
uniform float snow_noise_scale : hint_range(0.01, 1.0) = 0.15;
uniform float snow_edge_softness : hint_range(0.01, 0.5) = 0.2;
@@ -154,7 +154,7 @@ void fragment() {
float flat_accumulation = smoothstep(0.0, 0.35, v_snow_cap_mask) * snow_accumulation;
float snow_noise_mask = get_snow_noise_mask(world_pos, snow_progress);
float snow_variation = mix(0.75, 1.15, noise_val);
float snow_coverage = clamp(mix(snow_progress * 0.35, snow_progress * snow_variation, snow_noise_mask) + flat_accumulation * 0.15, 0.0, 1.0);
float snow_coverage = clamp(mix(snow_progress * 0.55, snow_progress * snow_variation, snow_noise_mask) + flat_accumulation * 0.2, 0.0, 1.0);
float snow_factor = max(snow_edge * snow_coverage, flat_accumulation);
float snow_opacity = clamp(snow_factor, 0.0, 1.0);
snow_opacity = max(snow_opacity, flat_accumulation * mix(0.45, 0.9, snow_noise_mask));