holodepth

Three.js · Performans · Bounding · Culling

Bounding ve culling: görünmeyen dünyayı yönetmek

3D motorlarının en güçlü optimizasyon stratejilerinden biri, kamera o an görmeyeceği veriyi çizim hattına hiç sokmamaktır. Bu genel işleme culling (ayıklama) denir: önce sahneyi basit sınırlayıcılarla temsil eder, sonra görünürlük testlerinden geçirirsiniz.

Bu sayfa dört ayrı «ayıklama» katmanına ayrılır (canlı lab): sınırlayıcı hacimler, frustum culling, occlusion ve backface culling. Son bölümde Three.js / HoloDepth pratik tablosu ve mesh.frustumCulled notu yer alır.

Amaç net ayrım: CPU tarafında «bu nesneyi bu karede işe almaya değer mi?» sorusunu ucuz matematikle yanıtlamak; GPU ise zaten seçilmiş üçgenler üzerinde çalışır. İyi kurgulanmış sınırlayıcılar, hem motorun iç gezintisini hem de gereksiz çizim komutlarını aynı anda hafifletir — tersine, gevşek veya güncellenmeyen bir kutu, görünür nesneyi yanlışlıkla dışarı itebilir.

Okuma sırası yukarıdan aşağıdır: önce veriyi nasıl «paketlediğinizi», sonra kameraya göre dışarı attığınızı, ardından başka yüzeylerin arkasındakileri ve en sonda yüzey yönünü düşünürsünüz; tablo ve kod özeti en sonda toparlar.

Canlı lab · Görünürlük hattı · r170

Gerçek zamanlı görünürlük pipeline: motor önce neyi çöpe atar? Aşağıdaki sıra bu sayfanın akışıyla örtüşür: ucuz sınırlayıcılar, kamera hacmi (frustum), duvarın arkasındakiler ve arka yüz ayrımı. Sayılar bu kareden gelen ölçümdür; motor içi optimizasyonlar farklı görünebilir.
Draw calls (bu kare)
Üçgen

Bounding volumes (sınırlayıcı hacimler)

Motorun karmaşık poligon ağını tek tek sorması pahalıdır. Bunun yerine her nesneyi görünmez ama ucuz testlere uygun bir kabın içine alırsınız: kutu veya küre. Kamera bu kabı görmüyorsa, içindeki yüz bin üçgeni tek tek denemeye gerek kalmaz; nesne o kare için «erken çıkış» yapar.

Bu soyutlama sadece görünürlük için değildir: aynı kabuk çarpışma broad-phase, ışık kapsama testleri ve mesafe sorgularında da tekrar kullanılır. Yani bir kez doğru hesaplanmış sınır, sahne boyunca birçok alt sistemde «paylaşılan gerçek» olur.

Kutu mu, küre mi?

  • Bounding box (AABB): Objeyi eksenlere hizalı en küçük dikdörtgenle sarar; dünya uzayında hızlı güncellenir, çarpışma ve frustum testlerinde çok kullanılır. Dik uzun çubuklar veya köşeli silüetlerde kabın «boş köşeleri» artar; yine de test maliyeti düşük kaldığı için oyun motorlarında varsayılan tercihtir.
  • Bounding sphere: Objeyi içine alan en küçük küre; dönüşlerde yeniden hesap gerektirmez, mesafe ve görünürlük için tek radius yeterlidir. Uzun ve ince formlarda küre gevşek kalır ama tek skalar ile hızlı «yakın mı / uzak mı?» sorusuna cevap verir.
  • OBB (dikdörtgen, eksene bağlı): İleri seviye seçenek; uzun gemi veya tünel gibi dönel nesnelerde daha sıkı sarar, fakat güncelleme ve test maliyeti AABB’ye göre yükselir — gerçekten dar boğaz görüyorsanız düşünün.

Ne zaman yeniden hesaplarsınız?

Örgüyü kodla değiştirdiyseniz (morph, skin, vertex shader ile pozisyon oynatma, birleştirilmiş buffer yazımı) sınır hacim artık eski üçgenlere uymayabilir. Three.js tarafında computeBoundingBox / computeBoundingSphere çağrıları bu yüzden rutindir; unutulduğunda ise kutu hâlâ eski hacimde kalır ve frustum testi yanıltıcı olur.

Mantık: Basit kabuk görünmüyorsa, içindeki milyonlarca üçgeni GPU kuyruğuna taşımaya bile gerek yoktur — doğrudan atlanır. Tersine kabuk hâlâ ekranda görünür alana taşıyorsa, içerik o kare için yine de değerlendirilir; bu yüzden «gevşek ama güvenli» kutular bazen kasıtlıdır.

Frustum culling (kamera açısı ayıklama)

Oyun motorları ve Three.js gibi kütüphanelerin otomatik yaptığı en temel kazançlardan biridir. View frustum, kameranın gördüğü piramit benzeri hacimdir; sahne grafiğindeki her nesnenin sınırlayıcı kutusu bu hacimle kesiştirilir. Dışarıda kalanlar çizim komutlarına girmez.

Pratikte bu, her karede «önce kabuk, sonra detay» sırasıdır: önce ucuz bir kutu–frustum kesişimi yapılır; geçen nesneler için zaten planlanmış çizim yolları çalışır. Binlerce küçük nesne olduğunda kazanç doğrudan CPU süresine ve komut listesi uzunluğuna yansır; tek dev bir mesh’te ise kutu da büyük olduğundan test nadiren «hayır» der.

Three.js ve hiyerarşi

Frustum culling genelde Object3D düzeyinde düşünülür: ebeveyn dışarı atıldıysa altındaki mesh’ler o karede denenmeyebilir. Bu yüzden mantıksal grupları (level parçası, oda, bölüm) anlamlı düğümlere toplamak, sadece görsel düzen için değil — ayıklama verimliliği için de önemlidir.

Popping ve sınır kayması

Kritik not: Pivot yanlışsa veya sınırlayıcı kutu güncel değilse, nesne ekrandayken aniden kaybolabilir (popping). Özel geometri ürettiyseniz kutuyu yeniden hesaplamak için geometry.computeBoundingBox() (ve gerekirse küre için computeBoundingSphere()) çağrısı sık kullanılır.

Animasyonlu karakterlerde sınır kutusu her karede genişleyip daralabilir; sabit bir «kozmetik» kutu yerine gerçek hareket alanına uyum sağlamak, hem popping’i azaltır hem de gereksiz yere sahneyi açık tutmamaya yardım eder. Kamera yakın–uzak keskin geçiş yaptığında (near / far planları) da aynı belirtiler ortaya çıkabilir — o zaman önce kamera kliplerini gözden geçirmek gerekir.

Occlusion culling (engelleyici ayıklama)

Frustum içinde olmak, görünür olmak demek değildir. Başka bir yüzeyin arkasında kalan içerik yine de çizilebilir — işte occlusion culling bu «görüşü kapatan» engelleri hesaba katar.

Varsayılan forward hatlarda derinlik tamponu çoğu zaman «görünmeyeni» maskeleyerek kurtarır; fakat önce gereksiz geometriyi çizmek hâlâ doldurma ve shader maliyeti doğurur. Occlusion culling’in hedefi, bu çizimi hiç başlatmaktır — yani sadece piksel değil, komut seviyesinde tasarruf.

Kapalı mekân ve açık dünya

Senaryo: Duvarın arkasındaki araba; kamera frustum içinde arabaya doğru baksa bile duvar çizildikten sonra derinlik tamponu zaten arabayı örter. İleri seviye çözümler Z-buffer ile birlikte çalışır veya occlusion query gibi daha pahalı yollar gerektirir; her sahnede otomatik değildir — büyük şehir veya labirentlerde genelde elle kurgu veya portallar gerekir.

Kapalı koridorlarda «oda bazlı» portal veya cell / portal graph klasik yaklaşımdır; açık dünyada ise genelde sektörleme, LOD ve ön Z geçişi kombinasyonu tercih edilir. Hangi yolun seçileceği sahne topolojisine ve bütçeye bağlıdır; tek bir sihirli anahtar yoktur.

Maliyet–kazanç uyarısı

Her karede binlerce occlusion query çalıştırmak, kazandığınız doldurmayı GPU senkronizasyonu ile geri verebilir. Bu yüzden üretimde sık görülen desen: kaba hücre ızgarası veya el ile çizilmiş «görünürlük hacimleri» ile kaba eleme, sonra zaten ucuz kalan içerik için derinlik tamponuna güvenmektir.

Backface culling (arka yüz ayıklama)

Kapalı bir yüzeyin kameraya ters dönük üçgenlerini raster aşamasında çizmeyi atlamaktır. Kapalı bir kürenin kabaca yarısı her karede arkadadır; bunları elemek dolgu hızını doğrudan iyileştirir.

Donanım bu kararı üçgenin düzenlenme sırasına (winding order) ve kamera konumuna göre verir; WebGL / Three.js tarafında çoğu materyal varsayılan olarak tek yüzü çizer. Bu, özellikle yoğun ağlarda bellek bant genişliği ve fragment sayısı için hissedilir.

Yön ve materyal

Dikkat: İnce bir plane arkasından bakınca kayboluyorsa sebep genelde budur. Çift taraflı görünüm için Three.js’te
material.side = THREE.DoubleSide ayarı gerekir — maliyeti de artırır; gerçekten iki yüz mü gerekiyor, ona göre seçin.

Model içe doğru ters yüzeyler üretiyorsa (ör. dışa aktarımda normaller ters), önce winding / normal yönünü düzeltmek daha doğrudur; aksi halde DoubleSide ile semptomu maskelersiniz ama gereksiz üçgen sayısı artmış olur. Şeffaf veya kesilen materyallerde de arka yüzler bazen bilinçli olarak görünür kılınır — o durumda maliyet planını buna göre yapın.

HoloDepth uygulama rehberi: culling’i verimli kullanmak

Aşağıdaki tablo özet pusuladır; sayılar proje ve donanıma göre değişir, ama hangi tekniğin ne işe yaradığını hızlı hatırlatır.

Gerçek projede bu satırlar üst üste binmez; örneğin frustum her yerde açıkken occlusion yalnızca belirli harita parçalarında devreye alınır. Tabloyu «hangi aşamada hangi soruyu soruyorum?» listesi gibi düşünün: önce kabukta mıyım, sonra kamera içinde miyim, sonra başka bir şeyin arkasında mıyım, en son üçgen yüzü bana mı dönük?

Tabloyu nasıl okumalısınız?

Kazanç sütunu tek başına yeterli değildir; aynı satırdaki Ne zaman kritik? ifadesi, o tekniği ne zaman bilinçli mühendislik gerektiren noktaya taşıdığınızı gösterir. Düşük kazançlı ama ucuz bir adımı atlama pahasına genişletmek bazen yanlış optimizasyon olur.

Teknik Kazanç Ne zaman kritik?
Frustum culling Yüksek Çok geniş haritalar ve çok sayıda nesne varken.
Bounding sphere Orta Objenin ekranda kalıp kalmadığını hızlı kontrol etmek için.
Backface culling Orta Karmaşık modellerde gereksiz arka yüzleri elemek için (motor varsayılanı).
Manual frustum / parça bölme Maksimum Devasa objeleri (ör. şehir) parçalara bölüp parça parça elemek için.

HoloDepth teknik notu: frustumCulled mülkiyeti

Three.js’te her mesh için mesh.frustumCulled = true varsayılan olarak açıktır. Gökyüzü kubbesi veya devasa zemin gibi her karede test edilmesi gereksiz ama sahneyi kaplayan parçalarınız varsa, bu bayrağı false yaparak motorun sınırlayıcı testini atlamasını sağlayıp CPU tarafına nefes aldırabilirsiniz — tabii o geometrinin gerçekten her zaman çizilmesi gerektiğinden emin olun.

Bu bayrak «görünmez yap» anlamına gelmez; yalnızca frustum testinin atlandığını belirtir. Yanlış kullanımda sahne dışında kalan dev bir zemin yine de çizilmeye devam eder — bu yüzden genelde birlikte LOD, sektörleme veya düşük poligon yedekleri düşünülür. Profilde hâlâ CPU zamanı görüyorsanız, sıradaki adım grafik gezintisi veya animasyon matrisleri olabilir; tek bir bayrak her derde deva değildir.

// Özel geometri / vertex değişimi sonrası sınırlayıcıları yenile
geometry.computeBoundingBox();
geometry.computeBoundingSphere();

// Çok büyük statik zemin / gökyüzü: frustum testini kapatma örneği
mesh.frustumCulled = false;