Three.js · İleri konular · Shader yazarı perspektifi
Shader mantığı ve veri akış hattı
Özel shader yazarken elinizdeki «fabrika» değil, iki istasyon arasında akan bir sözleşme vardır: köşe başına çalışan vertex programı ve piksel adayı başına çalışan fragment programı. Bu sayfa, hattın tamamını yeniden anlatmak yerine — bunun için Render Pipeline: bir kare nasıl doğar? sayfasına yönlendirir — verinin bu iki istasyonda kimden geldiğini, nerede dönüştüğünü ve aradaki köprüde hangi varsayımlara güvendiğinizi sabitlemenize odaklanır.
GLSL sayfası dilbilgisini ve
uniform / attribute / varying ayrımını açar; burada
ise
aynı kavramları akış hikâyesi olarak okuyacaksınız: «Bu değer neden
vertex’te
doğdu, fragment’te neden farklı bir tonda kullanılabiliyor?» sorusunun cevabı, çoğu zaman
rasterizasyonun araya girmesindedir.
Veri paketi: CPU’dan GPU’ya hiyerarşi
Three.js tarafında bir mesh çizilirken, sizin shader’ınızın «gördüğü» dünya soyut bir bellek düzenidir: köşe tamponları, çağrı başına sabit parametreler ve iki aşama arasında taşınması üzere anlaşılmış ara kanallar. Bunları üç başlıkta paketlemek, kodu okurken zihninizi yormaz; üstüne bir de şu CPU zamanlamasını ekleyin: JavaScript çoğu zaman kare başında uniform’ları günceller, geometriyi ve sahneyi düzenler; GPU ise çizim komutu işlendiğinde tamponları okur ve programı yürütür. İki taraf aynı «an»ı paylaşmıyormuş gibi düşünmek, senkronizasyon ve sürüm hatalarını daha hızlı yakalamanızı sağlar.
Aşağıdaki üçlü, GLSL · Depolama ve arabirim başlığındaki dilbilgisini akış paketine çevirir; orada anlatılan WebGL 1 / 2 sözdizimi farkları ve isim eşleşmesi burada tekrarlanmaz — burada yalnızca «bu paket hangi istasyonda açılır?» sorusu ön plandadır.
Attributes · köşe tamponundan gelen satırlar
Her köşeye özel alanlar — konum, UV, normal, özel kanallar — vertex
invokasyonu başına bir kez okunur. Bunları JavaScript ile «her karede
tek tek yazıyorum» hissi, çoğu zaman BufferGeometry üzerinde
setAttribute / önbellek güncellemesi ile yapılır; yani veri hâlâ tampondadır,
sadece içeriği animasyon veya simülasyonla değişir. Örnekleme (instancing) kullanıyorsanız, aynı geometri satırını tekrarlarken köşeye
ekstra «örnek kimliği» sütunları eklersiniz — bu da vertex girişidir; uniform
dizisi gibi düşünmek genelde yanlış ölçeklenmeye gider.
Uniforms · çağrı anına kilitlenen ortak parametreler
Aynı çizim çağrısı içinde tüm köşeler ve ondan doğan fragment’ler için ortak kalan değerler
—
zaman, ışık rengi, kamera / model matrisleri, malzeme katsayıları — tipik olarak uniform olarak gelir. Three.js’te bunların çoğu
material.uniforms
sözlüğünde yaşar; sahne genelinde paylaşılanlar ise bazen renderer veya özel yöneticilerle
beslenir. «Köşeden köşeye farklı olsun» isteğini buraya tıkıştırmak, çoğu zaman yanlış
çekmeceyi
açar: ihtiyaç attribute, örnekleme verisi veya ekstra bir çizim çağrısıdır — aksi halde tüm
köşelere aynı sayıyı zorlamış olursunuz.
Varyings / in · out · iki program arasındaki isimli hat
Vertex çıktısı ile fragment girdisi aynı «iş paketi» içinde eşleşir; klasik sürümde varying, modern sürümde out/in çiftleriyle
kurulur. Üçgen içinde bu değerler donanım tarafından çoğu kanal için enterpolasyonla iç
bölgelere taşınır — fragment tarafında «köşede ne vardı?» sorusunun cevabı, tek köşe değil,
üç köşenin karışımıdır. flat gibi niteliklerle bu yumuşamayı kasıtlı olarak
kesmek mümkündür; ayrıntılı enterpolasyon düşüncesi bu sayfada
Rasterizasyon köprüsünde
derinleşir, burada yalnızca «paketin içinde neler var?» listesi yeterlidir.
Bu hiyerarşi, Render pipeline · üç ana blok metnindeki «uygulama / geometri / piksel» ayrımını kod yazarken kullanacağınız sözlük haline getirir; orada anlatılan draw call, frustum culling ve tampon politikaları gibi konulara burada tekrar girilmez — zira amaç, makro haritayı değil iki shader arasındaki mesaj trafiğini netleştirmektir.
Vertex shader: «Neredeyim?»
Vertex aşamasının görevi, sahne hikâyesini clip uzayına yakın bir temsilde
toplamak
ve bunu gl_Position ile sabitlemektir. Zihinsel soru: «Bu köşe, kamera ve
projeksiyon
gözünden ekranın hangi tarafına düşecek?» Dönüşüm zinciri — yerel → dünya → görüntü →
projeksiyon
— burada işletilir; hangi matrisin motor tarafından hangi uniform
adıyla
geldiği projeden projeye değişse de sıranın matematiksel anlamı aynı kalır.
Bu aşamada henüz «ekrandaki nihai renk» yoktur; üretilen şey, rasterizasyonun tüketeceği konum + vertex’ten fragment’e aktarılacak ara alanlardır. Işıklandırmanın bir kısmını vertex’te erken yakalamak mümkündür; fakat detaylı gölge–doku yüzeyi çoğu zaman fragment bütçesinde kalır — aksi halde köşe seyreltmesinden kaynaklanan yumuşaklık kaybı veya tersine aşırı maliyet ortaya çıkar.
Rasterizasyon: görünmez köprü
Vertex bittiğinde donanım, primitifleri üçgenlere bağlar ve bu üçgenlerin kapladığı ızgarayı fragment adaylarına böler. Bu köprüyü siz GLSL ile satır satır yazmazsınız; fakat enterpolasyon varsayımına güvenirsiniz: vertex’ten çıkan bir varying, fragment’te artık «köşe değeri» değil, üçgen içinde konuma göre karışmış bir değerdir. Bu yüzden aynı kodu vertex ve fragment’ta çalıştırdığınızda sonuçların farklılaşması şaşırtıcı değildir — veri aynı adda olsa bile anlamı değişmiştir.
Pipeline haritasında bu köprünün nereye oturduğunu Rasterizasyon ve fragment alt başlığından okuyabilirsiniz; burada yalnızca shader yazarı için kritik çıkarım durur: fragment’e taşıdığınız her vektörün, üçgen üzerinde nasıl yumuşatılacağını tasarımınızda hesaba katın.
Fragment shader: «Hangi renkteyim?»
Fragment aşaması, aday piksel başına çalışan «boyahane»dir: doku okumaları, ışık terimleri,
renk
karışımları ve çoklu hedeflere yazma burada yoğunlaşır. Çıktı olarak klasik örneklerde gl_FragColor görürsünüz; WebGL 2 /
GLSL ES 3.00 yolunda ise çoğu zaman kendi tanımladığınız
out vec4 değişkenine yazarsınız. Hangi sözdiziminin
geçerli
olduğu, bağlam ve Three.js’in ürettiği öneklere bağlıdır; ayrıntı için
GLSL · Depolama ve
arabirim
ile
İlk fragment örneği
bölümüne
dönmek yeterlidir — burada tekrar etmeyiz.
Fragment çıktısı, ekranda gördüğünüz piksel ile bire bir aynı şey olmak zorunda değildir: derinlik testi, karıştırma ve çok örnekleme gibi adımlar hâlâ arkada çalışır. Shader mantığını «nihai piksel = fragment sonucu» diye kilitlemek, hata ayıklarken yanıltıcı olabilir; doğru soru: «Bu aday, tamponlara yazılmadan önce hangi testlerden geçecek?» — tam cevap yine makro pipeline metnindedir; burada yalnızca renk adayını kim üretti? sorusunun adresi fragment’tir.
Holodepth okuma sırası
Önce bu sayfayı (veri akışı ve iki istasyon), ardından GLSL (dil ve depolama), en sonda Render pipeline (karenin tam fabrikası) ile tamamlayın — böylece aynı kavram üç kez değil, üç katmanda birbirini tamamlayan şekilde oturur.