holodepth

Shader & GLSL · Sözdizimi & türler

Variables (GLSL değişkenler)

Türü yaz, kapsamı bil — arayüz değişkeni değil

GLSL’de her değişkenin türü kaynakta bellidir; JavaScript’teki let / const esnekliği yoktur. Bu sayfa, shader içinde yerel ve sabit değişkenleri nasıl tanımlayacağınızı anlatır: bildirim, başlangıç değeri, const ve fonksiyon kapsamı.

Uniform, attribute ve varying / in · out arabirimi başka bir katmandır — CPU’dan veya köşe tamponundan gelen «dış veri» sözleşmesidir ( GLSL giriş · depolama, Uniforms & varyings konusu). Burada yalnızca programın kendi hesapladığı ara değerler vardır.

Genel dil çerçevesi ve vertex / fragment rolleri GLSL nedir? sayfasındadır; vec2 / vec3 yapıcıları ve swizzle Vec types konusuna bırakılır. Geometri tarafındaki buffer attribute kavramı Attributes & buffer’lar ile karıştırılmamalıdır — isim benzerliği, farklı katmanlardır.

Shader içinde değişken ne demek?

Bir GLSL programı (vertex veya fragment gövdesi), C’ye yakın sözdiziminde ifadelerden oluşur; ifadelerin çoğu bir türde saklanan değişkenlere dayanır. Değişken, o invokasyonda (bir köşe veya bir fragment adayı) geçici olarak tuttuğunuz skaler, vektör veya matristir — örneğin dönüştürülmüş normal, karıştırılmış renk veya gürültü örneklemesi için ara float.

JavaScript tarafında sahne nesneleri ve uniform köprüleri vardır; GPU tarafında ise derleyici her değişkenin boyutunu ve yaşam süresini statik olarak bilir. Bu yüzden «sonradan başka türe atama» çoğu zaman mümkün değildir; uyumsuzluk derleme aşamasında yakalanır — bu, çalışma anında sürpriz azaltır.

Pratikte shader’daki isimler üç katmana ayrılır: programın kendi hesapladığı yerel ve global değişkenler ile dış dünyadan veya önceki aşamadan gelen arayüz değişkenleri. Aşağıda her katmanın rolü kısaca özetlenir; arayüz ayrıntısı kardeş sayfalara bırakılır.

Yerel değişken

Fonksiyon gövdesi veya { … } bloğu içinde tanımlanır; yalnızca o kapsamda yaşar — main() de bir fonksiyondur, oradaki float t yalnızca o invokasyonda geçerlidir. Vertex shader’da her köşe, fragment’te her aday piksel için ayrı invokasyon düşünüldüğünde yerel değişkenler «o çalıştırma örneğine» özeldir; bir köşede hesaplanan ndotl başka köşenin register’ına taşınmaz.

Efekt yazarken çoğu satır burada kalır: UV ölçekleme, maske, ışık terimi, renk karışımı… Okunabilirlik için uzun ifadeleri parçalayıp ara isim vermek ( Holodepth notu) bu katmandadır. Blok açarsanız (if, for gövdesi), blok içi isim dışarıdan görünmez — aynı ismi iç ve dış blokta farklı türle kullanmak hataya yol açabilir.

Üst düzey (global) değişken

Shader dosyasında, fonksiyonların dışında tanımlanır; dosyadaki tüm fonksiyonlar okuyup yazabilir (paylaşılan sabit veya yardımcı durum). Küçük örneklerde const float PI = 3.14159; gibi sabitleri üst düzeye koymak yaygındır; büyük shader’larda ise «kimin hangi global’i güncellediği» takibi zorlaşır — isim çakışması ve yan etkili yanlış atama riski artar.

Mümkün olduğunda hesabı main veya yardımcı fonksiyon içinde yerel tutun; global yalnızca gerçekten paylaşılan, değişmeyen veya tüm fonksiyonların ihtiyaç duyduğu değerler için kalsın. const global sabitler ( const bölümü) çoğu zaman yeterlidir; her kare değişmesi gereken değer global değil uniform olmalıdır.

Arayüz değişkeni

uniform, attribute, varying veya GLSL ES 3.00’daki in / out anahtar sözcükleriyle bildirilir — bunlar «shader içi hesap» değil, sözleşme katmanıdır: CPU’dan gelen parametreler, köşe tamponları veya vertex → fragment köprüsü. Örneğin uniform float uTime tüm çizim çağrısı boyunca sabittir; attribute vec3 position yalnızca vertex aşamasında köşe başına okunur.

Bu isimler yerel değişkenle karıştırılmamalıdır: Three.js’te BufferGeometry attribute’u ( Attributes & buffer’lar) JavaScript / GPU tampon katmanındadır; shader’daki attribute vec3 aPosition o tamponun vertex shader girişidir. Rol tablosu ve köprü mantığı GLSL giriş · depolama ve Uniforms & varyings konusundadır — burada yalnızca «yerel/global değil, arayüz» ayrımını sabitliyoruz.

Bildirim ve başlangıç değeri

Temel kalıp: tür + isim, isteğe bağlı = başlangıç, sonda ;. Tür yazılmadan değişken tanımlanamaz.

void main() {
    float t = 0.5;
    vec3 baseColor = vec3(0.2, 0.4, 0.9);
    vec2 uvScaled = vUv * 2.0;
    float edge = smoothstep(0.45, 0.55, uvScaled.x);
    gl_FragColor = vec4(baseColor * edge, 1.0);
}

Vektör ve matrisler için yapıcı çağrıları sık kullanılır (vec3(1.0, 0.0, 0.0), mat4(1.0) birim matris). Bileşen erişimi ve swizzle kuralları Vec types konusundadır. Skaler literallerde çoğu bağlamda 1.0 tercih edilir; 1 tek başına int sayılabilir — karışık aritmetikte Operators konusuna bakın.

JavaScript’e alışkın iseniz aşağıdaki üç kural özellikle önemlidir — GLSL derleyicisi türü önceden bilir; «sonra anlarım» diye bırakılan satırlar çoğu zaman kırmızı konsola düşer.

Tip zorunluluğu

var x = 1.0; gibi tür çıkarımı yoktur; her bildirimde tür açık yazılır: float x = 1.0;, vec3 c = vec3(1.0);. Bu, C ve HLSL’e yakın disiplindir; GPU derleyicisi register boyutunu ve opcode seçimini buna göre planlar. JavaScript’teki let / const esnekliği shader gövdesinde karşılık bulmaz — tip güvenli çerçevenin özeti GLSL giriş · tipler bölümündedir; vec yapıcıları Vec types konusuna bırakılır.

Yanlış türle birleştirme (ör. float ile int toplama) çoğu zaman açık dönüşüm veya yapıcı ister; sessiz genişleme beklemeden hatayı okuyun. Literal yazarken 1.0 / 1 ayrımı Operators konusunda netleşir.

Başlatılmamış okuma

float mask; yazıp hemen mask = smoothstep(…); demek güvenlidir; fakat float mask; sonrası doğrudan color *= mask; kullanmak tanımsız davranış (undefined behavior) üretebilir — register’da önceki invokasyondan kalan çöp değer okunabilir. Bazı derleyiciler uyarır, bazıları sessizce geçer; üretim kodunda «muhtemelen sıfırdır» varsaymayın.

Pratik kural: bildirirken mümkünse başlangıç verin (float mask = 0.0;) veya ilk kullanımdan önce tek bir atama yolunun her dallada çalıştığından emin olun. Dalgalı if / else dallarında bir kolun mask’i atamaması, sonraki satırda başlatılmamış okuma riskidir.

Atama ve yeniden bildirim

Aynı kapsamda aynı isimle ikinci kez float foo; yazmak yeniden bildirimdir ve derleme hatası verir — JavaScript’teki gölgeleme ( shadowing) burada genelde izin verilmez. Değeri güncellemek için türü tekrar yazmayın; yalnızca foo = yeniDeger; yeterlidir ( const tanımlılar hariç).

// Hatalı: aynı kapsamda ikinci bildirim
float intensity = 0.5;
float intensity = 1.0;   // redefinition

// Doğru: atama
float intensity = 0.5;
intensity = 1.0;

İç içe blokta dışarıdaki isimle aynı ada sahip yeni bir yerel tanımlamak bazı profillerde iç gölgeleme yapar; okunabilirlik için farklı isim seçmek daha güvenlidir. Üst düzey (global) ile yerel aynı ismi taşıyorsa hangi satırın hangisini kastettiği karışabilir — global bölüm uyarısına bakın.

const · derleme zamanı sabiti

const ile tanımlanan değişken, shader yürütülmeden önce bilinen bir değere kilitlenir; sonradan atama yapılamaz. Büyüme hızı, renk paleti eşiği veya döngü üst sınırı gibi «kod içi sabitler» için okunabilirliği artırır.

const float PI = 3.14159265;
const vec3 SKY = vec3(0.05, 0.08, 0.15);

const ifadesi, optimizasyon sırasında sabit katlanmaya (constant folding) uygun veri üretir; mobilde gereksiz register baskısını hafifletebilir. Dışarıdan her kare değişmesi gereken değerler için const değil uniform kullanılır — ayrım Uniforms & varyings konusunun kalbidir.

Kapsam: global, fonksiyon ve blok

Shader dosyasının en üstünde (precision bildirimleri ve arayüz değişkenlerinden sonra) tanımlanan global değişkenler tüm fonksiyonlardan erişilebilir. Küçük örneklerde cazip olsa da, büyük projede «kim yazdı, kim okuyor?» takibini zorlaştırır; mümkünse hesabı main veya yardımcı fonksiyon içinde yerel tutun.

Fonksiyon parametreleri de yerel kapsamdadır; GLSL’de referans veya «pointer» yoktur — vektör ve matrisler değer semantiğiyle kopyalanır (optimizasyonlar derleyiciye bağlıdır). İç içe blok { … } açarsanız, blok içi değişken dışarıdan görünmez.

Precision bildirimi (kısa not)

Özellikle fragment shader kökünde precision mediump float; gibi satırlar, float değişkenlerin varsayılan hassasiyetini belirler — bu bir değişken tanımı değil, tür için varsayılan kalitedir. Mobil performans / bant sorunlarında önemlidir; tam tablo GLSL giriş · altın kurallar bölümünde özetlenir.

İsimlendirme ve karışıklık önleme

GLSL’de önek kullanmak zorunlu değildir; fakat uTime gördüğünüzde «bu uniform, muhtemelen JS’ten geliyor» demek okumayı hızlandırır. Aşağıdaki dört kalıp, arayüz değişkeni katmanı ile yerel değişken katmanını bir bakışta ayırmanız içindir — tam sözleşme Uniforms & varyings konusunda.

  • u öneki: uniform — çizim çağrısı boyunca sabit kalan parametreler. Örnekler: uTime (animasyon zamanı), uResolution (viewport boyutu), uMouse (fare). Değerler JavaScript tarafında material.uniforms veya ShaderMaterial köprüsüyle her kare veya her çizim öncesi yazılır; köşe başına farklılaşmaz. «Her vertex’e farklı renk» isteği uniform ile çözülmez — attribute veya texture örnekleme gerekir.
  • v öneki: Vertex’ten fragment’e taşınan varying (GLSL ES 3.00’da vertex’te out, fragment’te aynı isimle in). Örnekler: vUv (doku koordinatı), vNormal (dünya veya model uzayında normal). Üçgen içinde enterpolasyon uygulanır — köşe değerleri arasında yumuşak geçiş fragment aşamasında oluşur; gradient ve desenlerde sık görülür.
  • a öneki: Vertex attribute — yalnızca vertex shader girişi; BufferGeometry tampon kanallarından beslenir. Örnek: aPosition ham köşe konumu. Three.js’te MeshStandardMaterial gibi hazır materyaller attribute’ları motor üretir; özel shader’da kanal adlarını siz geometri ile eşleştirirsiniz ( Attributes & buffer’lar).
  • Yerel değişken: Önek zorunlu değil; shader gövdesinde hesaplanan ara değerler. Anlamlı isimler okunurluğu artırır: diffuse (yayılan renk), ndotl (normal · ışık yönü), mask (maskeleme). Bunlar uniform değildir; yalnızca o invokasyonda yaşar — §1’deki yerel katman.

ShaderMaterial veya RawShaderMaterial kullanırken motor bazen modelViewMatrix gibi ek uniform’lar enjekte eder; sizin u* isimlerinizle çakışmaması için tanımları kontrol edin. İsim benzerliği tuzakları: geometry’deki position attribute’u ile shader içindeki yerel vec3 position aynı şey değildir; biri CPU tamponu, diğeri GPU’daki yerel register’dır.

Sık hatalar ve teşhis

Shader hataları çoğu zaman derleme aşamasında kalır; «siyah ekran» gördüğünüzde önce tarayıcı konsolundaki vertex / fragment log’una bakın. Aşağıdaki dört kalıp, bu sayfadaki değişken katmanlarıyla doğrudan ilişkilidir — kök neden çoğu zaman «yanlış tür» veya «yanlış veri katmanı»dır.

  • Tür uyumsuzluğu: float a = 1; bazı profillerde uyarı veya hata üretir; literali 1.0 yazın veya float(1) ile açık dönüşüm yapın. GLSL’de int ile float sessizce birleşmez ( tip zorunluluğu, Operators). JavaScript alışkanlığıyla yazılan satır, shader derleyicisinde ilk kırmızı satır olabilir.
  • Yanlış katman: Köşe başına farklı renk, konum veya ID için uniform yeterli değildir — uniform tüm çizim çağrısında sabittir. Çözüm: attribute (vertex girişi), varying köprüsü veya texture / buffer örnekleme. «u ile her köşeye farklı değer» denemesi bu hataya düşer ( önekler, arayüz katmanı).
  • varying eşleşmesi: Vertex’te out vec2 vUv, fragment’te in vec2 vUv — isim ve tür birebir aynı olmalı; biri vec2 diğeri vec3 ise link hatası alırsınız. Yerel vec2 uv tanımlamak köprü oluşturmaz; varying / in·out sözleşmesi şarttır. Ayrıntı Uniforms & varyings konusunda.
  • GLSL ES 3.00: Eski örneklerde attribute / varying ve gl_FragColor görülür; WebGL2 / Three.js GLSL3 yolunda in / out ve kendi
    out vec4 fragColor tanımınız gerekir. Forumdan kopyalanan shader’ı yapıştırmadan önce hedef sürümü ( GLSL giriş · depolama) ile eşleştirin.

Konsolda vertex ve fragment shader ayrı ayrı raporlanır; ilk hatayı düzeltmeden alttaki «undeclared identifier» zincirine takılmayın — çoğu zaman üstteki noktalı virgül veya yeniden bildirim ( atama vs bildirim) kaynaklıdır. Tek bir uniform’u güncelledim ama görüntü değişmedi ise önce isim eşleşmesi ve güncellemenin çizim çağrısından önce yapıldığını doğrulayın; bu, değişken katmanı değil JavaScript köprüsü sorunudur.

Holodepth notu

Yeni bir efekt yazarken önce girdileri (uniform / varying) ve çıktıyı (gl_FragColor veya out vec4) sabitleyin; aradaki her adımı küçük yerel değişkenlere bölün. Tek satırlık dev mix(mix(…)) zincirleri yerine float mask = …; gibi ara isimler, hem hatayı bulmayı hem de sonraki built-in birleştirmelerini kolaylaştırır.