holodepth

Three.js · Çoklu model · Sahne birleştirme · Runtime

Çoklu model ve sahne birleştirme: Runtime yönetimi

Bir 3D sahne, çoğu zaman tek bir .glb dosyasından ibaret değildir. Karakterler, çevre öğeleri, silahlar ve araçlar farklı kaynaklardan gelir. Scene graph (sahne grafiği), bu bağımsız parçaların birbirine nasıl «eklemleneceğini» belirleyen motor odasıdır.

Bu sayfa, dosyayı yükledikten sonraki runtime aşamasına odaklanır: düğümleri birleştirmek, güvenli şekilde ayırmak, dönüşümün çocuklara yayılmasını anlamak ve glTF pipeline ile uyumlu «kanca» noktaları kurmak.

glTF içi scene graph konusu, içe aktarılmış dosyanın ağacını okumayı merkeze alır; burada ise aynı kurallarla birden fazla kaynaktan gelen modelleri çalışma anında bir araya getirmeyi anlatıyoruz — yani «grafik nedir?» yerine «grafikle oynayıp sahneyi nasıl kurarım?» sorusu.

Önce çekirdek kavramlar için Sahne — scene graph ve Object3D hiyerarşisi sayfalarına göz atın.

Düğüm birleştirme (merging) ve ayrıştırma

Statik bir dosyayı sahneye yüklediğinizde, hiyerarşi dosyadaki haliyle «donmuş» gelir. Oysa interaktif bir deneyimde bu ağacı canlı olarak yeniden düzenlemeniz gerekir: bir parçayı başka bir modelin altına taşımak, geçici olarak sahneye çıkarmak veya tak-çıkar ekipman akışı kurmak gibi.

Birleştirme sırasında sıra önemlidir: önce hedef ebeveynin (transform’unun) mantıklı olduğundan emin olun, sonra çocuğu taşıyın. Gerekirse taşıma sonrası child.updateMatrixWorld(true) ile alt ağacın dünya matrislerini tazeleyin; böylece fizik gövdeleri veya raycast hemen doğru uzayda çalışır.

Bağımsız model ekleme

İki farklı glTF sahnesini birleştirirken, bir modelin belirli bir düğümünü (node / Object3D) alıp diğerinin altına taşıyabilirsiniz. Pratikte sık görülen kalıp: kök veya alt grup üzerinde parent.add(child) ile hiyerarşiyi yeniden kurmak.

Aynı anda birden fazla yükleme yapıyorsanız, her kökü geçici bir THREE.Group altında toplayıp sahneye tek seferde eklemek; sonra alt düğümleri hedef hiyerarşiye dağıtmak hata ayıklamayı kolaylaştırır — hangi paketin hangi dalı taşıdığı net kalır.

Detach — dünya konumunu korumak

Bir objeyi ebeveyninden ayırdığınızda, ekranda aynı yerde kalması için scene.attach(object) kullanın. Bu yöntem, nesneyi mevcut ebeveyninden çıkarırken dünya matrisini korur; yalnızca remove + yeniden add ile bazen beklenmedik sıçramalar oluşabilir. Detaylı düğüm yönetimi için Node hiyerarşisi konusuna bakın.

scene.attach, nesneyi doğrudan sahnenin çocuğu yapar ve yerel transform’u dünya uzayına göre yeniden yazar. Yeni bir ebeveyne (ör. başka bir karakterin eline) tekrar bağlamadan önce gerekirse yerel eksenleri sıfırlamak için kısa bir «pivot düzeltme» adımı eklemeniz normaldir.

Transform propagation (dönüşüm yayılımı)

Hiyerarşinin kalbi, ebeveyndeki bir değişikliğin çocuklara nasıl aktığıdır: çocuğun yerel (local) transform’ı sabit kalsa bile, zincirlenen matris çarpımıyla dünya uzayında yeni konuma taşınır.

Matematiksel zincir: bir çocuğun dünya matrisi, kökten kendisine kadar tüm üst düğümlerin çarpımıdır
M_world = M_root × M_parent × M_local.

Varsayılan olarak matrixAutoUpdate açıktır; ebeveyn hareket ettikçe çocukların matrixWorld değerleri güncellenir. Özel animasyon veya IK sistemlerinde düğümleri elle iterken, bir karede tutarsızlık görürseniz önce bu zincirin güncellenip güncellenmediğini kontrol edin.

Canlı etki

Örnek: araba gövdesi (parent) 5 birim ileri gittiğinde, tekerlekler (child) yerel konumda (0, 0, 0) civarında kalsa bile dünya konumları otomatik güncellenir. Bu davranış transform propagation sayesindedir; motor her karede (veya matrixWorldNeedsUpdate akışında) bu zinciri hesaplar.

Ölçek (scale) de zincire girer: ebeveyn üzerinde küçültme varsa çocuğun dünya ölçeği de çarpılarak değişir. Bu yüzden «tekerlek neden inceldi?» gibi görsel hatalarda önce üst gruplarda beklenmedik scale birikimi aranır.

glTF pipeline ile bağlantı (runtime stratejisi)

glTF dosyanızı hazırlarken (pipeline aşamasında), sahne grafiğini nasıl kurguladığınız runtime performansını ve kod sadeliğini doğrudan etkiler.

İsim tabanlı arama (getObjectByName) sözleşmesi, tasarım–kod arasında sözleşme gibidir: isimleri değiştirmek CI kırılması yaratır; bu yüzden kritik kancaları belgeleyin ve mümkünse tek bir «kanca öneki» (SOCKET_ gibi) kullanın.

Boş düğüm (empty) stratejisi

Blender veya dışa aktarma hattında «kanca» noktaları oluşturun: örneğin karakterin elinde silah tutacağı yeri boş bir THREE.Object3D / grup düğümü olarak işaretleyin. İsimleri anlamlı verin (weapon_socket gibi).

Boş düğüm geometri taşımaz; yalnızca dönüşüm ve hiyerarşi için vardır. Bu sayede silah modeli değişse bile el animasyonu aynı kanca üzerinden bağlanır — asset sürümü değiştiğinde kodun tek dokunduğu yer genelde bu düğüm olur.

Runtime eşleşmesi

Kodda model.getObjectByName('weapon_socket') ile bu düğümü bulun; yeni yüklenen silah modelini doğrudan bu düğümün altına add() edin. Silah, el ve parmak animasyonlarıyla aynı hiyerarşi zincirinde hareket eder.

Aynı isimden iki düğüm kalmamasına dikkat edin; çakışmada getObjectByName ilk eşleşeni döndürür. Karmaşık sahnelerde traverse ile filtreleyip kullanıcı verisiyle eşleştirmek veya yükleme sonrası isimleri kodda yeniden adlandırmak da yaygın bir güvenlik önlemidir.

Aşağıdaki lab, aynı fikri «tech demo» estetiğinde gösterir: ortada yüzen modüler hub (cam çekirdek + metal gövde), yanda sabit sensör halkaları; ana parça ise prosedürel enerji modülü (bıçak + emissive çekirdek). weapon_socket yine boş bir gruptur; modül URL ile çekilmez. Attach to hub hedefe ease-out ile yaklaşıp socket.attach ile kilitlenir; Detach (world keep) yalnızca scene.attach + hafif drift.

Runtime birleştirme · modüler hub + enerji modülü · r170 Başlatılıyor…

Hub, sensörler ve enerji modülü yalnızca bu sayfada Three.js ile üretilir; harici .glb veya ağ adresi yok. RoomEnvironment ile IBL (metal / cam yansıması) kullanılır.

Ne izliyorsunuz? Ortada hafifçe yüzen ve dönen modüler hub; yüzeyde weapon_socket adlı boş bir kanca. Add module enerji modülünü (metal gövde + üçgen bıçak + emissive çekirdek) sahneye koyar. Attach to hub modülü dünya uzayında hedefe doğru ease-out ile taşır, son karede socket.attach ile kancaya bağlar — modül hub ile birlikte döner; bağlanırken parıltı (emissive) güçlenir. Detach (world keep) yalnızca scene.attach: dünya matrisi korunur, ardından çok kısa bir drift. Remove module geometriyi dispose eder.

Performans etkisi: derinlik vs. genişlik

Sahne grafiğinizin şekli, Three.js’in her karede güncellediği matris zincirinin uzunluğunu etkiler.

Bu başlık, GPU draw call sayısından farklı bir eksendir: çoğu zaman derinlik «daha pahalı matris güncellemesi» demektir; fakat görünürlük külçesi ve mantıksal gruplama maliyeti yassı ağaçta farklı şekilde ödenir. Amaç, projeniz için okunabilir ve ölçülebilir bir denge bulmaktır.

  • Derin ağaç: Üst üste 10–20 katmanlı ebeveyn–çocuk ilişkisi, Mworld için daha uzun çarpım zinciri demektir; CPU tarafında ek yük oluşturabilir.
  • Geniş / yassı ağaç: Çok nesneyi doğrudan sahneye bağlamak matris güncellemelerini bazen sadeleştirir; oysa toplu taşıma, seçim ve mantıksal gruplama zorlaşır.
  • Instancing ile ilişki: Aynı geometriden çok kopya çiziyorsanız hiyerarşi yerine InstancedMesh gibi yollar grafik yükünü farklı kanaldan çözer; bu sayfa hiyerarşi odaklı kaldığı için ikisini birbirinin yerine koymayın — birlikte planlayın.

HoloDepth önerisi

Birlikte hareket etmesi gerekmeyen statik objeleri gereksiz yere derin hiyerarşiye bağlamayın. Yalnızca fiziksel olarak birbirine bağımlı parçalar (kapı–menteşe, tekerlek–aks gibi) için anlamlı grup yapısı kullanın.

Transform propagation — canlı izleme

Bir objenin o anki gerçek yerini anlamak için yalnızca position alanına bakmak yanıltıcıdır; bu değer ebeveyne göre yerel ötelemeyi gösterir.

Yönelim için benzer kural geçerlir: quaternion / rotation yereldir; dünya uzayındaki yön için getWorldQuaternion kullanılır. Fizik motorlarına veya debug çizimlerine beslerken çoğu zaman dünya uzayındaki temsil doğrudur.

// Yanlış: yalnızca ebeveyne göre yerel mesafe
console.log(wheel.position);

// Doğru: sahne grafiği zincirini hesaplar, dünya konumunu döner
const worldPos = new THREE.Vector3();
wheel.getWorldPosition(worldPos);
console.log(worldPos);

HoloDepth özeti: runtime birleştirme API’leri

Aşağıdaki tablo, bu sayfadaki tekrarlayan dört işlemi tek satırda hatırlatır. glTF dosyası içindeki düğüm ağacını okumak için model içi grafik rehberine; düğüm adları ve içe aktarma sonrası yapı için Node hiyerarşisi sayfasına dönün.

İşlem → metot → amaç

İşlem Metot Amaç
Grup oluşturma new THREE.Group() Birden fazla modeli tek mantıksal konteynerde toplamak.
Dinamik bağlama parent.add(child) Bir modeli (ör. silah) diğerine (ör. el) hiyerarşik olarak bağlamak.
Dünya konumu korumalı ayırma scene.attach(object) Objeyi gruptan çıkarırken ekrandaki yerini kaydırmamak.
Hiyerarşik tarama model.traverse() Tüm alt düğümlere (mesh, ışık vb.) tek geçişte ulaşıp işlem yapmak.