holodepth

Three.js · Geometri

Custom BufferGeometry (özel geometri oluşturma)

Ham vertex verisini siz yazın, GPU çizer

Three.js’in sunduğu hazır şekiller (küp, küre vb.) çoğu zaman yeterli olsa da, bazen doğada bulunmayan, tamamen matematiksel verilerle tanımlanan veya dinamik olarak değişen formlara ihtiyaç duyarız. Custom BufferGeometry, GPU’ya giden ham vertex verisini sizin yazdığınız Float32Array ve setAttribute ile yönetmenizi sağlar; “şekil sınıfı seç” yerine “diziyi sen üret” moduna geçersiniz.

Bu sayfa özel geometri pratiğine odaklanır: neden primitive yetmez, attribute kanallarını bir arada kurmak, indeksli / indekssiz karşılaştırma, canlı demolar ve dinamik güncelleme (needsUpdate). Attribute ve buffer dilinin temeli Attributes & buffer’lar konusundadır; hazır formlar ve segment seçimi Primitive geometriler konusuna bırakılır; burada tekrar etmiyoruz.

Genel geometri çerçevesi (vertex, yüzey, dispose) için Geometri giriş; indeks ayrıntısı Indexed vs non-indexed konusunda. Aşağıda Vertex Playground, attribute toggle, indeks split demoları ve wireframe kod örneği bulunur.

Neden özel geometri?

Hazır geometriler (BoxGeometry, SphereGeometry …) belirli matematiksel kalıplara bağlıdır: genişlik, yarıçap, segment gibi parametreleri verirsiniz, Three.js vertex ve üçgen listesini sizin yerinize üretir ( Primitive geometriler konusu bu modeli anlatır). Özel bir BufferGeometry ise o listeyi siz yazarsınız; kütüphane “şekil sınıfı” seçmez, GPU’ya gidecek ham diziyi siz doldurursunuz.

Bu geçiş her projede gerekli değildir; prototipte küp ve küre çoğu zaman yeterlidir. Aşağıdaki üç avantaj, primitive’in yetmediği veya pahalıya geldiği durumlarda özel geometriye neden geçildiğini özetler.

  • Tam kontrol: Her bir noktanın (vertex) konumunu siz belirlersiniz. Silüet kütüphanede yoksa (spiral, veri noktası bulutu, import sonrası düzenlenmiş mesh) tek yol budur. İsteğe bağlı normal, uv, color ve setIndex de aynı geometri üzerinde sizin kararınızdır — kanal rolleri Attributes sayfasında, bir arada kurma bu sayfanın §2’sinde.
  • Performans: Yalnızca ihtiyacınız olan veriyi (attribute) GPU’ya göndererek bellek kullanımını optimize edersiniz; gereksiz kanal taşımazsınız. Paylaşılan köşelerde aynı position satırını her üçgende tekrarlamak yerine setIndex ile dört köşe + indeks listesi kullanabilirsiniz — kare örneği indeksli bölüm ve split demoda. Segment / triangle bütçesi tartışması primitive tarafında kalır; burada odak “hangi buffer’lar gidecek ve köşe paylaşılacak mı” sorusudur.
  • Esneklik: Arazi (terrain), veri görselleştirme veya prosedürel nesne üretimi doğrudan dizi yazımıyla yapılır; formu her kare güncelleyerek dalga, gürültü veya morph benzeri efektler elde edersiniz. CPU’daki diziyi değiştirdikten sonra GPU’yu haberdar etmek için sayfa sonundaki needsUpdate notuna bakın; canlı örnek Vertex Playground demosunda.

Ne zaman primitive, ne zaman özel?

Kutu, küre, düzlem veya silindir yeterliyse primitive kullanın; parametrik modelleme ve segment seçimi primitiv mantığı konusunda. Özel geometriye, veri kaynağınız zaten bir dizi olduğunda (yükseklik haritası, CSV noktaları, simülasyon çıktısı) veya primitive’in üreteceği topoloji istediğiniz şey değilken geçin. Genel vertex / dispose çerçevesi Geometri giriş sayfasındadır; özel geometriye geçmeden önce setAttribute kalıbını oturtun, sonra aşağıdaki demolarla ölçün.

Geometrinin atomları: BufferAttributes

GPU, köşeleri ve yüzeyleri anlamak için attribute (öznitelik) adı verilen veri dizilerine ihtiyaç duyar. Primitive kullandığınızda bu dizileri Three.js üretir; özel bir geometride genelde Float32Array olarak siz hazırlar, ardından BufferAttribute ile setAttribute üzerinden geometriye bağlarsınız. Typed array ve itemSize ayrıntısı Attributes · Typed Arrays konusundadır; burada kanalların özel geometride ne anlama geldiğine odaklanıyoruz.

Standart kanallar (özel geometri)

Aynı vertex indeksinde hizalanan kanallar birlikte okunur: 5. köşenin konumu, normali ve UV’si aynı satır numarasına denk gelmelidir. Tam tanımlar ve shader tarafı standart kanallar konusunda; özet liste:

  • position: Noktaların [x, y, z] koordinatları. Olmazsa olmazdır — mesh çizilemez. Özel geometride ilk yazdığınız kanal budur; uzunluk köşe sayısı × 3 olmalıdır.
  • normal: Işığın yüzeyden nasıl yansıyacağını belirleyen yön vektörleri. Elle yazmadıysanız computeVertexNormals() çoğu basit ağ için yeterlidir; MeshStandardMaterial gibi ışığa duyarlı materyaller normal bekler.
  • uv: 2D dokuların yüzeye nasıl sarılacağını belirleyen [u, v] koordinatları. Düz renkte şart değil; desen veya PBR dokusu için eklenir — sarma mantığı UV mapping konusundadır.
  • color: Her köşeye özel renk vermek için [r, g, b] değerleri (isteğe bağlı). Materyalde vertexColors: true açılmadan etkisi görünmez; indeks split demosunda ve attribute toggle demosunda açıp kapatabilirsiniz.

Minimum kurulum akışı

Boş BufferGeometryposition dizisi → setAttribute → (gerekirse) uv, colorcomputeVertexNormals() veya elle normal → (isteğe bağlı) setIndex. §1’deki performans notu: paylaşımlı köşe için indeks buffer’ı §5 konusunda.

Vertex Playground önce yalnızca position ile çalışır; attribute toggle demo ise aynı ızgarada normal, UV ve vertex color’ın görünüme etkisini yan yana gösterir; kanalı eklemek ile materyal ayarını açmak arasındaki farkı orada test edebilirsiniz.

Dizi ne işe yarıyor? Vertex Playground

Float32Array içindeki her üçlü, bir köşenin (x, y, z) koordinatıdır. Aşağıdaki ızgara düzleminde segment sayısı arttıkça köşe sayısı büyür; GPU bu diziyi okuyarak üçgenleri çizer. Görünümü nokta, tel kafes veya dolu yüzey olarak değiştirerek aynı verinin nasıl yorumlandığını görebilirsiniz; gürültü ve dalga seçenekleri dizinin animasyonla güncellenmesine örnek teşkil eder. Ölçüler için demo sabitleri tablosuna bakın.

İsteğe bağlı ses: demo kutusundan ayrı sütun genişliğinde şerit; dosya adı Custom-Buffer-Geometry-Demo-1 (uzantı sırasıyla denenir).

Vertex Playground · özel ızgara

Özet: Segment = ızgara yoğunluğu; köşe sayısı ≈ (segment+1)². position dizisi köşe sayısı × 3 uzunluğundadır; yani bu sayfada gördüğünüz şekil, doğrudan bu sayıların yorumlanmasıdır.

Demo sabitleri tablosu (Vertex Playground)

Bu blok diagram-custom-buffer-geometri.js içindeki initVertexPlayground ile eşlenir. Düzlem PlaneGeometry üzerinde baseRest saklanır; gürültü/dalga position buffer’ını her kare veya yeniden kurulumda günceller.

initVertexPlayground — özel düzlem ızgarası
Sahne / rol Parametre Değer Tür
Ortam setClearColor 0x05060c 🔒 Sabit
Düzlem PlaneGeometry(w, h, seg, seg) w = h = 5 · sonra rotateX(-π/2) 🔒 Sabit
Yerleşim PLAYGROUND_MESH_Y 0.75 🔒 Sabit
UI segment data-cbuffer-segments min 3 · max 40 · varsayılan 16 ↔ HTML
Nokta modu PointsMaterial.size 0.09 🔒 Sabit
Dalga sin/cos terimleri bx*1.8 + t*1.6 · genlik 0.45 · bz*2.1 + t*1.2 · genlik 0.32 🔒 Sabit
Gürültü hash11 karışımı iki örnek · toplam ölçek ≈ ±0.65 (yarım genlik) 🔒 Sabit
Zaman tAnim += 0.016 / kare 🔒 Sabit

Önemli kod kesiti

Taban köşeler baseRest içinde saklanır; applyDisplacement aynı geometride position ve isteğe bağlı color attribute’unu yazar.

const g = new THREE.PlaneGeometry(w, h, seg, seg);
g.rotateX(-Math.PI / 2);
// baseRest[i] = dünya köşesi (bx, by, bz)

if (useNoise) {
  dy += (hash11(i * 17.1 + bz * 3.2) - 0.5) * 0.85
    + (hash11(i * 31.7 - bx * 2.1) - 0.5) * 0.45;
}
if (useWave) {
  dy += Math.sin(bx * 1.8 + tAnim * 1.6) * 0.45
    + Math.cos(bz * 2.1 + tAnim * 1.2) * 0.32;
}
pos.setXYZ(i, bx, by + dy, bz);
geoRef.computeVertexNormals();

Attribute anahtarları: normal, renk, UV

GPU bir öznitelik olmadan o kanaldaki hesabı yapamaz: normal yoksa yönlü ışık yok; color yoksa köşe başına renk yok; uv yoksa doku sarılamaz. Aşağıda normal için MeshStandardMaterial (PBR) ile MeshBasicMaterial (ışıksız düz ton) arasında geçiş yapıyoruz; fark bilinçli olarak sertleştirildi. position her zaman gereklidir; arayüzde kilitli. Rakamlar demo sabitleri tablosunda initAttributeToggle ile satır satır eşlenir.

İsteğe bağlı ses: demo kutusundan ayrı sütun genişliğinde şerit; dosya adı Custom-Buffer-Geometry-Demo-2 (uzantı sırasıyla denenir).

Attribute toggle · küre
position

Normal kapalı = Basic (sahne ışıkları görünmez); normal açık = Standard (gölgeli yüzey). Renk açıkken altta kırmızı / üstte mavi köşe gradient’i; UV açıkken dama tahtası dokusu; kapalıyken doku kaybolur.

Demo sabitleri tablosu (Attribute toggle)

initAttributeToggle: yüksek segmentli küre üzerinde vertex color gradient’i; makeCheckerTexture ile CanvasTexture + map.repeat(5,5). Normal açıkken MeshStandardMaterial, kapalıyken MeshBasicMaterial.

initAttributeToggle · küre + öznitelik anahtarları
Sahne / rol Parametre Değer Tür
Küre SphereGeometry yarıçap 1.15 · segment 56 × 56 🔒 Sabit
Dama dokusu makeCheckerTexture ızgara 10×10 hücre · canvas 256² · repeat(5, 5) 🔒 Sabit
PBR MeshStandardMaterial metalness: 0.22 · roughness: 0.42 · vertexColors: true 🔒 Sabit
Işık ortam + yönlü ×2 AmbientLight(0xffffff, 0.28) · ana DirectionalLight(0xfff0e0, 1.05) konum (4, 7, 5) · dolgu 0xaaccff yoğunluk 0.45 konum (-5, 2, -4) 🔒 Sabit
UI normal / uv / color varsayılan üçü de açık; position kilitli ↔ HTML

Önemli kod kesiti

Malzeme sınıfı normal bayrağına göre değişir; map yalnızca UV açıkken atanır.

function syncMaterialProps(mat, wantC, wantUv) {
  mat.vertexColors = wantC;
  mat.map = wantUv ? checkerTex : null;
  if (!wantC) {
    mat.color.set(0x8899bb);
  } else {
    mat.color.set(0xffffff);
  }
  mat.needsUpdate = true;
}

function applyAttrs() {
  const wantN = !!(nOn && nOn.checked);
  const wantC = !!(cOn && cOn.checked);
  const wantUv = !!(uvOn && uvOn.checked);

  if (wantN) {
    geo.computeVertexNormals();
  }

  mesh.material = wantN ? matStandard : matBasic;
  syncMaterialProps(matStandard, wantC, wantUv);
  syncMaterialProps(matBasic, wantC, wantUv);
}

İndeksli (indexed) vs. indekssiz geometri

Bir kareyi iki üçgenle çizmek için GPU’ya altı köşe göndermek gerekir; oysa dünyada yalnızca dört benzersiz köşe vardır. Primitive’ler çoğu zaman indeksli buffer üretir; özel geometride bu ayrımı siz kurarsınız: setIndex ile paylaşım, veya indekssiz listede her üçgen için ayrı position satırları. İki düzen de geçerlidir; fark, veriyi nasıl paketlediğinizdedir; görünür silüet aynı kalabilir.

Kare örneği: altı kayıt, dört köşe

İndekssiz karede position buffer’ında altı satır vardır: ortak köşeler (örneğin sağ üst) iki üçgende iki kez yazılır; dünya konumu aynı, buffer indeksi farklı. İndeksli tarafta dört satır + index dizisi (0, 1, 2 ve 0, 2, 3) üçgenleri bu dört köşeye bağlar. Ham position baytı kabaca 6×3×4 B’ye karşı 4×3×4 B (+ indeks buffer) düşer; büyük ağlarda bu fark katlanır. Sayıları canlı görmek için split demo ve kod kesiti aşağıdadır.

İki düzen

  • İndekssiz: Paylaşılan köşeyi her üçgende yeniden yazarsınız; kod basit, “her üçgen üç ardışık vertex” okuması doğrudandır. Bellek ve bant genişliği büyüyebilir; köşe başına farklı normal veya vertex color istediğinizde (keskin kenar, farklı UV köşesi) bazen bilinçli tercih edilir — split demo sol panel.
  • İndeksli: Dört köşe + index buffer (“0,1,2” ve “0,2,3”); büyük modellerde bellek tasarrufı belirgin olabilir. Paylaşılan köşede normal / uv tek kayıt taşır — yumuşak yüzey için uygundur; keskin ayrım gerekiyorsa indekssiz veya vertex bölme gerekir. Split demo sağ panel; Uint16Array / Uint32Array, toNonIndexed() ve GPU önbelleği Indexed vs non-indexed konusundadır.

Ne zaman hangi düzen?

Hangi düzeni seçeceğiniz vertex sayısı, güncelleme sıklığı ve köşe başına attribute ihtiyacına bağlıdır. Statik, paylaşımlı köşeli büyük mesh’lerde indeksli düzen genelde önceliklidir; hızlı prototip, tek seferlik çizim veya üçgen başına farklı köşe verisi gerektiğinde indekssiz liste yeterli olabilir. Animasyonda yalnızca position güncelliyorsanız indeks listesi sabit kalabilir; her köşeyi ayrı oynatıyorsanız indekssiz akış bazen daha az kafa karıştırıcıdır.

Bu sayfada kare üzerinden görsel karşılaştırma yapıyoruz; genel karar çerçevesi, bellek formülleri ve küp demosu advanced sayfada. §2’deki minimum akışta setIndex son isteğe bağlı adımdır; önce position (ve gerekli kanallar) oturmalıdır.

Yan yana: indeks bellek karşılaştırması

Aynı kare için iki veri düzeni: indekssiz tarafta her üçgen kendi köşe kopyasını taşır (aynı dünya konumunda farklı buffer id’ler üst üste); indeksli tarafta dört benzersiz köşe ve index ile üçgenler paylaşılır. Aşağıda üçgenler farklı renkle, köşeler renkli nokta, üstte ince kenar çizgisi ile bu fark görselleştirilir; fare ile vertex / yüzey üzerinde buffer id okunur. Sayfanın altındaki demo sabitleri tablosu, initIndexedSplit içindeki rakamlarla birebir özetlenir.

Split · veri yapısı
İndekssiz · 6 köşe Sol · 6 vertex
İndeksli · 4 köşe Sağ · 4 vertex

Sol: 6 nokta; üst üste binen id’ler aynı köşede iki kez sayılır. Sağ: 4 nokta; köşe paylaşımı yüz hover metninde görülür. Üçgen renkleri (turuncu / camgöbeği) iki tarafta da aynı mantıkla eşleştirilir; sağda materyal grupları, solda vertex color ile ayrılır.

Demo sabitleri tablosu (İndeks split)

initIndexedSplit: iki BufferGeometry aynı kareyi çizer; sol tarafta vertexColors, sağda setIndex + addGroup ile iki MeshStandardMaterial.

initIndexedSplit · kare · iki düzen
Sahne / rol Parametre Değer Tür
Üçgen renkleri TRI_A / TRI_B 0xff7733 · 0x33ccee 🔒 Sabit
İndekssiz position Float32Array 6 köşe (iki üçgen, köşe kopyası) 🔒 Sabit
İndekssiz color 18 float (köşe başına RGB); ilk üç köşe TRI_A, son üç TRI_B 🔒 Sabit
İndeksli position + setIndex 4 köşe · indeks [0,1,2, 0,2,3] 🔒 Sabit
İndeksli addGroup (0, 3, 0) turuncu üçgen · (3, 3, 1) camgöbeği üçgen 🔒 Sabit
Üst üste id NON_INDEXED_STACK_GROUPS [[0,3], [2,4]] (hover metni) 🔒 Sabit
Raycaster params.Points.threshold 0.12 🔒 Sabit
Köşe noktaları PointsMaterial size: 0.13 · vertexColors: true · HSL 0.05…0.8 aralığında id başına renk 🔒 Sabit
Kenar çizgisi EdgesGeometry(geo, 18) LineBasicMaterial 0xf0f4ff · opaklık 0.55 🔒 Sabit
Mesh MeshStandardMaterial sol: vertexColors: true · sağ: iki materyal metalness 0.08 roughness 0.48 DoubleSide flatShading: true 🔒 Sabit
Kamera PerspectiveCamera FOV 38 · yakın/uzak 0.1 / 50 · konum (0, 0, 3.35) 🔒 Sabit
Işık ortam + yönlü ×2 Ambient(0xffffff, 0.42) · ana beyaz 1.0 (2.2, 3.8, 4.2) · dolgu 0xaaccff 0.28 (-3, -1, 2) 🔒 Sabit
Arka plan scene.background 0x05060c 🔒 Sabit
Bellek özeti ham position oranı 6×3×4 B ÷ (4×3×4 B + 6×2 B indeks) ≈ 1.20× (üst sınır ~2× metni HUD’da) 🔒 Hesap

Önemli kod kesiti

İki geometrinin çekirdeği: solda aynı dünya köşesinin farklı buffer satırları, sağda paylaşılan köşe + indeks + grup.

const TRI_A = 0xff7733;
const TRI_B = 0x33ccee;

function buildNonIndexedColored() {
  const g = new THREE.BufferGeometry();
  const positions = new Float32Array([
    -1, -1, 0, 1, -1, 0, 1, 1, 0, -1, -1, 0, 1, 1, 0, -1, 1, 0,
  ]);
  g.setAttribute("position", new THREE.BufferAttribute(positions, 3));
  // … her üç köşe için TRI_A / TRI_B RGB → color attribute
  g.computeVertexNormals();
  return g;
}

function buildIndexedGrouped() {
  const g = new THREE.BufferGeometry();
  const positions = new Float32Array([-1, -1, 0, 1, -1, 0, 1, 1, 0, -1, 1, 0]);
  g.setAttribute("position", new THREE.BufferAttribute(positions, 3));
  g.setIndex([0, 1, 2, 0, 2, 3]);
  g.clearGroups();
  g.addGroup(0, 3, 0);
  g.addGroup(3, 3, 1);
  g.computeVertexNormals();
  return g;
}

Teknik tablo: özel geometri akış şeması

Adım İşlem Araç
1. Veri hazırlığı Koordinatları bir diziye yazın. Float32Array
2. Attribute atama Veriyi GPU’nun okuyacağı biçime sokun. THREE.BufferAttribute
3. Geometriye bağlama Özniteliği isimle kaydedin. geometry.setAttribute('position', …)
4. Normalleri hesaplama Işıklandırma için yüzey yönlerini üretin (gerekirse). geometry.computeVertexNormals()

Uygulama: rastgele üçgen çorbası (wireframe)

Aşağıdaki örnek, çok sayıda rastgele üçgen üreterek “nokta bulutu” veya dağınık yüzey hissi verir: her üçgen için üç vertex, her vertex için üç bileşen (x, y, z) doldurulur; toplam uzunluk üçgen sayısı × 3 × 3.

const geometry = new THREE.BufferGeometry();
const triangleCount = 1000;
// Her üçgen: 3 vertex × 3 bileşen (x, y, z)
const positions = new Float32Array(triangleCount * 3 * 3);

for (let i = 0; i < positions.length; i++) {
    positions[i] = (Math.random() - 0.5) * 10; // yaklaşık -5 … 5
}

geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));

const material = new THREE.MeshBasicMaterial({ color: 0xff0000, wireframe: true });
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

Holodepth notu: dinamik güncelleme (needsUpdate)

CPU’daki diziyi değiştirdikten sonra GPU’yu haberdar edin

Özel geometrinin köşelerini animasyon sırasında güncellemek istiyorsanız (örneğin dalgalanan bir yüzey), yalnızca Float32Array içindeki sayıları değiştirmek yetmez. Verinin değiştiğini GPU’ya bildirmek için geometry.attributes.position.needsUpdate = true; kullanın; böylece güncel buffer ekran kartına yeniden aktarılır.