holodepth

Three.js · İleri geometri

Geometry Optimization: Performansın Görünmez Mimarisini İnşa Etmek

Görsel kaliteyi koruyarak GPU yükünü bilinçle düşürmek

HoloDepth’in yüksek performanslı 3D dünyasında bir modelin yalnızca iyi görünmesi yetmez; aynı zamanda saniyede 60 (veya 120) karede akıcı çalışması gerekir. Geometry optimization, ham vertex verisini ve çizim stratejisini, görünür kaliteyi gereksiz yere düşürmeden GPU üzerindeki maliyeti minimize etme disiplinidir. Altın cümle: Performansın düşmanı poligon değil, koordinasyon maliyetidir.

Bu sayfada darboğazları tanıyıp draw call ve geometri birleştirme, çözünürlük / LOD, buffer kullanım ipuçları ve InstancedMesh gibi araçlarla “akıllı veri” tarafını özetliyoruz. Bağlam için Geometri giriş, Indexed vs non-indexed ve Instancing sayfalarına dönebilirsiniz. Canlı ölçüler için demo sabitleri tablosuna da bakabilirsiniz.

Optimization Playground
Draw call test
Draw calls Üçgen Özet

1000 obje → CPU ~1000 komut; birleştirilmiş veya instanced tek yükte ~1 komut — farkı hem sayaçta hem görselde (renk / hover) okuyun. Aynı ızgarada 1000 küp: mod sekmeleri sahneyi yeniden kurar; draw call modunda birleştirilmiş tarafta hafif sınır ızgarası tek mesh alanını vurgular. LOD’da orbit veya Yakın/Uzak kısayolları; draw / instancing’de imleci sahne üzerinde gezdirerek hover vurgusunu deneyin.

Draw call / instancing: çoklu mesh modunda her küp farklı renkte; birleştirilmiş veya InstancedMesh modunda tek “batch” rengi (Holodepth camgöbeği) ile alan bütünleşir — gözle de “tek yük” ayrımı netleşir. Birleştirilmiş draw modunda zemin üzerindeki ince ızgara, tek geometri sınırını görsel olarak pekiştirir. Hover: ayrı mesh’te yalnızca bir küp parlar; birleşik blokta tüm gövde aydınlanır; instanced modda ise tek bir örnek vurgulanır. Üstteki Draw calls bir kareden sonra sıfırlanır; LOD ve buffer modlarında ise sahne farklı bir stres profili gösterir.

Demo sabitleri tablosu (Optimization Playground)

Aşağıdaki tablo doc-geometry-optimization-demo.js içindeki initGeometryOptimizationDemo ile aynı sabitleri listeler; dört sekme sahneyi yeniden kurar, alt satırlardaki toggles ise ilgili modun alt durumunu değiştirir.

initGeometryOptimizationDemo · Optimization Playground
Sahne / rol Parametre Değer Tür
Örneklem N 1000 küp / instance 🔒 Sabit
Izgara yerleşimi GRID_COLS / GRID_ROWS / GRID_SPACING 40 · 26 · 0.21 🔒 Sabit
Paylaşılan küp BoxGeometry (sharedBox) (0.11, 0.11, 0.11) 🔒 Sabit
Çoklu mesh renk randomHueColor HSL: altın oran ile i · S 0.74 · L 0.56 🔒 Sabit
Birleştirilmiş draw mergeGeometries BufferGeometryUtils · klon + applyMatrix4 çeviri · useGroups: false 🔒 Sabit
Birleşik gövde mergedSolidMat MeshBasicMaterial · 0x5ec8ff 🔒 Sabit
Instancing InstancedMesh + renk instanceColor · DynamicDrawUsage · başlangıç cyan tabanı 🔒 Sabit
Sınır ızgarası buildBatchBoundaryLines LineBasicMaterial 0x7ae8ff · opaklık 0.32 · depthWrite: false 🔒 Koşul
LOD gövde lodHigh / lodMid / lodLow boyut (2.35)³ · segment 14³ / / 🔒 Sabit
LOD hedef / eşik lodTarget · mesafe d (0, 1.05, 0) · d > 17 düşük · d > 9.2 orta · aksi yüksek 🔒 Sabit
LOD materyal lodMat MeshStandardMaterial · 0x6ad0ff · metalness 0.12 · roughness 0.42 🔒 Sabit
Buffer düzlem PlaneGeometry 7×7 · segment 42×42 · rotateX(-π/2) 🔒 Sabit
Buffer materyal bufferMat 0x7c5cff · DoubleSide 🔒 Sabit
Buffer usage position.setUsage StaticDrawUsage · DynamicDrawUsage (alt mod) ↔ UI
Dynamic dalga updateBufferDynamic y += sin(x×0.85 + t×0.0018) × 0.06 × cos(z×0.7 + t×0.0012) (taban üzerine) 🔒 Koşul
Işık HemisphereLight 0xb8dcff / 0x101520 · yoğunluk 0.95 🔒 Sabit
Işık DirectionalLight beyaz · yoğunluk 1.35 · konum (4.5, 8.2, 5) 🔒 Sabit
Kamera PerspectiveCamera FOV 42 · yakın/uzak 0.06 / 220 · konum (4.6, 3.15, 5.85) 🔒 Sabit
LOD kısayol btnLodNear / far kamera (3.2, 2.4, 4.2) · (22, 16, 28) · hedef lodTarget ↔ UI
OrbitControls target / dampingFactor (0, 0.25, 0) · 0.06 · maxPolarAngle ≈ 0.49π 🔒 Sabit
Renderer makeRenderer ACESFilmicToneMapping · exposure 1.28 · SRGBColorSpace · setPixelRatio(min(dpr,2)) · arka plan 0x04060e 🔒 Sabit
Metrik renderer.info.render calls · triangles (her kare sonunda) 🔒 Sabit
JS başlangıç mainMode / drawSub / instanceSub / bufferSub 'draw' · 'many' · 'many' · 'static' (HTML ile uyumlu) 🔒 + ↔ UI

Önemli kod kesiti

Dört sekme aynı sahne grubunu (content) temizleyip yeniden doldurur; çekirdek yönlendirme rebuild() içinde toplanır.

function rebuild() {
  clearContent();
  if (mainMode === 'draw') {
    if (drawSub === 'many') buildManyCubes();
    else buildMergedCubes();
  } else if (mainMode === 'instance') {
    if (instanceSub === 'many') buildManyCubes();
    else buildInstanced();
  } else if (mainMode === 'lod') {
    buildLodObject();
  } else if (mainMode === 'buffer') {
    buildBufferPlane();
  }
}

Draw call kavramı ve darboğazlar

Optimizasyonun ilk kuralı, maliyetin nereden geldiğini doğru okumaktır. Birçok projede en büyük sürtünme yalnızca poligon sayısı değil; CPU’nun GPU’ya sürekli “şimdi bunu çiz” komutu göndermesiyle büyüyen draw call yüküdür.

Sorun: Her ayrı çizim çağrısında sürücü ve API katmanı devreye girer; binlerce küçük mesh aynı sahneyi kalabalıklaştırırken iletişim hattı (bus / validation) tıkanabilir.

Geometry merging: Birbirine yakın ve aynı materyali paylaşan parçaları tek bir BufferGeometry içinde birleştirerek yüzlerce küçük emri tek büyük çizime indirgemek klasik ve etkili bir çözümdür — tabii materyal ve attribute düzeni uyumlu olmalıdır.

Batching ve instancing: merging (batching) tek geometri üretir; instancing aynı geometriyi çoğaltır. Batching’de vertex/index listesi gerçekten birleşir (UV atlası, materyal uyumu gibi konular gündeme gelir); instancing’de ise taban BufferGeometry paylaşılır, her kopyanın dönüşümü (ve gerekiyorsa renk gibi veriler) instance tarafında taşınır — aşağıdaki bölümde bu ikinci modeli ayrıntılandırıyoruz.

Bu farkı sayılarla görmek için yukarıdaki Optimization Playground demosuna göz atın; ölçüler için demo sabitleri tablosuna bakın.

Vertex sayısı ve hassasiyet (çözünürlük)

Bir modelin detay seviyesi (çözünürlük), optimizasyonun merkezindedir: gereksiz her vertex, vertex shader’da işlenecek ekstra matematik ve bellek / bant genişliği demektir.

LOD (Level of Detail) stratejisi

Kullanıcıya yakın nesneler yüksek poligonlu gösterilirken, uzaktakiler daha basit geometriyle değiştirilmelidir. Kameraya göre dinamik LOD kuran sistemler, sahne karmaşıklığını ciddi ölçüde düşürebilir — HoloDepth tarafında bu, “her karede en pahalı mesh’i çizmek zorunda değiliz” disiplininin ta kendisidir.

Normal map ile detay

Milyonlarca üçgenle modellenmiş yüzey mikro detayını, düşük poligonlu bir gövdeye giydirilen normal map ile taklit edebilirsiniz: ışık, yüzeyde sanki ekstra geometri varmış gibi hesaplanır; oysa taban mesh yalnızca az sayıda vertex taşıyabilir.

Buffer optimizasyonu: static vs dynamic

Verinin GPU belleğinde nasıl işaretlendiği, güncelleme sıklığına göre performansı etkiler. BufferGeometry attribute’larında kullanım amacını doğru seçmek kritiktir.

  • THREE.StaticDrawUsage (varsayılan): Veri bir kez yüklenir ve seyrek veya hiç değişmez. Statik sahneler, binalar, prop’lar için uygundur.
  • THREE.DynamicDrawUsage: Veri her karede veya sık sık güncellenecekse (ör. dalgalanan su, deformasyon) kullanılır; sürücü veriyi daha sık güncellenebilir kabul eder.

Yanlış seçim: Hareket etmeyen bir yapıyı “dynamic” işaretlemek, gereksiz bellek trafiği ve doğrulama maliyeti yaratarak FPS’i düşürebilir. Gerçekten değişen attribute’lar için dynamic; geri kalan için static düşünün.

Yoğun attribute güncellemelerinde veri düzenini interleaved buffer ile ele almak da ayrı bir optimizasyon eksenidir.

InstancedMesh: klonlama ordusu

Aynı geometriden çok sayıda kopya (orman ağaçları, sunucu rafları, parçacık benzeri tekrarlar) gerekiyorsa her birini ayrı Mesh olarak tutmak çoğu zaman verimsizdir. Bu senaryoda hedef, üst bölümdeki batching gibi tek bir süper-mesh üretmek değil; aynı örgüyü defalarca referanslayıp draw call’ı yine düşürmektir.

  • Geometri GPU’ya tek kez yüklenir.
  • Her örnek için yalnızca değişen veri (dönüşüm, renk vb.) instance kanalı veya InstancedMesh matrisleriyle taşınır.
  • Tek draw call altında binlerce kopya çizilebilir.

Ayrıntılar ve API için Instancing sayfasına bakın; bu bölüm geometri optimizasyonu perspektifinde “tekrar eden şekil = instancing adayı” kuralını pekiştirir.

HoloDepth için altın kurallar

Geometri optimizasyonu yaparken şu kontrol listesini yanınızda tutun:

  • Veri tiplerini küçültün: Gereksiz Float32 hassasiyetinden kaçının; aralık uygunsa daha dar tipler veya normalize edilmiş tam sayı formatları değerlendirin ( Attributes & buffer’lar).
  • Vertex paylaşımı: Indexed geometry ile aynı köşeyi gereksiz yere çoğaltmayın.
  • Frustum culling: Kameranın dışında kalan nesneler için Three.js varsayılan culling’i kullanın; çok özel sistemlerde manuel katmanlar veya BVH tabanlı seçim gerekebilir.

Derin not

Gerçek bir optimizasyon uzmanı “daha ne eklerim?” değil, görünürlüğü bozmadan neyi çıkarırım? diye sorar — vertex, draw call ve buffer kullanımı bu sorunun doğrudan cevaplarıdır.