holodepth

Three.js · İleri geometri

Indexed vs Non-Indexed Geometry

WebGL ve Three.js’te bellek verimliliğinin matematiği

3D dünyasında bir objeyi ekrana çizmek, yalnızca noktaları (vertex) birleştirmekten çok daha fazlasıdır. Modern grafikte darboğaz çoğu zaman GPU belleği ve veri transferidir. Indexed (indisli) ile non-indexed (indissiz) geometri ayrımını bilmek, yalnızca kod yazmanızı değil — binlerce poligon altında o kodun nasıl ayakta kalacağını belirler.

Temel geometri haritası için Geometri Giriş; vertex katmanı için Attributes & buffer’lar; sahne bağlamı için Sahne (Scene).

Temel mantık: ham veri mi, akıllı referans mı?

Bir üçgen çizmek için üç nokta yeter. Peki bitişik iki üçgenden oluşan bir dörtgen (quad) düşünün: ortak kenarda aslında aynı uzaydaki iki köşe vardır.

Non-indexed (indissiz) yapı

Bu yöntemde her üçgen birbirinden bağımsızdır: “şu üç noktayı birleştir, sonra şu üç noktayı” dersiniz. Ortak köşeler pratikte aynı koordinatlara sahip olsa bile bellekte ayrı kayıtlar olarak durur — yani aynı veri çoğu zaman tekrarlanır. Model karmaşıklaştıkça (küre, karakter ağı vb.) bu tekrar katlanarak büyür.

Mini örnek: aynı köşe neden iki kez yazılır?

İki bitişik üçgeni (ortak kenarlı) non-indexed düşünün: her üçgen kendi üç “slot”unu sırayla listeler; ortak köşeler aynı harfle etiketlense bile bellekte ayrı kayıt olarak tekrarlanır.

Non-indexed · üçgen başına vertex sırası
Üçgen 1
A B C
Üçgen 2
A C D

Burada A ve C iki kez listelenir → vertex buffer’da iki kez yer kaplar.

Indexed düzende ise önce benzersiz köşeler (A, B, C, D) tek listede durur; üçgenler yalnızca indekslerle (ör. 0,1,2 ve 0,2,3) bu listeye işaret eder.

Indexed (indisli) yapı

Burada bir adres defteri mantığı vardır: önce benzersiz noktaları bir vertex dizisine yazarsınız; sonra “hangi üç nokta hangi üçgeni oluşturur?” sorusunu ayrı bir index dizisiyle yanıtlarsınız. Örneğin “0, 1, 2 ve ardından 2, 1, 3” gibi. Sonuç: pozisyon, normal, UV gibi attribute verisi mümkün olduğunca tek kez saklanır; üçgenler bu kayıtlara referans verir.

Index dizisinin tipi: Uint16Array mı Uint32Array mı?

Three.js / WebGL tarafında index verisi çoğu zaman Uint16Array veya Uint32Array olarak tutulur. 16 bit indekslerle tek draw’da adreslenebilen vertex indeks aralığı üst sınırı 65535 civarındadır (uygulama ve draw moduna göre ayrıntı değişir); benzersiz vertex sayınız veya gereken indeks aralığı bunu aşıyorsa Uint32Array gibi geniş tipe geçmek gerekir — büyük sahnelerde bu ayrım “neden export bozuluyor?” sorusunun sık cevabıdır.

WebGL bağlamında bu indeks listesi, çizim komutlarında tipik olarak ELEMENT_ARRAY_BUFFER (öğe dizisi buffer’ı) olarak GPU belleğine bağlanır; vertex attribute’ları ise ARRAY_BUFFER tarafında durur — ileri seviye hata ayıklamada bu isimler dokümantasyonla hizalanır.

Canlı demo: aynı küp, indexed ↔ toNonIndexed()

Üstteki “8 köşe / 36 slot” sayımı kavramsal bir küp içindi; Three.js BoxGeometry ise yüzey başına ayrı köşe tutarak (doku ve keskin yüzey için) varsayılan olarak indexed bir buffer üretir. Aşağıda aynı kutuyu geometry.toNonIndexed() ile indissiz hâle getirince üçgen sayısı aynı kalırken position satırı uzar; tel kafes yoğunluğu değişmez ama pembe vertex noktaları “patlama” gibi çoğalır — veri gerçekten büyümüş olur. Aşağıdaki demo sabitleri, doc-indexed-nonindexed-demo.js içindeki initIndexedNonindexedCubeDemo ile aynı değerleri listeler.

BoxGeometry · buffer karşılaştırma
Vertices Üçgen İndis öğesi
Gölgelendirme

Özet

Mod değiştikçe metrikler güncellenir; Vertices sayısı geçişte kısa bir animasyonla sayılır (ör. 24↔36) — “aha” anını güçlendirir. Küp hafif asimetrik ve hafif eğik tutulur; böylece yüz normalleri ve UV ayrımı daha okunur. Indexed modda pembe→turkuaz arası nokta renkleri, aynı vertex indeksinin üçgenlerde kaç kez kullanıldığını gösterir; sarı çizgiler “paylaşılan köşe” ipucu verir. Non-indexed modda aynı uzay koordinatına düşen birden fazla vertex kaydı hafifçe ayrıştırılır ve nabızla büyütülür — aynı köşede iki ayrı veri satırı fikrini pekiştirir. Non-indexed + Keskin (flat) birlikteyken sahnede daha sert specular (düşük roughness, daha güçlü yönlü ışık) uygulanır. Yumuşak modda material.flatShading = false ile fragment tarafında normal interpolasyonu açılır; Normal okları her vertex kaydının kendi normal vektörünü gösterir.

Öğretmen notu

Indexed vs non-indexed aslında bir render stili değil, buffer / örgü düzeni farkıdır; ekranda çoğu zaman ince farklar görünür ama veri yolu (vertex sayısı, indeks buffer’ı) net değişir.

Demo sabitleri tablosu (BoxGeometry · buffer)

Varsayılan BoxGeometry segmentleriyle bu örnekte indexed tarafta tipik olarak 24 köşe kaydı, 12 üçgen, 36 indeks öğesi görünür; toNonIndexed() sonrası köşe satırı 36’ya çıkar, indeks satırı yoktur (metrikte ).

initIndexedNonindexedCubeDemo · küp + görselleştirme
Sahne / rol Parametre Değer Tür
Temel geometri BoxGeometry (1.52, 1.2, 1.38) · varsayılan segment 1×1×1 🔒 Sabit
Non-indexed kopya toNonIndexed() geoIndexed.clone().toNonIndexed() 🔒 Sabit
Gövde materyali MeshStandardMaterial renk 0x3d8fa9 · başlangıç flatShading: true · roughness/metalness moda göre (LIGHT) 🔒 + ↔ UI
Işık (genel) HemisphereLight 0xa7d8ff / 0x151820 · yoğunluk 1.2 🔒 Sabit
Işık (indexed / düz) LIGHT ortam 0.42 · yönlü 2.4 · roughness 0.38 · metalness 0.22 🔒 Sabit
Işık (non-indexed + flat) LIGHT ortam 0.26 · yönlü 3.05 · roughness 0.11 · metalness 0.06 🔒 Koşul
Yönlü konum DirectionalLight.position (4.2, 6.5, 5) 🔒 Sabit
Kamera PerspectiveCamera FOV 42 · yakın/uzak 0.08 / 80 · konum (2.85, 2.15, 3.55) · lookAt(0, 0.05, 0) 🔒 Sabit
Mesh dönüşü mesh.rotation (0.2, -0.14, 0.11) 🔒 Sabit
Tel kafes WireframeGeometry + çizgi LineBasicMaterial 0x7ae8ff · opaklık 0.55 🔒 Sabit
Vertex noktası SphereGeometry yarıçap 0.036 · segment 10×10 🔒 Sabit
Indexed renk eşlemesi colorForIndexReuse tekrar 1 (veya diğer) → 0xff6a9a · 20xffc14d · 30x44e8d8 · ≥40x5cf0ff 🔒 Sabit
Non-indexed çakışma clusterCoincidentPositions epsScale = 5000 · halka yarıçap spread = 0.034 🔒 Sabit
Indexed “paylaşım” çizgisi buildIndexedReuseLines yardımcı vektör (0.417, 0.709, -0.212) · ofset ölçeği 0.052 · çizgi 0xffd080 opaklık 0.42 🔒 Sabit
Vertex sayı animasyonu VERT_TWEEN_MS 560 · easeOutCubic 🔒 Sabit
Normal okları VertexNormalsHelper boy 0.16 · renk 0x7dffc4 ↔ HTML
Dönen pivot her kare pivot.rotation.y += dt × 0.52 · pivot.rotation.x = sin(t×0.00035)×0.12 🔒 Sabit
Non-indexed nabız tickNonIndexedDotPulse sin(now×0.0031) ile ölçek nabzı 🔒 Sabit
Renderer makeRenderer ACESFilmicToneMapping · exposure 1.45 · SRGBColorSpace · setPixelRatio(min(dpr,2)) · setSize(…, true) 🔒 Sabit
UI varsayılanları segment / onay kutuları Indexed · tel kafes açık · vertex noktaları açık · Keskin (flat) · normal okları kapalı ↔ HTML

Önemli kod kesiti

Aynı kutu iki geometri örneğine bağlanır; mod değişince mesh.geometry ve tel kafes yenilenir.

const geoIndexed = new THREE.BoxGeometry(1.52, 1.2, 1.38);
const geoNonIndexed = geoIndexed.clone().toNonIndexed();

const LIGHT = {
  ambIndexed: 0.42,
  dirIndexed: 2.4,
  ambNonFlat: 0.26,
  dirNonFlat: 3.05,
  roughIndexed: 0.38,
  roughNonFlat: 0.11,
  metalIndexed: 0.22,
  metalNonFlat: 0.06,
};

function applyGeometry() {
  const g = indexedMode ? geoIndexed : geoNonIndexed;
  mesh.geometry = g;
  disposeWireframe();
  wire.geometry = new THREE.WireframeGeometry(g);
  // … vertex noktaları, indeks-tekrar çizgileri, metrik animasyonu
  applyLightingAndRoughness();
}

Derinlemesine: bellek, bant genişliği ve GPU önbelleği

Neden her zaman indexed kullanmıyoruz — ve bu ayrım neden kritik? Kısaca vertex reuse (nokta yeniden kullanımı) ve donanım davranışı.

Vertex reuse ve kabaca boyut

Tipik bir düzenlemede her vertex; örneğin pozisyon (3 float), normal (3 float) ve UV (2 float) taşıyorsa toplam 8 float ≈ 32 byte (Float32) mertebesindedir — kesin sayı kanal sayısına göre değişir.

Non-indexed örnek (kavramsal): bir küp için 12 üçgen vardır; her üçgen 3 vertex listesi gerektirir: 12 × 3 = 36 vertex slotu. Kabaca 36 × 32 byte ≈ 1152 byte yalnızca bu kanallar için (üst bağlam; gerçek primitive’de ek köşe/UV bölümleri sayıyı artırabilir).

Indexed örnek (kavramsal): aynı köşe verisi tek listede tutulur; üçgen bağlantısı çoğu zaman 16- veya 32-bit indekslerle tutulur. Toplam bellek genelde çok daha düşük kalır; karmaşık modellerde fark MB düzeyine çıkabilir.

GPU cache avantajı

Modern GPU’lar işlenen vertex’leri kısa süreli önbellekte tutar. Bir sonraki üçgen, az önce kullanılan bir indeksi tekrar çağırıyorsa, dönüşüm maliyeti düşebilir. Non-indexed düzende aynı köşe koordinatları bile ayrı slotlar olarak akabileceği için bu yeniden kullanım fırsatı zayıflar; veri “her seferinde yeni”ymiş gibi işlenebilir.

İleri seviye sezgi: Indexed çizimde aynı vertex indeksi kısa süre içinde yeniden kullanıldığında, GPU tarafındaki önbellek / post-transform mekanizmaları sayesinde vertex shader işi pratikte daha az tekrarlanabilir — bu, “draw call düştü” etkisinden ayrıca, yoğun geometride hissedilen bir rahatlamadır (her sürücü ve mesh düzeni için kesin sayı garantisi değildir).

Ne zaman hangisini seçmelisiniz?

Özellik Indexed geometry Non-indexed geometry
Bellek kullanımı Genelde çok verimli (tekrar az) Yüksek (vertex verisi sık tekrarlanır)
Render / veri yolu Cache ve reuse’a daha uygun Büyük veride maliyet artabilir
Keskin kenarlar / yüz başına farklı normal Zor (komşu yüzeyler vertex paylaşımı) Kolay (yüzeyler bağımsız vertex ile ayrılabilir)
Tipik kullanım Organik modeller, pürüzsüz yüzeyler Patlama parçacıkları, keskin low-poly hatlar

Kritik not: keskin yüzey ve farklı renk

Her yüzeyin görsel olarak tamamen bağımsız olması gerekiyorsa (örneğin küpün her yüzüne farklı renk veya keskin gölgeler), indexed yapıda bir vertex komşu tüm yüzeylerle aynı normali “paylaşmak” zorunda kalabileceği için kenarlar yumuşak görünebilir. Bu durumda bazen BufferGeometry.prototype.toNonIndexed() ile veriyi expand etmek veya vertex’leri bilinçli olarak klonlamak gerekir — trade-off olarak bellek ve bant genişliği artar.

Holodepth uygulama stratejisi

Three.js’te birçok hazır BufferGeometry (ör. BoxGeometry, SphereGeometry) varsayılan olarak indexed gelir. Özel shader veya manuel vertex manipülasyonunda ise veriyi nasıl düzenlediğiniz, FPS’i doğrudan etkiler.

  • Verimlilik önceliği: Mümkün olduğunca index kullanın; gereksiz vertex kopyasından kaçının.
  • Görsel keskinlik önceliği: Yumuşak normal paylaşımı istemiyorsanız non-indexed’e geçin veya köşeleri klonlayın; maliyeti göz önünde bulundurun.

Bir sonraki adımda bu yapı taşlarını kullanarak vertex verisini tek bir interleaved bellek düzeninde paketlemeyi Interleaved buffers üzerinden inceleyebilirsiniz.

İleri geometri dünyasına hoş geldiniz: burada yalnızca şekil çizmiyoruz — veriyi bilinçle yönetiyoruz.