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 gönderilen ham veriyi manuel olarak yönetmemizi sağlayarak sınırsız bir yaratıcılık alanı sunar. Çerçeve için Geometri Giriş; hazır formlar için Primitive geometriler.

Neden özel geometri?

Hazır geometriler belirli matematiksel kalıplara bağlıdır. Özel bir BufferGeometry kullanmak şu avantajları sağlar:

  • Tam kontrol: Her bir noktanın (vertex) konumunu siz belirlersiniz.
  • Performans: Sadece ihtiyacınız olan veriyi (attribute) göndererek bellek kullanımını optimize edersiniz.
  • Esneklik: Arazi (terrain), veri görselleştirme veya prosedürel nesne üretimi için doğrudan yol.

Geometrinin atomları: BufferAttributes

GPU, köşeleri ve yüzeyleri anlamak için attribute (öznitelik) adı verilen veri dizilerine ihtiyaç duyar. Özel bir geometride bu dizileri genelde Float32Array formatında siz hazırlarsınız:

  • position: Noktaların [x, y, z] koordinatları. Olmazsa olmazdır.
  • normal: Işığın yüzeyden nasıl yansıyacağını belirleyen yön vektörleri.
  • uv: 2D dokuların yüzeye nasıl sarılacağını belirleyen [u, v] koordinatları.
  • color: Her köşeye özel renk vermek için [r, g, b] değerleri.

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 toplam altı vertex gerekir; oysa karenin yalnızca dört köşesi vardır.

  • İndekssiz: Paylaşılan köşeyi her üçgende yeniden tanımlarsınız; bellek israfı doğabilir.
  • İndeksli: Dört köşeyi bir kez tanımlayıp GPU’ya “0, 1, 2 ve 0, 2, 3 köşelerini birleştir” dersiniz. Büyük modellerde bellek kullanımını önemli ölçüde azaltır (senaryoya göre %50’ye kadar tasarruf mümkündü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.