Как сделать лес в Godot 4, который не убивает FPS: MultiMesh, LOD и диапазоны видимости
Практическое руководство по быстрой растительности в Godot 4: бюджет треугольников, автоматический LOD против диапазонов видимости, MultiMesh по чанкам, прозрачность листвы, тени, отсечение перекрытых объектов и замеры.

Первый лес в проекте на Godot обычно выглядит отлично — пока в него не зайдёшь. Двести деревьев спустя частота кадров падает вдвое, профайлер показывает тысячи вызовов отрисовки, и непонятно, какую настройку крутить. На форуме Godot один разработчик описал ровно это: самодельная система LOD для травы дала около 10 FPS и больше 3000 вызовов отрисовки на маленьком участке земли.
Хорошая новость: в Godot 4 есть всё, что нужно для большого и быстрого леса. Инструменты просто надо правильно сложить вместе. В этом руководстве они идут в том порядке, в каком важны, — бюджет, LOD, инстансинг, листва, тени, отсечение, — а в конце есть чек-лист.
Почему лес тормозит
Дерево — один из самых дорогих объектов в игре. Оно стоит дорого сразу в четырёх местах:
- Вызовы отрисовки (draw calls). Каждое дерево — минимум две сетки (кора и листва), часто с разными материалами. Сотня отдельных деревьев — это сотни вызовов отрисовки, а вы ещё ничего другого не нарисовали.
- Треугольники и вершины. У детального дерева легко 40 000+ треугольников. Для одного дерева крупным планом — нормально, для целого склона — смертельно.
- Перерисовка (overdraw). Листья — карточки с прозрачными пикселями. Видеокарта всё равно обсчитывает невидимые части, слой за слоем, для каждой карточки в кроне.
- Тени. Каждое дерево рисуется ещё раз — в карту теней, иногда несколько раз, по разу на каждый каскад.
Каждый приём ниже бьёт по одной или нескольким из этих статей расходов.
Шаг 1. Решите бюджет до того, как что-то строить
Самая полезная привычка — заранее решить, сколько треугольников дереву можно стоить на каждой дистанции, ещё до того, как вы его смоделируете или сгенерируете. Вот настоящий дуб из FoliMesh — пример того, как выглядит игровая цепочка:
| Уровень | Дистанция (пример) | Треугольников |
|---|---|---|
| LOD0 | 0–30 м | 14 490 |
| LOD1 | 30–70 м | 3 984 |
| LOD2 | 70–150 м | 1 154 |
| Импостор | 150 м и дальше | 2 |
Важны не сами числа, а форма кривой: каждый уровень примерно в 3–4 раза легче предыдущего, а самые дальние деревья почти ничего не стоят. На мобильных делите всё на два-три. Большая часть леса далеко — значит, большая часть леса должна быть дешёвой.
Одно дерево на четырёх уровнях детализации: силуэт тот же, треугольников всё меньше.
Шаг 2. Выберите механизм LOD: автоматический или диапазоны видимости
В Godot 4 две разные системы LOD, и неправильный выбор для деревьев — частый источник проблем.
Автоматический LOD сеток. При импорте сцен glTF, .blend или FBX Godot сам строит упрощённые версии каждой сетки через meshoptimizer и сам переключает их. По официальной документации, выбор зависит от ближайшей к камере точки ограничивающего объёма узла и от того, сколько пикселей займёт ошибка (Rendering → Mesh LOD → LOD Change → Threshold Pixels, по умолчанию 1 пиксель). У каждого узла есть ещё LOD Bias.
Это хорошо работает для камней, зданий и пропсов — сплошных поверхностей. Крона дерева устроена иначе: это сотни отдельных карточек листвы. Обычное упрощение на такое не рассчитано, и крона может поредеть или исказиться. Сама документация для LOD, сделанных вручную, советует другой инструмент.
Диапазоны видимости (HLOD). Здесь вы сами даёте сетки LOD и говорите каждой, на какой дистанции она видна. У каждого GeometryInstance3D есть:
visibility_range_begin/visibility_range_end— полоса дистанций, в которой узел рисуется (считается до центра его ограничивающего объёма);visibility_range_begin_margin/visibility_range_end_margin— запас, чтобы уровень не мигал, когда камера стоит ровно на границе;visibility_range_fade_mode— Disabled (мгновенная смена, быстрее всего), Self или Dependencies (плавное растворение; на время перехода включает прозрачную отрисовку, а это стоит производительности).
Для деревьев диапазоны видимости — надёжный выбор: уровни сделаны специально под листву, дистанции под вашим контролем, а на последнем уровне можно выключить тени или поставить импостор. Минимальная настройка для одного дерева:
# Tree.gd — дочерние узлы: 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
Если смена уровня заметна, сначала проверьте, что у всех LOD один силуэт, — это важнее любой настройки растворения.
Шаг 3. Много деревьев: MultiMesh, но по чанкам
Отдельные узлы годятся для нескольких десятков деревьев. Для тысяч нужен MultiMesh: один MultiMeshInstance3D рисует сколько угодно копий сетки за один вызов отрисовки.
Есть подвох, который удивляет почти всех. Документация говорит, что узлы, которые рисуют много сеток сразу, — MultiMeshInstance3D и частицы, — рисуют все экземпляры на одном уровне LOD, а отсечение может убрать только узел целиком, не отдельные экземпляры. Разработчик Godot подтвердил это в issue #76436: один MultiMesh на весь уровень — значит, ни LOD по каждому дереву, ни отсечения по пирамиде видимости и перекрытию.
Решение — чанки. Разбейте мир на сетку и дайте каждой клетке свой MultiMesh на каждый уровень LOD со своим диапазоном видимости. Дальние чанки показывают лёгкий LOD, ближние — детальный, а чанки за спиной камеры отсекаются целиком.
# ForestChunks.gd — строит MultiMesh по чанкам с диапазонами видимости
extends Node3D
@export var lod_meshes: Array[Mesh] = [] # LOD0, LOD1, LOD2, импостор
@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)
Два практических правила:
- Размер чанка — около дистанции LOD0. Дистанция считается до центра чанка, поэтому при чанке в 100 м дерево прямо рядом с игроком будет показано на LOD1. Если LOD0 до 35 м, хорошо работают чанки по 24–32 м.
- Но и не слишком мелко. Каждый чанк добавляет вызовы отрисовки (по одному на уровень LOD и материал). Замеряйте: обычно несколько сотен чанков в кадре на ПК ещё нормально, на мобильных — намного меньше.
У MultiMesh нет коллизии. Добавляйте простые формы только там, куда может дойти игрок, — капсулу или цилиндр на ствол, никогда не крону.
Сотни деревьев-экземпляров: при правильном устройстве число вызовов отрисовки почти не растёт вместе с лесом.
Шаг 4. Листва: Alpha Scissor, а не смешивание
Именно на листве большинство лесов теряет производительность — и красоту тоже, если прозрачность настроена неправильно.
- Alpha Scissor (вырез), а не Alpha. В материале поставьте Transparency → Alpha Scissor. Смешиваемая прозрачность требует сортировки, ломает глубину и тени и намного дороже на кроне из сотен перекрывающихся карточек.
- Сгладьте края через alpha to coverage. С включённым MSAA поставьте Alpha Antialiasing → Alpha Edge Clip (или Blend) — края листьев станут мягкими без настоящей прозрачности.
- Боритесь с перерисовкой формой карточки. Квадратная карточка вокруг круглой веточки тратит много пикселей впустую. Карточки, обрезанные по форме пучка листьев, обсчитывают куда меньше невидимых пикселей. Проверяйте режимом вьюпорта Display Overdraw: ярко-красные кроны — слишком много слоёв.
- Уберите то, чего никто не видит. Пучки глубоко внутри кроны снаружи не видны; дальние LOD должны убирать их первыми.
Шаг 5. Тени: скрытый второй лес
Каждое дерево, которое отбрасывает тень, рисуется ещё раз — в карту теней. Три настройки дают большую часть выигрыша:
- Ограничьте дальность теней.
DirectionalLight3D→ Directional Shadow → Max Distance. Дальше 100–200 м мало кто заметит, что у отдельных деревьев нет теней. - Выключите тени у дальних LOD. Поставьте Cast Shadow → Off у LOD2 и импосторов (для MultiMesh — у
MultiMeshInstance3Dэтого уровня). - Меньше каскадов на слабом железе. Два каскада вместо четырёх — часто незаметная разница и ощутимая экономия.
Шаг 6. Отсечение перекрытых объектов и ветер
Occlusion culling помогает, когда лес закрывают крупные объекты: холмы, скалы, здания, сам рельеф. Включите его в Project Settings → Rendering → Occlusion Culling, добавьте OccluderInstance3D и запеките. Сами деревья — плохие заслоны, они все в дырах, поэтому пусть работу делает рельеф. Больше всего выигрывают MultiMesh по чанкам: целый скрытый чанк просто пропускается.
Ветер должен жить в вершинном шейдере и читать заранее посчитанные веса из сетки: ствол почти не двигается, толстые ветки качаются медленно, кончики листвы дрожат быстро. Считать это с нуля в шейдере дорого, прочитать два числа на вершину — почти бесплатно.
Шаг 7. Замеряйте на целевом железе
Никогда не оптимизируйте вслепую. В Godot есть всё нужное:
- Debugger → Monitors: вызовы отрисовки, объекты и примитивы (треугольники) в кадре.
- Debugger → Visual Profiler: сколько процессор и видеокарта тратят на каждую часть кадра.
- Вьюпорт → Display Overdraw / Wireframe: куда уходят пиксели и треугольники.
Замеряйте до и после каждого изменения — на самом слабом устройстве, которое хотите поддерживать, а не только на своём рабочем ПК.
Чек-лист
- У каждого дерева есть бюджет треугольников по дистанциям, каждый уровень в 3–4 раза легче.
- Деревья используют диапазоны видимости со своими LOD; автоматический LOD — только для камней и пропсов.
- Тысячи деревьев разбиты на MultiMesh по чанкам размером около дистанции LOD0.
- Листва — Alpha Scissor плюс alpha to coverage; карточки повторяют форму пучков.
- Дальность теней ограничена; дальние LOD и импосторы теней не отбрасывают.
- Occlusion culling запечён по рельефу и крупным объектам.
- Ветер — дешёвый вершинный шейдер, который читает веса из вершин.
- Вызовы отрисовки и время кадра замерены на целевом устройстве.
Как здесь помогает FoliMesh
Мы сделали FoliMesh для своей большой игры, поэтому он выдаёт деревья, которые уже следуют этим правилам. Пакет для Godot — папка, которую достаточно перетащить в проект: GLB с уровнями LOD0, LOD1, LOD2…, построенными под ваши бюджеты треугольников, и готовая сцена .tscn, скрипт которой сам выставляет диапазоны видимости каждому уровню, шейдер ветра (изгиб ствола и качание веток хранятся во втором UV-канале сетки) и StaticBody3D с капсулой по стволу для коллизии. Карты нормалей уже в формате OpenGL, как ждёт Godot.
Любую модель перед импортом можно проверить нашими бесплатными инструментами в браузере: просмотр LOD, счётчик полигонов и валидатор glTF. Общую картину по инструментам для деревьев — в нашем сравнении генераторов 3D-деревьев.
Частые вопросы
Godot 4 делает LOD автоматически? Да — для импортированных сцен glTF, .blend, Collada и FBX он строит LOD сеток при импорте. Лучше всего это работает на сплошных поверхностях; для деревьев ручные или сгенерированные LOD с диапазонами видимости дают более предсказуемый результат.
Почему LOD не работает с моим MultiMesh?
Godot выбирает один уровень LOD на весь MultiMeshInstance3D. Разбейте экземпляры на чанки — у каждого свой узел и свой диапазон видимости.
Сколько деревьев может нарисовать Godot? Это зависит от бюджета на дерево, а не от движка. С MultiMesh по чанкам, дешёвыми дальними LOD и импосторами десятки тысяч деревьев в кадре реальны на ПК; замеряйте на своём целевом устройстве.
Alpha Scissor или Alpha Hash для листвы? Обычный выбор — Alpha Scissor с alpha to coverage. Alpha Hash даёт мягкие края без MSAA, но выглядит шумно и лучше всего работает вместе с TAA.

