Foliage is the first thing that tanks performance in an outdoor scene — and the first thing players notice when it looks wrong. Picking the right free tree assets and wiring them up with LODs and a convincing wind shader can make a mid-range PC scene run at 120 fps instead of 40.
This post walks through the full pipeline: evaluating free GLB tree assets from BitSoul, importing them into Godot 4, building a LOD chain, and writing a simple vertex-displacement wind shader that scales across your whole forest without per-tree overhead.
Evaluating free GLB tree assets for real-time use
![]()
Not all free tree models are equal. Before importing anything, check these four things:
Polygon budget by asset type
| Asset type | Mobile target | PC/console target |
|---|---|---|
| Hero tree (close camera) | 500–1 500 tris | 2 000–6 000 tris |
| Mid-distance tree | 200–500 tris | 500–2 000 tris |
| Billboard impostor | 2 tris (quad) | 2 tris (quad) |
Checklist before you commit to an asset:
- [ ] Manifold geometry — no open edges, no internal faces
- [ ] Single UV island per material (no overlapping UVs)
- [ ] PBR textures included: albedo, roughness, normal, optional AO
- [ ] Trunk and leaves on separate materials (needed for alpha cutout)
- [ ] Reasonable triangle count for the intended LOD level
- [ ] No subdivision modifiers baked into the export
For leaf planes, check that the alpha cutout mask is embedded in the albedo alpha channel. Godot 4's `StandardMaterial3D` uses Transparency: Alpha Scissor for this — it's a single texture sample, not a separate mask fetch.
GLB is the correct format here. FBX embeds textures inconsistently across DCC tools; OBJ has no material standard. GLB packs geometry, textures, and PBR material definitions in one binary, and Godot 4's importer resolves them without manual reassignment.
Importing tree assets into Godot 4 and setting up LODs
Drop the GLB into your project and open Import → Advanced. Key settings:
```gdscript
# In the Import dock for your tree GLB:
Import As: MeshLibrary # for MultiMesh scatter
# OR
Import As: Scene # for individual placement
# Under Meshes:
GenerateLODs: true
LOD Bias: 1.0
NormalMapInvert Y: false # GLB uses OpenGL convention — matches Godot
```
Godot 4's LOD generator uses the MeshLOD system, which computes screen-size thresholds automatically. For a 6 000-tri hero tree, the auto-generated chain looks roughly like:
- LOD0: 6 000 tris — ≥ 15% screen coverage
- LOD1: 1 500 tris — 5–15% screen coverage
- LOD2: 400 tris — 1–5% screen coverage
- LOD3: billboard — < 1% screen coverage
For the billboard LOD, use GeometryInstance3D → Visibility Range with a cross-fade margin of 0.1 to avoid popping at the transition distance.
MultiMesh for forest scatter
Placing individual `MeshInstance3D` nodes for a 10 000-tree forest is a guaranteed frame rate killer. Use `MultiMesh` instead:
```gdscript
func scatter_trees(mesh: Mesh, count: int, bounds: AABB) -> MultiMeshInstance3D:
var mm := MultiMesh.new()
mm.transform_format = MultiMesh.TRANSFORM_3D
mm.instance_count = count
mm.mesh = mesh
for i in count:
var t := Transform3D()
t.origin = Vector3(
randf_range(bounds.position.x, bounds.end.x),
0.0,
randf_range(bounds.position.z, bounds.end.z)
)
t = t.rotated(Vector3.UP, randf() * TAU)
mm.set_instance_transform(i, t)
var mmi := MultiMeshInstance3D.new()
mmi.multimesh = mm
return mmi
```
This collapses all draw calls for identically-meshed trees to one draw call per material, regardless of instance count. On an AMD RX 6700 XT, 8 000 instances of a 500-tri LOD2 tree run at ~0.4 ms GPU time.
Wind shader for foliage in Godot 4
![]()
A convincing wind shader needs two layers: trunk sway (slow, large displacement on the whole mesh) and leaf flutter (fast, small displacement on leaf planes only). The trick is encoding leaf-plane identity into vertex color — red channel = 1.0 on leaves, 0.0 on trunk geometry.
```glsl
// Godot 4 spatial shader — vertex wind
shader_type spatial;
render_mode cull_disabled;
uniform float wind_strength : hint_range(0.0, 1.0) = 0.3;
uniform float wind_speed : hint_range(0.0, 5.0) = 1.2;
uniform sampler2D wind_noise : hint_default_white;
void vertex() {
float time = TIME * wind_speed;
// Trunk sway — affected by height (UV2.y or VERTEX.y)
float height_factor = max(0.0, VERTEX.y / 4.0);
float sway_x = sin(time + VERTEX.z * 0.5) * wind_strength * height_factor * 0.15;
float sway_z = cos(time * 0.7 + VERTEX.x * 0.5) * wind_strength * height_factor * 0.08;
// Leaf flutter — vertex color red channel flags leaf geometry
float is_leaf = COLOR.r;
float flutter = sin(time * 3.0 + VERTEX.x * 2.0 + VERTEX.z * 1.5) * is_leaf;
VERTEX.x += sway_x + flutter * wind_strength * 0.05;
VERTEX.y += abs(flutter) * wind_strength * 0.02;
VERTEX.z += sway_z;
}
```
Apply this to a `ShaderMaterial` on the leaf sub-mesh. The trunk mesh keeps a plain `StandardMaterial3D` — the vertex color flag means you don't need separate shader variants.
Performance notes
- Vertex shaders run once per vertex per frame — keep leaf planes low-poly (4–8 tris per leaf cluster)
- On `MultiMesh`, the shader runs on the GPU instanced draw — no per-instance CPU overhead
- Use `VisualShader` nodes if you need an artist-tweakable graph; the GLSL above compiles to equivalent bytecode
With LODs, MultiMesh scatter, and this shader, a 10 000-tree outdoor scene on a mid-range GPU should stay well under 3 ms for foliage — leaving the rest of your budget for characters, post-processing, and gameplay logic.
---
Browse free GLB tree assets and other environment props at BitSoul's 3D marketplace — all files are game-ready and engine-tested.
---
*This post is part of the Ultimate Guide to Free 3D Game Assets — BitSoul's complete reference for formats, texturing, rigging, optimization, and engine integration.*