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).
Ö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.
| 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).
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.
| 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.
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.
| 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.