holodepth

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.