diff --git a/3d/visibility_ranges/README.md b/3d/visibility_ranges/README.md new file mode 100644 index 0000000000..efd9b06552 --- /dev/null +++ b/3d/visibility_ranges/README.md @@ -0,0 +1,68 @@ +# Visibility Ranges (HLOD) + +This demo showcases how to set up a hierarchical LOD system using visibility ranges. + +This can improve performance significantly in 3D scenes by reducing the number of +draw calls and polygons that have to be drawn every frame. + +Use WASD or arrow keys to move, and use the mouse to look around. Press +L to toggle the use of visibility ranges. Press F to +toggle the fade mode between *transparency* (the default in this demo) and +*hysteresis* (which is slightly faster, but results in more jarring +transitions). + +> **Note** +> +> Performance is expected to decrease significantly after disabling visibility ranges, +> as all trees will be drawn with full detail regardless of distance. + +Language: GDScript + +Renderer: Forward Plus + +## How does it work? + +There are 2 goals when using visibility ranges to improve performance: + +- Reduce the number of polygons that need to be drawn. +- Reduce the number of draw calls, while also preserving culling opportunities when up close. + +To achieve this, the demo contains four levels of LOD for each cluster of 16 trees. +These are the levels displayed from closest to furthest away: + +- Individual tree, with high geometric detail. +- Individual tree, with low geometric detail. +- Tree cluster, with high geoemtric detail. +- Tree cluster, with low geometric detail. + +When the distance between the camera and the tree's origin is greater than 20 +units, the high-detail tree blends into a low-detail tree (transition period +lasts 5 units). + +When the distance between the camera and the tree's origin is greater than 150 +units, all low-detail trees in the cluster are hidden, and the trees blend into +a high-detail tree cluster. This transition period lasts for a longer distance +(50 units) as the visual difference between these LOD levels is greater. + +When the distance between the camera and the cluster's origin is greater than +450 units, the high-detail tree cluster blends into a low-detail tree cluster +(also with a transition period of 50 units). + +When the distance between the camera and the cluster's origin is greater than +1,900 units, the low-detail tree cluster fades away with a transition period of +100 units. At this distance, the fog present in the scene makes this transition +harder to notice. + +There are several ways to further improve this LOD system: + +- Use MultiMeshInstance3D to draw clusters of geometry in a single draw call. + However, individual meshes will not benefit from frustum or occlusion culling + (only the entire cluster is culled at once). Therefore, this must be done + carefully to balance the number of draw calls with culling efficiency. +- Use impostor sprites in the distance. These can be drawn with Sprite3D, or + using MeshInstance3D + QuadMesh with a StandardMaterial3D that has + billboarding enabled. + +## Screenshots + +![Screenshot](screenshots/visibility_ranges.webp) diff --git a/3d/visibility_ranges/camera.gd b/3d/visibility_ranges/camera.gd new file mode 100644 index 0000000000..8dc3de6933 --- /dev/null +++ b/3d/visibility_ranges/camera.gd @@ -0,0 +1,43 @@ +extends Camera3D + +const MOUSE_SENSITIVITY = 0.002 +const MOVE_SPEED = 10.0 + +var rot = Vector3() +var velocity = Vector3() + + +func _ready(): + Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) + + +func _input(event): + # Mouse look (only if the mouse is captured, and only after the loading screen has ended). + if event is InputEventMouseMotion and Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED and Engine.get_process_frames() > 2: + # Horizontal mouse look. + rot.y -= event.relative.x * MOUSE_SENSITIVITY + # Vertical mouse look. + rot.x = clamp(rot.x - event.relative.y * MOUSE_SENSITIVITY, -1.57, 1.57) + transform.basis = Basis.from_euler(rot) + + if event.is_action_pressed("toggle_mouse_capture"): + if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED: + Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) + else: + Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) + + +func _process(delta): + var motion = Vector3( + Input.get_axis(&"move_left", &"move_right"), + 0, + Input.get_axis(&"move_forward", &"move_back") + ) + + # Normalize motion to prevent diagonal movement from being + # `sqrt(2)` times faster than straight movement. + motion = motion.normalized() + + velocity += MOVE_SPEED * delta * (transform.basis * motion) + velocity *= 0.85 + position += velocity diff --git a/3d/visibility_ranges/fps_label.gd b/3d/visibility_ranges/fps_label.gd new file mode 100644 index 0000000000..6feeacb507 --- /dev/null +++ b/3d/visibility_ranges/fps_label.gd @@ -0,0 +1,5 @@ +extends Label + + +func _process(_delta): + text = "%d FPS (%.2f mspf)" % [Engine.get_frames_per_second(), 1000.0 / Engine.get_frames_per_second()] diff --git a/3d/visibility_ranges/project.godot b/3d/visibility_ranges/project.godot new file mode 100644 index 0000000000..1ebd867263 --- /dev/null +++ b/3d/visibility_ranges/project.godot @@ -0,0 +1,74 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=5 + +[application] + +config/name="Visibility Ranges (HLOD)" +config/description="This demo showcases how to set up a hierarchical LOD system +using visibility ranges. + +This can improve performance significantly in 3D scenes by reducing +the number of draw calls and polygons that have to be drawn every frame." +run/main_scene="res://test.tscn" +config/features=PackedStringArray("4.1") + +[display] + +window/vsync/vsync_mode=0 +window/stretch/mode="canvas_items" +window/stretch/aspect="expand" + +[input] + +move_forward={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":122,"echo":false,"script":null) +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194320,"key_label":0,"unicode":0,"echo":false,"script":null) +] +} +move_back={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"echo":false,"script":null) +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194322,"key_label":0,"unicode":0,"echo":false,"script":null) +] +} +move_left={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":113,"echo":false,"script":null) +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194319,"key_label":0,"unicode":0,"echo":false,"script":null) +] +} +move_right={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"echo":false,"script":null) +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194321,"key_label":0,"unicode":0,"echo":false,"script":null) +] +} +toggle_mouse_capture={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194305,"key_label":0,"unicode":0,"echo":false,"script":null) +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194341,"key_label":0,"unicode":0,"echo":false,"script":null) +] +} +toggle_visibility_ranges={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":76,"physical_keycode":0,"key_label":0,"unicode":108,"echo":false,"script":null) +] +} +toggle_fade_mode={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":70,"physical_keycode":0,"key_label":0,"unicode":102,"echo":false,"script":null) +] +} + +[rendering] + +textures/default_filters/anisotropic_filtering_level=4 +anti_aliasing/quality/msaa_3d=2 diff --git a/3d/visibility_ranges/screenshots/.gdignore b/3d/visibility_ranges/screenshots/.gdignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/3d/visibility_ranges/screenshots/visibility_ranges.webp b/3d/visibility_ranges/screenshots/visibility_ranges.webp new file mode 100644 index 0000000000..16c540d996 Binary files /dev/null and b/3d/visibility_ranges/screenshots/visibility_ranges.webp differ diff --git a/3d/visibility_ranges/test.tscn b/3d/visibility_ranges/test.tscn new file mode 100644 index 0000000000..b341cb6af7 --- /dev/null +++ b/3d/visibility_ranges/test.tscn @@ -0,0 +1,190 @@ +[gd_scene load_steps=17 format=3 uid="uid://bshkqyd3jv7xc"] + +[ext_resource type="Script" path="res://camera.gd" id="1_yepcp"] +[ext_resource type="Script" path="res://tree_clusters.gd" id="2_ydews"] +[ext_resource type="Script" path="res://fps_label.gd" id="3_vep8a"] + +[sub_resource type="Gradient" id="Gradient_hp0a8"] +interpolation_mode = 2 +colors = PackedColorArray(1, 1, 1, 1, 0, 0, 0, 1) + +[sub_resource type="GradientTexture2D" id="GradientTexture2D_6fgiw"] +gradient = SubResource("Gradient_hp0a8") +width = 128 +fill = 1 +fill_from = Vector2(0.5, 0.38) +fill_to = Vector2(0.1, 0.4) + +[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_ymxen"] +sky_top_color = Color(0.385, 0.4125, 0.55, 1) +sky_horizon_color = Color(0.6432, 0.647667, 0.67, 1) +sky_cover = SubResource("GradientTexture2D_6fgiw") +sky_cover_modulate = Color(1, 0.776471, 0.129412, 1) +ground_horizon_color = Color(0.643137, 0.647059, 0.670588, 1) +sun_angle_max = 40.0 +sun_curve = 0.235375 + +[sub_resource type="Sky" id="Sky_tq5wf"] +sky_material = SubResource("ProceduralSkyMaterial_ymxen") + +[sub_resource type="Environment" id="Environment_w7n8k"] +background_mode = 2 +sky = SubResource("Sky_tq5wf") +ambient_light_color = Color(1, 1, 1, 1) +ambient_light_sky_contribution = 0.75 +tonemap_mode = 3 +tonemap_white = 6.0 +fog_enabled = true +fog_light_color = Color(0.517647, 0.552941, 0.607843, 1) +fog_density = 0.001 +fog_aerial_perspective = 1.0 + +[sub_resource type="BoxMesh" id="BoxMesh_qxf28"] +lightmap_size_hint = Vector2i(327684, 163856) +add_uv2 = true +size = Vector3(32768, 1, 32768) +subdivide_width = 15 +subdivide_depth = 15 + +[sub_resource type="Gradient" id="Gradient_urgs4"] +offsets = PackedFloat32Array(0, 0.243902, 0.357724, 0.617886, 1) +colors = PackedColorArray(0.164706, 0.101961, 0, 1, 0.123774, 0.283202, 0.173896, 1, 0.354642, 0.374758, 0.206693, 1, 0.490333, 0.5, 0.48, 1, 0.1961, 0.37, 0.271457, 1) + +[sub_resource type="FastNoiseLite" id="FastNoiseLite_g0yjr"] +fractal_octaves = 9 +fractal_lacunarity = 2.717 +fractal_gain = 0.6 + +[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_0q3y6"] +width = 1024 +height = 1024 +seamless = true +color_ramp = SubResource("Gradient_urgs4") +noise = SubResource("FastNoiseLite_g0yjr") + +[sub_resource type="Gradient" id="Gradient_63ydg"] +colors = PackedColorArray(0, 0.0431373, 0, 1, 0, 0, 0, 0) + +[sub_resource type="FastNoiseLite" id="FastNoiseLite_dddeo"] +noise_type = 0 +fractal_type = 3 +domain_warp_enabled = true + +[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_j3exn"] +seamless = true +color_ramp = SubResource("Gradient_63ydg") +noise = SubResource("FastNoiseLite_dddeo") + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_lf55d"] +albedo_texture = SubResource("NoiseTexture2D_0q3y6") +detail_enabled = true +detail_uv_layer = 1 +detail_albedo = SubResource("NoiseTexture2D_j3exn") +uv1_scale = Vector3(2048, 1536, 1) +uv2_scale = Vector3(64, 32, 1) +texture_filter = 5 + +[node name="Node3D" type="Node3D"] + +[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."] +transform = Transform3D(-0.292632, 0.955563, -0.0355886, -0.845857, -0.241319, 0.4757, 0.445973, 0.169308, 0.878887, 0, 11, 0) +shadow_enabled = true +shadow_bias = 0.05 +shadow_blur = 1.5 +directional_shadow_max_distance = 200.0 + +[node name="WorldEnvironment" type="WorldEnvironment" parent="."] +environment = SubResource("Environment_w7n8k") + +[node name="Ground" type="MeshInstance3D" parent="."] +mesh = SubResource("BoxMesh_qxf28") +surface_material_override/0 = SubResource("StandardMaterial3D_lf55d") + +[node name="Camera3D" type="Camera3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 10, 200) +fov = 74.0 +script = ExtResource("1_yepcp") + +[node name="TreeClusters" type="Node3D" parent="."] +script = ExtResource("2_ydews") + +[node name="Loading" type="Control" parent="TreeClusters"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="ColorRect" type="ColorRect" parent="TreeClusters/Loading"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +color = Color(0, 0, 0, 0.501961) + +[node name="Label" type="Label" parent="TreeClusters/Loading"] +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -170.5 +offset_top = -24.0 +offset_right = 170.5 +offset_bottom = 24.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/outline_size = 8 +theme_override_font_sizes/font_size = 32 +text = "Loading, please wait…" + +[node name="VisibilityRanges" type="Label" parent="TreeClusters"] +offset_left = 16.0 +offset_top = 16.0 +offset_right = 208.0 +offset_bottom = 42.0 +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/outline_size = 4 +text = "Visibility ranges: Enabled" + +[node name="FadeMode" type="Label" parent="TreeClusters"] +offset_left = 16.0 +offset_top = 48.0 +offset_right = 208.0 +offset_bottom = 74.0 +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/outline_size = 4 +text = "Fade mode: Enabled (Transparency)" + +[node name="FPSLabel" type="Label" parent="."] +anchors_preset = 1 +anchor_left = 1.0 +anchor_right = 1.0 +offset_left = -214.0 +offset_top = 16.0 +offset_right = -16.0 +offset_bottom = 39.0 +grow_horizontal = 0 +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/outline_size = 4 +horizontal_alignment = 2 +script = ExtResource("3_vep8a") + +[node name="Help" type="Label" parent="."] +anchors_preset = 2 +anchor_top = 1.0 +anchor_bottom = 1.0 +offset_left = 16.0 +offset_top = -39.0 +offset_right = 56.0 +offset_bottom = -16.0 +grow_vertical = 0 +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/outline_size = 4 +text = "L: Toggle visibility ranges +F: Toggle fade mode" diff --git a/3d/visibility_ranges/tree.tscn b/3d/visibility_ranges/tree.tscn new file mode 100644 index 0000000000..c81590f7f4 --- /dev/null +++ b/3d/visibility_ranges/tree.tscn @@ -0,0 +1,73 @@ +[gd_scene load_steps=7 format=3 uid="uid://bv4peo31bj672"] + +[ext_resource type="Material" uid="uid://bo08s5dqyqpon" path="res://tree_material.tres" id="1_784bs"] +[ext_resource type="Material" uid="uid://cqmgkacgqkl5c" path="res://trunk_material.tres" id="2_ap7c2"] + +[sub_resource type="CylinderMesh" id="CylinderMesh_6uoay"] +top_radius = 0.0 +bottom_radius = 1.75 +height = 4.0 +radial_segments = 32 + +[sub_resource type="CylinderMesh" id="CylinderMesh_qqg4e"] +bottom_radius = 0.6 +radial_segments = 20 +rings = 1 +cap_bottom = false + +[sub_resource type="CylinderMesh" id="CylinderMesh_74uyc"] +top_radius = 0.0 +bottom_radius = 1.75 +height = 4.0 +radial_segments = 8 +rings = 1 + +[sub_resource type="CylinderMesh" id="CylinderMesh_43jmr"] +bottom_radius = 0.6 +radial_segments = 4 +rings = 1 +cap_bottom = false + +[node name="Tree" type="Node3D"] + +[node name="HighDetail" type="Node3D" parent="."] + +[node name="Top" type="MeshInstance3D" parent="HighDetail" groups=["tree_high_detail"]] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 4, 0) +visibility_range_end = 20.0 +visibility_range_end_margin = 5.0 +visibility_range_fade_mode = 1 +mesh = SubResource("CylinderMesh_6uoay") +skeleton = NodePath("../..") +surface_material_override/0 = ExtResource("1_784bs") + +[node name="Trunk" type="MeshInstance3D" parent="HighDetail" groups=["tree_high_detail"]] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0) +visibility_range_end = 20.0 +visibility_range_end_margin = 5.0 +visibility_range_fade_mode = 1 +mesh = SubResource("CylinderMesh_qqg4e") +skeleton = NodePath("../..") +surface_material_override/0 = ExtResource("2_ap7c2") + +[node name="LowDetail" type="Node3D" parent="."] + +[node name="Top" type="MeshInstance3D" parent="LowDetail" groups=["tree_low_detail"]] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 4, 0) +visibility_range_begin = 15.0 +visibility_range_end = 200.0 +visibility_range_end_margin = 50.0 +visibility_range_fade_mode = 1 +mesh = SubResource("CylinderMesh_74uyc") +skeleton = NodePath("../..") +surface_material_override/0 = ExtResource("1_784bs") + +[node name="Trunk" type="MeshInstance3D" parent="LowDetail" groups=["tree_low_detail"]] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0) +visibility_range_begin = 15.0 +visibility_range_end = 200.0 +visibility_range_end_margin = 50.0 +visibility_range_fade_mode = 1 +mesh = SubResource("CylinderMesh_43jmr") +skeleton = NodePath("../..") +surface_material_override/0 = ExtResource("2_ap7c2") diff --git a/3d/visibility_ranges/tree_cluster.tscn b/3d/visibility_ranges/tree_cluster.tscn new file mode 100644 index 0000000000..93988bbcff --- /dev/null +++ b/3d/visibility_ranges/tree_cluster.tscn @@ -0,0 +1,133 @@ +[gd_scene load_steps=8 format=3 uid="uid://bqjwf4fg6gvu5"] + +[ext_resource type="PackedScene" uid="uid://bv4peo31bj672" path="res://tree.tscn" id="1_xp6ld"] +[ext_resource type="Material" uid="uid://bo08s5dqyqpon" path="res://tree_material.tres" id="2_dw421"] +[ext_resource type="Material" uid="uid://cqmgkacgqkl5c" path="res://trunk_material.tres" id="3_dl0c6"] + +[sub_resource type="CylinderMesh" id="CylinderMesh_6uoay"] +top_radius = 0.0 +bottom_radius = 1.75 +height = 4.0 +radial_segments = 32 + +[sub_resource type="CylinderMesh" id="CylinderMesh_qqg4e"] +bottom_radius = 0.6 +radial_segments = 20 +rings = 1 +cap_bottom = false + +[sub_resource type="CylinderMesh" id="CylinderMesh_74uyc"] +top_radius = 0.0 +bottom_radius = 1.75 +height = 4.0 +radial_segments = 8 +rings = 1 + +[sub_resource type="CylinderMesh" id="CylinderMesh_43jmr"] +bottom_radius = 0.6 +radial_segments = 4 +rings = 1 +cap_bottom = false + +[node name="TreeCluster" type="Node3D"] + +[node name="Tree" parent="." instance=ExtResource("1_xp6ld")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.23853, -0.604848, 2.36929) + +[node name="Tree2" parent="." instance=ExtResource("1_xp6ld")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 8.17988, 9.53674e-07, 11.1474) + +[node name="Tree3" parent="." instance=ExtResource("1_xp6ld")] +transform = Transform3D(1, 0, 0, 0, 0.994625, 0.103539, 0, -0.103539, 0.994625, -9.7838, 0, 15.4517) + +[node name="Tree4" parent="." instance=ExtResource("1_xp6ld")] +transform = Transform3D(1, 0, 0, 0, 0.999611, -0.0278867, 0, 0.0278867, 0.999611, -6.71813, 0, 6.46976) + +[node name="Tree5" parent="." instance=ExtResource("1_xp6ld")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 12.6214, -0.095274, 3.48558) + +[node name="Tree6" parent="." instance=ExtResource("1_xp6ld")] +transform = Transform3D(1, 0, 0, 0, 0.990373, 0.13843, 0, -0.13843, 0.990373, 7.85683, -1.13758, -8.35561) + +[node name="Tree7" parent="." instance=ExtResource("1_xp6ld")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.4899, -0.270895, -12.7505) + +[node name="Tree8" parent="." instance=ExtResource("1_xp6ld")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3.88321, -0.0901899, 15.154) + +[node name="Tree9" parent="." instance=ExtResource("1_xp6ld")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.8513, 0, 1.831) + +[node name="Tree10" parent="." instance=ExtResource("1_xp6ld")] +transform = Transform3D(0.999923, 0.0124439, 0, -0.0124439, 0.999923, 0, 0, 0, 1, -12.9166, -0.497135, -3.58321) + +[node name="Tree11" parent="." instance=ExtResource("1_xp6ld")] +transform = Transform3D(1, 0, 0, 0, 0.999997, 0.00230383, 0, -0.00230383, 0.999997, -12.6297, 0, 8.99774) + +[node name="Tree12" parent="." instance=ExtResource("1_xp6ld")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 17.9091, -0.833557, 9.89316) + +[node name="Tree13" parent="." instance=ExtResource("1_xp6ld")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 17.5096, -0.271834, -8.60772) + +[node name="Tree14" parent="." instance=ExtResource("1_xp6ld")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 13.9704, 0, 16.1838) + +[node name="Tree15" parent="." instance=ExtResource("1_xp6ld")] +transform = Transform3D(0.999292, 0.0376204, 0, -0.0376204, 0.999292, 0, 0, 0, 1, -12.6976, 0, -14.5755) + +[node name="Tree16" parent="." instance=ExtResource("1_xp6ld")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4.76961, -0.509378, 20.9842) + +[node name="Cluster" type="Node3D" parent="."] +transform = Transform3D(8, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0) + +[node name="HighDetail" type="Node3D" parent="Cluster"] + +[node name="Top" type="MeshInstance3D" parent="Cluster/HighDetail" groups=["tree_cluster_high_detail"]] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 4, 0) +cast_shadow = 0 +visibility_range_begin = 200.0 +visibility_range_begin_margin = 50.0 +visibility_range_end = 500.0 +visibility_range_end_margin = 50.0 +visibility_range_fade_mode = 1 +mesh = SubResource("CylinderMesh_6uoay") +skeleton = NodePath("../..") +surface_material_override/0 = ExtResource("2_dw421") + +[node name="Trunk" type="MeshInstance3D" parent="Cluster/HighDetail" groups=["tree_cluster_high_detail"]] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0) +cast_shadow = 0 +visibility_range_begin = 200.0 +visibility_range_begin_margin = 50.0 +visibility_range_end = 500.0 +visibility_range_end_margin = 50.0 +visibility_range_fade_mode = 1 +mesh = SubResource("CylinderMesh_qqg4e") +skeleton = NodePath("../..") +surface_material_override/0 = ExtResource("3_dl0c6") + +[node name="LowDetail" type="Node3D" parent="Cluster"] + +[node name="Top" type="MeshInstance3D" parent="Cluster/LowDetail" groups=["tree_cluster_low_detail"]] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 4, 0) +cast_shadow = 0 +visibility_range_begin = 450.0 +visibility_range_end = 2000.0 +visibility_range_end_margin = 100.0 +visibility_range_fade_mode = 1 +mesh = SubResource("CylinderMesh_74uyc") +skeleton = NodePath("../..") +surface_material_override/0 = ExtResource("2_dw421") + +[node name="Trunk" type="MeshInstance3D" parent="Cluster/LowDetail" groups=["tree_cluster_low_detail"]] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0) +cast_shadow = 0 +visibility_range_begin = 450.0 +visibility_range_end = 2000.0 +visibility_range_end_margin = 100.0 +visibility_range_fade_mode = 1 +mesh = SubResource("CylinderMesh_43jmr") +skeleton = NodePath("../..") +surface_material_override/0 = ExtResource("3_dl0c6") diff --git a/3d/visibility_ranges/tree_clusters.gd b/3d/visibility_ranges/tree_clusters.gd new file mode 100644 index 0000000000..42def20bd0 --- /dev/null +++ b/3d/visibility_ranges/tree_clusters.gd @@ -0,0 +1,85 @@ +extends Node3D + +const NUM_TREE_CLUSTERS = 2000 +const SPREAD = 1250 +const TREE_CLUSTER_SCENE = preload("res://tree_cluster.tscn") + +## If `false`, highest detail is always used (slower). +var visibility_ranges_enabled = true + +## `true` = use transparencdy fade, `false` = use hysteresis. +var fade_mode_enabled = true + +func _ready(): + for i in 2: + # Draw two frames to let the loading screen be visible. + await get_tree().process_frame + + # Use a predefined random seed for better reproducibility of results. + seed(0x60d07) + + for i in NUM_TREE_CLUSTERS: + var tree_cluster = TREE_CLUSTER_SCENE.instantiate() + tree_cluster.position = Vector3(randf_range(-SPREAD, SPREAD), 0, randf_range(-SPREAD, SPREAD)) + add_child(tree_cluster) + + $Loading.visible = false + + +func _input(event): + if event.is_action_pressed(&"toggle_visibility_ranges"): + visibility_ranges_enabled = not visibility_ranges_enabled + $VisibilityRanges.text = "Visibility ranges: %s" % ("Enabled" if visibility_ranges_enabled else "Disabled") + $VisibilityRanges.modulate = Color.WHITE if visibility_ranges_enabled else Color.YELLOW + $FadeMode.visible = visibility_ranges_enabled + + # When disabling visibility ranges, display the high-detail trees at any range. + for node in get_tree().get_nodes_in_group(&"tree_high_detail"): + if visibility_ranges_enabled: + node.visibility_range_begin = 0 + node.visibility_range_end = 20 + else: + node.visibility_range_begin = 0 + node.visibility_range_end = 0 + for node in get_tree().get_nodes_in_group(&"tree_low_detail"): + node.visible = visibility_ranges_enabled + for node in get_tree().get_nodes_in_group(&"tree_cluster_high_detail"): + node.visible = visibility_ranges_enabled + for node in get_tree().get_nodes_in_group(&"tree_cluster_low_detail"): + node.visible = visibility_ranges_enabled + + if event.is_action_pressed(&"toggle_fade_mode"): + fade_mode_enabled = not fade_mode_enabled + $FadeMode.text = "Fade mode: %s" % ("Enabled (Transparency)" if fade_mode_enabled else "Disabled (Hysteresis)") + + for node in get_tree().get_nodes_in_group(&"tree_high_detail"): + if fade_mode_enabled: + node.visibility_range_fade_mode = GeometryInstance3D.VISIBILITY_RANGE_FADE_SELF + else: + node.visibility_range_fade_mode = GeometryInstance3D.VISIBILITY_RANGE_FADE_DISABLED + + for node in get_tree().get_nodes_in_group(&"tree_low_detail"): + if fade_mode_enabled: + node.visibility_range_fade_mode = GeometryInstance3D.VISIBILITY_RANGE_FADE_SELF + node.visibility_range_end_margin = 50 + else: + node.visibility_range_fade_mode = GeometryInstance3D.VISIBILITY_RANGE_FADE_DISABLED + node.visibility_range_end_margin = 0 + + for node in get_tree().get_nodes_in_group(&"tree_cluster_high_detail"): + if fade_mode_enabled: + node.visibility_range_fade_mode = GeometryInstance3D.VISIBILITY_RANGE_FADE_SELF + node.visibility_range_begin_margin = 50 + node.visibility_range_end_margin = 50 + else: + node.visibility_range_fade_mode = GeometryInstance3D.VISIBILITY_RANGE_FADE_DISABLED + node.visibility_range_begin_margin = 0 + node.visibility_range_end_margin = 0 + + for node in get_tree().get_nodes_in_group(&"tree_cluster_low_detail"): + if fade_mode_enabled: + node.visibility_range_fade_mode = GeometryInstance3D.VISIBILITY_RANGE_FADE_SELF + node.visibility_range_end_margin = 100 + else: + node.visibility_range_fade_mode = GeometryInstance3D.VISIBILITY_RANGE_FADE_DISABLED + node.visibility_range_end_margin = 0 diff --git a/3d/visibility_ranges/tree_material.tres b/3d/visibility_ranges/tree_material.tres new file mode 100644 index 0000000000..db6e12f9aa --- /dev/null +++ b/3d/visibility_ranges/tree_material.tres @@ -0,0 +1,17 @@ +[gd_resource type="StandardMaterial3D" load_steps=3 format=3 uid="uid://bo08s5dqyqpon"] + +[sub_resource type="FastNoiseLite" id="FastNoiseLite_uhsu1"] +fractal_octaves = 9 +fractal_lacunarity = 7.0 +fractal_gain = 1.0 +fractal_weighted_strength = 1.0 + +[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_xslbn"] +seamless = true +noise = SubResource("FastNoiseLite_uhsu1") + +[resource] +albedo_color = Color(0.1705, 0.55, 0.297, 1) +albedo_texture = SubResource("NoiseTexture2D_xslbn") +uv1_scale = Vector3(1, 8, 1) +uv1_triplanar_sharpness = 3.73213 diff --git a/3d/visibility_ranges/trunk_material.tres b/3d/visibility_ranges/trunk_material.tres new file mode 100644 index 0000000000..56f4f746a0 --- /dev/null +++ b/3d/visibility_ranges/trunk_material.tres @@ -0,0 +1,17 @@ +[gd_resource type="StandardMaterial3D" load_steps=3 format=3 uid="uid://cqmgkacgqkl5c"] + +[sub_resource type="FastNoiseLite" id="FastNoiseLite_uhsu1"] +fractal_octaves = 9 +fractal_lacunarity = 7.0 +fractal_gain = 1.0 +fractal_weighted_strength = 1.0 + +[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_xslbn"] +seamless = true +noise = SubResource("FastNoiseLite_uhsu1") + +[resource] +albedo_color = Color(0.5, 0.371667, 0.225, 1) +albedo_texture = SubResource("NoiseTexture2D_xslbn") +uv1_scale = Vector3(8, 1, 1) +uv1_triplanar_sharpness = 3.73213