feat(environment): add painterly skies and living meadows
This commit is contained in:
+132
-14
@@ -1,4 +1,4 @@
|
||||
[gd_scene load_steps=68 format=3]
|
||||
[gd_scene load_steps=71 format=3]
|
||||
|
||||
[ext_resource type="Terrain3DAssets" path="res://terrain/jajce/assets.tres" id="1_assets"]
|
||||
[ext_resource type="PackedScene" path="res://world/resource_nodes/ResourceNode.tscn" id="2_resource"]
|
||||
@@ -21,8 +21,8 @@
|
||||
[ext_resource type="PackedScene" path="res://addons/terrain_3d/extras/particle_example/Terrain3DParticles.tscn" id="19_terrain_particles"]
|
||||
[ext_resource type="Material" path="res://world/jajce/materials/cozy_grass_material.tres" id="20_grass_material"]
|
||||
[ext_resource type="Script" path="res://world/jajce/GrassInteractionController.gd" id="21_grass_controller"]
|
||||
[ext_resource type="PackedScene" path="res://assets/foliage/tree.glb" id="22_conifer"]
|
||||
[ext_resource type="PackedScene" path="res://assets/foliage/tree-high.glb" id="23_conifer_high"]
|
||||
[ext_resource type="PackedScene" path="res://world/jajce/PainterlyConifer.tscn" id="22_conifer"]
|
||||
[ext_resource type="PackedScene" path="res://world/jajce/PainterlyConifer.tscn" id="23_conifer_high"]
|
||||
[ext_resource type="PackedScene" path="res://world/jajce/CozyButterfly.tscn" id="24_butterfly"]
|
||||
[ext_resource type="Material" path="res://world/jajce/materials/cozy_grass_process_material.tres" id="25_grass_process"]
|
||||
[ext_resource type="PackedScene" path="res://world/jajce/RiverbankResourceCluster.tscn" id="26_resource_cluster"]
|
||||
@@ -33,8 +33,12 @@
|
||||
|
||||
[ext_resource type="ArrayMesh" path="res://assets/storybook/meadow_grass.res" id="31_meadow"]
|
||||
[ext_resource type="Shader" path="res://world/jajce/materials/storybook_sky.gdshader" id="32_sky"]
|
||||
[ext_resource type="Texture2D" path="res://assets/painterly/painted_sky.png" id="36_painted_sky"]
|
||||
[ext_resource type="PackedScene" path="res://world/jajce/StylizedBerryPatch.tscn" id="33_berry"]
|
||||
|
||||
[ext_resource type="Script" path="res://world/jajce/PainterlyMeadow.gd" id="34_meadow"]
|
||||
[ext_resource type="PackedScene" path="res://world/jajce/HarvestableTreePresentation.tscn" id="35_harvest_tree"]
|
||||
|
||||
[sub_resource type="Terrain3DMaterial" id="Terrain3DMaterial_jajce"]
|
||||
_shader_parameters = {
|
||||
"auto_base_texture": 0,
|
||||
@@ -42,8 +46,8 @@ _shader_parameters = {
|
||||
"auto_slope": 0.62,
|
||||
"blend_sharpness": 0.88,
|
||||
"enable_macro_variation": true,
|
||||
"macro_variation1": Color(0.84, 0.95, 0.73, 1),
|
||||
"macro_variation2": Color(0.60, 0.79, 0.61, 1),
|
||||
"macro_variation1": Color(0.91, 0.98, 0.80, 1),
|
||||
"macro_variation2": Color(0.76, 0.89, 0.76, 1),
|
||||
"macro_variation_slope": 0.48
|
||||
}
|
||||
world_background = 0
|
||||
@@ -179,6 +183,7 @@ size = Vector3(2.9, 0.08, 1.4)
|
||||
[sub_resource type="ShaderMaterial" id="SkyMaterial_jajce"]
|
||||
resource_local_to_scene = true
|
||||
shader = ExtResource("32_sky")
|
||||
shader_parameter/painted_panorama = ExtResource("36_painted_sky")
|
||||
|
||||
[sub_resource type="Sky" id="Sky_jajce"]
|
||||
resource_local_to_scene = true
|
||||
@@ -188,17 +193,17 @@ sky_material = SubResource("SkyMaterial_jajce")
|
||||
resource_local_to_scene = true
|
||||
background_mode = 2
|
||||
sky = SubResource("Sky_jajce")
|
||||
background_energy_multiplier = 0.9
|
||||
ambient_light_source = 3
|
||||
ambient_light_color = Color(0.95, 0.9, 0.76, 1)
|
||||
ambient_light_energy = 0.75
|
||||
background_energy_multiplier = 1.0
|
||||
ambient_light_source = 2
|
||||
ambient_light_color = Color(0.65, 0.8, 0.87, 1)
|
||||
ambient_light_energy = 0.65
|
||||
reflected_light_source = 2
|
||||
tonemap_mode = 2
|
||||
glow_enabled = true
|
||||
glow_normalized = true
|
||||
glow_intensity = 0.18
|
||||
glow_intensity = 0.12
|
||||
glow_strength = 0.9
|
||||
glow_bloom = 0.03
|
||||
glow_bloom = 0.015
|
||||
fog_enabled = true
|
||||
fog_light_color = Color(0.88, 0.84, 0.7, 1)
|
||||
fog_light_energy = 0.78
|
||||
@@ -216,8 +221,8 @@ volumetric_fog_length = 96.0
|
||||
volumetric_fog_detail_spread = 1.6
|
||||
adjustment_enabled = true
|
||||
adjustment_brightness = 1.0
|
||||
adjustment_contrast = 1.03
|
||||
adjustment_saturation = 1.04
|
||||
adjustment_contrast = 1.015
|
||||
adjustment_saturation = 1.07
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="Material_guard"]
|
||||
albedo_color = Color(0.55, 0.45, 0.35, 1)
|
||||
@@ -384,6 +389,9 @@ scale = Vector3(1.1, 1, 9)
|
||||
cast_shadow = 0
|
||||
mesh = SubResource("Mesh_path_readability")
|
||||
|
||||
[node name="PainterlyMeadow" type="Node3D" parent="."]
|
||||
script = ExtResource("34_meadow")
|
||||
|
||||
[node name="FoliageRoot" type="Node3D" parent="."]
|
||||
|
||||
[node name="Tree_West_01" parent="FoliageRoot" instance=ExtResource("4_tree")]
|
||||
@@ -448,6 +456,91 @@ position = Vector3(8, 0, -31)
|
||||
rotation_degrees = Vector3(0, -20, 0)
|
||||
scale = Vector3(2.65, 2.65, 2.65)
|
||||
|
||||
[node name="Woodland_Edge_01" parent="FoliageRoot" instance=ExtResource("4_tree")]
|
||||
position = Vector3(-34, 0, -29)
|
||||
rotation_degrees = Vector3(0, 0, 0)
|
||||
scale = Vector3(1.3, 1.3, 1.3)
|
||||
|
||||
[node name="Woodland_Edge_02" parent="FoliageRoot" instance=ExtResource("4_tree")]
|
||||
position = Vector3(-39, 0, -25)
|
||||
rotation_degrees = Vector3(0, 137, 0)
|
||||
scale = Vector3(1.6, 1.6, 1.6)
|
||||
|
||||
[node name="Woodland_Edge_03" parent="FoliageRoot" instance=ExtResource("4_tree")]
|
||||
position = Vector3(-44, 0, -18)
|
||||
rotation_degrees = Vector3(0, 274, 0)
|
||||
scale = Vector3(1.5, 1.5, 1.5)
|
||||
|
||||
[node name="Woodland_Edge_04" parent="FoliageRoot" instance=ExtResource("4_tree")]
|
||||
position = Vector3(-45, 0, -8)
|
||||
rotation_degrees = Vector3(0, 51, 0)
|
||||
scale = Vector3(1.8, 1.8, 1.8)
|
||||
|
||||
[node name="Woodland_Edge_05" parent="FoliageRoot" instance=ExtResource("4_tree")]
|
||||
position = Vector3(-43, 0, 4)
|
||||
rotation_degrees = Vector3(0, 188, 0)
|
||||
scale = Vector3(1.15, 1.15, 1.15)
|
||||
|
||||
[node name="Woodland_Edge_06" parent="FoliageRoot" instance=ExtResource("4_tree")]
|
||||
position = Vector3(-41, 0, 16)
|
||||
rotation_degrees = Vector3(0, 325, 0)
|
||||
scale = Vector3(1.3, 1.3, 1.3)
|
||||
|
||||
[node name="Woodland_Edge_07" parent="FoliageRoot" instance=ExtResource("4_tree")]
|
||||
position = Vector3(-36, 0, 28)
|
||||
rotation_degrees = Vector3(0, 102, 0)
|
||||
scale = Vector3(1.2, 1.2, 1.2)
|
||||
|
||||
[node name="Woodland_Edge_08" parent="FoliageRoot" instance=ExtResource("4_tree")]
|
||||
position = Vector3(-26, 0, -35)
|
||||
rotation_degrees = Vector3(0, 239, 0)
|
||||
scale = Vector3(1.2, 1.2, 1.2)
|
||||
|
||||
[node name="Woodland_Edge_09" parent="FoliageRoot" instance=ExtResource("4_tree")]
|
||||
position = Vector3(-19, 0, -37)
|
||||
rotation_degrees = Vector3(0, 16, 0)
|
||||
scale = Vector3(1.55, 1.55, 1.55)
|
||||
|
||||
[node name="Woodland_Edge_10" parent="FoliageRoot" instance=ExtResource("4_tree")]
|
||||
position = Vector3(-11, 0, -38)
|
||||
rotation_degrees = Vector3(0, 153, 0)
|
||||
scale = Vector3(1.35, 1.35, 1.35)
|
||||
|
||||
[node name="Woodland_Edge_11" parent="FoliageRoot" instance=ExtResource("4_tree")]
|
||||
position = Vector3(-3, 0, -36)
|
||||
rotation_degrees = Vector3(0, 290, 0)
|
||||
scale = Vector3(1.4, 1.4, 1.4)
|
||||
|
||||
[node name="Woodland_Edge_12" parent="FoliageRoot" instance=ExtResource("4_tree")]
|
||||
position = Vector3(5, 0, -39)
|
||||
rotation_degrees = Vector3(0, 67, 0)
|
||||
scale = Vector3(1.25, 1.25, 1.25)
|
||||
|
||||
[node name="Woodland_Edge_13" parent="FoliageRoot" instance=ExtResource("4_tree")]
|
||||
position = Vector3(13, 0, -35)
|
||||
rotation_degrees = Vector3(0, 204, 0)
|
||||
scale = Vector3(1.3, 1.3, 1.3)
|
||||
|
||||
[node name="Woodland_Edge_14" parent="FoliageRoot" instance=ExtResource("4_tree")]
|
||||
position = Vector3(43, 0, -14)
|
||||
rotation_degrees = Vector3(0, 341, 0)
|
||||
scale = Vector3(1.5, 1.5, 1.5)
|
||||
|
||||
[node name="Woodland_Edge_15" parent="FoliageRoot" instance=ExtResource("4_tree")]
|
||||
position = Vector3(46, 0, -5)
|
||||
rotation_degrees = Vector3(0, 118, 0)
|
||||
scale = Vector3(1.5, 1.5, 1.5)
|
||||
|
||||
[node name="Woodland_Edge_16" parent="FoliageRoot" instance=ExtResource("4_tree")]
|
||||
position = Vector3(43, 0, 4)
|
||||
rotation_degrees = Vector3(0, 255, 0)
|
||||
scale = Vector3(1.15, 1.15, 1.15)
|
||||
|
||||
[node name="Woodland_Edge_17" parent="FoliageRoot" instance=ExtResource("4_tree")]
|
||||
position = Vector3(43, 0, 14)
|
||||
rotation_degrees = Vector3(0, 32, 0)
|
||||
scale = Vector3(1.3, 1.3, 1.3)
|
||||
|
||||
[node name="WildlifeRoot" type="Node3D" parent="."]
|
||||
|
||||
[node name="Butterfly_Meadow_01" parent="WildlifeRoot" instance=ExtResource("24_butterfly")]
|
||||
@@ -579,6 +672,12 @@ yield_per_action = 3.0
|
||||
safety_risk = 0.12
|
||||
comfort_distance = 24.0
|
||||
|
||||
[node name="MeshInstance3D" parent="WorldObjects/ResourceNodes/Tree_01/Visual"]
|
||||
visible = false
|
||||
|
||||
[node name="TreePresentation" parent="WorldObjects/ResourceNodes/Tree_01/Visual" instance=ExtResource("35_harvest_tree")]
|
||||
scale = Vector3(0.82, 0.82, 0.82)
|
||||
|
||||
[node name="Tree_02" parent="WorldObjects/ResourceNodes" instance=ExtResource("2_resource")]
|
||||
position = Vector3(10, 0, -4)
|
||||
node_id = &"tree_02"
|
||||
@@ -589,6 +688,12 @@ yield_per_action = 3.0
|
||||
safety_risk = 0.16
|
||||
comfort_distance = 26.0
|
||||
|
||||
[node name="MeshInstance3D" parent="WorldObjects/ResourceNodes/Tree_02/Visual"]
|
||||
visible = false
|
||||
|
||||
[node name="TreePresentation" parent="WorldObjects/ResourceNodes/Tree_02/Visual" instance=ExtResource("35_harvest_tree")]
|
||||
scale = Vector3(0.82, 0.82, 0.82)
|
||||
|
||||
[node name="Tree_North_Outskirts_01" parent="WorldObjects/ResourceNodes" instance=ExtResource("2_resource")]
|
||||
position = Vector3(6, 0, 12)
|
||||
node_id = &"tree_north_outskirts_01"
|
||||
@@ -600,6 +705,12 @@ safety_risk = 0.2
|
||||
comfort_distance = 28.0
|
||||
discovery_priority = 0.25
|
||||
|
||||
[node name="MeshInstance3D" parent="WorldObjects/ResourceNodes/Tree_North_Outskirts_01/Visual"]
|
||||
visible = false
|
||||
|
||||
[node name="TreePresentation" parent="WorldObjects/ResourceNodes/Tree_North_Outskirts_01/Visual" instance=ExtResource("35_harvest_tree")]
|
||||
scale = Vector3(0.82, 0.82, 0.82)
|
||||
|
||||
[node name="Tree_South_Edge_01" parent="WorldObjects/ResourceNodes" instance=ExtResource("2_resource")]
|
||||
position = Vector3(-12, 0, -12)
|
||||
node_id = &"tree_south_edge_01"
|
||||
@@ -610,6 +721,12 @@ yield_per_action = 2.0
|
||||
safety_risk = 0.24
|
||||
comfort_distance = 26.0
|
||||
|
||||
[node name="MeshInstance3D" parent="WorldObjects/ResourceNodes/Tree_South_Edge_01/Visual"]
|
||||
visible = false
|
||||
|
||||
[node name="TreePresentation" parent="WorldObjects/ResourceNodes/Tree_South_Edge_01/Visual" instance=ExtResource("35_harvest_tree")]
|
||||
scale = Vector3(0.82, 0.82, 0.82)
|
||||
|
||||
[node name="WoodPile_Village_01" parent="WorldObjects/ResourceNodes" instance=ExtResource("2_resource")]
|
||||
position = Vector3(5, 0, -13)
|
||||
node_id = &"wood_pile_village_01"
|
||||
@@ -914,7 +1031,8 @@ text = "Rest Bench"
|
||||
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
|
||||
rotation_degrees = Vector3(-55, -35, 0)
|
||||
light_color = Color(1, 0.87, 0.72, 1)
|
||||
light_energy = 1.45
|
||||
light_energy = 1.35
|
||||
light_angular_distance = 0.6
|
||||
shadow_enabled = true
|
||||
directional_shadow_max_distance = 180.0
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
extends Node3D
|
||||
## Shared opaque boughs; decorative presentation without simulation state.
|
||||
|
||||
const WIND_SHADER := preload("res://world/jajce/materials/wind.gdshader")
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
var canopy := $Geometry/ConiferCanopy as MeshInstance3D
|
||||
var material := ShaderMaterial.new()
|
||||
material.shader = WIND_SHADER
|
||||
material.set_shader_parameter("albedo", Color(0.28, 0.47, 0.33))
|
||||
material.set_shader_parameter("painted_vertex_weight", 1.0)
|
||||
material.set_shader_parameter("wind_strength", 0.055)
|
||||
material.set_shader_parameter("wind_speed", 0.55)
|
||||
material.set_shader_parameter("wind_phase", float(posmod(hash(global_position), 6283)) / 1000.0)
|
||||
material.set_shader_parameter("wind_base_height", 0.45)
|
||||
material.set_shader_parameter("wind_full_height", 3.1)
|
||||
canopy.material_override = material
|
||||
var trunk := $Geometry/ConiferTrunk as MeshInstance3D
|
||||
var bark := StandardMaterial3D.new()
|
||||
bark.vertex_color_use_as_albedo = true
|
||||
bark.roughness = 0.95
|
||||
bark.specular_mode = BaseMaterial3D.SPECULAR_DISABLED
|
||||
trunk.material_override = bark
|
||||
@@ -0,0 +1 @@
|
||||
uid://bk3cxfe3kph2x
|
||||
@@ -0,0 +1,9 @@
|
||||
[gd_scene load_steps=3 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://world/jajce/PainterlyConifer.gd" id="1_script"]
|
||||
[ext_resource type="PackedScene" path="res://assets/painterly/painterly_conifer.glb" id="2_geometry"]
|
||||
|
||||
[node name="PainterlyConifer" type="Node3D"]
|
||||
script = ExtResource("1_script")
|
||||
|
||||
[node name="Geometry" parent="." instance=ExtResource("2_geometry")]
|
||||
@@ -0,0 +1,217 @@
|
||||
class_name PainterlyMeadow
|
||||
extends Node3D
|
||||
## Decorative flower drifts. Local seeded placement never touches simulation RNG.
|
||||
|
||||
const FLOWER_SCENES: Array[PackedScene] = [
|
||||
preload("res://assets/meadow/cream_daisy.glb"),
|
||||
preload("res://assets/meadow/pink_cosmos.glb"),
|
||||
preload("res://assets/meadow/yellow_buttercup.glb"),
|
||||
preload("res://assets/meadow/lavender_spire.glb"),
|
||||
]
|
||||
const FLOWER_SHADER := preload("res://world/jajce/materials/meadow_flowers.gdshader")
|
||||
const PROFILE_DENSITY := [1.0, 0.65, 0.32]
|
||||
const PROFILE_DISTANCE := [56.0, 46.0, 34.0]
|
||||
|
||||
@export var terrain_path := NodePath("../TerrainRoot/Terrain3D")
|
||||
@export_range(0, 450, 1) var clumps_per_species := 420
|
||||
@export var placement_seed := 57091
|
||||
## Local x/z center, then ellipse x/z radii. Each drift favors one flower color.
|
||||
@export var drifts: Array[Vector4] = [
|
||||
Vector4(7.0, 20.0, 7.0, 3.5),
|
||||
Vector4(15.0, 13.0, 2.4, 4.0),
|
||||
Vector4(-5.0, 20.0, 5.0, 3.0),
|
||||
Vector4(-18.0, -21.0, 4.5, 3.2),
|
||||
Vector4(14.0, -10.0, 3.8, 4.5),
|
||||
Vector4(-25.0, -17.0, 3.5, 4.0),
|
||||
Vector4(8.0, -20.0, 5.0, 2.5),
|
||||
Vector4(-2.0, -24.0, 6.0, 2.2),
|
||||
Vector4(17.0, 24.0, 4.0, 3.0),
|
||||
Vector4(-9.0, 21.0, 3.6, 2.5),
|
||||
Vector4(9.0, 7.2, 3.2, 1.8),
|
||||
Vector4(-16.0, 19.0, 3.0, 2.4),
|
||||
]
|
||||
|
||||
var _terrain: Terrain3D
|
||||
var _batches: Array[MultiMeshInstance3D] = []
|
||||
var _material: ShaderMaterial
|
||||
var _quality := 0
|
||||
var _clearings: Array[Vector3] = []
|
||||
var _path_strips: Array[MeshInstance3D] = []
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
call_deferred("_build_meadow")
|
||||
|
||||
|
||||
func apply_presentation_quality(quality: int) -> void:
|
||||
_quality = clampi(quality, 0, PROFILE_DENSITY.size() - 1)
|
||||
for batch in _batches:
|
||||
batch.multimesh.visible_instance_count = floori(
|
||||
batch.multimesh.instance_count * PROFILE_DENSITY[_quality]
|
||||
)
|
||||
if _material != null:
|
||||
_material.set_shader_parameter("fade_begin", PROFILE_DISTANCE[_quality] - 12.0)
|
||||
_material.set_shader_parameter("fade_end", PROFILE_DISTANCE[_quality])
|
||||
|
||||
|
||||
func get_presentation_stats() -> Dictionary:
|
||||
var total := 0
|
||||
var visible_count := 0
|
||||
for batch in _batches:
|
||||
total += batch.multimesh.instance_count
|
||||
visible_count += batch.multimesh.visible_instance_count
|
||||
return {
|
||||
"flower_clump_count": total,
|
||||
"visible_flower_clump_count": visible_count,
|
||||
"multimesh_batch_count": _batches.size(),
|
||||
}
|
||||
|
||||
|
||||
func _build_meadow() -> void:
|
||||
if not _batches.is_empty():
|
||||
return
|
||||
_terrain = get_node_or_null(terrain_path) as Terrain3D
|
||||
if _terrain == null or _terrain.data == null or drifts.is_empty():
|
||||
return
|
||||
_collect_clearings()
|
||||
_material = ShaderMaterial.new()
|
||||
_material.shader = FLOWER_SHADER
|
||||
for species in FLOWER_SCENES.size():
|
||||
var source := FLOWER_SCENES[species].instantiate()
|
||||
var mesh := _find_mesh(source)
|
||||
if mesh != null:
|
||||
_add_batch(mesh, species)
|
||||
source.free()
|
||||
apply_presentation_quality(_quality)
|
||||
|
||||
|
||||
func _find_mesh(node: Node) -> Mesh:
|
||||
if node is MeshInstance3D:
|
||||
return (node as MeshInstance3D).mesh
|
||||
for child in node.get_children():
|
||||
var mesh := _find_mesh(child)
|
||||
if mesh != null:
|
||||
return mesh
|
||||
return null
|
||||
|
||||
|
||||
func _add_batch(mesh: Mesh, species: int) -> void:
|
||||
var rng := RandomNumberGenerator.new()
|
||||
rng.seed = placement_seed + species * 173
|
||||
var transforms: Array[Transform3D] = []
|
||||
var variations: Array[Color] = []
|
||||
for _attempt in clumps_per_species * 18:
|
||||
if transforms.size() >= clumps_per_species:
|
||||
break
|
||||
var drift_index := rng.randi_range(0, drifts.size() - 1)
|
||||
# Broad color masses with a few companion flowers at the edges.
|
||||
if drift_index % FLOWER_SCENES.size() != species and rng.randf() > 0.12:
|
||||
continue
|
||||
var drift := drifts[drift_index]
|
||||
var angle := rng.randf_range(0.0, TAU)
|
||||
var radius := pow(rng.randf(), 0.7) * (0.9 + 0.12 * sin(angle * 3.0 + drift_index))
|
||||
var offset := Vector3(
|
||||
drift.x + cos(angle) * radius * drift.z, 0.0, drift.y + sin(angle) * radius * drift.w
|
||||
)
|
||||
var world_sample := to_global(offset)
|
||||
if _is_cleared(world_sample):
|
||||
continue
|
||||
var height := _terrain.data.get_height(world_sample)
|
||||
if is_nan(height):
|
||||
continue
|
||||
var nearby := _terrain.data.get_height(world_sample + Vector3(0.35, 0.0, 0.35))
|
||||
if is_nan(nearby) or absf(height - nearby) > 0.32:
|
||||
continue
|
||||
var local_position := to_local(Vector3(world_sample.x, height - 0.018, world_sample.z))
|
||||
var scale_variation := rng.randf_range(0.80, 1.28)
|
||||
var basis := Basis(Vector3.UP, rng.randf_range(0.0, TAU)).scaled(
|
||||
Vector3.ONE * scale_variation
|
||||
)
|
||||
transforms.append(Transform3D(basis, local_position))
|
||||
variations.append(Color(rng.randf(), 0.0, 0.0, 1.0))
|
||||
var multimesh := MultiMesh.new()
|
||||
multimesh.transform_format = MultiMesh.TRANSFORM_3D
|
||||
# Compatibility needs an explicit neutral color alongside the custom buffer
|
||||
# to preserve the GLB's painted vertex colors.
|
||||
multimesh.use_colors = true
|
||||
multimesh.use_custom_data = true
|
||||
multimesh.mesh = mesh
|
||||
multimesh.instance_count = transforms.size()
|
||||
for index in transforms.size():
|
||||
multimesh.set_instance_transform(index, transforms[index])
|
||||
multimesh.set_instance_color(index, Color.WHITE)
|
||||
multimesh.set_instance_custom_data(index, variations[index])
|
||||
var batch := MultiMeshInstance3D.new()
|
||||
batch.name = "FlowerDrifts_%d" % species
|
||||
batch.multimesh = multimesh
|
||||
batch.material_override = _material
|
||||
batch.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
||||
batch.extra_cull_margin = 0.4
|
||||
add_child(batch)
|
||||
_batches.append(batch)
|
||||
|
||||
|
||||
func _collect_clearings() -> void:
|
||||
var village := get_node_or_null("../VillageRoot")
|
||||
if village != null:
|
||||
for child in village.get_children():
|
||||
if child is MeshInstance3D and str(child.name).begins_with("Path_"):
|
||||
_path_strips.append(child)
|
||||
elif child is Node3D:
|
||||
var radius := 4.3 if str(child.name).begins_with("House_") else 4.5
|
||||
_add_clearing(child, radius)
|
||||
var fortress := get_node_or_null("../FortressBlockout") as Node3D
|
||||
if fortress != null:
|
||||
_add_clearing(fortress, 11.0)
|
||||
for root_path in [
|
||||
"../WorldObjects/ResourceNodes",
|
||||
"../WorldObjects/StorageSites",
|
||||
"../WorldObjects/ActivitySites",
|
||||
"../WorldObjects/AnimalHabitats",
|
||||
]:
|
||||
var root := get_node_or_null(root_path)
|
||||
if root != null:
|
||||
for child in root.get_children():
|
||||
if child is Node3D:
|
||||
_add_worksite_clearing(child)
|
||||
var clusters := get_node_or_null("../WorldObjects/ResourceClusters")
|
||||
if clusters != null:
|
||||
for cluster in clusters.get_children():
|
||||
var anchors := cluster.get_node_or_null("ResourceAnchors")
|
||||
if anchors == null:
|
||||
continue
|
||||
for anchor in anchors.get_children():
|
||||
if anchor is Node3D:
|
||||
_add_worksite_clearing(anchor)
|
||||
|
||||
|
||||
func _add_worksite_clearing(node: Node3D) -> void:
|
||||
_add_clearing(node, 2.0)
|
||||
var interaction := node.get_node_or_null("InteractionPoint") as Node3D
|
||||
if interaction != null:
|
||||
_add_clearing(interaction, 1.2)
|
||||
|
||||
|
||||
func _add_clearing(node: Node3D, radius: float) -> void:
|
||||
_clearings.append(Vector3(node.global_position.x, node.global_position.z, radius))
|
||||
|
||||
|
||||
func _is_cleared(point: Vector3) -> bool:
|
||||
var flat := Vector2(point.x, point.z)
|
||||
for clearing in _clearings:
|
||||
if flat.distance_squared_to(Vector2(clearing.x, clearing.y)) < clearing.z * clearing.z:
|
||||
return true
|
||||
for strip in _path_strips:
|
||||
var box := strip.mesh as BoxMesh
|
||||
if box == null:
|
||||
continue
|
||||
var local := strip.to_local(point)
|
||||
var half_width := box.size.x * 0.5 + 0.35 / strip.global_basis.x.length()
|
||||
if absf(local.x) < half_width and absf(local.z) < box.size.z * 0.5 + 0.08:
|
||||
return true
|
||||
var river_distance := absf(point.x - JajceWatercourse.downstream_center_x(point.z))
|
||||
return (
|
||||
point.z >= JajceWatercourse.DOWNSTREAM_START_Z - 3.0
|
||||
and point.z <= JajceWatercourse.DOWNSTREAM_END_Z
|
||||
and river_distance < JajceWatercourse.downstream_half_width(point.z) + 1.7
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
uid://cacjl430m8mcw
|
||||
@@ -4,7 +4,7 @@
|
||||
[ext_resource type="PackedScene" path="res://world/resource_nodes/ResourceNode.tscn" id="2_resource"]
|
||||
[ext_resource type="PackedScene" path="res://world/jajce/StylizedTree.tscn" id="3_tree"]
|
||||
[ext_resource type="PackedScene" path="res://world/jajce/StylizedBerryPatch.tscn" id="4_berry"]
|
||||
[ext_resource type="PackedScene" path="res://assets/foliage/tree.glb" id="5_conifer"]
|
||||
[ext_resource type="PackedScene" path="res://world/jajce/PainterlyConifer.tscn" id="5_conifer"]
|
||||
[ext_resource type="PackedScene" path="res://world/jajce/HarvestableTreePresentation.tscn" id="6_harvestable_tree"]
|
||||
|
||||
[node name="RiverbankResourceCluster" type="Node3D"]
|
||||
|
||||
+33
-51
@@ -7,15 +7,20 @@ enum CanopyVariant {
|
||||
}
|
||||
|
||||
const COLORS := {
|
||||
CanopyVariant.GREEN: Color(0.32, 0.51, 0.29),
|
||||
CanopyVariant.DARK_GREEN: Color(0.22, 0.41, 0.30),
|
||||
CanopyVariant.YELLOW_GREEN: Color(0.49, 0.60, 0.28),
|
||||
CanopyVariant.GREEN: Color(0.40, 0.62, 0.28),
|
||||
CanopyVariant.DARK_GREEN: Color(0.26, 0.51, 0.34),
|
||||
CanopyVariant.YELLOW_GREEN: Color(0.55, 0.67, 0.29),
|
||||
}
|
||||
|
||||
const TREE_MODEL := preload("res://assets/painterly/painterly_tree.glb")
|
||||
const WIND_SHADER := preload("res://world/jajce/materials/wind.gdshader")
|
||||
const WIND_STRENGTH := 0.07
|
||||
const WIND_SPEED := 0.55
|
||||
|
||||
static var _canopy_mesh: Mesh
|
||||
static var _trunk_mesh: Mesh
|
||||
static var _bark_material: StandardMaterial3D
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
for child in get_children():
|
||||
@@ -28,76 +33,56 @@ func _ready() -> void:
|
||||
var canopy_radius := 1.25 + rng.randf() * 0.6
|
||||
var wind_phase := rng.randf_range(0.0, TAU)
|
||||
|
||||
var trunk_mesh := CylinderMesh.new()
|
||||
trunk_mesh.top_radius = 0.22
|
||||
trunk_mesh.bottom_radius = 0.32
|
||||
trunk_mesh.height = 3.4
|
||||
trunk_mesh.radial_segments = 9
|
||||
|
||||
var trunk_mat := StandardMaterial3D.new()
|
||||
trunk_mat.albedo_color = Color(0.27, 0.16, 0.09)
|
||||
trunk_mat.roughness = 0.95
|
||||
trunk_mesh.material = trunk_mat
|
||||
|
||||
_ensure_shared_meshes()
|
||||
var trunk := MeshInstance3D.new()
|
||||
trunk.name = "Trunk"
|
||||
trunk.mesh = trunk_mesh
|
||||
trunk.position = Vector3(0, 1.7, 0)
|
||||
trunk.mesh = _trunk_mesh
|
||||
trunk.material_override = _bark_material
|
||||
add_child(trunk)
|
||||
|
||||
_add_branch(Vector3(-0.18, 2.45, 0.0), Vector3(0.0, 0.0, 42.0), 1.45, trunk_mat)
|
||||
_add_branch(Vector3(0.2, 2.6, 0.08), Vector3(24.0, 0.0, -38.0), 1.25, trunk_mat)
|
||||
|
||||
var canopy_color: Color = COLORS[canopy_variant]
|
||||
var crown_height := 3.7 + (canopy_radius - 1.2) * 0.45
|
||||
_add_canopy(
|
||||
"Canopy",
|
||||
Vector3(0.0, crown_height, 0.0),
|
||||
Vector3(canopy_radius * 1.12, canopy_radius, canopy_radius),
|
||||
Vector3(canopy_radius * 1.18, canopy_radius * 0.96, canopy_radius * 1.05),
|
||||
canopy_color,
|
||||
wind_phase
|
||||
)
|
||||
_add_canopy(
|
||||
"CanopyLeft",
|
||||
Vector3(-0.82, crown_height - 0.15, 0.1),
|
||||
Vector3(canopy_radius * 0.78, canopy_radius * 0.72, canopy_radius * 0.76),
|
||||
Vector3(-0.94, crown_height - 0.27, 0.14),
|
||||
Vector3(canopy_radius * 0.84, canopy_radius * 0.79, canopy_radius * 0.84),
|
||||
canopy_color.lightened(0.05),
|
||||
wind_phase + 0.12
|
||||
)
|
||||
_add_canopy(
|
||||
"CanopyRight",
|
||||
Vector3(0.78, crown_height - 0.05, -0.12),
|
||||
Vector3(canopy_radius * 0.74, canopy_radius * 0.68, canopy_radius * 0.72),
|
||||
Vector3(0.91, crown_height - 0.08, -0.2),
|
||||
Vector3(canopy_radius * 0.82, canopy_radius * 0.73, canopy_radius * 0.79),
|
||||
canopy_color.darkened(0.04),
|
||||
wind_phase - 0.1
|
||||
)
|
||||
_add_canopy(
|
||||
"CanopyTop",
|
||||
Vector3(0.08, crown_height + 0.72, 0.04),
|
||||
Vector3(canopy_radius * 0.72, canopy_radius * 0.66, canopy_radius * 0.7),
|
||||
Vector3(0.13, crown_height + 0.68, -0.08),
|
||||
Vector3(canopy_radius * 0.78, canopy_radius * 0.74, canopy_radius * 0.75),
|
||||
canopy_color.lightened(0.08),
|
||||
wind_phase + 0.05
|
||||
)
|
||||
|
||||
|
||||
func _add_branch(
|
||||
branch_position: Vector3,
|
||||
branch_rotation_degrees: Vector3,
|
||||
branch_height: float,
|
||||
material: StandardMaterial3D
|
||||
) -> void:
|
||||
var mesh := CylinderMesh.new()
|
||||
mesh.top_radius = 0.08
|
||||
mesh.bottom_radius = 0.14
|
||||
mesh.height = branch_height
|
||||
mesh.radial_segments = 7
|
||||
mesh.material = material
|
||||
var branch := MeshInstance3D.new()
|
||||
branch.name = "Branch"
|
||||
branch.mesh = mesh
|
||||
branch.position = branch_position
|
||||
branch.rotation_degrees = branch_rotation_degrees
|
||||
add_child(branch)
|
||||
static func _ensure_shared_meshes() -> void:
|
||||
if _canopy_mesh != null:
|
||||
return
|
||||
var source := TREE_MODEL.instantiate()
|
||||
_canopy_mesh = (source.find_child("Canopy", true, false) as MeshInstance3D).mesh
|
||||
_trunk_mesh = (source.find_child("Trunk", true, false) as MeshInstance3D).mesh
|
||||
_bark_material = StandardMaterial3D.new()
|
||||
_bark_material.vertex_color_use_as_albedo = true
|
||||
_bark_material.roughness = 0.95
|
||||
_bark_material.specular_mode = BaseMaterial3D.SPECULAR_DISABLED
|
||||
source.free()
|
||||
|
||||
|
||||
func _add_canopy(
|
||||
@@ -107,11 +92,6 @@ func _add_canopy(
|
||||
canopy_color: Color,
|
||||
wind_phase: float
|
||||
) -> void:
|
||||
var mesh := SphereMesh.new()
|
||||
mesh.radius = 1.0
|
||||
mesh.height = 2.0
|
||||
mesh.radial_segments = 12
|
||||
mesh.rings = 6
|
||||
var material := ShaderMaterial.new()
|
||||
material.shader = WIND_SHADER
|
||||
material.set_shader_parameter("albedo", canopy_color)
|
||||
@@ -119,10 +99,12 @@ func _add_canopy(
|
||||
material.set_shader_parameter("wind_strength", WIND_STRENGTH)
|
||||
material.set_shader_parameter("wind_speed", WIND_SPEED)
|
||||
material.set_shader_parameter("wind_phase", wind_phase)
|
||||
mesh.material = material
|
||||
material.set_shader_parameter("painted_vertex_weight", 1.0)
|
||||
var canopy := MeshInstance3D.new()
|
||||
canopy.name = canopy_name
|
||||
canopy.mesh = mesh
|
||||
canopy.mesh = _canopy_mesh
|
||||
canopy.material_override = material
|
||||
canopy.position = canopy_position
|
||||
canopy.scale = canopy_scale
|
||||
canopy.rotation.y = wind_phase * 0.43 + canopy_position.x * 0.31
|
||||
add_child(canopy)
|
||||
|
||||
@@ -48,6 +48,9 @@ func _build_ribbon(
|
||||
var uvs := PackedVector2Array()
|
||||
var indices := PackedInt32Array()
|
||||
var row_size := width_segments + 1
|
||||
var pool := $PlungePool as MeshInstance3D
|
||||
var pool_mesh := pool.mesh as PlaneMesh
|
||||
var pool_radii := pool_mesh.size * Vector2(pool.scale.x, pool.scale.z) * 0.5
|
||||
for z_index in segment_count + 1:
|
||||
var progress := float(z_index) / float(segment_count)
|
||||
var world_z := lerpf(start_z, end_z, progress)
|
||||
@@ -61,16 +64,30 @@ func _build_ribbon(
|
||||
if is_upper
|
||||
else Watercourse.downstream_half_width(world_z)
|
||||
)
|
||||
if not is_upper:
|
||||
# The outlet continues the existing pool silhouette without drawing a
|
||||
# second, coplanar sheet on top of the pool's transparent material.
|
||||
var pool_progress := (world_z - Watercourse.PLUNGE_POOL_CENTER.y) / pool_radii.y
|
||||
if absf(pool_progress) < 1.0:
|
||||
var pool_width := pool_radii.x * sqrt(1.0 - pool_progress * pool_progress)
|
||||
half_width = maxf(half_width, pool_width)
|
||||
# A stream has one surface across its width. Sampling each bank separately
|
||||
# wraps the water mesh up the terrain and makes it read as a solid ramp.
|
||||
var terrain_height := terrain.data.get_height(Vector3(center_x, 0.0, world_z))
|
||||
if is_nan(terrain_height):
|
||||
terrain_height = global_position.y
|
||||
var surface_height := terrain_height + water_offset
|
||||
var left_x := _shoreline_x(
|
||||
terrain, center_x, center_x - half_width, world_z, surface_height
|
||||
)
|
||||
var right_x := _shoreline_x(
|
||||
terrain, center_x, center_x + half_width, world_z, surface_height
|
||||
)
|
||||
for x_index in width_segments + 1:
|
||||
var across := float(x_index) / float(width_segments)
|
||||
var world_x := center_x + lerpf(-half_width, half_width, across)
|
||||
var world_x := lerpf(left_x, right_x, across)
|
||||
var world_sample := Vector3(world_x, 0.0, world_z)
|
||||
var terrain_height := terrain.data.get_height(world_sample)
|
||||
if is_nan(terrain_height):
|
||||
terrain_height = global_position.y
|
||||
vertices.append(
|
||||
to_local(Vector3(world_sample.x, terrain_height + water_offset, world_sample.z))
|
||||
)
|
||||
vertices.append(to_local(Vector3(world_sample.x, surface_height, world_sample.z)))
|
||||
normals.append(Vector3.UP)
|
||||
uvs.append(Vector2(across, progress))
|
||||
|
||||
@@ -96,8 +113,37 @@ func _build_ribbon(
|
||||
return mesh
|
||||
|
||||
|
||||
func _shoreline_x(
|
||||
terrain: Terrain3D, center_x: float, edge_x: float, world_z: float, surface_height: float
|
||||
) -> float:
|
||||
var edge_height := terrain.data.get_height(Vector3(edge_x, 0.0, world_z))
|
||||
if is_nan(edge_height) or edge_height <= surface_height:
|
||||
return edge_x
|
||||
# The carved basin is wider on one side. Stop its visible surface at the
|
||||
# shore instead of extending the pool's ideal ellipse metres under a hill.
|
||||
var wet_x := center_x
|
||||
var dry_x := edge_x
|
||||
for step in range(1, 17):
|
||||
var sample_x := lerpf(center_x, edge_x, float(step) / 16.0)
|
||||
var sample_height := terrain.data.get_height(Vector3(sample_x, 0.0, world_z))
|
||||
if is_nan(sample_height) or sample_height > surface_height:
|
||||
dry_x = sample_x
|
||||
break
|
||||
wet_x = sample_x
|
||||
for _step in 10:
|
||||
var sample_x := (wet_x + dry_x) * 0.5
|
||||
var sample_height := terrain.data.get_height(Vector3(sample_x, 0.0, world_z))
|
||||
if is_nan(sample_height) or sample_height > surface_height:
|
||||
dry_x = sample_x
|
||||
else:
|
||||
wet_x = sample_x
|
||||
return wet_x
|
||||
|
||||
|
||||
func _align_pool_to_terrain(terrain: Terrain3D) -> void:
|
||||
var pool := $PlungePool as MeshInstance3D
|
||||
var pool_material := pool.mesh.surface_get_material(0) as ShaderMaterial
|
||||
pool_material.set_shader_parameter("pool_outlet_z", Watercourse.DOWNSTREAM_START_Z)
|
||||
var pool_sample := Vector3(
|
||||
Watercourse.PLUNGE_POOL_CENTER.x, 0.0, Watercourse.PLUNGE_POOL_CENTER.y
|
||||
)
|
||||
|
||||
+22
-23
@@ -1,34 +1,32 @@
|
||||
[gd_scene load_steps=8 format=3]
|
||||
[gd_scene load_steps=11 format=3]
|
||||
|
||||
[ext_resource type="Shader" path="res://world/jajce/materials/water.gdshader" id="1_water_shader"]
|
||||
[ext_resource type="Script" path="res://world/jajce/TerrainRiverRibbon.gd" id="2_river_script"]
|
||||
[ext_resource type="Shader" path="res://world/jajce/materials/water_foam.gdshader" id="3_foam_shader"]
|
||||
|
||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_water"]
|
||||
shader = ExtResource("1_water_shader")
|
||||
|
||||
[sub_resource type="CylinderMesh" id="Mesh_plunge_pool"]
|
||||
material = SubResource("ShaderMaterial_water")
|
||||
top_radius = 7.6
|
||||
bottom_radius = 7.6
|
||||
height = 0.08
|
||||
radial_segments = 32
|
||||
rings = 2
|
||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_pool"]
|
||||
shader = ExtResource("1_water_shader")
|
||||
shader_parameter/pool_surface = true
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="Material_foam"]
|
||||
transparency = 1
|
||||
shading_mode = 0
|
||||
albedo_color = Color(0.8, 0.93, 0.86, 0.22)
|
||||
emission_enabled = true
|
||||
emission = Color(0.34, 0.56, 0.48, 1)
|
||||
emission_energy_multiplier = 0.06
|
||||
roughness = 0.74
|
||||
[sub_resource type="PlaneMesh" id="Mesh_plunge_pool"]
|
||||
material = SubResource("ShaderMaterial_pool")
|
||||
size = Vector2(15.2, 15.2)
|
||||
subdivide_width = 20
|
||||
subdivide_depth = 20
|
||||
|
||||
[sub_resource type="TorusMesh" id="Mesh_foam_ring"]
|
||||
material = SubResource("Material_foam")
|
||||
inner_radius = 5.15
|
||||
outer_radius = 5.5
|
||||
rings = 28
|
||||
ring_segments = 10
|
||||
[sub_resource type="ShaderMaterial" id="Material_foam"]
|
||||
shader = ExtResource("3_foam_shader")
|
||||
|
||||
[sub_resource type="ShaderMaterial" id="Material_pool_foam"]
|
||||
shader = ExtResource("3_foam_shader")
|
||||
shader_parameter/circular_wash = true
|
||||
|
||||
[sub_resource type="PlaneMesh" id="Mesh_foam_ring"]
|
||||
material = SubResource("Material_pool_foam")
|
||||
size = Vector2(11, 11)
|
||||
|
||||
[sub_resource type="PlaneMesh" id="Mesh_foam_patch"]
|
||||
material = SubResource("Material_foam")
|
||||
@@ -49,10 +47,11 @@ cast_shadow = 0
|
||||
|
||||
[node name="PlungePool" type="MeshInstance3D" parent="."]
|
||||
scale = Vector3(1.25, 1, 0.82)
|
||||
cast_shadow = 0
|
||||
mesh = SubResource("Mesh_plunge_pool")
|
||||
|
||||
[node name="FoamRing" type="MeshInstance3D" parent="."]
|
||||
scale = Vector3(1.25, 0.12, 0.82)
|
||||
scale = Vector3(1.25, 1, 0.82)
|
||||
cast_shadow = 0
|
||||
mesh = SubResource("Mesh_foam_ring")
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[gd_scene load_steps=12 format=3]
|
||||
[gd_scene load_steps=13 format=3]
|
||||
|
||||
[ext_resource type="Shader" path="res://world/jajce/materials/waterfall.gdshader" id="1_waterfall_shader"]
|
||||
[ext_resource type="Shader" path="res://world/jajce/materials/water_foam.gdshader" id="2_foam_shader"]
|
||||
|
||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_waterfall"]
|
||||
shader = ExtResource("1_waterfall_shader")
|
||||
@@ -34,14 +35,8 @@ size = Vector3(4.8, 5.5, 3.4)
|
||||
material = SubResource("Material_rock_moss")
|
||||
size = Vector3(10.4, 0.65, 3.0)
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="Material_lip_foam"]
|
||||
transparency = 1
|
||||
shading_mode = 0
|
||||
albedo_color = Color(0.82, 0.95, 0.87, 0.72)
|
||||
emission_enabled = true
|
||||
emission = Color(0.46, 0.72, 0.62, 1)
|
||||
emission_energy_multiplier = 0.22
|
||||
roughness = 0.62
|
||||
[sub_resource type="ShaderMaterial" id="Material_lip_foam"]
|
||||
shader = ExtResource("2_foam_shader")
|
||||
|
||||
[sub_resource type="PlaneMesh" id="Mesh_lip_foam"]
|
||||
material = SubResource("Material_lip_foam")
|
||||
|
||||
@@ -7,17 +7,17 @@ extends Node
|
||||
|
||||
const TO_RAD := PI / 180.0
|
||||
|
||||
const DAY_SKY_TOP := Color(0.27, 0.57, 0.74, 1)
|
||||
const DAY_SKY_HORIZON := Color(0.79, 0.88, 0.82, 1)
|
||||
const DAY_SKY_TOP := Color(0.12, 0.49, 0.69, 1)
|
||||
const DAY_SKY_HORIZON := Color(0.65, 0.84, 0.84, 1)
|
||||
const DAY_GROUND_BOTTOM := Color(0.12, 0.17, 0.12, 1)
|
||||
const DAY_GROUND_HORIZON := Color(0.56, 0.69, 0.57, 1)
|
||||
const DAY_AMBIENT := Color(0.72, 0.83, 0.88, 1)
|
||||
const DAY_AMBIENT_ENERGY := 0.55
|
||||
const DAY_FOG_LIGHT := Color(0.70, 0.83, 0.81, 1)
|
||||
const DAY_GROUND_HORIZON := DAY_SKY_HORIZON
|
||||
const DAY_AMBIENT := Color(0.65, 0.80, 0.87, 1)
|
||||
const DAY_AMBIENT_ENERGY := 0.65
|
||||
const DAY_FOG_LIGHT := Color(0.63, 0.80, 0.82, 1)
|
||||
const DAY_FOG_LIGHT_ENERGY := 0.8
|
||||
const DAY_FOG_DENSITY := 0.0015
|
||||
const DAY_FOG_DENSITY := 0.0018
|
||||
const DAY_LIGHT_COLOR := Color(1, 0.94, 0.79, 1)
|
||||
const DAY_LIGHT_ENERGY := 1.2
|
||||
const DAY_LIGHT_ENERGY := 1.35
|
||||
|
||||
const NIGHT_SKY_TOP := Color(0.04, 0.04, 0.12, 1)
|
||||
const NIGHT_SKY_HORIZON := Color(0.08, 0.06, 0.14, 1)
|
||||
@@ -76,8 +76,13 @@ func _process(delta: float) -> void:
|
||||
func _apply_light_rotation(cycle: float) -> void:
|
||||
if directional_light == null:
|
||||
return
|
||||
var angle_rad: float = cycle * 2.0 * PI
|
||||
directional_light.rotation = Vector3(-PI / 4.0 + sin(angle_rad) * PI / 4.0, angle_rad, 0)
|
||||
# The bright interval and the sun's arc share dawn/noon/dusk. The old
|
||||
# quarter-cycle offset put the brightest morning sun on the horizon.
|
||||
var solar_progress := clampf((cycle - 0.15) / 0.7, 0.0, 1.0)
|
||||
var elevation := 8.0 + sin(solar_progress * PI) * 52.0
|
||||
if cycle < 0.15 or cycle > 0.85:
|
||||
elevation = 32.0 # One cool moon fill during the night interval.
|
||||
directional_light.rotation = Vector3(-elevation * TO_RAD, cycle * TAU, 0)
|
||||
|
||||
|
||||
func _apply_environment(cycle: float) -> void:
|
||||
@@ -85,7 +90,6 @@ func _apply_environment(cycle: float) -> void:
|
||||
return
|
||||
|
||||
var day_factor := _day_factor(cycle)
|
||||
var night_factor := 1.0 - day_factor
|
||||
|
||||
directional_light.light_color = _lerp_color(NIGHT_LIGHT_COLOR, DAY_LIGHT_COLOR, day_factor)
|
||||
directional_light.light_energy = lerpf(NIGHT_LIGHT_ENERGY, DAY_LIGHT_ENERGY, day_factor)
|
||||
@@ -136,6 +140,7 @@ func _apply_environment(cycle: float) -> void:
|
||||
"ground_horizon_color", NIGHT_GROUND_HORIZON.lerp(DAY_GROUND_HORIZON, day_factor)
|
||||
)
|
||||
mat.set_shader_parameter("daylight", day_factor)
|
||||
mat.set_shader_parameter("golden_hour", transition_peak)
|
||||
|
||||
|
||||
func _find_simulation_node() -> void:
|
||||
@@ -155,10 +160,10 @@ func _day_factor(cycle: float) -> float:
|
||||
|
||||
|
||||
func _sunrise_sunset_weight(cycle: float) -> float:
|
||||
if cycle < 0.15:
|
||||
return smoothstep(0.05, 0.15, cycle) * (1.0 - smoothstep(0.1, 0.15, cycle))
|
||||
if cycle > 0.85:
|
||||
return smoothstep(0.85, 0.95, cycle) * (1.0 - smoothstep(0.9, 0.95, cycle))
|
||||
if cycle < 0.3:
|
||||
return smoothstep(0.15, 0.21, cycle) * (1.0 - smoothstep(0.23, 0.3, cycle))
|
||||
if cycle > 0.7:
|
||||
return smoothstep(0.7, 0.77, cycle) * (1.0 - smoothstep(0.79, 0.85, cycle))
|
||||
return 0.0
|
||||
|
||||
|
||||
|
||||
+36
-10
@@ -32,6 +32,7 @@ static var _viewport_scale_states: Dictionary = {}
|
||||
var _active_presentation_quality := -1
|
||||
var _authored_presentation_captured := false
|
||||
var _authored_render_scale := 1.0
|
||||
var _authored_msaa: int = Viewport.MSAA_DISABLED
|
||||
var _authored_glow_enabled := false
|
||||
var _authored_volumetric_fog_enabled := HIGH_VOLUMETRIC_FOG_ENABLED
|
||||
var _authored_adjustment_enabled := false
|
||||
@@ -45,6 +46,7 @@ var _authored_grass_interactors := 1
|
||||
var _authored_grass_update_interval := 0.1
|
||||
var _authored_grass_controller_enabled := true
|
||||
var _last_applied_render_scale := -1.0
|
||||
var _last_applied_msaa := -1
|
||||
var _viewport_instance_id := 0
|
||||
|
||||
|
||||
@@ -78,7 +80,8 @@ func apply_presentation_quality(quality: int) -> bool:
|
||||
_authored_volumetric_fog_enabled,
|
||||
_authored_adjustment_enabled,
|
||||
_authored_shadow_distance,
|
||||
_authored_shadow_mode
|
||||
_authored_shadow_mode,
|
||||
_authored_msaa
|
||||
)
|
||||
_apply_grass_quality(
|
||||
_authored_grass_visible,
|
||||
@@ -96,7 +99,8 @@ func apply_presentation_quality(quality: int) -> bool:
|
||||
false,
|
||||
_authored_adjustment_enabled,
|
||||
minf(_authored_shadow_distance, BALANCED_SHADOW_DISTANCE),
|
||||
DirectionalLight3D.SHADOW_PARALLEL_2_SPLITS
|
||||
DirectionalLight3D.SHADOW_PARALLEL_2_SPLITS,
|
||||
mini(_authored_msaa, Viewport.MSAA_2X)
|
||||
)
|
||||
_apply_grass_quality(
|
||||
_authored_grass_visible,
|
||||
@@ -114,7 +118,8 @@ func apply_presentation_quality(quality: int) -> bool:
|
||||
false,
|
||||
false,
|
||||
minf(_authored_shadow_distance, LOW_SHADOW_DISTANCE),
|
||||
DirectionalLight3D.SHADOW_ORTHOGONAL
|
||||
DirectionalLight3D.SHADOW_ORTHOGONAL,
|
||||
Viewport.MSAA_DISABLED
|
||||
)
|
||||
_apply_grass_quality(
|
||||
false,
|
||||
@@ -126,6 +131,9 @@ func apply_presentation_quality(quality: int) -> bool:
|
||||
false
|
||||
)
|
||||
|
||||
var meadow := get_node_or_null("PainterlyMeadow")
|
||||
if meadow != null:
|
||||
meadow.call("apply_presentation_quality", quality)
|
||||
_active_presentation_quality = quality
|
||||
return true
|
||||
|
||||
@@ -223,11 +231,14 @@ func _apply_environment_quality(
|
||||
volumetric_fog_enabled: bool,
|
||||
adjustment_enabled: bool,
|
||||
shadow_distance: float,
|
||||
shadow_mode: int
|
||||
shadow_mode: int,
|
||||
msaa: int
|
||||
) -> void:
|
||||
_claim_viewport_scale_owner()
|
||||
get_viewport().scaling_3d_scale = render_scale
|
||||
get_viewport().msaa_3d = msaa
|
||||
_last_applied_render_scale = render_scale
|
||||
_last_applied_msaa = msaa
|
||||
var environment := _environment()
|
||||
if environment != null:
|
||||
environment.glow_enabled = glow_enabled
|
||||
@@ -285,8 +296,13 @@ func _register_viewport_scale_owner() -> void:
|
||||
_viewport_instance_id = viewport.get_instance_id()
|
||||
var state: Dictionary = _viewport_scale_states.get(_viewport_instance_id, {})
|
||||
if state.is_empty():
|
||||
state = {"base_scale": viewport.scaling_3d_scale, "owners": []}
|
||||
state = {
|
||||
"base_scale": viewport.scaling_3d_scale,
|
||||
"base_msaa": viewport.msaa_3d,
|
||||
"owners": [],
|
||||
}
|
||||
_authored_render_scale = float(state["base_scale"])
|
||||
_authored_msaa = int(state["base_msaa"])
|
||||
_viewport_scale_states[_viewport_instance_id] = state
|
||||
_claim_viewport_scale_owner()
|
||||
|
||||
@@ -324,13 +340,20 @@ func _release_viewport_scale_owner() -> void:
|
||||
if not owners.is_empty():
|
||||
state["owners"] = owners
|
||||
_viewport_scale_states[_viewport_instance_id] = state
|
||||
if (
|
||||
was_active_owner
|
||||
and is_equal_approx(viewport.scaling_3d_scale, _last_applied_render_scale)
|
||||
):
|
||||
if was_active_owner:
|
||||
var previous_owner := (owners[-1] as WeakRef).get_ref() as JajceWorld
|
||||
if previous_owner != null and previous_owner._last_applied_render_scale >= 0.0:
|
||||
if (
|
||||
previous_owner != null
|
||||
and previous_owner._last_applied_render_scale >= 0.0
|
||||
and is_equal_approx(viewport.scaling_3d_scale, _last_applied_render_scale)
|
||||
):
|
||||
viewport.scaling_3d_scale = previous_owner._last_applied_render_scale
|
||||
if (
|
||||
previous_owner != null
|
||||
and previous_owner._last_applied_msaa >= 0
|
||||
and viewport.msaa_3d == _last_applied_msaa
|
||||
):
|
||||
viewport.msaa_3d = previous_owner._last_applied_msaa
|
||||
else:
|
||||
_viewport_scale_states.erase(_viewport_instance_id)
|
||||
if (
|
||||
@@ -338,8 +361,11 @@ func _release_viewport_scale_owner() -> void:
|
||||
and is_equal_approx(viewport.scaling_3d_scale, _last_applied_render_scale)
|
||||
):
|
||||
viewport.scaling_3d_scale = float(state["base_scale"])
|
||||
if was_active_owner and viewport.msaa_3d == _last_applied_msaa:
|
||||
viewport.msaa_3d = int(state["base_msaa"])
|
||||
_viewport_instance_id = 0
|
||||
_last_applied_render_scale = -1.0
|
||||
_last_applied_msaa = -1
|
||||
|
||||
|
||||
func _initialize_world_presentation() -> void:
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// Original opaque petal geometry: soft wrap lighting without alpha overdraw.
|
||||
shader_type spatial;
|
||||
render_mode skip_vertex_transform, cull_disabled, specular_disabled;
|
||||
|
||||
uniform vec2 wind_direction = vec2(1.0, 0.7);
|
||||
uniform float wind_amount : hint_range(0.0, 1.0) = 0.115;
|
||||
uniform float fade_begin = 42.0;
|
||||
uniform float fade_end = 56.0;
|
||||
varying float palette_variation;
|
||||
|
||||
void vertex() {
|
||||
vec3 anchor = MODEL_MATRIX[3].xyz;
|
||||
float distance_to_camera = distance(anchor, CAMERA_POSITION_WORLD);
|
||||
float presence = 1.0 - smoothstep(fade_begin, fade_end, distance_to_camera);
|
||||
float height = max(VERTEX.y, 0.0);
|
||||
float phase = dot(anchor.xz, vec2(0.38, 0.29));
|
||||
float gust = sin(TIME * 1.15 + phase) * 0.65 + sin(TIME * 0.58 + phase * 0.42) * 0.35;
|
||||
VERTEX *= presence;
|
||||
vec3 world_vertex = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
|
||||
world_vertex.xz += normalize(wind_direction) * wind_amount * gust * height * height * presence;
|
||||
VERTEX = (VIEW_MATRIX * vec4(world_vertex, 1.0)).xyz;
|
||||
vec3 local_normal = normalize(NORMAL + vec3(0.0, 1.6, 0.0));
|
||||
NORMAL = normalize((MODELVIEW_MATRIX * vec4(local_normal, 0.0)).xyz);
|
||||
palette_variation = mix(0.92, 1.06, INSTANCE_CUSTOM.r);
|
||||
}
|
||||
|
||||
void fragment() {
|
||||
ALBEDO = COLOR.rgb * palette_variation;
|
||||
ROUGHNESS = 1.0;
|
||||
}
|
||||
|
||||
void light() {
|
||||
float sunlight = smoothstep(-0.35, 0.55, dot(NORMAL, LIGHT));
|
||||
vec3 shade = mix(vec3(0.43, 0.58, 0.55), vec3(1.0, 0.98, 0.90), sunlight);
|
||||
DIFFUSE_LIGHT += shade * LIGHT_COLOR * ATTENUATION / PI;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://b1r81tjhi1o0y
|
||||
@@ -1,27 +1,26 @@
|
||||
shader_type sky;
|
||||
uniform vec3 sky_top_color : source_color = vec3(0.27, 0.57, 0.74);
|
||||
uniform vec3 sky_horizon_color : source_color = vec3(0.79, 0.88, 0.82);
|
||||
// Original painted panorama; no volume or per-frame sky pass.
|
||||
uniform sampler2D painted_panorama : source_color, filter_linear_mipmap, repeat_enable;
|
||||
uniform vec3 sky_top_color : source_color = vec3(0.12, 0.49, 0.69);
|
||||
uniform vec3 sky_horizon_color : source_color = vec3(0.65, 0.84, 0.84);
|
||||
uniform vec3 ground_bottom_color : source_color = vec3(0.12, 0.17, 0.12);
|
||||
uniform vec3 ground_horizon_color : source_color = vec3(0.56, 0.69, 0.57);
|
||||
uniform vec3 ground_horizon_color : source_color = vec3(0.65, 0.84, 0.84);
|
||||
uniform float daylight : hint_range(0.0, 1.0) = 1.0;
|
||||
|
||||
float hash21(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); }
|
||||
float noise(vec2 p) {
|
||||
vec2 i = floor(p);
|
||||
vec2 f = fract(p);
|
||||
f = f * f * (3.0 - 2.0 * f);
|
||||
return mix(mix(hash21(i), hash21(i + vec2(1.0, 0.0)), f.x), mix(hash21(i + vec2(0.0, 1.0)), hash21(i + vec2(1.0)), f.x), f.y);
|
||||
}
|
||||
uniform float golden_hour : hint_range(0.0, 1.0) = 0.0;
|
||||
|
||||
void sky() {
|
||||
float height = max(EYEDIR.y, 0.0);
|
||||
vec3 sky_color = mix(sky_horizon_color, sky_top_color, pow(height, 0.48));
|
||||
vec2 p = EYEDIR.xz / max(0.17, height) * 1.15;
|
||||
float cloud = noise(p) * 0.65 + noise(p * 2.1 + 7.3) * 0.25 + noise(p * 4.3) * 0.10;
|
||||
float mask = smoothstep(0.55, 0.64, cloud) * smoothstep(0.02, 0.20, height);
|
||||
vec3 cloud_shadow = mix(vec3(0.08, 0.10, 0.19), vec3(0.70, 0.80, 0.80), daylight);
|
||||
vec3 cloud_light = mix(vec3(0.15, 0.18, 0.29), vec3(1.0, 0.98, 0.87), daylight);
|
||||
vec3 cloud_color = mix(cloud_shadow, cloud_light, smoothstep(0.61, 0.77, cloud));
|
||||
sky_color = mix(sky_color, cloud_color, mask);
|
||||
vec3 sky_gradient = mix(sky_horizon_color, sky_top_color, pow(height, 0.45));
|
||||
// Art horizon is at 69% of the sheet. Remap only the upper hemisphere;
|
||||
// the ground bounce uses the day/night palette and never paints terrain.
|
||||
vec2 panorama_uv = vec2(fract(SKY_COORDS.x * 2.0 + 0.40), clamp((SKY_COORDS.y - 0.26) * 2.875, 0.0, 0.69));
|
||||
vec3 painted = texture(painted_panorama, panorama_uv).rgb;
|
||||
vec3 seam_color = (texture(painted_panorama, vec2(0.001, panorama_uv.y)).rgb + texture(painted_panorama, vec2(0.999, panorama_uv.y)).rgb) * 0.5;
|
||||
painted = mix(seam_color, painted, smoothstep(0.0, 0.025, min(panorama_uv.x, 1.0 - panorama_uv.x)));
|
||||
painted = mix(painted, painted * vec3(1.18, 0.79, 0.66), golden_hour * 0.50);
|
||||
vec3 night_paint = painted * vec3(0.045, 0.07, 0.14) + sky_gradient * 0.12;
|
||||
vec3 sky_color = mix(night_paint, painted, daylight);
|
||||
// Dissolve the panorama edge into the haze at the actual horizon.
|
||||
sky_color = mix(sky_gradient, sky_color, smoothstep(0.0, 0.075, height));
|
||||
COLOR = EYEDIR.y >= 0.0 ? sky_color : mix(ground_horizon_color, ground_bottom_color, pow(-EYEDIR.y, 0.4));
|
||||
}
|
||||
|
||||
@@ -1,42 +1,81 @@
|
||||
shader_type spatial;
|
||||
render_mode blend_mix, depth_prepass_alpha, cull_disabled, diffuse_burley, specular_schlick_ggx;
|
||||
|
||||
uniform float wave_strength : hint_range(0.0, 0.3) = 0.055;
|
||||
uniform float wave_strength : hint_range(0.0, 0.3) = 0.025;
|
||||
uniform float wave_speed : hint_range(0.0, 3.0) = 0.72;
|
||||
uniform float flow_speed : hint_range(0.0, 2.0) = 0.46;
|
||||
uniform vec3 deep_color : source_color = vec3(0.03, 0.39, 0.45);
|
||||
uniform vec3 shallow_color : source_color = vec3(0.07, 0.67, 0.64);
|
||||
uniform vec3 sunlit_color : source_color = vec3(0.55, 0.95, 0.78);
|
||||
uniform vec3 foam_color : source_color = vec3(0.82, 0.94, 0.84);
|
||||
uniform float flow_speed : hint_range(0.0, 2.0) = 0.28;
|
||||
uniform bool pool_surface = false;
|
||||
uniform float pool_outlet_z = -20.0;
|
||||
uniform vec3 deep_color : source_color = vec3(0.035, 0.34, 0.54);
|
||||
uniform vec3 shallow_color : source_color = vec3(0.12, 0.65, 0.69);
|
||||
uniform vec3 sunlit_color : source_color = vec3(0.57, 0.83, 0.88);
|
||||
uniform vec3 foam_color : source_color = vec3(0.9, 0.96, 0.91);
|
||||
|
||||
varying vec3 water_position;
|
||||
|
||||
|
||||
float painted_noise(vec2 p) {
|
||||
vec2 cell = floor(p);
|
||||
vec2 blend = fract(p);
|
||||
blend = blend * blend * (3.0 - 2.0 * blend);
|
||||
vec4 corners = fract(sin(vec4(
|
||||
dot(cell, vec2(127.1, 311.7)),
|
||||
dot(cell + vec2(1.0, 0.0), vec2(127.1, 311.7)),
|
||||
dot(cell + vec2(0.0, 1.0), vec2(127.1, 311.7)),
|
||||
dot(cell + vec2(1.0), vec2(127.1, 311.7))
|
||||
)) * 43758.5453);
|
||||
return mix(mix(corners.x, corners.y, blend.x), mix(corners.z, corners.w, blend.x), blend.y);
|
||||
}
|
||||
|
||||
|
||||
void vertex() {
|
||||
float broad_wave = sin(VERTEX.x * 0.42 + VERTEX.z * 0.18 + TIME * wave_speed);
|
||||
float crossing_wave = cos(VERTEX.x * 0.7 - VERTEX.z * 0.31 + TIME * wave_speed * 0.73);
|
||||
// World coordinates keep the pool and both ribbons at the same ripple scale.
|
||||
water_position = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
|
||||
float broad_wave = sin(water_position.x * 0.42 + water_position.z * 0.18 + TIME * wave_speed);
|
||||
float crossing_wave = cos(water_position.x * 0.7 - water_position.z * 0.31 + TIME * wave_speed * 0.73);
|
||||
VERTEX.y += (broad_wave + crossing_wave * 0.45) * wave_strength;
|
||||
}
|
||||
|
||||
|
||||
void fragment() {
|
||||
vec2 flowing_uv = UV * vec2(7.0, 10.0);
|
||||
flowing_uv.y -= TIME * flow_speed;
|
||||
float ribbon_a = sin(flowing_uv.y + sin(flowing_uv.x * 0.72) * 1.25) * 0.5 + 0.5;
|
||||
float ribbon_b = sin(flowing_uv.y * 1.7 - flowing_uv.x * 0.55 + TIME * 0.23) * 0.5 + 0.5;
|
||||
float quiet_ripple = smoothstep(0.72, 0.98, ribbon_a * 0.62 + ribbon_b * 0.38);
|
||||
float fresnel = pow(1.0 - clamp(dot(normalize(NORMAL), normalize(VIEW)), 0.0, 1.0), 2.4);
|
||||
float bank_distance = min(UV.x, 1.0 - UV.x);
|
||||
float bank_foam = 1.0 - smoothstep(0.0, 0.07, bank_distance);
|
||||
bank_foam *= 0.72 + sin(UV.y * 52.0 + TIME * 0.35) * 0.12;
|
||||
|
||||
vec3 water_color = mix(deep_color, shallow_color, ribbon_a * 0.2 + 0.48);
|
||||
water_color = mix(water_color, sunlit_color, fresnel * 0.42);
|
||||
water_color = mix(water_color, foam_color, quiet_ripple * 0.05);
|
||||
water_color = mix(water_color, foam_color, bank_foam * 0.3);
|
||||
// The ribbon owns the outlet so two transparent surfaces never overlap.
|
||||
if (pool_surface && water_position.z >= pool_outlet_z) {
|
||||
discard;
|
||||
}
|
||||
vec2 flow = water_position.xz - vec2(0.0, TIME * flow_speed);
|
||||
float broad_patch = painted_noise(flow * vec2(0.18, 0.3));
|
||||
float small_patch = painted_noise(flow * vec2(0.64, 0.85));
|
||||
float ripple_phase = flow.y * 3.2 + sin(flow.x * 0.83) * 1.2 + small_patch * 0.8;
|
||||
float ribbon = sin(ripple_phase);
|
||||
float stroke_width = max(fwidth(ribbon) * 0.7, 0.012);
|
||||
float strokes = smoothstep(0.975 - stroke_width, 0.975 + stroke_width, ribbon);
|
||||
strokes *= smoothstep(0.58, 0.8, painted_noise(flow * vec2(1.1, 1.8))) * 0.16;
|
||||
strokes *= 1.0 - smoothstep(0.28, 0.8, fwidth(ripple_phase));
|
||||
vec3 ripple_normal = normalize(vec3(
|
||||
cos(flow.x * 0.83 + flow.y * 0.37 + broad_patch * 6.0) * 0.006,
|
||||
1.0,
|
||||
cos(ripple_phase + small_patch * 5.0) * 0.008
|
||||
));
|
||||
NORMAL = normalize((VIEW_MATRIX * vec4(ripple_normal, 0.0)).xyz);
|
||||
float fresnel = pow(1.0 - clamp(dot(NORMAL, normalize(VIEW)), 0.0, 1.0), 2.6);
|
||||
// Painted sky patches need no screen/depth texture and work in Compatibility.
|
||||
vec3 world_view = normalize((INV_VIEW_MATRIX * vec4(VIEW, 0.0)).xyz);
|
||||
vec3 reflection = reflect(-world_view, ripple_normal);
|
||||
vec2 sky_uv = reflection.xz / max(reflection.y + 0.35, 0.25);
|
||||
float clouds = painted_noise(sky_uv * 2.0 + vec2(0.7, 2.4));
|
||||
clouds = smoothstep(0.49, 0.77, clouds + painted_noise(sky_uv * 4.5) * 0.18);
|
||||
float bank_distance = min(UV.x, 1.0 - UV.x) * 2.0;
|
||||
if (pool_surface) {
|
||||
bank_distance = 1.0 - length(UV * 2.0 - 1.0);
|
||||
}
|
||||
float bank_foam = (1.0 - smoothstep(0.01, 0.045, bank_distance));
|
||||
bank_foam *= smoothstep(0.4, 0.75, small_patch) * 0.48;
|
||||
vec3 water_color = mix(deep_color, shallow_color, 0.19 + broad_patch * 0.2);
|
||||
water_color = mix(water_color, sunlit_color, clouds * (0.13 + fresnel * 0.35));
|
||||
water_color = mix(water_color, foam_color, strokes + bank_foam);
|
||||
|
||||
ALBEDO = water_color;
|
||||
ROUGHNESS = mix(0.34, 0.5, ribbon_b);
|
||||
SPECULAR = 0.38;
|
||||
EMISSION = water_color * 0.12 + sunlit_color * (quiet_ripple * 0.05 + fresnel * 0.03);
|
||||
EMISSION += foam_color * bank_foam * 0.025;
|
||||
ALPHA = mix(0.94, 0.99, fresnel);
|
||||
ROUGHNESS = 0.38 + broad_patch * 0.08;
|
||||
SPECULAR = 0.14;
|
||||
ALPHA = smoothstep(0.0, 0.018, bank_distance) * 0.97;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
shader_type spatial;
|
||||
render_mode blend_mix, depth_prepass_alpha, cull_disabled, diffuse_burley;
|
||||
|
||||
uniform bool circular_wash = false;
|
||||
uniform vec3 foam_color : source_color = vec3(0.83, 0.94, 0.93);
|
||||
|
||||
|
||||
void fragment() {
|
||||
vec2 p = UV * 2.0 - 1.0;
|
||||
float wash = sin(p.x * 18.0 + sin(p.y * 12.0 + TIME * 0.8) * 1.5);
|
||||
wash *= sin(p.y * 23.0 - TIME * 1.4 + p.x * 3.0);
|
||||
float mask = (1.0 - smoothstep(0.45, 1.0, abs(p.x)));
|
||||
mask *= 1.0 - smoothstep(0.2, 1.0, abs(p.y));
|
||||
if (circular_wash) {
|
||||
float radius = length(p);
|
||||
float angle = atan(p.y, p.x);
|
||||
float broken_arcs = sin(radius * 32.0 - TIME * 0.75 + sin(angle * 5.0) * 0.6);
|
||||
mask = smoothstep(0.65, 0.94, broken_arcs);
|
||||
mask *= smoothstep(0.1, 0.35, radius) * (1.0 - smoothstep(0.65, 0.98, radius));
|
||||
mask *= smoothstep(-0.3, 0.8, sin(angle * 3.0 + radius * 9.0));
|
||||
}
|
||||
ALBEDO = foam_color;
|
||||
ROUGHNESS = 0.65;
|
||||
ALPHA = mask * smoothstep(-0.45, 0.6, wash) * 0.48;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://bcw0dj01f0m3h
|
||||
@@ -2,9 +2,9 @@ shader_type spatial;
|
||||
render_mode blend_mix, depth_prepass_alpha, cull_disabled, diffuse_burley, specular_schlick_ggx;
|
||||
|
||||
uniform float scroll_speed : hint_range(0.0, 4.0) = 1.35;
|
||||
uniform vec3 shadow_color : source_color = vec3(0.075, 0.33, 0.34);
|
||||
uniform vec3 water_color : source_color = vec3(0.31, 0.68, 0.64);
|
||||
uniform vec3 light_color : source_color = vec3(0.78, 0.94, 0.83);
|
||||
uniform vec3 shadow_color : source_color = vec3(0.11, 0.39, 0.54);
|
||||
uniform vec3 water_color : source_color = vec3(0.39, 0.73, 0.82);
|
||||
uniform vec3 light_color : source_color = vec3(0.85, 0.96, 0.94);
|
||||
uniform vec3 foam_color : source_color = vec3(0.91, 0.97, 0.9);
|
||||
|
||||
|
||||
@@ -22,17 +22,16 @@ void fragment() {
|
||||
float soft_thread = sin(flow_uv.x * 13.0 - flow_uv.y * 7.0) * 0.5 + 0.5;
|
||||
float falling_detail = sin(flow_uv.y * 27.0 + flow_uv.x * 7.0) * 0.5 + 0.5;
|
||||
float thread = smoothstep(0.36, 0.9, long_thread * 0.6 + soft_thread * 0.4);
|
||||
float broken_foam = smoothstep(0.72, 0.98, falling_detail * thread);
|
||||
float broken_foam = smoothstep(0.61, 0.94, falling_detail * thread);
|
||||
float base_foam = smoothstep(0.58, 1.0, UV.y);
|
||||
float edge_fade = smoothstep(0.0, 0.08, UV.x) * smoothstep(1.0, 0.92, UV.x);
|
||||
float edge_fade = smoothstep(0.0, 0.08, UV.x) * (1.0 - smoothstep(0.92, 1.0, UV.x));
|
||||
|
||||
vec3 color = mix(shadow_color, water_color, 0.5 + thread * 0.35);
|
||||
color = mix(color, light_color, broken_foam * 0.5);
|
||||
color = mix(color, light_color, thread * 0.35 + broken_foam * 0.35);
|
||||
color = mix(color, foam_color, base_foam * (0.38 + broken_foam * 0.42));
|
||||
|
||||
ALBEDO = color;
|
||||
ROUGHNESS = mix(0.16, 0.32, broken_foam);
|
||||
SPECULAR = 0.65;
|
||||
EMISSION = light_color * (broken_foam * 0.07 + base_foam * 0.04);
|
||||
ALPHA = edge_fade * mix(0.62, 0.9, thread * 0.56 + base_foam * 0.3);
|
||||
ALPHA = edge_fade * mix(0.76, 0.95, thread * 0.56 + base_foam * 0.3);
|
||||
}
|
||||
|
||||
@@ -10,8 +10,11 @@ uniform float gust_speed : hint_range(0.0, 1.0) = 0.12;
|
||||
uniform float gust_strength : hint_range(0.0, 0.8) = 0.35;
|
||||
uniform float flutter_strength : hint_range(0.0, 0.05) = 0.012;
|
||||
uniform float wind_phase : hint_range(0.0, 6.2832) = 0.0;
|
||||
uniform float wind_base_height = -1.2;
|
||||
uniform float wind_full_height = 1.2;
|
||||
uniform vec3 albedo : source_color = vec3(0.19, 0.38, 0.17);
|
||||
uniform float roughness : hint_range(0.0, 1.0) = 0.9;
|
||||
uniform float painted_vertex_weight : hint_range(0.0, 1.0) = 0.0;
|
||||
uniform vec3 contact_root = vec3(0.0);
|
||||
uniform float contact_strength = 0.0;
|
||||
uniform int interactor_count = 0;
|
||||
@@ -20,8 +23,7 @@ varying vec3 paint_position;
|
||||
|
||||
void vertex() {
|
||||
paint_position = VERTEX;
|
||||
float half_height = 1.2;
|
||||
float height_factor = clamp((VERTEX.y + half_height) / (half_height * 2.0), 0.0, 1.0);
|
||||
float height_factor = clamp((VERTEX.y - wind_base_height) / max(wind_full_height - wind_base_height, 0.01), 0.0, 1.0);
|
||||
height_factor = height_factor * height_factor;
|
||||
|
||||
vec2 direction = normalize(wind_direction);
|
||||
@@ -32,8 +34,10 @@ void vertex() {
|
||||
float bend = sin(TIME * wind_speed + world_phase) * wind_strength * gust;
|
||||
float flutter = sin(TIME * 1.7 + world_phase * 1.9 + VERTEX.y * 2.0) * flutter_strength;
|
||||
|
||||
VERTEX.xz += direction * bend * height_factor;
|
||||
VERTEX.xz += crosswind * flutter * height_factor;
|
||||
// Crown orientation varies, but all foliage follows the same world breeze.
|
||||
vec3 world_bend = vec3(direction.x, 0.0, direction.y) * bend;
|
||||
world_bend += vec3(crosswind.x, 0.0, crosswind.y) * flutter;
|
||||
VERTEX += (inverse(MODEL_MATRIX) * vec4(world_bend, 0.0)).xyz * height_factor;
|
||||
if (foliage_trails_enabled && contact_strength > 0.0) {
|
||||
// One bend for the whole bush, including fruit; its base remains planted.
|
||||
vec3 contact = sample_foliage_trail(contact_root);
|
||||
@@ -69,7 +73,10 @@ void vertex() {
|
||||
void fragment() {
|
||||
float patches = sin(paint_position.x * 6.0 + sin(paint_position.z * 7.0)) * sin(paint_position.y * 5.0);
|
||||
float top = smoothstep(-0.65, 0.8, paint_position.y);
|
||||
ALBEDO = albedo * mix(0.85, 1.17, top) * (1.0 + smoothstep(0.2, 0.4, patches) * 0.10);
|
||||
vec3 pigment = mix(vec3(1.0), COLOR.rgb, painted_vertex_weight);
|
||||
vec3 shade_tint = mix(vec3(0.77, 0.95, 1.01), vec3(1.10, 1.06, 0.87), top);
|
||||
ALBEDO = albedo * pigment * shade_tint * mix(0.91, 1.13, top);
|
||||
ALBEDO *= 1.0 + smoothstep(0.18, 0.65, patches) * 0.07;
|
||||
ROUGHNESS = roughness;
|
||||
SPECULAR = 0.0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user