mirror of
https://github.com/godotengine/godot-demo-projects.git
synced 2025-12-16 05:20:06 +01:00
Add a 3D visibility ranges (HLOD) demo (#860)
This commit is contained in:
68
3d/visibility_ranges/README.md
Normal file
68
3d/visibility_ranges/README.md
Normal 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
|
||||
|
||||

|
||||
43
3d/visibility_ranges/camera.gd
Normal file
43
3d/visibility_ranges/camera.gd
Normal 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
|
||||
1
3d/visibility_ranges/camera.gd.uid
Normal file
1
3d/visibility_ranges/camera.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bic02fqt5yf40
|
||||
6
3d/visibility_ranges/fps_label.gd
Normal file
6
3d/visibility_ranges/fps_label.gd
Normal 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]
|
||||
1
3d/visibility_ranges/fps_label.gd.uid
Normal file
1
3d/visibility_ranges/fps_label.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bd856mfo4l8g8
|
||||
74
3d/visibility_ranges/project.godot
Normal file
74
3d/visibility_ranges/project.godot
Normal 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
|
||||
0
3d/visibility_ranges/screenshots/.gdignore
Normal file
0
3d/visibility_ranges/screenshots/.gdignore
Normal file
BIN
3d/visibility_ranges/screenshots/visibility_ranges.webp
Normal file
BIN
3d/visibility_ranges/screenshots/visibility_ranges.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 494 KiB |
190
3d/visibility_ranges/test.tscn
Normal file
190
3d/visibility_ranges/test.tscn
Normal 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"
|
||||
73
3d/visibility_ranges/tree.tscn
Normal file
73
3d/visibility_ranges/tree.tscn
Normal 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")
|
||||
133
3d/visibility_ranges/tree_cluster.tscn
Normal file
133
3d/visibility_ranges/tree_cluster.tscn
Normal 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")
|
||||
85
3d/visibility_ranges/tree_clusters.gd
Normal file
85
3d/visibility_ranges/tree_clusters.gd
Normal 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
|
||||
1
3d/visibility_ranges/tree_clusters.gd.uid
Normal file
1
3d/visibility_ranges/tree_clusters.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b7erm757yjt5k
|
||||
17
3d/visibility_ranges/tree_material.tres
Normal file
17
3d/visibility_ranges/tree_material.tres
Normal 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
|
||||
17
3d/visibility_ranges/trunk_material.tres
Normal file
17
3d/visibility_ranges/trunk_material.tres
Normal 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
|
||||
Reference in New Issue
Block a user