holodepth

Shader & GLSL · Sözdizimi & türler

Operators (GLSL operatörleri)

Türler neyi toplar, neyi çarpar — derleyici önce bunu bilir

GLSL’de operatörler yalnızca «matematik sembolü» değildir; her ifadenin sonuç türünü ve geçerliliğini belirler. vec3 + float ile vec3 + vec2 aynı + işaretini kullanır; biri çoğu profilde geçer, diğeri derleme hatasıdır. Bu sayfa, sözdizimi ve türler serisinde «işlemlerin kuralları» katmanıdır.

Değişken bildirimi ve kapsam Variables; vec2 / vec3 paketleri, swizzle ve broadcast özeti Vec types konusundadır — burada vektör–matris çarpımına kısa köprü verilir, ayrıntı Vec types · mat4 × vec4 bölümünde kalır. Yerleşik fonksiyonlar (dot, mix …) Built-in functions sayfasına bırakılır.

Genel dil çerçevesi GLSL giriş · tipler sayfasındadır; JavaScript’teki gevşek birleştirme alışkanlığı ( 1 + 0.5 sorunsuz ) burada çoğu zaman geçmez — tip zorunluluğu ile birlikte düşünün.

Neden operatör kuralları?

JavaScript tarafında bir ifade «çalışma anında» şekillenir; GLSL’de ise vertex veya fragment programı çoğu zaman önce derlenir, sonra GPU’da binlerce kez çalıştırılır. Bu bölüm, aşağıdaki aritmetik ve matris bölümlerine geçmeden önce «operatör neden ayrı bir konu?» sorusuna kısa cevap verir.

Derleme zamanı ve tür çözümü

Shader derleyicisi her satırı çalıştırmadan önce türleri çözer. Operatör, «bu iki ifade birleştirilebilir mi ve sonuç ne tür olur?» sorusunun cevabıdır — örneğin vec3 + float çoğu profilde geçerken vec3 + vec2 reddedilir ( Vec types · boyut uyumu).

Bu yüzden hata çoğu zaman tarayıcı konsolunda değil, shader derleme aşamasında görünür. Özellikle iki tuzak öne çıkar: boyut uyumsuzluğu ( mat4 * vec3 gibi) ve int / float karışımı — JavaScript’te sorunsuz görünen 1 + 0.5 alışkanlığı GLSL’de literallerde 1.0 veya açık dönüşüm ister ( tip zorunluluğu, §6 · sık hatalar).

Üç operatör ailesi

Pratikte shader kodunun büyük kısmı üç infix ailesiyle yazılır; geri kalanı yerleşik fonksiyon çağrılarıdır (sin, texture2D, dot …). Aşağıdaki özet, sayfanın geri kalanına yol haritasıdır — her madde ilgili bölümde açılır, burada yalnızca «hangi işaret ne işe yarar?» sorusuna cevap verilir.

  • Aritmetik (+, -, *, /): Skalerde C’ye benzer toplama ve çarpma; vektörlerde çoğu zaman bileşen bazlı birleşim. vec3 + float gibi broadcast örnekleri bu ailenin parçasıdır — renk tonlama, UV ölçekleme, maske ile çarpma. vec3 * vec3 ise matris çarpımı değil, kanal kanal çarpımdır; iç çarpım için dot ayrı sözdizimidir. Ayrıntı §2 · Aritmetik.
  • Karşılaştırma / mantık (==, !=, &&, || …): «Koşul sağlanıyor mu?» sorusu; çıktı çoğu zaman skaler bool veya 0/1 maske. Fragment shader’da koşullu renk için if yerine mix / step tercih edilmesinin nedeni bu katmandadır — operatör koşulu üretir, Built-in fonksiyonları koşulu renge çevirir. Ayrıntı §3 · Karşılaştırma ve mantık.
  • Matris–vektör çarpımı
    ( mat4 * vec4, mat3 * vec3 ): Vertex’ta konum ve normal taşıma; lineer dönüşüm. Aynı * sembolü olsa da vec4 * vec4 bileşen çarpımından farklı anlama gelir — boyut eşleşmesi zorunludur ( mat4 yalnızca vec4 ile). Dönüşüm zinciri ve normalMatrix özeti §5 ile Vec types · matris konusundadır.

Bu üç aile dışındaki işlemler parantez ve öncelikle birleştirilir ( §4). Tam yerleşik API Built-in functions sayfasına bırakılır — bu sayfa bilinçli olarak infix operatör sözdizimine odaklanır.

Aritmetik: +, -, *, /

vec2 uv2 = vUv * 2.0;
vec3 tinted = baseColor + vec3(0.1);
vec3 scaled = normal * uBumpScale;
float ratio = color.r / max(color.g, 1e-4);

Skalerlerde aritmetik C’ye benzer. Vektörlerde ise çoğu işlem bileşen bazında uygulanır: vec3(1,2,3) + vec3(4,5,6)vec3(5,7,9). Çarpma ve bölme de aynı mantıkla kanal kanal yapılır — matris çarpımı değildir ( §5).

Broadcast: vektör + skaler

vec3 + float ve vec3 * float çoğu profilde geçerlidir: skaler her bileşene uygulanır (broadcast). Örnek: position + uOffsetuOffset tek sayıysa üç eksene eklenir. Aynı fikir Vec types · boyut uyumu bölümünde paket mantığıyla anlatılmıştı; burada operatör tarafı netleşir.

vec3 + vec2 gibi farklı boyutlu vektör toplamı çoğu zaman derleme hatasıdır. Gerekirse swizzle veya yapıcı ile boyutu eşitleyin ( yapıcılar).

Bileşen çarpımı vs matris çarpımı

vec3 * vec3 çoğu shader’da Hadamard (bileşen çarpımı) üretir — renk maskeleme veya kanal bazlı modülasyon için kullanılır. İç çarpım için dot(a,b) yerleşiğini kullanın ( Vec types · dot), matris dönüşümü için mat * vec ( §5).

Karşılaştırma ve mantık

Aritmetik operatörler «değeri hesaplar»; karşılaştırma ve mantık operatörleri «koşul doğru mu?» sorusuna cevap verir. Fragment shader’da bu cevap çoğu zaman doğrudan renge bağlanır — maske, kenar çizgisi veya iki malzeme arasında geçiş. Bu bölüm, §2’deki toplama/çarpmanın ötesinde koşullu ifade katmanını tanıtır; ayrıntılı fonksiyon listesi Built-in sayfasına bırakılır.

Karşılaştırma: skaler ve maske

Karşılaştırma operatörleri (==, !=, <, <=, >, >=) skalerde bool üretir — bazı GLSL profillerinde sonuç doğrudan 0.0 / 1.0 «maske» gibi de kullanılır. Örneğin float inside = step(0.0, uTime - 1.0); düşüncesine yakın: eşik geçildi mi?

Karşılaştırma, aritmetikten daha düşük öncelikli sayılır; karmaşık ifadelerde parantez kullanın ( §4 · öncelik). Örnek: vUv.x > 0.5 tek bir koşul üretir; sonucu renkle birleştirmek ayrı adımdır.

Vektörlerde karşılaştırma

vec3 == vec3 her profilde «üç bileşen de eşit mi?» anlamında tek bir bool vermeyebilir — bazen bileşen bazında vektör maskesi, bazen derleme uyarısı veya hata görülür. «Hepsi birden» veya «herhangi biri» testleri için genelde yerleşikler ( equal, lessThan …) veya bileşen bazlı mantık ( color.r == color.g gibi skaler parçalar) tercih edilir.

Pratik kural: karşılaştırmayı mümkün olduğunca skaler kanala indirgemek — UV bandı için vUv.y, alpha testi için texel.a. Vektör paketinin kendisiyle «eşitlik» aramak yerine swizzle ile tek bileşen seçin ( Vec types · bileşen erişimi).

Mantık: &&, || ve if yerine mix

Mantıksal birleştirme: &&, ||, !. İki koşulu birleştirip tek maske üretmek mümkündür; fragment shader’da ise if ile dallanmak her zaman ucuz değildir — GPU dal başına farklı yürütme yolları oluşturabilir.

Bu yüzden koşullu renk çoğu zaman aritmetik ile yazılır: mix(colorA, colorB, mask), step(edge, vUv.x) veya smoothstep(0.2, 0.8, vUv.y). Operatör bilgisi, Built-in sayfasına geçerken «neden if yerine mix?» sorusuna zemin hazırlar — mix aslında §2’deki aritmetik ile aynı düşüncenin devamıdır ( Vec types · mix).

float band = step(0.5, vUv.x);
vec3 col = mix(vec3(0.1), vec3(0.9), band);
// if (vUv.x > 0.5) col = ...;  // çoğu efektte mix tercih edilir

Tam step / smoothstep imzaları ve diğer yardımcılar Built-in functions konusundadır; burada yalnızca karşılaştırma + mantığın shader stiline nasıl dönüştüğünü sabitliyoruz.

Öncelik ve parantez

Operatör önceliği, aynı satırda birden fazla işaret varken «hangisi önce uygulanır?» sorusunu cevaplar. GLSL, C ve matematikteki alışkanlığa büyük ölçüde uyar; yine de shader satırları swizzle ve fonksiyon çağrılarıyla hızla karmaşıklaşır — bu bölüm §2–3’teki kuralları okunabilir ifade yazımına bağlar.

Aritmetik sırası

Çarpma ve bölme, toplama ve çıkarmadan önce gelir. a + b * c ifadesinde önce b * c, sonra a ile toplanır; farklı bir sıra istiyorsanız parantez şarttır: (a + b) * c. Vektörlerde de aynı kural geçerlidir — baseColor + mask * highlight önce maskeyle çarpılmış vurguyu, sonra taban renge ekler ( §2 · aritmetik).

Karşılaştırma ve mantık

Karşılaştırma (<, == …) ve mantıksal birleştirme ( &&, ||) genelde aritmetikten daha düşük önceliklidir. Örneğin a + b > c okunurken önce a + b hesaplanır, sonra c ile karşılaştırılır — bu, §3’teki maske üretiminin temelidir.

Karmaşık koşullarda parantez ile niyeti yazın: (ndotl > 0.0) && (shadow < 0.5). Matris çarpımı ( §5) kendi gruplamasına sahiptir;
mat4 * vec4 tek bir «yüksek öncelikli» birleşim gibi düşünülür — yine de uzun zincirlerde ara isim kullanmak hatayı azaltır.

Swizzle ve parantez

Swizzle, nokta ile bağlandığı için çoğu zaman fonksiyon çağrısından sonra «yapışık» okunur; şüphe duyduğunuz her yerde parantez kullanın — özellikle swizzle ile aritmetik karışınca:

vec3 c = baseColor * (0.5 + 0.5 * sin(uTime));
float edge = smoothstep(0.4, 0.6, vUv.y) * mask.r;
vec2 uvFlip = (vUv * 2.0 - 1.0).yx;  // önce ölçek, sonra ters UV

vUv.yx * 2.0 ile (vUv * 2.0).yx farklı sonuç verebilir; swizzle kuralları Vec types · swizzle konusundadır — burada yalnızca «parantez, hangi adımın önce olduğunu sabitler» notunu düşünün.

Ara değişken: en güvenli parantez

Uzun tek satırlı ifadeler hem okunurluğu düşürür hem yanlış öncelik hatasına açıktır. İfadeyi parçalayıp yerel isim vermek, görünür parantez gibidir:

float wave = 0.5 + 0.5 * sin(uTime);
vec3 c = baseColor * wave;
float edge = smoothstep(0.4, 0.6, vUv.y);
vec3 finalCol = mix(c, highlight, edge * mask.r);

Bu kalıp yerel değişken ve Holodepth notundaki «ara vektör» önerisiyle uyumludur — derleyici için sonuç aynı kalabilir, insan okuyucusu için sıra netleşir.

Matris × vektör

mat4 * vec4 lineer dönüşümün standart yazımıdır — vertex shader’da gl_Position zinciri bununla kurulur. Bu, bileşen bazlı vec4 * vec4 değildir; matris sütunları vektörle çarpılır ( Vec types · dönüşüm zinciri).

gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
vec3 n = normalize(normalMatrix * normal);

Boyut uyumu: mat4 yalnızca vec4 ile; mat3 yalnızca vec3 ile. Matris tanımları ve column-major notu GLSL giriş · matris konusundadır — bu sayfa operatör sözdizimini sabitler.

Sık derleme hataları

Bu bölüm, sayfa boyunca anlatılan kuralların derleyicide nasıl «kırmızı satır»a döndüğünü toplar. Çoğu hata mantık hatası değil, operand türü uyumsuzluğudur — konsol mesajında operatörü görürseniz bile önce sol ve sağ tarafın türüne bakın ( §1 · derleme zamanı).

  • int + float: JavaScript’te 1 + 0.5 sorunsuz; GLSL’de çoğu zaman açık dönüşüm gerekir — float(i) veya literallerde 1.0 yazın. Döngü sayacı int iken zaman float ise çarpım öncesi dönüştürün; bu, Variables konusundaki tip zorunluluğu ile aynı disiplindir — operatör satırı değil, bildirim tarafı da kontrol edilmeli.
  • Boyut uyumsuzluğu: vec3 + vec2 veya mat4 * vec3 çoğu profilde reddedilir. Çözüm: yapıcı veya swizzle ile boyutu eşitleyin — örneğin konum için vec4(position, 1.0) ( §2 · broadcast, §5 · matris, Vec types · boyut uyumu). «Aynı * işareti» her zaman aynı anlama gelmez.
  • Swizzle atama: vColor.xyx = … gibi okuma serbest, yazmada çakışan kanallar yasaktır — hangi bileşene yazılacağı belirsizleşir. Okuma tarafında vec3 v = c.xyx; geçerli olabilir; atama kuralları Vec types · swizzle atama konusunda — burada yalnızca «sol taraf swizzle’ı da denetlenir» notu.
  • Matris sırası: vec4 * mat4 ile mat4 * vec4 farklı sonuç verir; Three.js’te projectionMatrix * modelViewMatrix * vec4(position, 1.0) sırası column-major düzenle uyumludur. Matrisi JavaScript’ten elle kopyalarken satır/sütun karışıklığı bu hatayı üretir — operatör doğru görünür, bellek düzeni yanlıştır ( Vec types · column-major).

Hata ayıklarken ifadeyi ara değişkenlere bölün ( §4 · ara değişken) — hangi satırın derlemeyi kırdığı hemen görünür. Holodepth notundaki «önce sonuç türü» alışkanlığı bu listeyle birlikte düşünülmelidir.

Holodepth notu

Yeni bir satır yazarken önce «sonuç türü ne?» diye sorun: vec2 uv2 = vUv * 2.0; ardından float band = smoothstep(0.2, 0.8, uv2.y); — her adımda tür net kalır. Kırmızı derleyici satırında operatörü değil, çoğu zaman sol ve sağ operand türlerini kontrol edin.