Level of detail (LOD) add-on for Godot 3.2.x

Level of Detail (LOD) add-on

This add-on provides level of detail for meshes, lights and particles. It can be used to improve performance in large scenes significantly.

This repository only contains the add-on. See godot-extended-libraries/godot-lod-demo for the demonstration project.

Features

  • Supports lights, meshes and particles (Particles and CPUParticles).
  • Each node has its own LOD distance properties, but you can also define a LOD bias project setting to improve visual quality or performance for the target hardware.
  • Lights can have their shadow smoothly fade off with distance, then the light itself smoothly fade off as well.
  • Works with both the GLES3 and GLES2 renderers.
  • Written in performance-conscious GDScript. Easy to install and use, but scales to hundreds of LOD instances (and perhaps thousands on more powerful hardware).

The LOD add-on only runs in the running project, not while in the editor. This is done by design to avoid accidentally saving modified versions of the scene. You need to run the project to test LOD functionality.

Installation

Using the Asset Library

  • Open the Godot editor.
  • Navigate to the AssetLib tab at the top of the editor and search for "lod".
  • Install the Level of Detail (LOD) plugin. Keep all files checked during installation.
  • In the editor, open Project > Project Settings, go to Plugins and enable the Level of Detail (LOD) plugin.

Manual installation

Manual installation lets you use pre-release versions of this add-on by following its master branch.

  • Clone this Git repository:
git clone https://github.com/godot-extended-libraries/godot-lod.git

Alternatively, you can download a ZIP archive if you do not have Git installed.

  • Move the addons/ folder to your project folder.
  • In the editor, open Project > Project Settings, go to Plugins and enable the Level of Detail (LOD) plugin.

Usage

Meshes

  1. After enabling the plugin (see above), add a LODSpatial node.

  2. Add 2 or 3 child nodes. These must have with the -lod0 (high detail), -lod1 (medium detail) or -lod2 (low detail). -lod2 is optional. If you only have the -lod0 and -lod1 LOD levels (high and low), keep in mind the node will be invisible if -lod2 should be displayed given the current distance.

    Your scene tree should now look like this:

    - LODSpatial
      - MeshInstance-lod0
      - MeshInstance-lod1
      - MeshInstance-lod2
      - (other, unrelated nodes can be placed here too)
    
  3. Configure the distance properties in the LODSpatial node.

  4. To smoothly fade out the least detailed LOD before it disappears completely, see TIPS.md.

Note: As its name implies, LODSpatial is not limited to MeshInstances. It can hide and show any node that inherits from Spatial.

Lights

  1. After enabling the plugin (see above), add a LODOmniLight or LODSpotLight node.
  2. Configure the node as you would configure a typical light node.
  3. Set a maximum light and shadow distance in the inspector. By default, lights will no longer be visible at all if the camera is more than 50 units away. Their shadows will also no longer be visible if the camera is move than 25 units away.
  4. You can also configure the fade factors to make the fade-in less dramatic. This can be useful to make transitions less noticeable if the camera moves quickly.

Note: Since the light's energy will be tweaked for LOD purposes, don't change it manually while the project is running. You can still set a fixed energy value in the inspector, though. The add-on will take it into account for its calculations. To change the light's intensity over time (e.g. using AnimationPlayer), darken or lighten its color instead.

Note: If you add child nodes to a LODOmniLight or LODSpotLight, they will no longer be visible if the light is fully hidden due to the distance.

Particles

  1. After enabling the plugin (see above), add a LODParticles or LODCPUParticles node.
  2. Configure the node as you would configure a typical particles node.
  3. Set a maximum emission distance in the inspector. By default, particles will no longer emit if the camera is more than 50 units away.

You can safely add child nodes to a LODParticles or LODCPUParticles node, as the node itself is never hidden.

Configuration

There are several Project Settings you can define to customize this add-on's behavior. You can set them directly in a script (ProjectSettings.set_setting("section/key", value)) or use the Project Settings in the editor to do so.

lod/spatial_bias, lod/light_bias, lod/particle_bias

Default: 0

The distance bias (in 3D units) to use for LOD calculations. Positive values will improve performance at the cost of visual quality, whereas negative values will improve visual quality at the cost of performance.

lod/refresh_rate

Default: 0.25

The rate at which the LOD mesh and particle instances update (in seconds). Lower values are more reactive but use more CPU. Each LOD instance uses a random jitter to avoid applying updates on all instances at the same time.

Since meshes and particles are updated in a "discrete" manner rather than a continuus one, the default refresh rate is quite low. The difference is hardly visible, yet it helps decrease CPU usage significantly in scenes with hundreds of instances (or more).

lod/light_refresh_rate

Default: 0.05

The rate at which the LOD light instances update (in seconds). Lower values are more reactive but use more CPU. Each LOD instance uses a random jitter to avoid applying updates on all instances at the same time.

Since lights are updated in a "continuous" manner rather than a discrete one, the default refresh rate is relatively high. Despite not quite being 60 FPS (0.01666), it often looks very close in practice unless the camera is moving really fast.

Tips and tricks

See TIPS.md for useful tips on improving performance and visual quality of your 3D project. These tips can be followed even if you don't use this add-on.

License

Copyright © 2020 Hugo Locurcio and contributors

Unless otherwise specified, files in this repository are licensed under the MIT license. See LICENSE.md for more information.

Owner
Godot Extended Libraries
An unofficial Godot organization for sharing ideas, addons, modules, and community projects.
Godot Extended Libraries
Comments
  • Debug LODs in editor

    Debug LODs in editor

    Is it possible to view LOD changes in editor view or is it supposed to work in render only? I set up a scene with LODSpatial and a camera, but when I move the camera, LODs do not change in the camera preview viewport.

  • Doesn't work in 3.2 editor

    Doesn't work in 3.2 editor

    Hello.

    • reproduced all from installation steps, but it didn't hide any objects.
    • downloaded demo project, launched it, see no spheres were disappeared after looked from far distance

    But when I launched game debug (triangle button) - than it showed me hiding. Not sure where problem is.

    Maybe some version mismatch (used latest master of this plugin, Godot v3.2.3 stable). Does anybody has any hints on fixing this ?

    Thanks.

  • LODs don't switch in game in Godot 3.5 rc6

    LODs don't switch in game in Godot 3.5 rc6

    I have a tree I modeled in Blender and exported to Godot, as well as a billboard impostor (image on a quad), but the LODs don't switch in game.

    Both LODs are visible in editor as expected image

    Both LODs are also still visible in game, unexpected behaviour image

    How it's supposed to look in game: image

    Exact Godot version: v3.5.rc6.mono.official [f05cecdc4]

  • A small suggestion to script (refactor and usability improvement)

    A small suggestion to script (refactor and usability improvement)

    I'll leave it here just in case. don't want to poke you over this since Godot 4 has it covered, but still. What's changed:

    1. Structured scene.
    2. Meshes are attached as children to Scene_items nodes, so no need to name them in any specific way.
    3. LOD distances are relative, values are multiplied by the size of the parent node LODs
    4. removed get_children() loop.

    Scene with embedded script: image

    [gd_scene load_steps=2 format=2]
    
    [sub_resource type="GDScript" id=1]
    script/source = "# Copyright © 2020 Hugo Locurcio and contributors - MIT License
    # See `LICENSE.md` included in the source distribution for details.
    extends Spatial
    
    # If `false`, LOD won't update anymore. This can be used for performance comparison
    # purposes.
    export var enable_lod = true
    
    # The maximum LOD 0 (high quality) distance in units.
    export var lod_0_relative_distance = 5
    
    # The maximum LOD 1 (medium quality) distance in units.
    export var lod_1_relative_distance = 25
    
    # The maximum LOD 2 (low quality) distance in units.
    # Past this distance, all LOD variants are hidden.
    export var lod_2_relative_distance = 150
    
    # The rate at which LODs will be updated (in seconds). Lower values are more reactive
    # but use more CPU, which is especially noticeable with large amounts of LOD-enabled nodes.
    # Set this accordingly depending on your camera movement speed.
    # The default value should suit most projects already.
    # Note: Slow cameras don't need to have LOD-enabled objects update their status often.
    # This can overridden by setting the project setting `lod/refresh_rate`.
    export var refresh_rate = 0.25
    
    # The internal refresh timer.
    var timer = 0.0
    
    
    func _ready():
    
    	# Add random jitter to the timer to ensure LODs don't all swap at the same time.
    	randomize()
    	timer += rand_range(0, refresh_rate)
    
    
    # Despite LOD not being related to physics, we chose to run in `_physics_process()`
    # to minimize the amount of method calls per second (and therefore decrease CPU usage).
    func _physics_process(delta):
    	if not enable_lod:
    		# Show
    		show_scenes(\"LOD0\")
    		# Hide
    		hide_scenes(\"LOD1\")
    		hide_scenes(\"LOD2\")
    		hide_scenes(\"LOD3\")
    		return
    
    	# We need a camera to do the rest.
    	var camera = get_viewport().get_camera()
    	if camera == null:
    		return
    
    	if timer <= refresh_rate:
    		timer += delta
    		return
    
    	timer = 0.0
    
    	var distance = camera.global_transform.origin.distance_to(global_transform.origin)
    	# The LOD level to choose (lower is more detailed).
    	# Multiply distance values by scale for relativism.
    	if distance < lod_0_relative_distance * self.scale.length():
    		# Show
    		show_scenes(\"LOD0\")
    		# Hide
    		hide_scenes(\"LOD1\")
    		hide_scenes(\"LOD2\")
    		hide_scenes(\"LOD3\")
    	elif distance < lod_1_relative_distance * self.scale.length():
    		# Show
    		show_scenes(\"LOD1\")
    		# Hide
    		hide_scenes(\"LOD2\")
    		hide_scenes(\"LOD0\")
    		hide_scenes(\"LOD3\")
    	elif distance < lod_2_relative_distance * self.scale.length():
    		# Show
    		show_scenes(\"LOD2\")
    		# Hide
    		hide_scenes(\"LOD1\")
    		hide_scenes(\"LOD0\")
    		hide_scenes(\"LOD3\")
    	else:
    		# Show
    		show_scenes(\"LOD3\")
    		# Hide
    		hide_scenes(\"LOD0\")
    		hide_scenes(\"LOD1\")
    		hide_scenes(\"LOD2\")
    		
    
    func show_scenes(lod_name):
    	self.get_node(lod_name).get_node(\"Scene_items\").show()
    			
    func hide_scenes(lod_name):
    	self.get_node(lod_name).get_node(\"Scene_items\").hide()
    "
    
    [node name="LODs" type="Spatial"]
    script = SubResource( 1 )
    __meta__ = {
    "_editor_description_": "Do not rename or edit the nodes.
    Put meshes into Scene Items nodes"
    }
    
    [node name="LOD0" type="Spatial" parent="."]
    __meta__ = {
    "_edit_lock_": true
    }
    
    [node name="Scene_items" type="Spatial" parent="LOD0"]
    __meta__ = {
    "_edit_lock_": true
    }
    
    [node name="LOD1" type="Spatial" parent="."]
    __meta__ = {
    "_edit_lock_": true
    }
    
    [node name="Scene_items" type="Spatial" parent="LOD1"]
    __meta__ = {
    "_edit_lock_": true
    }
    
    [node name="LOD2" type="Spatial" parent="."]
    __meta__ = {
    "_edit_lock_": true
    }
    
    [node name="Scene_items" type="Spatial" parent="LOD2"]
    __meta__ = {
    "_edit_lock_": true
    }
    
    [node name="LOD3" type="Spatial" parent="."]
    __meta__ = {
    "_edit_lock_": true
    }
    
    [node name="Scene_items" type="Spatial" parent="LOD3"]
    __meta__ = {
    "_edit_lock_": true
    }
    
  • Debug view

    Debug view

    Hello, great addon, i propose an improve update, add a debug view with a color system ; the idea is to show all LOD level in the viewport with a color identifier.

    Actually, hard to see if the distance set is efficient.

    Edit : i can make a quick draw with toshop to give how to see this system.

  • Implemented pooled LOD updating via LODManager.

    Implemented pooled LOD updating via LODManager.

    Remember that suggestion @lawnjelly made about the way LODs are updated? Well this pull request has this feature implemented. the refresh project settings have been replaced with refresh_threshold_ms. The lower this value is, the less time the plugin has to update LODs in the scene, and thus the less that can be updated in one frame. LODs that aren't processed in a certain frame will have to wait till the next one. This proves to be very flexible as no matter how many LODs are in a scene. Only so much can be updated within the allocated time, improving performance and scalability.

    The Node inspector UIs were also given a makeover.

    Maintainer edit: This closes #3.

  • Possible more efficient updates

    Possible more efficient updates

    I don't know whether this is possible in gdscript with a singleton, but you may be able to make the updates more efficient:

    Calling _physics_process for a large number of nodes is quite inefficient even if just a stub function, especially if physics tick rate is e.g. 60 tps. With 1000 nodes, that is 60,000 updates per second, most of which are stubs.

    A more efficient paradigm might be instead to register each LOD node when added to the scenetree with a central updater, and unregister when removed from the scene tree.

    The central updater could then run on a single _physics_process (or _process) and go through the list of registered nodes and update a certain number each update, calculated to give them all on average an update every 0.25 seconds.

    In our example of 1000 nodes that would be: desired updates per second = (1000 * 4) updates per tick at 60tps = 4000 / 60 = 66.6

    4000 updates per second is a reduction of 15x. In addition, you could do further approaches like reduce the update rate on far away nodes, and check them every second.

    There are also a number of other optimizations which are common with collision detection, but these may well be overkill unless you had a large number of nodes.

  • Take the camera FOV into account for LOD calculations

    Take the camera FOV into account for LOD calculations

    When the camera FOV decreases, the distances should be multiplied accordingly so distant objects still look detailed. The user-supplied camera distances will be specified for the default camera FOV (which is 70 in Godot 3.2.x).

    There should be a project setting to disable this LOD adjustment in case it's not desired (e.g. for a game where the FOV is only changed for aesthetic reasons).

    I've also considered using screen density-based LOD calculations, but they have an additional performance cost and some downsides (such as making high-resolution gaming even slower than it would be otherwise).

"go build" wrapper to add version info to Golang applications

govvv The simple Go binary versioning tool that wraps the go build command. Stop worrying about -ldflags and go get github.com/ahmetb/govvv now. Build

Dec 16, 2022
Addon Operator coordinates the lifecycle of Add-ons in managed OpenShift
Addon Operator coordinates the lifecycle of Add-ons in managed OpenShift

Addon Operator Addon Operator coordinates the lifecycle of Addons in managed OpenShift. dev tools setup pre-commit hooks: make pre-commit-install glob

Dec 29, 2022
Tackle Add-on to discover information from a source repository

Tackle Add-ons - Discovery - Languages This add-on explores the source code repository and finds the languages using GitHub Linguist. It's common that

Dec 24, 2021
Application open new tab in chrome when your favourite youtuber add new video.

youtube-opener This application open new tab in Chrome when your favourite youtuber add new video. It checks channel every one minute. How to run go r

Jan 16, 2022
Add, remove, and manage different versions of web-distributed software binaries. No elevated permissions required!
Add, remove, and manage different versions of web-distributed software binaries. No elevated permissions required!

A cross-platform package manager for the web! Add, remove, and manage different versions of web-distributed software binaries. No elevated permissions

Nov 21, 2022
At LinkedIn, we are using this curriculum for onboarding our entry-level talents into the SRE role.
At LinkedIn, we are using this curriculum for onboarding our entry-level talents into the SRE role.

School of SRE In early 2019, we started visiting campuses across India to recruit the best and brightest minds to ensure LinkedIn, and all the service

Dec 30, 2022
KubeCube is an open source enterprise-level container platform
KubeCube is an open source enterprise-level container platform

KubeCube English | 中文文档 KubeCube is an open source enterprise-level container platform that provides enterprises with visualized management of Kuberne

Jan 4, 2023
HBase Exporter,fetch data from jmx for region-level data.

HBase Exporter Prometheus exporter for HBase which fetch data from hbase jmx, written in Go. You can even see region-level metrics. Installation and U

Nov 4, 2022
How to build production-level services in Go leveraging the power of Kubernetes

Ultimate Service Copyright 2018, 2019, 2020, 2021, Ardan Labs [email protected] Ultimate Service 3.0 Classes This class teaches how to build producti

Oct 22, 2021
LLS-Exporter exports fuel level sensor data (rs-485 lls protocol) as prometheus metrics

LLS Exporter LLS Exporter reads rs485/rs232 data from serial port, decodes lls protocol and exports fuel level sensor data as prometheus metrics. Lice

Dec 14, 2021
Kepler (Kubernetes-based Efficient Power Level Exporter) uses eBPF to probe energy related system stats and exports as Prometheus metrics
Kepler (Kubernetes-based Efficient Power Level Exporter) uses eBPF to probe energy related system stats and exports as Prometheus metrics

kepler Kepler (Kubernetes Efficient Power Level Exporter) uses eBPF to probe energy related system stats and exports as Prometheus metrics Architectur

Dec 26, 2022
An easy way to add useful startup banners into your Go applications
An easy way to add useful startup banners into your Go applications

Try browsing the code on Sourcegraph! Banner Add beautiful banners into your Go applications Table of Contents Motivation Usage API Command line flags

Jan 1, 2023
A feature flag solution, with only a YAML file in the backend (S3, GitHub, HTTP, local file ...), no server to install, just add a file in a central system and refer to it. 🎛️
A feature flag solution, with only a YAML file in the backend (S3, GitHub, HTTP, local file ...), no server to install, just add a file in a central system and refer to it. 🎛️

??️ go-feature-flag A feature flag solution, with YAML file in the backend (S3, GitHub, HTTP, local file ...). No server to install, just add a file i

Dec 29, 2022
Add interceptors to GO http.Client

mediary Add interceptors to http.Client and you will be able to Dump request and/or response to a Log Alter your requests before they are sent or resp

Nov 17, 2022
"go build" wrapper to add version info to Golang applications

govvv The simple Go binary versioning tool that wraps the go build command. Stop worrying about -ldflags and go get github.com/ahmetb/govvv now. Build

Dec 16, 2022
A kubernetes plugin which enables dynamically add or remove GPU resources for a running Pod
A kubernetes plugin which enables dynamically add or remove GPU resources for a running Pod

GPU Mounter GPU Mounter is a kubernetes plugin which enables add or remove GPU resources for running Pods. This Introduction(In Chinese) is recommende

Jan 5, 2023
Monitor newznab/torznab rss and add new media to sonarr/radarr

Nabarr Nabarr monitors Newznab/Torznab RSS feeds to find new media to add to Sonarr and or Radarr. Table of contents Installing nabarr Introduction Me

Dec 28, 2022
"go build" wrapper to add version info to Golang applications

govvv The simple Go binary versioning tool that wraps the go build command. Stop worrying about -ldflags and go get github.com/ahmetb/govvv now. Build

Dec 16, 2022
Data Connector is a Google Sheets Add-on that lets you import (and export) data to/from Google Sheets

Data Connector Data Connector is a Google Sheets Add-on that lets you import (and export) data to/from Google Sheets. Our roadmap: Connect to JSON/XML

Jul 30, 2022
Addon Operator coordinates the lifecycle of Add-ons in managed OpenShift
Addon Operator coordinates the lifecycle of Add-ons in managed OpenShift

Addon Operator Addon Operator coordinates the lifecycle of Addons in managed OpenShift. dev tools setup pre-commit hooks: make pre-commit-install glob

Dec 29, 2022