Jak zbudować las w Godot 4, który nie zabije FPS: MultiMesh, LOD i visibility ranges
Praktyczny poradnik szybkiej roślinności w Godot 4: budżety trójkątów, automatyczny LOD siatek kontra visibility ranges, MultiMesh w chunkach, przezroczystość liści, cienie, occlusion culling i pomiar efektów.

Pierwszy las w projekcie Godot zwykle wygląda świetnie — dopóki do niego nie wejdziesz. Dwieście drzew później liczba klatek spada o połowę, profiler pokazuje tysiące draw calli i nikt nie wie, które ustawienie zmienić. Jeden z użytkowników forum Godota opisał dokładnie taką sytuację: domowej roboty system LOD dla trawy skończył na około 10 FPS i ponad 3000 draw calli na niewielkim kawałku terenu.
Dobra wiadomość: Godot 4 ma wszystkie narzędzia potrzebne do dużego i szybkiego lasu. Trzeba je tylko odpowiednio połączyć. Ten poradnik omawia je w kolejności, która ma znaczenie — budżet, LOD, instancing, liście, cienie, culling — i kończy się checklistą.
Dlaczego lasy są wolne
Drzewo to jeden z najdroższych obiektów w grze. Kosztuje w czterech miejscach jednocześnie:
- Draw calle. Każde drzewo to co najmniej dwie siatki (kora i liście), często z różnymi materiałami. Sto osobnych drzew to setki draw calli, zanim narysujesz cokolwiek innego.
- Trójkąty i wierzchołki. Szczegółowe drzewo bez trudu przekracza 40 000 trójkątów. W porządku dla jednego drzewa na pierwszym planie, zabójcze dla całego zbocza.
- Overdraw. Liście to karty z przezroczystymi pikselami. GPU i tak cieniuje niewidoczne fragmenty, warstwa po warstwie, dla każdej karty w koronie.
- Cienie. Każde drzewo jest rysowane ponownie do shadow mapy — czasem kilka razy, raz na każdą kaskadę cieni.
Każda z poniższych technik atakuje jeden lub kilka z tych kosztów.
Krok 1. Ustal budżet, zanim cokolwiek zbudujesz
Najbardziej przydatny nawyk to ustalenie, ile trójkątów może kosztować drzewo na każdym dystansie, zanim je wymodelujesz lub wygenerujesz. Oto prawdziwy dąb z FoliMesh jako przykład łańcucha gotowego do gry:
| Poziom | Dystans (przykład) | Trójkąty |
|---|---|---|
| LOD0 | 0–30 m | 14 490 |
| LOD1 | 30–70 m | 3984 |
| LOD2 | 70–150 m | 1154 |
| Impostor | 150 m + | 2 |
Ważne są nie konkretne liczby, lecz kształt krzywej: każdy poziom jest mniej więcej 3–4 razy lżejszy od poprzedniego, a najdalsze drzewa nie kosztują prawie nic. Na urządzeniach mobilnych podziel wszystko przez dwa lub trzy. Większość lasu jest daleko, więc większość lasu musi być tania.
To samo drzewo na czterech poziomach szczegółowości: sylwetka zostaje, liczba trójkątów spada.
Krok 2. Wybierz mechanizm LOD: automatyczny LOD siatek czy visibility ranges
Godot 4 oferuje dwa różne systemy LOD, a wybór niewłaściwego dla drzew to częste źródło problemów.
Automatyczny LOD siatek. Gdy importujesz scenę glTF, .blend lub FBX, Godot generuje uproszczone wersje każdej siatki za pomocą meshoptimizera i sam się między nimi przełącza. Według oficjalnej dokumentacji wybór zależy od punktu bounding boxa węzła najbliższego kamerze oraz od tego, ile pikseli zająłby błąd (Rendering → Mesh LOD → LOD Change → Threshold Pixels, domyślnie 1 piksel). Każdy węzeł ma też LOD Bias.
To dobrze działa dla skał, budynków i propów — powierzchni ciągłych. Korona drzewa jest inna: to setki osobnych kart z liśćmi. Ogólne upraszczanie nie było do tego projektowane, a efektem może być przerzedzona, zniekształcona korona. Sama dokumentacja zaleca inne narzędzie, gdy masz LOD-y wykonane ręcznie.
Visibility ranges (HLOD). Tutaj sam dostarczasz siatki LOD i określasz dla każdej, na jakim dystansie jest widoczna. Każdy GeometryInstance3D ma:
visibility_range_begin/visibility_range_end— przedział dystansu, w którym węzeł jest rysowany (mierzony do środka jego bounding boxa);visibility_range_begin_margin/visibility_range_end_margin— strefę buforową, żeby poziom nie migotał, gdy kamera stoi dokładnie na granicy;visibility_range_fade_mode— Disabled (natychmiastowe przełączenie, najszybsze), Self lub Dependencies (przenikanie przez alfę; na czas przejścia wymusza renderowanie przezroczyste, co kosztuje wydajność).
Dla drzew visibility ranges to pewny wybór: LOD-y są projektowane pod listowie, dystanse masz pod kontrolą, a na ostatnim poziomie możesz wyłączyć cienie albo przełączyć się na impostor. Minimalna konfiguracja dla jednego drzewa:
# Tree.gd — dzieci: LOD0, LOD1, LOD2 (MeshInstance3D)
extends Node3D
@export var lod_starts := PackedFloat32Array([0.0, 30.0, 70.0])
@export var cull_distance := 150.0
func _ready() -> void:
for i in get_child_count():
var mi := get_child(i) as MeshInstance3D
if mi == null:
continue
mi.visibility_range_begin = lod_starts[i]
mi.visibility_range_end = lod_starts[i + 1] if i + 1 < lod_starts.size() else cull_distance
mi.visibility_range_end_margin = 2.0
mi.visibility_range_fade_mode = GeometryInstance3D.VISIBILITY_RANGE_FADE_DISABLED
Jeśli przełączenie jest widoczne, najpierw upewnij się, że LOD-y zachowują tę samą sylwetkę — to ważniejsze niż jakiekolwiek ustawienia przenikania.
Krok 3. Dużo drzew: MultiMesh, ale w chunkach
Osobne węzły wystarczą dla kilkudziesięciu drzew. Dla tysięcy potrzebujesz MultiMesh: jeden MultiMeshInstance3D rysuje dowolną liczbę kopii siatki w jednym draw callu.
Jest tu haczyk, który zaskakuje prawie wszystkich. Dokumentacja mówi, że węzły rysujące wiele siatek naraz — MultiMeshInstance3D i cząsteczki — rysują wszystkie instancje na tym samym poziomie LOD, a culling może usunąć tylko cały węzeł, a nie pojedyncze instancje. Jeden z maintainerów Godota potwierdził to w issue #76436: jeden MultiMesh na cały poziom oznacza brak LOD dla poszczególnych drzew i brak frustum oraz occlusion cullingu.
Rozwiązaniem są chunki. Podziel świat na siatkę i daj każdej komórce własny MultiMesh dla każdego poziomu LOD, każdy z własnym visibility range. Dalekie chunki pokazują lekki LOD, bliskie — szczegółowy, a chunki za kamerą są całkowicie odrzucane.
# ForestChunks.gd — buduje MultiMeshe w chunkach z visibility ranges
extends Node3D
@export var lod_meshes: Array[Mesh] = [] # LOD0, LOD1, LOD2, impostor
@export var lod_starts := PackedFloat32Array([0.0, 35.0, 80.0, 160.0])
@export var cull_distance := 400.0
@export var chunk_size := 32.0
func build(transforms: Array[Transform3D]) -> void:
var chunks := {}
for t in transforms:
var key := Vector2i(floori(t.origin.x / chunk_size), floori(t.origin.z / chunk_size))
if not chunks.has(key):
chunks[key] = []
chunks[key].append(t)
for key in chunks:
_build_chunk(chunks[key])
func _build_chunk(items: Array) -> void:
for i in lod_meshes.size():
var mm := MultiMesh.new()
mm.transform_format = MultiMesh.TRANSFORM_3D
mm.mesh = lod_meshes[i]
mm.instance_count = items.size()
for k in items.size():
mm.set_instance_transform(k, items[k])
var node := MultiMeshInstance3D.new()
node.multimesh = mm
node.visibility_range_begin = lod_starts[i]
node.visibility_range_end = lod_starts[i + 1] if i + 1 < lod_starts.size() else cull_distance
node.visibility_range_end_margin = 4.0
add_child(node)
Dwie praktyczne zasady:
- Rozmiar chunka zbliżony do dystansu LOD0. Dystans jest mierzony do środka chunka, więc chunk o boku 100 m pokazałby LOD1 dla drzewa tuż obok gracza. Przy LOD0 do 35 m dobrze sprawdzają się chunki 24–32 m.
- Ale nie za małe. Każdy chunk dodaje draw calle (jeden na LOD na materiał). Mierz: zwykle kilkaset chunków na ekranie to wciąż nic strasznego na desktopie, na urządzeniach mobilnych — dużo mniej.
MultiMesh nie ma kolizji. Dodawaj proste kształty tylko tam, gdzie gracz może dotrzeć — kapsułę lub walec na pień, nigdy na koronę.
Setki instancjonowanych drzew: właściwa struktura sprawia, że liczba draw calli prawie nie rośnie wraz z lasem.
Krok 4. Liście: alpha scissor, nie alpha blending
To na liściach większość lasów traci wydajność — i sporo urody, jeśli przezroczystość jest źle ustawiona.
- Używaj Alpha Scissor (cutout), a nie Alpha. W materiale ustaw Transparency → Alpha Scissor. Alfa z blendingiem wymusza sortowanie, psuje głębię i cienie i jest dużo droższa w koronie z setkami nakładających się kart.
- Wygładź krawędzie przez alpha to coverage. Przy włączonym MSAA ustaw Alpha Antialiasing → Alpha Edge Clip (lub Blend), aby uzyskać miękkie krawędzie liści bez prawdziwej przezroczystości.
- Walcz z overdraw kształtem karty. Kwadratowa karta wokół okrągłej gałązki marnuje mnóstwo pikseli. Karty przycięte do kształtu kępy liści cieniują znacznie mniej niewidocznych pikseli. Sprawdź to trybem Display Overdraw w viewporcie: jaskrawoczerwone korony oznaczają zbyt wiele warstw.
- Usuń to, czego nikt nie widzi. Kępy głęboko w koronie są niewidoczne z zewnątrz; dalekie LOD-y powinny pozbywać się ich w pierwszej kolejności.
Krok 5. Cienie: ukryty drugi las
Każde drzewo rzucające cień jest renderowane ponownie do shadow mapy. Trzy ustawienia dają większość oszczędności:
- Ogranicz zasięg cieni.
DirectionalLight3D→ Directional Shadow → Max Distance. Powyżej 100–200 m niewielu graczy zauważy brak cieni pojedynczych drzew. - Wyłącz cienie na dalekich LOD-ach. Ustaw Cast Shadow → Off na LOD2 i na impostorach (w przypadku MultiMesh — na
MultiMeshInstance3Ddanego poziomu). - Używaj mniej kaskad na słabszym sprzęcie. Dwie kaskady zamiast czterech to często niewidoczna zmiana i odczuwalna oszczędność.
Krok 6. Occlusion culling i wiatr
Occlusion culling pomaga, gdy las zasłaniają duże obiekty: wzgórza, klify, budynki, sam teren. Włącz go w Project Settings → Rendering → Occlusion Culling, dodaj OccluderInstance3D i zrób bake. Same drzewa są słabymi okluderami — są pełne dziur — więc niech robotę wykona teren. Najwięcej zyskują MultiMeshe w chunkach: cały zasłonięty chunk jest pomijany.
Wiatr powinien działać w vertex shaderze i odczytywać z siatki wstępnie obliczone wagi: pień prawie się nie rusza, duże gałęzie kołyszą się powoli, końcówki liści drgają szybko. Liczenie tego od zera w shaderze jest drogie; odczyt dwóch liczb na wierzchołek jest niemal darmowy.
Krok 7. Mierz na docelowym sprzęcie
Nigdy nie optymalizuj na ślepo. Godot daje ci wszystko, czego potrzebujesz:
- Debugger → Monitors: draw calle w klatce, obiekty, prymitywy (trójkąty) w klatce.
- Debugger → Visual Profiler: ile czasu CPU i GPU spędzają na każdej części klatki.
- Viewport → Display Overdraw / Wireframe: gdzie idą piksele i trójkąty.
Mierz przed każdą zmianą i po niej, na najsłabszym urządzeniu, które chcesz wspierać — nie tylko na swoim komputerze deweloperskim.
Checklista
- Każde drzewo ma budżet trójkątów na dystans, a każdy poziom jest 3–4 razy lżejszy.
- Drzewa używają visibility ranges z własnymi LOD-ami; automatyczny LOD siatek tylko dla skał i propów.
- Tysiące drzew podzielone są na MultiMeshe w chunkach, mniej więcej wielkości dystansu LOD0.
- Liście używają Alpha Scissor plus alpha to coverage; karty podążają za kształtem kęp.
- Zasięg cieni jest ograniczony; dalekie LOD-y i impostory nie rzucają cieni.
- Occlusion culling jest wypiekany z terenu i dużych obiektów.
- Wiatr to tani vertex shader odczytujący wagi dla każdego wierzchołka.
- Draw calle i czas klatki są zmierzone na urządzeniu docelowym.
Gdzie w tym wszystkim jest FoliMesh
Stworzyliśmy FoliMesh dla naszej własnej gry o dużej skali, więc generuje drzewa, które już przestrzegają tych zasad. Paczka dla Godota to folder, który wrzucasz do projektu: GLB z LOD0, LOD1, LOD2… wygenerowanymi według twoich budżetów trójkątów oraz gotowa scena .tscn, której skrypt ustawia visibility ranges dla każdego poziomu, shader wiatru (zgięcie pnia i kołysanie gałęzi zapisane są w drugim kanale UV siatki) i StaticBody3D z kapsułą wokół pnia do kolizji. Normal mapy są już w formacie OpenGL, którego oczekuje Godot.
Każdy model możesz sprawdzić przed importem naszymi darmowymi narzędziami w przeglądarce: przeglądarką LOD, licznikiem polygonów i walidatorem glTF. Szerszy obraz narzędzi do drzew znajdziesz w naszym porównaniu generatorów drzew 3D.
FAQ
Czy Godot 4 tworzy LOD-y automatycznie? Tak — dla importowanych scen glTF, .blend, Collada i FBX generuje LOD-y siatek przy imporcie. Najlepiej działa to na powierzchniach ciągłych; dla drzew przewidywalniejsze wyniki dają LOD-y wykonane ręcznie lub wygenerowane, w połączeniu z visibility ranges.
Dlaczego LOD nie działa z moim MultiMeshem?
Godot wybiera jeden poziom LOD dla całego MultiMeshInstance3D. Podziel instancje na chunki, każdy z własnym węzłem i własnym visibility range.
Ile drzew Godot jest w stanie wyrenderować? To zależy od budżetu na drzewo, a nie od silnika. Z MultiMeshami w chunkach, tanimi dalekimi LOD-ami i impostorami dziesiątki tysięcy drzew na ekranie są realne na sprzęcie desktopowym; zmierz na swoim urządzeniu docelowym.
Alpha Scissor czy Alpha Hash dla liści? Standardowym wyborem jest Alpha Scissor z alpha to coverage. Alpha Hash daje miękkie krawędzie bez MSAA, ale wygląda na zaszumiony i najlepiej sprawdza się z TAA.

