WebGL · Buffer’lar · Attribute sözleşmesi
WebGL: Buffer ve Attribute mantığı
WebGL’de her şey bir sayı dizisiyle başlar. GPU’nun paralel çalışabilmesi için veri, son derece düzenli bir bellek düzeniyle hazırlanır ve çizim anında shader’lara “akacak” şekilde bağlanır. Burada mesele “kodu çalıştırmak” değil; GPU’ya doğru veriyi, doğru anlamla okutabilmektir. Çünkü 3D’de en yaygın hataların çoğu, shader’ın kendisinden önce layout (stride/offset/format) sözleşmesinde doğar. Bu sayfada amaç, üst seviye kullanım örneklerine girmeden veri → attribute → vertex shader hattının temel anlaşmasını kurmak; böylece “niye yamuk/niye kayık/niye bozuk?” sorularını doğru yerden yakalamaktır.
Buffer nedir? (veri yükleme mekanizması)
Buffer (tampon), CPU tarafındaki ham verilerin (JavaScript) GPU’nun yüksek hızlı belleğine taşınmasını sağlayan depolama birimidir. GPU, çalışma anında “JS dizisini” okumaz; ona, kendi belleğinde duran ham sayılar gerekir.
Bu noktada WebGL’in zihniyetini yakalarsınız: sahnedeki “nesne” kavramı, GPU tarafında bir bellek bloğu ve o bloğun nasıl okunacağını söyleyen bir sözleşme hâline gelir. Buffer, bu sözleşmenin “ham veri” tarafıdır.
- CPU → GPU transferi:
Float32Arraygibi typed array’ler, WebGL API üzerinden GPU belleğine kopyalanır. Bu, “veriyi fiziksel hedefe paketleme” adımıdır. - Veri yolu: Veri bir kez buffer’a yüklendiğinde, çizim sırasında CPU’ya tekrar danışılması gerekmez; GPU veriyi kendi içinden çok hızlı çeker.
- Yaşam döngüsü: Buffer’ı bir kez üretip (oluşturup) uzun süre kullanabilir, gerektiğinde içeriğini güncelleyebilirsiniz. Mantık: CPU “hazırlar”, GPU “tüketir”.
Küçük teknik çerçeve: WebGL’de veriyi yüklerken, çoğu zaman “bu veri sık mı değişecek?” sorusuna göre bir kullanım ipucu verirsiniz (ör. STATIC vs DYNAMIC). Bu, bir optimizasyon dersi değil; GPU’ya “bu belleği nasıl yönetmek istersin?” diye kibar bir sinyaldir.
Bir de pratik ayrım: bazı durumlarda veriyi tamamen yeniden yüklersiniz (bufferData mantığı), bazı durumlarda yalnızca bir kısmını “yama” yaparsınız (bufferSubData mantığı). İkisi de aynı hedefe hizmet eder: GPU belleğindeki sayıları doğru ve güncel tutmak.
Vertex data: geometrinin ham maddesi
GPU’ya gönderdiğiniz veri, çoğu senaryoda “bir model nesnesi” değil; verteks başına düzenli dizilerden oluşan bir ham maddedir. En yaygın üç bileşen şunlardır:
- Position (konum): Köşelerin 3D uzaydaki XYZ koordinatları.
- Normal: Yüzeyin baktığı yön (ışıklandırma hesabının temel girdisi).
- UV: Texture’ın yüzeye nasıl sarılacağını tanımlayan koordinatlar.
Bu üçlü, “görüntünün” üç temel sorusuna karşılık gelir: nerede? (position), hangi yöne dönük? (normal), hangi noktadan örnekle? (UV). Shader’lar çoğu zaman bu ham girdileri, kendi kurallarıyla bir leştirerek nihai rengi üretir.
Küçük ama önemli detay: bu veriler yalnızca “var/yok” değil, aynı zamanda ölçek ve tutarlılık meselesidir. Örneğin normal vektörlerinin normalize edilmemesi veya yanlış uzayda (object vs world) kullanılması, ışıklandırmayı anında “plastik/yanlış” hissettirebilir. UV tarafında ise \(0\)–\(1\) aralığının dışına çıkmak çoğu zaman “wrap/tiling” gibi davranışlara kapı açar.
Teknik tablo: vertex verisi ne işe yarar?
| Alan | Tipik boyut | Shader’da kullanım |
|---|---|---|
| Position | vec3 | Vertex shader’da gl_Position üretimi. |
| Normal | vec3 | Fragment tarafında aydınlatma / yüzey yönü. |
| UV | vec2 | Texture örnekleme adresi. |
Attribute kavramı (shader input)
Attribute, vertex shader içindeki bir giriş kapısıdır. Mantığı basittir: vertex başına değişen veriler (position/normal/UV gibi) bu kapıdan shader’a akar.
Bu yüzden attribute’ları, “GPU her verteks için hangi sayıları okuyacak?” sorusunun cevabı olarak düşünebilirsiniz. Aynı draw çağrısı içinde GPU, bir verteksin verilerini okur; vertex shader’ı çalıştırır; sonra bir sonraki verteks için aynı düzenle tekrar eder.
- Vertex başına veri: Her verteks için farklı olan değerler attribute’tur.
- Shader beslemesi: Buffer’daki ham sayılar, belirlediğiniz layout sözleşmesiyle vertex shader’a “pompalanır”. GPU, çizim sırasında her verteks için ilgili dilimi alıp attribute değişkenlerine koyar.
- Attribute vs uniform: Attribute “her verteks için değişen” veri taşır; uniform ise “tüm çizim boyunca aynı kalan” değerleri (ör. dönüşüm matrisleri, zaman, ışık parametreleri) taşır. Bu ayrımı oturtmak, shader girdilerini doğru sınıflandırmanın en kısa yoludur.
Sezgi: Buffer “ham depo”, attribute ise “shader’ın okuma kapısı”dır. İkisi arasında kurduğunuz layout, GPU’ya “bu sayılar ne anlama geliyor?” demektir.
Pratikte attribute’lar bir location üzerinden eşleşir: shader’daki giriş değişkeni ile CPU tarafında bağladığınız buffer “aynı kapıyı” paylaşır. Bu kapıyı açıp kapamak (enable/disable) ve hangi formatta okunacağını söylemek (boyut, tür, normalize, stride, offset) ise bir sonraki bölümdeki pointer sözleşmesinin kalbidir.
Buffer türleri (ARRAY vs INDEX)
WebGL’de veri tipine göre iki temel buffer ailesi öne çıkar. Bu ayrım, “veriyi nasıl okuyoruz?” sorusunun omurgasıdır.
Basit çerçeve: ARRAY_BUFFER “ne”yi taşır (verteks başına değerler), INDEX ise “nasıl bağlanacağını” söyler (hangi verteksler hangi yüzeyi oluşturuyor). Yani biri ham malzeme, diğeri o malzemenin topolojisidir.
- ARRAY_BUFFER: Verteks verilerini (konum, renk, normal, UV) tutan genel amaçlı buffer.
- ELEMENT_ARRAY_BUFFER (index buffer): Hangi vertekslerin birleşip hangi üçgeni oluşturacağını belirten indeks listesi. Aynı verteksi tekrar tekrar yazmadan geometri kurmanızı sağlar.
Index buffer’ın sezgisi şudur: aynı verteks, birden fazla üçgen tarafından paylaşılabilir. İndekssiz çizimde aynı koordinatı tekrar tekrar yazmanız gerekir; indeksli çizimde ise “aynı verteksi tekrar kullan” dersiniz. Bu, yalnızca bellek tarafında değil, geometriyi “tanımlama” biçiminde de daha temiz bir sözleşme kurar.
Bu ayrım, çizim çağrısında da kendini gösterir: indeks yoksa genelde
gl.drawArrays, indeks varsa gl.drawElements çizim modeline
yaklaşırsınız.
Burada optimizasyon anlatmıyoruz; sadece topolojinin “okuma biçimini” değiştirdiğini
sabitliyoruz.
Vertex attribute pointer: GPU veriyi nasıl okur?
GPU’ya sadece veriyi göndermek yetmez; ona bu veriyi nasıl okuyacağını da söylemeniz gerekir. Bu, layout (düzen) sözleşmesidir.
Layout; “bu attribute kaç bileşenli?”, “hangi sayı tipi?”, “normalize edilecek mi?” ve “bir verteksin verisi kaç byte sonra tekrar ediyor?” gibi kararların toplamıdır. GPU, buffer’ı bu tarif olmadan yalnızca ham byte’lar olarak görür.
- Size / bileşen sayısı: Attribute’un kaç parçadan oluştuğu (ör. position = 3, UV = 2).
- Type / veri tipi: Verinin nasıl yorumlanacağı (ör. FLOAT, UNSIGNED_BYTE).
- Normalized: Tamsayı verilerin (ör. 0–255) shader’a girerken 0–1 aralığına ölçeklenip ölçeklenmeyeceği.
- Stride (adım): Aynı verteks bloğunun byte cinsinden toplam genişliği (ör. position+color interleaved ise ikisinin toplamı).
- Offset: Bloğun içindeki başlangıç noktası. “Bu attribute, verteks bloğunun kaçıncı byte’ından başlıyor?” sorusu.
En sık yapılan hata: stride/offset’i “eleman sayısı” sanmak. WebGL’de bu değerler tipik olarak byte cinsindendir. Yani “3 float” demek, çoğu durumda 3 × 4 = 12 byte’tır.
Örnek sezgi: Tek dizide hem konum hem renk iç içeyse (interleaved), GPU’ya “ilk 3 sayı position, sonraki 3 sayı color” dersiniz. Böylece GPU aynı buffer’ı iki farklı attribute için iki farklı offset/stride ile okuyabilir.
İki yaygın düzen vardır:
- Interleaved: Tek buffer içinde “position + normal + UV …” blok blok dizilir. Avantajı, GPU’nun aynı verteks için ilgili verileri yakın bellekten çekebilmesidir.
- Separate buffers: Position ayrı, normal ayrı buffer’dadır. Avantajı, bazı verileri güncellerken diğerlerine dokunmamak ve düzeni daha modüler tutmaktır.
Hangi yolu seçerseniz seçin, kritik sözleşme aynıdır: shader’daki attribute kapıları ile buffer’daki byte düzeni birebir örtüşmelidir.
Aşağıdaki demo tek bir üçgen ve bir drawArrays çağrısı ile sınırlıdır; amaç
sahne kurmak değil, vertexAttribPointer sözleşmesi kaydığında GPU’nun ham
baytları nasıl yanlış “okuduğunu” hissettirmektir.
Ham WebGL — tek program,
gl.TRIANGLES
Pointer hatası (birini seçin)
Veri düzeni
Tek verteks — bellek şeridi
Byte layout & pointer özeti (debug)
vertexAttribPointer
özeti yer alır — öğrenme sırası: gör → anla → bayt düzenini incele → teknik detay.
Draw call ile bağlantı
Her şey yüklendiğinde, gl.drawArrays veya gl.drawElements
tetiklenir.
Bu çağrı, GPU’ya “buffer’daki veriyi, belirlenen attribute layout’u ile pipeline’dan geçir
ve
framebuffer’a yaz” demektir.
Bu sayfada draw call’ı optimize etmeyi anlatmıyoruz; sadece şunu sabitliyoruz: draw call, veri düzeni + shader sözleşmesi hazır olduğunda pipeline’ı çalıştıran tetiktir.
Three.js bağlantısı (BufferGeometry)
Altın bağlantı
Three.js’te kullandığın BufferGeometry, aslında WebGL
buffer’larının bir yönetim katmanıdır.
Siz geometry.setAttribute() dediğinizde, Three.js arka planda WebGL
buffer
oluşturur, veriyi GPU’ya yükler ve çizim anında pointer (stride/offset) ayarlarını
sizin
yerinize kurar.
Zihinsel model (pipeline özeti)
- Buffer: Veriyi GPU’ya yükler (giriş).
- Attribute / layout: Verinin shader tarafından doğru “anlamla” okunmasını sağlar (sözleşme).
- Shader: Veriyi işler (işlem).
- Framebuffer: Sonucu yazar (çıkış).
Bu hattı bir cümleye sıkıştırırsak: CPU sayıları hazırlar, GPU bu sayıları layout ile okur, shader’lar kuralları uygular ve sonuç “bir hedefe” yazılır. Bu sayfanın odağı, özellikle “layout ile okur” kısmıdır; çünkü en sık bozulma burada olur.
- Geometri dağılıyorsa: Çoğu zaman attribute bağlama, stride/offset veya bileşen sayısı (vec2/vec3) uyuşmazlığı vardır.
- Renk/texture garipse: UV attribute’ı veya interpolasyon için taşınan veriler (varying) beklediğiniz düzende gelmiyor olabilir.
- Işıklandırma “plastik”se: Normal verisi yanlış uzayda ya da normalize edilmeden kullanılıyor olabilir.
HoloDepth teknik özet: buffer ve attribute mantığı, 3D dünyamızın iskeletidir. Veriyi ne kadar düzenli ve doğru layout ile GPU’ya gönderirseniz, o kadar stabil ve ölçeklenebilir bir render hattı kurarsınız.