WebGL · Pipeline · GPU süreci
WebGL Render Pipeline: verinin piksele dönüşüm hattı
WebGL’de “pipeline”, bir kütüphane özelliği değil; GPU’nun donanım seviyesinde yürüttüğü disiplinli üretim hattıdır. JavaScript tarafında çizim tetiklendiğinde, GPU aynı tip işlemleri binlerce/milyonlarca veri üzerinde paralel çalıştırır ve sonuç framebuffer’a pikseller olarak yazılır.
Bu sayfada odak, Three.js tarafındaki sahne yönetimi veya optimizasyon reçeteleri değil; gerçekte GPU hangi istasyonlardan geçiyor? sorusudur. Yani: vertex → primitive → rasterization → fragment → framebuffer.
CPU’dan GPU’ya ilk temas (JS → WebGL API)
Her şey JavaScript tarafında başlar; fakat GPU JavaScript’i “anlamaz”. Bu yüzden CPU, WebGL API üzerinden GPU’nun anlayacağı iki şeye odaklanır: veri ve durum.
Buradaki “durum” (state) kelimesini önemli bir çerçeve olarak düşünün: GPU’ya yalnızca sayıları göndermek yetmez; o sayıların nasıl yorumlanacağını da tanımlarsınız. Yani “bu buffer’daki değerler pozisyon mu, normal mi?”, “hangi shader programı çalışacak?”, “hangi texture hangi örnekleyiciyle okunacak?” gibi kararlar da bu ilk temasın parçasıdır.
- Veri transferi: Koordinat/normal/UV/renk gibi diziler GPU belleğine buffer olarak taşınır. Bu aşamada önemli olan “nesne” değil; ham sayısal akıştır. WebGL’de model dediğiniz şey, çoğu zaman attribute dizileri + indeks dizisi şeklinde taşınır.
- Bağlama (yorum sözleşmesi): Buffer’ların hangi attribute’a bağlanacağı, shader’a hangi uniform’ların (matrisler, zaman, ışık parametreleri vb.) verileceği ve texture’ların hangi birime takılacağı belirlenir. GPU, ancak bu sözleşme kurulunca gelen veriyi “çözebilir”.
- Çizim tetiklemesi: CPU, “hazırım” anlamına gelen çizim çağrısını yapar; GPU da pipeline’ı çalıştırmaya başlar. Buradaki detaylar, bir üst seviye motorda tek komuta dönüşebilir; WebGL’de ise bu komut, hattın kapısını açan sinyaldir: GPU, artık vertex shader’dan başlayarak veriyi aşama aşama yürütür.
Özet sezgi: CPU tarafı “paketi hazırlar” (veri + state), GPU tarafı “paketi işler” (pipeline). Bu yüzden pipeline’ı anlamanın ilk adımı, GPU’ya ne gönderiyorum? sorusunu net cevaplayabilmektir.
HoloDepth notu
Three.js çoğu zaman bu veri trafiğini görünmez kılar: siz “mesh/material/camera” gibi kavramlarla niyeti tanımlarsınız; WebGL ise altta buffer’ları bağlar, shader’ı seçer ve GPU’yu çalıştırır. Burada o görünmeyen hattın iskeletini çıkarıyoruz.
Pipeline Step Visualizer — WebGL nasıl akıyor?
Bu vitrin tek bir üçgen sahnesi üzerinden GPU hattını keser: aynı verteks
verisiyle primitive türünü ve fragment işini değiştirerek CPU → framebuffer
zincirini görünür kılarız. Three.js yok — yalnızca buffer bağlama,
küçük GLSL programları ve drawArrays.
Tek bağlam · aynı üç köşe · farklı primitive / shader vurgusu
Pipeline istasyonu
Katmanlar (üst üste çizim)
POINTS — 1 yalın veri (küçük soluk noktalar), 2 vertex shader
vurgusu (daha büyük parlak noktalar + opsiyonel hareket). Adım 3:
LINE_LOOP;
varsayılan olarak köşe noktaları açık. Adım 4 raster: dolgu az, grid
baskın; adım 5 fragment: renk/gradient baskın, grid soluk. Adım 6
framebuffer/sunum hissi: daha tok renk + hafif vignette/salınım. Katmanlar öğrenme
sırasına göre varsayılanlanır; sahne grafiği veya kamera kontrolü yoktur.
Vertex processing (Vertex Shader)
Pipeline’ın ilk aktif istasyonu vertex shader’dır. GPU, gönderdiğiniz her verteksi paralel olarak işler ve “bu nokta ekranda nerede duracak?” sorusuna cevap üretir.
WebGL açısından bu, “3D noktayı ekrana çiz” cümlesinin ilk yarısıdır: vertex shader, geometriyi birden fazla uzay arasında taşır. Burada konuştuğumuz şey, obje isimleri değil; koordinat sistemleridir.
- Görev: 3D uzaydaki pozisyonu, kamera/projeksiyon üzerinden klip uzayına taşımak.
- Matematik: Tipik çekirdek işlem, model–view–projection zinciridir: Model × View × Projection.
- Çıktı: Her vertex için
gl_Positionhesaplanır; ayrıca sonraki aşamalara “taşınacak” ara veriler (ör. varying değerleri) hazırlanır.
Daha açık söylersek: vertex shader çoğu zaman object space’te başlayan
pozisyonu sırasıyla world, view ve clip
space’e
taşır. gl_Position klip uzayındadır; rasterization’dan hemen önce GPU,
perspective divide ile koordinatları NDC’ye indirger
(yaklaşık \([-1, 1]\) aralığı). Bu dönüşüm, “ekranın neresine düşecek?” sorusunun geometrik
temelidir.
Vertex shader ayrıca yalnızca pozisyon üretmez; fragment shader’ın ihtiyaç duyacağı verileri de “taşır”. Örneğin UV, vertex rengi veya normal gibi değerleri varying olarak çıkarsınız; rasterization bu değerleri üçgenin içine interpole eder ve her fragment’e giriş olarak verir.
Kritik sezgi: vertex shader, piksellerle ilgilenmez; o sadece geometriyi sahne uzayından “ekrana giden hat” üzerine yerleştirir.
Primitive assembly (ilkel oluşturma)
Verteksler tek tek işlendi; şimdi GPU bu noktaları “anlamlı şekiller”e dönüştürür: üçgen, çizgi, nokta… En yaygın senaryoda her 3 verteks bir üçgen oluşturur.
Bu aşamayı “geometrinin grameri” gibi düşünebilirsiniz: vertex shader size tekil noktalar verir; primitive assembly ise bu noktaların hangi topolojiyle bir araya geleceğini belirler. Yani GPU, artık “hangi üç nokta bir yüzey parçası?” sorusunu netleştirir.
- Bağlantı: İndeksli çizimde (index buffer) hangi vertekslerin bir araya geleceği açıkça tarif edilir. İndekssiz çizimde ise verteksler sırayla tüketilir ve seçtiğiniz primitive türüne göre gruplandırılır (ör. üçerli üçerli).
- Primitive türü: WebGL’de çizim, seçtiğiniz primitive’e göre yorumlanır: TRIANGLES, LINES, POINTS gibi. Aynı verteks dizisi, farklı primitive’lerle bambaşka bir “şekil dili” üretir.
- Kırpma (clipping): Kameranın görüş hacminin
dışında
kalan parçalar bu aşamada elenir veya sınırda kesilerek yeniden şekillenir. Bu işlem,
gl_Position’ın klip uzayında olduğu bölgede yapılır; yani ekranın dışındaki kısımlar rasterization’a taşınmaz. - Yüz yönü ve culling: Üçgenin verteks sırası (saat yönü / tersi) bir “ön yüz” tanımı üretir. İstenirse arka yüzler rasterization’a girmeden elenebilir (backface culling). Bu, görsel sözleşmenin bir parçasıdır: hangi yüzün “görünür” kabul edildiğini belirler.
Buradaki amaç, rasterization aşamasına “ekrana düşebilecek” primitifleri temiz bir şekilde hazırlamaktır.
Rasterization (piksel adaylarının üretilmesi)
Rasterization, matematiksel üçgenleri ekrandaki “piksel adayları”na (fragment) dönüştürür. Üçgenin kapladığı alan bulunur ve o alanı örten örnek noktalar için fragment’ler üretilir.
Buradaki “fragment” kelimesi kritik: fragment her zaman “nihai piksel” demek değildir. O, bir örnek noktası adayıdır. Sonraki aşamalarda derinlik testi, stencil, blending gibi kurallarla elenebilir ya da mevcut framebuffer rengiyle birleşebilir.
- Tarama (coverage): Üçgenin ekran uzayındaki kapsamı belirlenir ve hangi örnek noktalarının üçgenin içinde kaldığı saptanır. Yani GPU, “bu üçgen bu bölgede hangi piksellere/örneklere dokunuyor?” sorusunu çözer.
- Interpolasyon: Köşelerdeki ara veriler (renk, UV, normal vb.) üçgenin içine yayılır; böylece her fragment kendi giriş değerleriyle gelir. Bu yayılım pratikte barysentrik ağırlıklarla yapılır ve perspektif projeksiyonda çoğu zaman perspective-correct olacak şekilde düzeltilir.
- Derinlik (z) hazırlığı: Rasterization sırasında fragment’in ekrana düşen derinlik değeri de belirlenir; bu değer, biraz sonra depth test’in “önde mi arkada mı?” kararında kullanılır.
Bu aşama, “geometri”nin “piksel dünyası”na geçiş kapısıdır. Sonraki adımda artık renk konuşulur.
Fragment processing (Fragment Shader)
Fragment shader, her fragment için “bu örnek noktası hangi renge dönüşecek?” kararını verir. Aydınlatma, doku okuma ve görsel efektlerin büyük kısmı burada gerçekleşir.
- Hesaplama: Işık/materyal denklemleri ve texture örneklemeleri burada uygulanır.
- Paralellik: Aynı kural, ekrandaki çok büyük sayıda fragment üzerinde paralel yürür; bu yüzden küçük bir GLSL parçası milyonlarca kez çalıştırılır.
- Çıktı: Nihai renk (ve varsa ek render hedeflerine yazılan değerler) üretilir.
Kısa sezgi: vertex shader “nerede?”, fragment shader “ne renk?” sorusunu cevaplar.
Framebuffer (ekrana yazım ve testler)
Shader’lar rengi üretti; fakat görüntü “hemen” ekranda değildir. Son aşamada fragment’ler bazı testlerden geçer ve sonuç framebuffer’a yazılır. Tarayıcı da bu buffer’ı ekrana taşır.
Framebuffer, en sade hâliyle “çıktı hedefi”dir: renk çıktısı nereye yazılacak, derinlik karşılaştırması hangi belleğe bakacak, gerekirse stencil hangi maskeyi uygulayacak? Ekrana çiziyorsanız bu hedef “varsayılan framebuffer”dır; başka bir dokuya çiziyorsanız bu hedef bir render target (FBO) olabilir.
- Derinlik testi (depth test): Önde olan yüzeyin kazanması için fragment’ler z-derinliğine göre elenir. Rasterization’ın ürettiği derinlik değeri, depth buffer’daki değerle karşılaştırılır; testten geçmeyen fragment “yazılmadan” düşer.
- Stencil testi: İsteğe bağlı bir “maske kapısı”dır. Bazı piksellerin yazılmasına izin verir, bazılarını engeller; özellikle kesit/maskeleme gibi efektlerde devreye girer.
- Karışım (blending): Şeffaflık gibi durumlarda yeni renk, mevcut framebuffer rengiyle belirli kurallarla birleştirilebilir. Sezgi: çıktı = kaynak × α + hedef × (1−α) gibi bir birleşim, “üst üste boya” hissini üretir.
- Son durak: Onaylanan pikseller framebuffer’a yazılır ve görüntü “kare” olarak oluşur.
Son adımda tarayıcı, bu kareyi ekrana “sunarken” (present) genellikle çift tamponlu bir akış kullanır: bir buffer ekranda görünürken diğeri bir sonraki kare için doldurulur. Bu yüzden siz “ekrana yazdım” dediğiniz anda, aslında bir sonraki sunum için doğru hedefe yazıyor olursunuz.
Altın ayrım cümlesi: Three.js’te “render” dediğiniz tek bir çağrı, WebGL’de yukarıdaki 6 adımın donanım seviyesinde koşturulmasıdır.
Neden WebGL seviyesinde bilmelisin?
Three.js size genelde “ne çizmek istiyorsun?” diye sorar; pipeline bilgisi ise “nasıl çiziliyor?” kısmını görünür kılar. Görüntü garip görünüyorsa veya beklediğiniz sonucu alamıyorsanız, sorun çoğu zaman pipeline’ın belirli bir halkasında saklıdır.
WebGL seviyesinde bilmek, “hata oldu” anında sihirli bir çözüm üretmekten çok, sorunu doğru sınıfa yerleştirmektir: veri mi yanlış? (attribute/uniform/texture), program mı yanlış? (shader mantığı), yoksa state mi yanlış? (depth/blend/cull gibi testler). Bu ayrım, rastgele deneme-yanılmayı azaltır.
- Geometri yamuksa: Vertex dönüşümleri / matris zinciri /
gl_Positiontarafı şüphelidir. Yanlış eksen, ters ölçek, bozuk perspektif veya titreme gibi semptomlar çoğu zaman “uzay dönüşümü” (MVP) veya attribute bağlama hatasına işaret eder. - Renkler/ışık yanlışsa: Fragment shader mantığı veya doku örnekleme hattı şüphelidir. Yanlış gamma/renk uzayı, ters normal, kaymış UV, banding veya beklenmedik parlama; genelde fragment tarafındaki matematik ya da texture sampling sözleşmesinin bozulduğunu gösterir.
- Ön-arka sırası bozuluyorsa: Depth test, yazım sırası veya blending ayarları şüphelidir. Bir şeyler “arkadan öne geçiyor” gibi görünüyorsa, depth buffer yazımı/testi veya şeffaflık birleşim kuralı (blend) yanlış kurulmuş olabilir.
- Bir şey görünmüyorsa: Clipping/culling, viewport ayarı veya tamamen “state” kaynaklı bir engel olabilir. Örneğin üçgenler ters yönde sayıldığı için backface culling hepsini siliyor olabilir; ya da derinlik testi “hep kaybettiriyordur”.
Buradaki hedef, “optimizasyon listesi” vermek değil; teşhis için bir zihinsel harita kurmaktır.
HoloDepth bakışıyla özet: pipeline’ı bilmek, “sahne”yi değil; sonuç üreten hattı okumaktır. Hattı okuyunca, problemi doğru istasyona götürürsünüz.