Cómo crear un bosque en Godot 4 que no hunda tus FPS: MultiMesh, LOD y visibility ranges
Una guía práctica de vegetación rápida en Godot 4: presupuestos de triángulos, LOD de malla automático frente a visibility ranges, MultiMesh por chunks, transparencia de hojas, sombras, occlusion culling y cómo medir el resultado.

El primer bosque de un proyecto en Godot suele quedar increíble… hasta que entras en él. Doscientos árboles después, la tasa de fotogramas se ha reducido a la mitad, el profiler muestra miles de draw calls y nadie tiene claro qué ajuste tocar. Un usuario del foro de Godot describió exactamente esto: un sistema de LOD para hierba casero acabó en unos 10 FPS y más de 3,000 draw calls en un pequeño pedazo de terreno.
La buena noticia: Godot 4 tiene todas las herramientas necesarias para un bosque grande y rápido. Solo hay que combinarlas bien. Esta guía las recorre en el orden que importa —presupuesto, LOD, instancing, hojas, sombras, culling— y termina con una checklist.
Por qué los bosques son lentos
Un árbol es uno de los objetos más caros de un juego. Te cuesta en cuatro lugares a la vez:
- Draw calls. Cada árbol tiene al menos dos mallas (corteza y hojas), a menudo con materiales distintos. Cien árboles separados son cientos de draw calls antes de haber dibujado nada más.
- Triángulos y vértices. Un árbol detallado supera fácilmente los 40,000 triángulos. Bien para un árbol protagonista, letal para una ladera entera.
- Overdraw. Las hojas son tarjetas con píxeles transparentes. La GPU sombrea igualmente las partes invisibles, capa sobre capa, en cada tarjeta de la copa.
- Sombras. Cada árbol se vuelve a dibujar en el shadow map, a veces varias veces: una por cascada de sombras.
Cada técnica de las que siguen ataca uno o varios de estos costos.
Paso 1. Decide el presupuesto antes de construir nada
El hábito más útil es decidir cuántos triángulos puede costar un árbol a cada distancia antes de modelarlo o generarlo. Aquí tienes un roble real de FoliMesh como ejemplo de una cadena lista para juego:
| Nivel | Distancia (ejemplo) | Triángulos |
|---|---|---|
| LOD0 | 0–30 m | 14,490 |
| LOD1 | 30–70 m | 3,984 |
| LOD2 | 70–150 m | 1,154 |
| Impostor | 150 m + | 2 |
Lo importante no son las cifras exactas, sino la forma de la curva: cada nivel es aproximadamente 3–4 veces más ligero que el anterior, y los árboles más lejanos no cuestan casi nada. En celulares, divídelo todo entre dos o tres. La mayor parte de tu bosque está lejos, así que la mayor parte de tu bosque debe ser barata.
El mismo árbol en cuatro niveles de detalle: la silueta se mantiene, el número de triángulos baja.
Paso 2. Elige el mecanismo de LOD: LOD de malla automático o visibility ranges
Godot 4 ofrece dos sistemas de LOD distintos, y elegir el equivocado para los árboles es una fuente habitual de problemas.
LOD de malla automático. Al importar una escena glTF, .blend o FBX, Godot genera versiones simplificadas de cada malla con meshoptimizer y cambia entre ellas por su cuenta. Según la documentación oficial, la elección se basa en el punto de la bounding box del nodo más cercano a la cámara y en cuántos píxeles cubriría el error (Rendering → Mesh LOD → LOD Change → Threshold Pixels, 1 píxel por defecto). Además, cada nodo tiene un LOD Bias.
Esto funciona bien para rocas, edificios y props: superficies continuas. Una copa de árbol es otra cosa: cientos de tarjetas de hojas separadas. La simplificación genérica no se diseñó para eso, y el resultado puede ser una copa despoblada y deformada. La propia documentación recomienda otra herramienta cuando tienes LOD hechos a mano.
Visibility ranges (HLOD). Aquí proporcionas tú mismo las mallas de LOD e indicas a qué distancia es visible cada una. Todo GeometryInstance3D tiene:
visibility_range_begin/visibility_range_end: la franja de distancia en la que se dibuja el nodo (medida hasta el centro de su bounding box);visibility_range_begin_margin/visibility_range_end_margin: una zona de margen para que el nivel no parpadee cuando la cámara está justo en el límite;visibility_range_fade_mode: Disabled (cambio instantáneo, el más rápido), Self o Dependencies (fundido alfa; obliga a un renderizado transparente durante la transición, lo que cuesta rendimiento).
Para árboles, las visibility ranges son la opción fiable: los LOD están pensados para follaje, las distancias las controlas tú y puedes quitar las sombras o pasar a un impostor en el último nivel. Una configuración mínima para un árbol:
# Tree.gd — hijos: 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
Si el cambio se nota, asegúrate primero de que los LOD mantienen la misma silueta: eso importa más que cualquier ajuste de fundido.
Paso 3. Muchos árboles: MultiMesh, pero por chunks
Los nodos separados sirven para unas cuantas docenas de árboles. Para miles necesitas MultiMesh: un solo MultiMeshInstance3D dibuja cualquier número de copias de una malla en un único draw call.
Hay una trampa que sorprende a casi todo el mundo. La documentación indica que los nodos que dibujan muchas mallas a la vez —MultiMeshInstance3D y las partículas— dibujan todas las instancias en el mismo nivel de LOD, y el culling solo puede eliminar el nodo entero, no instancias sueltas. Un mantenedor de Godot lo confirmó en la issue #76436: un único MultiMesh para todo el nivel significa nada de LOD por árbol y nada de frustum ni occlusion culling.
La solución son los chunks. Divide el mundo en una cuadrícula y dale a cada celda su propio MultiMesh para cada nivel de LOD, cada uno con su propia visibility range. Los chunks lejanos muestran el LOD ligero, los cercanos el detallado, y los que quedan detrás de la cámara se descartan por completo.
# ForestChunks.gd — construye MultiMesh por chunks con 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)
Dos reglas prácticas:
- Tamaño de chunk cercano a tu distancia de LOD0. La distancia se mide hasta el centro del chunk, así que un chunk de 100 m mostraría el LOD1 para un árbol que está justo al lado del jugador. Con LOD0 hasta 35 m, los chunks de 24–32 m funcionan bien.
- Tampoco demasiado pequeños. Cada chunk añade draw calls (uno por LOD y por material). Mide: normalmente unos cientos de chunks en pantalla siguen funcionando bien en computadoras de escritorio, muchos menos en celulares.
MultiMesh no tiene colisión. Añade formas simples solo donde el jugador pueda llegar: una cápsula o un cilindro por tronco, nunca para la copa.
Cientos de árboles instanciados: con la estructura adecuada, los draw calls se mantienen casi planos aunque el bosque crezca.
Paso 4. Hojas: alpha scissor, no alpha blending
En las hojas es donde la mayoría de los bosques pierden su rendimiento, y también buena parte de su belleza si la transparencia está mal configurada.
- Usa Alpha Scissor (cutout), no Alpha. En el material, ajusta Transparency → Alpha Scissor. El alfa mezclado obliga a ordenar, rompe la profundidad y las sombras, y es mucho más caro en una copa con cientos de tarjetas superpuestas.
- Suaviza los bordes con alpha to coverage. Con MSAA activado, ajusta Alpha Antialiasing → Alpha Edge Clip (o Blend) para conseguir bordes de hoja suaves sin transparencia real.
- Combate el overdraw con la forma de la tarjeta. Una tarjeta cuadrada alrededor de una ramita redondeada desperdicia muchos píxeles. Las tarjetas recortadas con la forma del grupo de hojas sombrean muchos menos píxeles invisibles. Compruébalo con el modo Display Overdraw del viewport: copas de un rojo intenso significan demasiadas capas.
- Elimina lo que nadie ve. Los grupos de hojas del interior de la copa son invisibles desde fuera; los LOD lejanos deberían quitarlos primero.
Paso 5. Sombras: el segundo bosque oculto
Cada árbol que proyecta sombra se vuelve a renderizar para el shadow map. Tres ajustes dan la mayor parte del ahorro:
- Limita la distancia de sombras.
DirectionalLight3D→ Directional Shadow → Max Distance. Más allá de 100–200 m, pocos jugadores notan que faltan las sombras de árboles individuales. - Desactiva las sombras en los LOD lejanos. Ajusta Cast Shadow → Off en el LOD2 y en los impostores (en MultiMesh, en el
MultiMeshInstance3Dde ese nivel). - Usa menos cascadas en hardware modesto. Dos cascadas en lugar de cuatro suele ser un cambio invisible y un ahorro notable.
Paso 6. Occlusion culling y viento
El occlusion culling ayuda cuando objetos grandes tapan el bosque: colinas, acantilados, edificios, el propio terreno. Actívalo en Project Settings → Rendering → Occlusion Culling, añade un OccluderInstance3D y hornéalo (bake). Los árboles en sí son malos oclusores —están llenos de huecos—, así que deja que el terreno haga el trabajo. Los MultiMesh por chunks son los que más se benefician: un chunk oculto entero se omite.
El viento debe vivir en el vertex shader y leer pesos precalculados de la malla: el tronco apenas se mueve, las ramas grandes se balancean despacio y las puntas de las hojas tiemblan rápido. Calcular eso desde cero en el shader es caro; leer dos números por vértice es prácticamente gratis.
Paso 7. Mide en el hardware de destino
Nunca optimices a ciegas. Godot te da todo lo que necesitas:
- Debugger → Monitors: draw calls en el frame, objetos, primitivas (triángulos) en el frame.
- Debugger → Visual Profiler: cuánto tiempo dedican la CPU y la GPU a cada parte del frame.
- Viewport → Display Overdraw / Wireframe: adónde van los píxeles y los triángulos.
Mide antes y después de cada cambio, en el dispositivo más débil que quieras soportar, no solo en tu computadora de desarrollo.
Checklist
- Cada árbol tiene un presupuesto de triángulos por distancia, con cada nivel 3–4 veces más ligero.
- Los árboles usan visibility ranges con sus propios LOD; el LOD de malla automático, solo para rocas y props.
- Los miles de árboles están repartidos en MultiMesh por chunks, de un tamaño similar a la distancia de LOD0.
- Las hojas usan Alpha Scissor con alpha to coverage; las tarjetas siguen la forma de los grupos de hojas.
- La distancia de sombras está limitada; los LOD lejanos y los impostores no proyectan sombras.
- El occlusion culling está horneado a partir del terreno y los objetos grandes.
- El viento es un vertex shader barato que lee pesos por vértice.
- Los draw calls y el tiempo de frame están medidos en el dispositivo de destino.
Dónde encaja FoliMesh
Creamos FoliMesh para nuestro propio juego a gran escala, así que genera árboles que ya siguen estas reglas. El paquete para Godot es una carpeta que sueltas en el proyecto: un GLB con LOD0, LOD1, LOD2… generado según tus presupuestos de triángulos, una escena .tscn lista cuyo script ajusta las visibility ranges de cada nivel, un shader de viento (la flexión del tronco y el balanceo de las ramas se guardan en el segundo canal UV de la malla) y un StaticBody3D con una cápsula alrededor del tronco para la colisión. Los normal maps ya vienen en el formato OpenGL que espera Godot.
Puedes revisar cualquier modelo antes de importarlo con nuestras herramientas gratuitas en el navegador: el visor de LOD, el contador de polígonos y el validador de glTF. Para una visión general de las herramientas de árboles, consulta nuestra comparativa de generadores de árboles 3D.
Preguntas frecuentes
¿Godot 4 crea los LOD automáticamente? Sí: para escenas glTF, .blend, Collada y FBX importadas, genera LOD de malla al importar. Funciona mejor en superficies continuas; para árboles, los LOD hechos a mano o generados con visibility ranges dan resultados más predecibles.
¿Por qué no funciona el LOD con mi MultiMesh?
Godot selecciona un único nivel de LOD para todo el MultiMeshInstance3D. Divide las instancias en chunks, cada uno con su propio nodo y su propia visibility range.
¿Cuántos árboles puede renderizar Godot? Depende del presupuesto por árbol, no del motor. Con MultiMesh por chunks, LOD lejanos baratos e impostores, decenas de miles de árboles en pantalla son realistas en una computadora de escritorio; mide en tu dispositivo de destino.
¿Alpha Scissor o Alpha Hash para las hojas? Alpha Scissor con alpha to coverage es la opción habitual. Alpha Hash da bordes más suaves sin MSAA, pero se ve ruidoso y funciona mejor con TAA.

