Godot 4'te FPS'i Öldürmeyen Bir Orman Nasıl Yapılır: MultiMesh, LOD ve Visibility Range
Godot 4'te hızlı bitki örtüsü için pratik bir rehber: üçgen bütçeleri, otomatik mesh LOD ile visibility range karşılaştırması, chunk'lara bölünmüş MultiMesh, yaprak saydamlığı, gölgeler, occlusion culling ve sonucun nasıl ölçüleceği.

Bir Godot projesindeki ilk orman genellikle harika görünür — ta ki içine girene kadar. İki yüz ağaç sonra kare hızı yarıya düşer, profiler binlerce draw call gösterir ve hangi ayarı değiştireceğini kimse tam bilmez. Godot forumundaki bir kullanıcı tam olarak bunu anlatmıştı: kendi yazdığı çim LOD sistemi, küçük bir arazi parçasında yaklaşık 10 FPS'e ve 3.000'den fazla draw call'a ulaşmıştı.
İyi haber şu: Godot 4, büyük ve hızlı bir orman için gereken tüm araçlara sahip. Sadece doğru şekilde bir araya getirilmeleri gerekiyor. Bu rehber onları önem sırasına göre ele alıyor — bütçe, LOD, instancing, yapraklar, gölgeler, culling — ve bir kontrol listesiyle bitiyor.
Ormanlar neden yavaştır
Ağaç, bir oyundaki en pahalı nesnelerden biridir. Aynı anda dört yerden maliyet çıkarır:
- Draw call'lar. Her ağaç en az iki mesh'ten (gövde kabuğu ve yapraklar) oluşur, çoğu zaman farklı materyallerle. Yüz ayrı ağaç, başka hiçbir şey çizmeden önce yüzlerce draw call demektir.
- Üçgenler ve vertex'ler. Detaylı bir ağaç kolayca 40.000'den fazla üçgene sahip olur. Tek bir vitrin ağacı için sorun değil, koca bir yamaç için ölümcül.
- Overdraw. Yapraklar, saydam pikseller içeren kartlardır. GPU görünmeyen kısımları da katman katman, taçtaki her kart için gölgelendirir.
- Gölgeler. Her ağaç shadow map'e yeniden çizilir — bazen birkaç kez, her gölge kademesi (cascade) için bir kez.
Aşağıdaki her teknik bu maliyetlerden bir ya da birkaçına saldırır.
Adım 1. Bir şey inşa etmeden önce bütçeyi belirleyin
En faydalı alışkanlık, bir ağacı modellemeden ya da üretmeden önce her mesafede kaç üçgen tutabileceğine karar vermektir. İşte oyuna hazır bir zincirin nasıl göründüğüne örnek olarak FoliMesh'ten gerçek bir meşe:
| Seviye | Mesafe (örnek) | Üçgen |
|---|---|---|
| LOD0 | 0–30 m | 14.490 |
| LOD1 | 30–70 m | 3.984 |
| LOD2 | 70–150 m | 1.154 |
| Impostor | 150 m + | 2 |
Önemli olan tam sayılar değil, eğrinin şekli: her seviye bir öncekinden kabaca 3–4 kat daha hafiftir ve en uzaktaki ağaçlar neredeyse hiçbir şeye mal olmaz. Mobilde her şeyi ikiye ya da üçe bölün. Ormanın büyük kısmı uzaktadır; dolayısıyla ormanın büyük kısmı ucuz olmalıdır.
Aynı ağaç dört detay seviyesinde: siluet korunuyor, üçgen sayısı düşüyor.
Adım 2. LOD mekanizmasını seçin: otomatik mesh LOD mi, visibility range mi
Godot 4 iki farklı LOD sistemi sunar ve ağaçlar için yanlış olanı seçmek yaygın bir sorun kaynağıdır.
Otomatik mesh LOD. Bir glTF, .blend veya FBX sahnesini içe aktardığınızda Godot, meshoptimizer ile her mesh'in sadeleştirilmiş sürümlerini üretir ve aralarında kendi kendine geçiş yapar. Resmi dokümantasyona göre seçim, node'un bounding box'ının kameraya en yakın noktasına ve hatanın kaç piksel kaplayacağına göre yapılır (Rendering → Mesh LOD → LOD Change → Threshold Pixels, varsayılan 1 piksel). Her node'un ayrıca bir LOD Bias değeri vardır.
Bu; kayalar, binalar ve prop'lar gibi sürekli yüzeyler için iyi çalışır. Ağaç tacı ise farklıdır: yüzlerce ayrı yaprak kartından oluşur. Genel amaçlı sadeleştirme bunun için tasarlanmamıştır ve sonuç seyrelmiş, bozulmuş bir taç olabilir. Elle yapılmış LOD'larınız olduğunda dokümantasyonun kendisi de başka bir araç önerir.
Visibility range (HLOD). Burada LOD mesh'lerini kendiniz sağlar ve her birinin hangi mesafede görünür olacağını belirlersiniz. Her GeometryInstance3D şunlara sahiptir:
visibility_range_begin/visibility_range_end— node'un çizildiği mesafe aralığı (bounding box'ının merkezine kadar ölçülür);visibility_range_begin_margin/visibility_range_end_margin— kamera tam sınırda durduğunda seviyenin titrememesi için bir tampon bölge;visibility_range_fade_mode— Disabled (anında geçiş, en hızlısı), Self veya Dependencies (alpha ile geçiş; geçiş sırasında saydam render'ı zorunlu kılar, bu da performansa mal olur).
Ağaçlar için güvenilir seçim visibility range'dir: LOD'lar yaprak örtüsüne göre tasarlanır, mesafeler sizin kontrolünüzdedir ve son seviyede gölgeleri kapatabilir ya da bir impostor'a geçebilirsiniz. Tek bir ağaç için minimum kurulum:
# Tree.gd — alt node'lar: 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
Geçiş göze batıyorsa önce LOD'ların aynı silueti koruduğundan emin olun — bu, herhangi bir fade ayarından daha önemlidir.
Adım 3. Çok sayıda ağaç: MultiMesh, ama chunk'lar halinde
Birkaç düzine ağaç için ayrı node'lar yeterlidir. Binlercesi için MultiMesh gerekir: tek bir MultiMeshInstance3D, bir mesh'in istediğiniz sayıda kopyasını tek bir draw call'da çizer.
Neredeyse herkesi şaşırtan bir tuzak var. Dokümantasyona göre aynı anda çok sayıda mesh çizen node'lar — MultiMeshInstance3D ve partiküller — tüm instance'ları aynı LOD seviyesinde çizer ve culling yalnızca node'u bütün olarak kaldırabilir, tek tek instance'ları değil. Bir Godot geliştiricisi bunu issue #76436'da doğruladı: tüm seviye için tek bir MultiMesh, ağaç başına LOD olmaması ve frustum ya da occlusion culling olmaması demektir.
Çözüm chunk'lardır. Dünyayı bir ızgaraya bölün ve her hücreye, her LOD seviyesi için kendi visibility range'i olan ayrı bir MultiMesh verin. Uzaktaki chunk'lar hafif LOD'u, yakındakiler detaylı olanı gösterir; kameranın arkasındaki chunk'lar ise tamamen elenir.
# ForestChunks.gd — visibility range'li, chunk'lara bölünmüş MultiMesh'ler kurar
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)
İki pratik kural:
- Chunk boyutu LOD0 mesafesine yakın olsun. Mesafe chunk'ın merkezine kadar ölçülür; bu yüzden 100 m'lik bir chunk, oyuncunun hemen yanındaki bir ağaç için LOD1 gösterir. LOD0 35 m'ye kadarsa 24–32 m'lik chunk'lar iyi çalışır.
- Ama çok küçük de olmasın. Her chunk draw call ekler (materyal başına, LOD başına bir tane). Ölçün: genellikle ekranda birkaç yüz chunk masaüstünde hâlâ sorun olmaz, mobilde ise çok daha azı.
MultiMesh'in çarpışması (collision) yoktur. Basit şekilleri yalnızca oyuncunun ulaşabileceği yerlere ekleyin — her gövdeye bir kapsül veya silindir, asla taca değil.
Yüzlerce instance'lanmış ağaç: doğru yapı, orman büyüdükçe draw call sayısını neredeyse sabit tutar.
Adım 4. Yapraklar: alpha blending değil, alpha scissor
Çoğu orman performansını yapraklarda kaybeder — saydamlık yanlış ayarlanmışsa güzelliğinin çoğunu da.
- Alpha değil, Alpha Scissor (cutout) kullanın. Materyalde Transparency → Alpha Scissor ayarlayın. Blend edilen alpha sıralamayı zorunlu kılar, derinliği ve gölgeleri bozar ve yüzlerce kartın üst üste bindiği bir taçta çok daha pahalıdır.
- Kenarları alpha to coverage ile yumuşatın. MSAA açıkken, gerçek saydamlık olmadan yumuşak yaprak kenarları elde etmek için Alpha Antialiasing → Alpha Edge Clip (veya Blend) ayarlayın.
- Overdraw ile kartın şekliyle savaşın. Yuvarlak bir dalcığın etrafındaki kare bir kart çok fazla piksel israf eder. Yaprak kümesinin şekline göre kesilmiş kartlar çok daha az görünmez piksel gölgelendirir. Viewport'un Display Overdraw moduyla kontrol edin: parlak kırmızı taçlar çok fazla katman olduğu anlamına gelir.
- Kimsenin görmediğini kaldırın. Tacın derinliklerindeki kümeler dışarıdan görünmez; uzak LOD'lar önce bunlardan kurtulmalıdır.
Adım 5. Gölgeler: gizli ikinci orman
Gölge düşüren her ağaç, shadow map için yeniden render edilir. Tasarrufun çoğunu üç ayar sağlar:
- Gölge mesafesini sınırlayın.
DirectionalLight3D→ Directional Shadow → Max Distance. 100–200 m'den sonra tek tek ağaçların gölgelerinin eksik olduğunu çok az oyuncu fark eder. - Uzak LOD'larda gölgeleri kapatın. LOD2'de ve impostor'larda Cast Shadow → Off ayarlayın (MultiMesh için o seviyenin
MultiMeshInstance3D'sinde). - Zayıf donanımda daha az kademe kullanın. Dört yerine iki kademe çoğu zaman görünmez bir değişiklik ve hissedilir bir tasarruftur.
Adım 6. Occlusion culling ve rüzgâr
Occlusion culling, ormanı büyük nesneler gizlediğinde işe yarar: tepeler, uçurumlar, binalar, arazinin kendisi. Project Settings → Rendering → Occlusion Culling üzerinden etkinleştirin, bir OccluderInstance3D ekleyin ve bake edin. Ağaçların kendisi kötü occluder'dır — delik deşiktirler — bu yüzden işi araziye bırakın. En çok chunk'lara bölünmüş MultiMesh'ler kazanır: gizlenmiş bir chunk tamamen atlanır.
Rüzgâr vertex shader'da yaşamalı ve mesh'ten önceden hesaplanmış ağırlıkları okumalıdır: gövde neredeyse hiç kıpırdamaz, büyük dallar yavaşça salınır, yaprak uçları hızla titrer. Bunu shader'da sıfırdan hesaplamak pahalıdır; vertex başına iki sayı okumak ise neredeyse bedavadır.
Adım 7. Hedef donanımda ölçün
Asla körlemesine optimize etmeyin. Godot ihtiyacınız olan her şeyi verir:
- Debugger → Monitors: karedeki draw call'lar, nesneler, karedeki primitive'ler (üçgenler).
- Debugger → Visual Profiler: CPU ve GPU'nun karenin her bölümüne ne kadar zaman harcadığı.
- Viewport → Display Overdraw / Wireframe: piksellerin ve üçgenlerin nereye gittiği.
Her değişiklikten önce ve sonra, yalnızca geliştirme bilgisayarınızda değil, desteklemek istediğiniz en zayıf cihazda ölçüm yapın.
Kontrol listesi
- Her ağacın mesafe başına bir üçgen bütçesi var ve her seviye 3–4 kat daha hafif.
- Ağaçlar kendi LOD'larıyla visibility range kullanıyor; otomatik mesh LOD yalnızca kayalar ve prop'lar için.
- Binlerce ağaç, kabaca LOD0 mesafesi boyutunda chunk'lara bölünmüş MultiMesh'lerde.
- Yapraklar Alpha Scissor ve alpha to coverage kullanıyor; kartlar kümelerin şeklini takip ediyor.
- Gölge mesafesi sınırlı; uzak LOD'lar ve impostor'lar gölge düşürmüyor.
- Occlusion culling, arazi ve büyük nesnelerden bake edilmiş.
- Rüzgâr, vertex başına ağırlıkları okuyan ucuz bir vertex shader.
- Draw call'lar ve kare süresi hedef cihazda ölçülmüş.
FoliMesh bunun neresinde
FoliMesh'i kendi büyük ölçekli oyunumuz için geliştirdik; bu yüzden zaten bu kurallara uyan ağaçlar üretiyor. Godot paketi, projeye sürükleyip bırakacağınız bir klasör: üçgen bütçelerinize göre üretilmiş LOD0, LOD1, LOD2… içeren bir GLB ve script'i her seviye için visibility range'leri ayarlayan hazır bir .tscn sahnesi, bir rüzgâr shader'ı (gövde bükülmesi ve dal salınımı mesh'in ikinci UV kanalında saklanır) ve çarpışma için gövdenin etrafında kapsüllü bir StaticBody3D. Normal map'ler zaten Godot'nun beklediği OpenGL formatında.
Herhangi bir modeli içe aktarmadan önce ücretsiz tarayıcı araçlarımızla kontrol edebilirsiniz: LOD görüntüleyici, poligon sayacı ve glTF doğrulayıcı. Ağaç araçlarına daha geniş bir bakış için 3D ağaç üreticileri karşılaştırmamıza göz atın.
SSS
Godot 4 LOD'ları otomatik oluşturur mu? Evet — içe aktarılan glTF, .blend, Collada ve FBX sahneleri için içe aktarma sırasında mesh LOD'ları üretir. En iyi sürekli yüzeylerde çalışır; ağaçlar için visibility range ile birlikte elle yapılmış ya da üretilmiş LOD'lar daha öngörülebilir sonuçlar verir.
LOD neden MultiMesh'imle çalışmıyor?
Godot, MultiMeshInstance3D'nin tamamı için tek bir LOD seviyesi seçer. Instance'ları, her birinin kendi node'u ve visibility range'i olan chunk'lara bölün.
Godot kaç ağaç render edebilir? Bu, motora değil ağaç başına bütçeye bağlıdır. Chunk'lara bölünmüş MultiMesh'ler, ucuz uzak LOD'lar ve impostor'larla ekranda on binlerce ağaç masaüstü donanımda gerçekçidir; hedef cihazınızda ölçün.
Yapraklar için Alpha Scissor mı, Alpha Hash mi? Olağan seçim alpha to coverage ile Alpha Scissor'dır. Alpha Hash, MSAA olmadan daha yumuşak kenarlar verir ama gürültülü görünür ve en iyi TAA ile çalışır.

