Add a 3D visibility ranges (HLOD) demo (#860)

This commit is contained in:
Hugo Locurcio
2025-10-02 06:47:36 +02:00
committed by GitHub
parent 1d8fa9c44d
commit 05aeb11de9
15 changed files with 709 additions and 0 deletions

View File

@@ -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
<kbd>L</kbd> to toggle the use of visibility ranges. Press <kbd>F</kbd> 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)

View File

@@ -0,0 +1,43 @@
extends Camera3D
const MOUSE_SENSITIVITY = 0.002
const MOVE_SPEED = 10.0
var rot := Vector3()
var velocity := Vector3()
func _ready() -> void:
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
func _input(input_event: InputEvent) -> void:
# Mouse look (only if the mouse is captured, and only after the loading screen has ended).
if input_event is InputEventMouseMotion and Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED and Engine.get_process_frames() > 2:
# Horizontal mouse look.
rot.y -= input_event.relative.x * MOUSE_SENSITIVITY
# Vertical mouse look.
rot.x = clampf(rot.x - input_event.relative.y * MOUSE_SENSITIVITY, -1.57, 1.57)
transform.basis = Basis.from_euler(rot)
if input_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: float) -> void:
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

View File

@@ -0,0 +1 @@
uid://bic02fqt5yf40

View File

@@ -0,0 +1,6 @@
extends Label
func _process(_delta: float) -> void:
var fps: float = Engine.get_frames_per_second()
text = "%d FPS (%.2f mspf)" % [fps, 1000.0 / fps]

View File

@@ -0,0 +1 @@
uid://bd856mfo4l8g8

View File

@@ -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.5")
[display]
window/stretch/mode="canvas_items"
window/stretch/aspect="expand"
window/vsync/vsync_mode=0
[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,"location":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":4194320,"key_label":0,"unicode":0,"location":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,"location":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":4194322,"key_label":0,"unicode":0,"location":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,"location":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":4194319,"key_label":0,"unicode":0,"location":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,"location":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":4194321,"key_label":0,"unicode":0,"location":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,"location":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,"location":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,"location":0,"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,"location":0,"echo":false,"script":null)
]
}
[rendering]
textures/default_filters/anisotropic_filtering_level=4
anti_aliasing/quality/msaa_3d=2

Binary file not shown.

After

Width:  |  Height:  |  Size: 494 KiB

View File

@@ -0,0 +1,190 @@
[gd_scene load_steps=17 format=3 uid="uid://bshkqyd3jv7xc"]
[ext_resource type="Script" uid="uid://bic02fqt5yf40" path="res://camera.gd" id="1_yepcp"]
[ext_resource type="Script" uid="uid://b7erm757yjt5k" path="res://tree_clusters.gd" id="2_ydews"]
[ext_resource type="Script" uid="uid://bd856mfo4l8g8" 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
noise = SubResource("FastNoiseLite_g0yjr")
color_ramp = SubResource("Gradient_urgs4")
seamless = true
[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"]
noise = SubResource("FastNoiseLite_dddeo")
color_ramp = SubResource("Gradient_63ydg")
seamless = true
[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"

View File

@@ -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")

View File

@@ -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")

View File

@@ -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

View File

@@ -0,0 +1 @@
uid://b7erm757yjt5k

View File

@@ -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"]
noise = SubResource("FastNoiseLite_uhsu1")
seamless = true
[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

View File

@@ -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"]
noise = SubResource("FastNoiseLite_uhsu1")
seamless = true
[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