Interaktif ders / Etüt
WGSL Etütleri
Bu seri pikselle düşünmeyi öğretir: önce düz renk ve UV, sonra zaman, SDF, smoothstep, procedural noise, dönüşümler, raymarching ve compute tarafı. Her aşama kendi WGSL koduyla Studio'da açılabilir.
01 / fragment · vec4
Renk
Bir fragment shader'ın tek görevi var:
ekrandaki her piksel için bir vec4 renk döndürmek
(r, g, b, a). Burada hangi piksel olduğu önemli değil —
hepsi aynı rengi alıyor.
0.0–1.0).
vec4<f32>(1.0, 0.0, 0.5, 1.0) magenta yapar.// Aşama 01 — Düz renk
// Bir fragment shader, her piksel için tek bir vec4 döndürür.
@vertex
fn vs(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4<f32> {
// Tek bir üçgenle ekranı kapla (fullscreen triangle trick)
let p = vec2<f32>(f32((vi << 1u) & 2u), f32(vi & 2u));
return vec4<f32>(p * 2.0 - 1.0, 0.0, 1.0);
}
@fragment
fn fs() -> @location(0) vec4<f32> {
// r, g, b, a — değerleri 0..1 arasında
return vec4<f32>(0.13, 0.83, 0.93, 1.0);
}
02 / position · gradient
UV koordinatları
Fragment shader'a @builtin(position) gelirse her pikselin
nerede olduğunu öğreniriz. Çözünürlüğe bölünce UV
elde edilir: sol üst (0,0), sağ alt (1,1).
UV'yi doğrudan renge yazınca yatay/dikey gradyan çıkar.
uv.x ve uv.y'yi
kanallar arasında değiş, 0.5 sabitini animate etmek için
u.time kullan: 0.5 + 0.5 * sin(u.time).// Aşama 02 — UV koordinatları
// frag.xy / u.resolution → 0..1 aralığında konum.
struct U {
resolution: vec2<f32>,
time: f32,
};
@group(0) @binding(0) var<uniform> u: U;
@vertex
fn vs(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4<f32> {
let p = vec2<f32>(f32((vi << 1u) & 2u), f32(vi & 2u));
return vec4<f32>(p * 2.0 - 1.0, 0.0, 1.0);
}
@fragment
fn fs(@builtin(position) frag: vec4<f32>) -> @location(0) vec4<f32> {
let uv = frag.xy / u.resolution;
return vec4<f32>(uv.x, uv.y, 0.5, 1.0);
}
03 / uniform · animate
Zaman
u.time uniform'ı CPU'dan her frame yenileniyor.
Bunu sin/cos ile birleştirince animasyon çıkar.
Aynı shader saniyede 60 kez yeniden çalışıyor;
değişen tek şey time değeri.
* 3.14'ü
* 8.0 yap), ya da u.time'a çarpan ekle:
sin(u.time * 4.0 + ...).// Aşama 03 — Zaman ile animasyon
// u.time saniye cinsinden, her frame artar.
struct U {
resolution: vec2<f32>,
time: f32,
};
@group(0) @binding(0) var<uniform> u: U;
@vertex
fn vs(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4<f32> {
let p = vec2<f32>(f32((vi << 1u) & 2u), f32(vi & 2u));
return vec4<f32>(p * 2.0 - 1.0, 0.0, 1.0);
}
@fragment
fn fs(@builtin(position) frag: vec4<f32>) -> @location(0) vec4<f32> {
let uv = frag.xy / u.resolution;
let r = 0.5 + 0.5 * sin(u.time + uv.x * 3.14);
let g = 0.5 + 0.5 * sin(u.time * 1.3 + uv.y * 3.14);
let b = 0.85;
return vec4<f32>(r * 0.3, g, b, 1.0);
}
04 / length · step
Mesafe alanları (SDF)
Geometriyi çizmek yerine mesafeyle tarif ediyoruz.
length(uv - center) bir noktanın merkeze uzaklığını verir;
- r dersen şekil içeride negatif, kenarda 0,
dışarıda pozitif olur. step() ile bunu sert bir
iç/dış maskesine çevirebilirsin.
let r = 0.30 + 0.10 * sin(u.time * 2.0);.
Karenin SDF'i: max(abs(uv.x), abs(uv.y)) - r.// Aşama 04 — Signed Distance Field
// Daireyi mesafe fonksiyonuyla çiz.
struct U {
resolution: vec2<f32>,
time: f32,
};
@group(0) @binding(0) var<uniform> u: U;
@vertex
fn vs(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4<f32> {
let p = vec2<f32>(f32((vi << 1u) & 2u), f32(vi & 2u));
return vec4<f32>(p * 2.0 - 1.0, 0.0, 1.0);
}
@fragment
fn fs(@builtin(position) frag: vec4<f32>) -> @location(0) vec4<f32> {
// Aspect-correct UV, merkez (0, 0)
let aspect = u.resolution.x / u.resolution.y;
let uv = (frag.xy / u.resolution - 0.5) * vec2<f32>(aspect, 1.0);
let r = 0.30;
let d = length(uv) - r; // negatif: içeride · pozitif: dışarıda
let inside = step(d, 0.0); // 1.0 içerde, 0.0 dışarıda
let bg = vec3<f32>(0.05, 0.07, 0.10);
let fg = vec3<f32>(0.13, 0.83, 0.93);
return vec4<f32>(mix(bg, fg, inside), 1.0);
}
05 / smoothstep · edge
Smoothstep / AA
step() sert bir 0/1 keser — kenarda piksel piksel zigzag
olur. Smoothstep ise iki eşik arası yumuşak bir geçiş
döndürür. Eşikleri piksel boyutuna bağlarsan
(1.0 / resolution.y), her ekran çözünürlüğünde
tek piksellik bir anti-alias çıkar.
aa değerini 0.05 yap —
daire bulanıklaşır (yumuşak gölge etkisi). 0.0 yap, kenar
titremeli geri döner.// Aşama 05 — Smoothstep ile anti-aliasing
// Eşik genişliğini piksel boyutuna bağla, her çözünürlükte temiz kenar.
struct U {
resolution: vec2<f32>,
time: f32,
};
@group(0) @binding(0) var<uniform> u: U;
@vertex
fn vs(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4<f32> {
let p = vec2<f32>(f32((vi << 1u) & 2u), f32(vi & 2u));
return vec4<f32>(p * 2.0 - 1.0, 0.0, 1.0);
}
@fragment
fn fs(@builtin(position) frag: vec4<f32>) -> @location(0) vec4<f32> {
let aspect = u.resolution.x / u.resolution.y;
let uv = (frag.xy / u.resolution - 0.5) * vec2<f32>(aspect, 1.0);
let r = 0.30 + 0.05 * sin(u.time * 1.5);
let d = length(uv) - r;
// 1.5 piksellik yumuşatma bandı
let aa = 1.5 / u.resolution.y;
let inside = 1.0 - smoothstep(-aa, aa, d);
let bg = vec3<f32>(0.05, 0.07, 0.10);
let fg = vec3<f32>(0.13, 0.83, 0.93);
return vec4<f32>(mix(bg, fg, inside), 1.0);
}
06 / fract · hash
Procedural noise
WGSL'de hazır random() yok — kendin
deterministik bir hash yazarsın. sin · dot
kombinasyonu klasik trick: aynı girdiye her zaman aynı pseudo-random
çıktıyı verir. Komşu noktalara mix'lersen value noise
olur — doku, bulut, zemin için temel.
uv * 8.0'i * 24.0
yap — daha ince doku. Birkaç oktav topla (FBM):
noise(uv*4.0)*0.5 + noise(uv*8.0)*0.25 + ....// Aşama 06 — Value noise
// hash → komşu interpolation → smooth pseudo-random doku
struct U {
resolution: vec2<f32>,
time: f32,
};
@group(0) @binding(0) var<uniform> u: U;
fn hash(p: vec2<f32>) -> f32 {
return fract(sin(dot(p, vec2<f32>(12.9898, 78.233))) * 43758.5453);
}
fn noise(p: vec2<f32>) -> f32 {
let i = floor(p);
let f = fract(p);
// Hermite easing
let s = f * f * (3.0 - 2.0 * f);
let a = hash(i + vec2<f32>(0.0, 0.0));
let b = hash(i + vec2<f32>(1.0, 0.0));
let c = hash(i + vec2<f32>(0.0, 1.0));
let d = hash(i + vec2<f32>(1.0, 1.0));
return mix(mix(a, b, s.x), mix(c, d, s.x), s.y);
}
@vertex
fn vs(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4<f32> {
let p = vec2<f32>(f32((vi << 1u) & 2u), f32(vi & 2u));
return vec4<f32>(p * 2.0 - 1.0, 0.0, 1.0);
}
@fragment
fn fs(@builtin(position) frag: vec4<f32>) -> @location(0) vec4<f32> {
let uv = frag.xy / u.resolution;
let n = noise(uv * 8.0 + vec2<f32>(u.time * 0.3, u.time * 0.15));
let col = mix(
vec3<f32>(0.04, 0.10, 0.15),
vec3<f32>(0.13, 0.83, 0.93),
n
);
return vec4<f32>(col, 1.0);
}
07 / mat2x2 · rotate
Matris dönüşümleri
WGSL'de matris çarpımı yerleşik bir operatör:
mat2x2 * vec2 = vec2. Burada her piksel kendi UV'sini
2D dönme matrisiyle çarpıyor — sonuç: tüm sahne dönmüş
gibi görünüyor. Ama dönen sahne değil, koordinat sistemi.
Aynı mantıkla mat3x3 3D rotate, mat4x4 projeksiyon.
let S = mat2x2<f32>(1.5, 0.0, 0.0, 1.5); sonra
uv = S * R * uv;. Sıralama önemli (önce dön, sonra ölç).// Aşama 07 — Matris dönüşümleri
// 2x2 dönme matrisiyle UV'yi döndür.
struct U {
resolution: vec2<f32>,
time: f32,
};
@group(0) @binding(0) var<uniform> u: U;
@vertex
fn vs(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4<f32> {
let p = vec2<f32>(f32((vi << 1u) & 2u), f32(vi & 2u));
return vec4<f32>(p * 2.0 - 1.0, 0.0, 1.0);
}
@fragment
fn fs(@builtin(position) frag: vec4<f32>) -> @location(0) vec4<f32> {
let aspect = u.resolution.x / u.resolution.y;
var uv = (frag.xy / u.resolution - 0.5) * vec2<f32>(aspect, 1.0);
// 2D dönme matrisi (radyan cinsinden)
let a = u.time * 0.5;
let R = mat2x2<f32>(
cos(a), -sin(a),
sin(a), cos(a)
);
uv = R * uv;
// Kare SDF
let r = 0.25;
let d = max(abs(uv.x), abs(uv.y)) - r;
let aa = 1.5 / u.resolution.y;
let inside = 1.0 - smoothstep(-aa, aa, d);
let bg = vec3<f32>(0.05, 0.07, 0.10);
let fg = vec3<f32>(0.13, 0.83, 0.93);
return vec4<f32>(mix(bg, fg, inside), 1.0);
}
08 / smin · union
SDF kompozisyonu
İki SDF'i birleştirmek operatörlerle yapılır:
min(a, b) = union (sert kesişim),
max(a, b) = intersection,
max(a, -b) = subtraction. smin ("smooth min")
ise yumuşak birleşim — tipik metaball efekti, lav lambası,
organik şekiller.
smin'in k'sını
0.30 yap, daha akışkan birleşim. Üçüncü daire ekle.
max'a çevir: kesişim.// Aşama 08 — SDF kompozisyonu
// İki dairenin smooth-min ile yumuşak birleşmesi (metaball).
struct U {
resolution: vec2<f32>,
time: f32,
};
@group(0) @binding(0) var<uniform> u: U;
// Smooth minimum
fn smin(a: f32, b: f32, k: f32) -> f32 {
let h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);
return mix(b, a, h) - k * h * (1.0 - h);
}
@vertex
fn vs(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4<f32> {
let p = vec2<f32>(f32((vi << 1u) & 2u), f32(vi & 2u));
return vec4<f32>(p * 2.0 - 1.0, 0.0, 1.0);
}
@fragment
fn fs(@builtin(position) frag: vec4<f32>) -> @location(0) vec4<f32> {
let aspect = u.resolution.x / u.resolution.y;
let uv = (frag.xy / u.resolution - 0.5) * vec2<f32>(aspect, 1.0);
let p1 = vec2<f32>( 0.25 * cos(u.time), 0.0);
let p2 = vec2<f32>(-0.25 * cos(u.time), 0.0);
let d1 = length(uv - p1) - 0.18;
let d2 = length(uv - p2) - 0.18;
let d = smin(d1, d2, 0.12);
let aa = 1.5 / u.resolution.y;
let inside = 1.0 - smoothstep(-aa, aa, d);
let bg = vec3<f32>(0.05, 0.07, 0.10);
let fg = vec3<f32>(0.13, 0.83, 0.93);
return vec4<f32>(mix(bg, fg, inside), 1.0);
}
09 / atan2 · kaleidoscope
Polar koordinat
(x, y) → (r, θ) dönüşümü
(length ve atan2) radyal desenler için temel:
halka, ışın, kaleidoscope. θ üzerinde modulo + abs
simetri segmentleri çıkarır — tek bir formül 6 (veya 8, 12) kez
yansıyarak ayna efekti verir.
segments'ı 4.0 ya da
12.0 yap. r * 24.0'ü * 48.0 ile
değiştir, halkalar sıklaşır. cos(theta * 6.0)'daki
6'yı değiştir.// Aşama 09 — Polar koordinat ve kaleidoskop
// (x, y) → (r, θ) → simetri uygula → renklendir
struct U {
resolution: vec2<f32>,
time: f32,
};
@group(0) @binding(0) var<uniform> u: U;
@vertex
fn vs(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4<f32> {
let p = vec2<f32>(f32((vi << 1u) & 2u), f32(vi & 2u));
return vec4<f32>(p * 2.0 - 1.0, 0.0, 1.0);
}
@fragment
fn fs(@builtin(position) frag: vec4<f32>) -> @location(0) vec4<f32> {
let aspect = u.resolution.x / u.resolution.y;
let uv = (frag.xy / u.resolution - 0.5) * vec2<f32>(aspect, 1.0);
let r = length(uv);
var theta = atan2(uv.y, uv.x) + u.time * 0.3;
// 6'lı simetri
let segments = 6.0;
let segAng = 6.2831 / segments;
theta = abs(((theta + segAng * 0.5) % segAng) - segAng * 0.5);
// Halka + ışın
let ring = 0.5 + 0.5 * sin(r * 24.0 - u.time * 2.0);
let arm = pow(cos(theta * 6.0) * 0.5 + 0.5, 4.0);
let blend = ring * 0.5 + arm * 0.5;
let col = mix(
vec3<f32>(0.03, 0.07, 0.13),
vec3<f32>(0.13, 0.83, 0.93),
blend
);
return vec4<f32>(col, 1.0);
}
10 / sdf · lighting
Raymarching 3D
3D rendering tek bir fragment shader'da. Her piksel
için kameradan bir ışın atılır. Işın SDF'in verdiği güvenli mesafe
kadar ilerler — yüzeye varana kadar (d < 0.001).
Çarpınca: normal SDF gradyanından, ışıklandırma dot(n, light)'tan,
rim'i amber dokunuşla. Polygon yok, mesh yok — saf matematik.
lightDir'i
u.time ile animate et. Zemin: p.y + 1.0'ı
sahne SDF'e min'le ekle.// Aşama 10 — Raymarching 3D
// SDF + ışın yürüyüşü + ışıklandırma → mesh'siz 3D sahne.
struct U {
resolution: vec2<f32>,
time: f32,
};
@group(0) @binding(0) var<uniform> u: U;
fn sdSphere(p: vec3<f32>, r: f32) -> f32 {
return length(p) - r;
}
fn smin(a: f32, b: f32, k: f32) -> f32 {
let h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);
return mix(b, a, h) - k * h * (1.0 - h);
}
// Sahne: iki küre, smooth union
fn scene(p: vec3<f32>) -> f32 {
let t = u.time;
let s1 = sdSphere(p - vec3<f32>(0.0, 0.0, 0.0), 0.6);
let s2 = sdSphere(
p - vec3<f32>(cos(t) * 0.9, sin(t * 0.7) * 0.4, sin(t) * 0.9),
0.35
);
return smin(s1, s2, 0.20);
}
// Normal — SDF gradyanından
fn calcNormal(p: vec3<f32>) -> vec3<f32> {
let e = 0.0015;
return normalize(vec3<f32>(
scene(p + vec3<f32>(e, 0.0, 0.0)) - scene(p - vec3<f32>(e, 0.0, 0.0)),
scene(p + vec3<f32>(0.0, e, 0.0)) - scene(p - vec3<f32>(0.0, e, 0.0)),
scene(p + vec3<f32>(0.0, 0.0, e)) - scene(p - vec3<f32>(0.0, 0.0, e))
));
}
@vertex
fn vs(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4<f32> {
let p = vec2<f32>(f32((vi << 1u) & 2u), f32(vi & 2u));
return vec4<f32>(p * 2.0 - 1.0, 0.0, 1.0);
}
@fragment
fn fs(@builtin(position) frag: vec4<f32>) -> @location(0) vec4<f32> {
let aspect = u.resolution.x / u.resolution.y;
// Y'yi flip et (ekran yukarıdan aşağı, dünya aşağıdan yukarı)
let uv = (frag.xy / u.resolution - 0.5) * vec2<f32>(aspect, -1.0);
// Kamera
let ro = vec3<f32>(0.0, 0.0, -2.4);
let rd = normalize(vec3<f32>(uv, 1.5));
// Ray march
var t: f32 = 0.0;
var hit: bool = false;
for (var i = 0; i < 80; i = i + 1) {
let p = ro + rd * t;
let d = scene(p);
if d < 0.001 {
hit = true;
break;
}
if t > 10.0 { break; }
t = t + d;
}
var col: vec3<f32>;
if hit {
let p = ro + rd * t;
let n = calcNormal(p);
let lightDir = normalize(vec3<f32>(0.6, 0.8, -0.4));
let diffuse = max(dot(n, lightDir), 0.0);
let ambient = 0.18;
let lit = ambient + diffuse * 0.82;
let baseCol = vec3<f32>(0.13, 0.83, 0.93);
col = baseCol * lit;
// Rim ışık (amber)
let rim = pow(1.0 - max(dot(n, -rd), 0.0), 2.0);
col = col + vec3<f32>(0.96, 0.62, 0.04) * rim * 0.35;
} else {
let v = length(uv) * 0.6;
col = mix(
vec3<f32>(0.04, 0.08, 0.13),
vec3<f32>(0.01, 0.02, 0.04),
v
);
}
return vec4<f32>(col, 1.0);
}
11 / workgroup · buffer
Compute & storage
Pipeline'ın diğer yarısı: compute shader
bir storage buffer'ı doldurur, sonra fragment
onu okur. Burada compute her frame 1024 noktalık bir sinüs dalgası üretir,
fragment de aynı buffer'ı okuyup bar chart çiziyor.
@workgroup_size(64) = her workgroup'ta 64 thread,
runtime 16 workgroup dispatch ediyor → toplam 1024 invocation.
Not: WGSL fragment shader'da read_write
storage'a izin vermez, o yüzden aynı buffer iki binding'e bağlı:
compute @binding(1)'e yazıyor, fragment @binding(2)'den okuyor.
cs'i değiştir —
data_w[idx] = 0.5 + 0.4 * sin(x * 12.0 + u.time * 3.0)
hızlandırır. Ya da fragment'te bar yerine alan dolduran çizim:
let v = data_r[i]; return vec4(v, v*0.6, 1.0, 1.0);.// Aşama 11 — Compute → storage → fragment
// Compute her frame buffer'ı doldurur, fragment aynı buffer'ı okur.
// Aynı bellek, iki binding: compute write, fragment read.
struct U {
resolution: vec2<f32>,
time: f32,
};
@group(0) @binding(0) var<uniform> u: U;
@group(0) @binding(1) var<storage, read_write> data_w: array<f32>; // compute yazar
@group(0) @binding(2) var<storage, read> data_r: array<f32>; // fragment okur
const N: u32 = 1024u;
// 1) Compute: 1024 noktalık dalgayı üret
@compute @workgroup_size(64)
fn cs(@builtin(global_invocation_id) gid: vec3<u32>) {
let idx = gid.x;
if idx >= N { return; }
let x = f32(idx) / f32(N);
data_w[idx] = 0.5 + 0.4 * sin(x * 6.2831 * 3.0 + u.time * 2.0);
}
// 2) Render: aynı buffer'ı oku, bar chart çiz
@vertex
fn vs(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4<f32> {
let p = vec2<f32>(f32((vi << 1u) & 2u), f32(vi & 2u));
return vec4<f32>(p * 2.0 - 1.0, 0.0, 1.0);
}
@fragment
fn fs(@builtin(position) frag: vec4<f32>) -> @location(0) vec4<f32> {
let uv = frag.xy / u.resolution;
let i = u32(uv.x * f32(N));
let v = data_r[i];
let on = step(1.0 - uv.y, v);
let bg = vec3<f32>(0.04, 0.07, 0.10);
let fg = vec3<f32>(0.13, 0.83, 0.93);
return vec4<f32>(mix(bg, fg, on), 1.0);
}
12 / parallel · reduce
Workgroup memory
var<workgroup> sadece aynı workgroup'un
thread'lerinin gördüğü paylaşımlı bellek. workgroupBarrier()
ile thread'ler birbirini bekler. Burada her workgroup 64 değeri
log₂(64) = 6 paralel adımda topluyor — sıralı CPU 64
ekleme yaparken GPU 6'ya iner. Üst yarı: 1024 ham veri (cyan).
Alt yarı: 16 workgroup'un partial ortalamaları (amber).
+ yerine
max yap (her workgroup'un max'ı). WG'yi
32 yap (5 adımda biter), ama runtime dispatch'i de
buna göre değişmeli — değiştirmezsen yarı veri görünür.// Aşama 12 — Workgroup memory ve parallel reduce
// Her workgroup 64 değeri 6 adımda log₂ reduce ile toplar.
struct U {
resolution: vec2<f32>,
time: f32,
};
@group(0) @binding(0) var<uniform> u: U;
@group(0) @binding(1) var<storage, read_write> data_w: array<f32>;
@group(0) @binding(2) var<storage, read> data_r: array<f32>;
const N: u32 = 1024u;
const WG: u32 = 64u;
// Workgroup-yerel paylaşımlı bellek (sadece bu workgroup'un thread'leri görür)
var<workgroup> wg_buf: array<f32, 64>;
@compute @workgroup_size(64)
fn cs(
@builtin(local_invocation_id) lid: vec3<u32>,
@builtin(workgroup_id) wgid: vec3<u32>,
@builtin(global_invocation_id) gid: vec3<u32>,
) {
let idx = gid.x;
// 1) Her thread bir değer üretsin, ham sonucu yazsın
let val = 0.5 + 0.5 * sin(f32(idx) * 0.04 + u.time * 1.2);
data_w[idx] = val;
// 2) Workgroup-yerel reduce: 64 değer → 1 toplam, 6 paralel adımda.
// Barrier'lar uniform control flow'da olmalı — bu yüzden loop yerine unroll.
wg_buf[lid.x] = val;
workgroupBarrier();
if lid.x < 32u { wg_buf[lid.x] = wg_buf[lid.x] + wg_buf[lid.x + 32u]; }
workgroupBarrier();
if lid.x < 16u { wg_buf[lid.x] = wg_buf[lid.x] + wg_buf[lid.x + 16u]; }
workgroupBarrier();
if lid.x < 8u { wg_buf[lid.x] = wg_buf[lid.x] + wg_buf[lid.x + 8u]; }
workgroupBarrier();
if lid.x < 4u { wg_buf[lid.x] = wg_buf[lid.x] + wg_buf[lid.x + 4u]; }
workgroupBarrier();
if lid.x < 2u { wg_buf[lid.x] = wg_buf[lid.x] + wg_buf[lid.x + 2u]; }
workgroupBarrier();
if lid.x < 1u { wg_buf[lid.x] = wg_buf[lid.x] + wg_buf[lid.x + 1u]; }
workgroupBarrier();
// 3) Thread 0 partial ortalamayı yazsın (offset N'den)
if lid.x == 0u {
data_w[N + wgid.x] = wg_buf[0] / f32(WG);
}
}
@vertex
fn vs(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4<f32> {
let p = vec2<f32>(f32((vi << 1u) & 2u), f32(vi & 2u));
return vec4<f32>(p * 2.0 - 1.0, 0.0, 1.0);
}
@fragment
fn fs(@builtin(position) frag: vec4<f32>) -> @location(0) vec4<f32> {
let uv = frag.xy / u.resolution;
let bg = vec3<f32>(0.04, 0.07, 0.10);
let fg = vec3<f32>(0.13, 0.83, 0.93);
let warm = vec3<f32>(0.96, 0.62, 0.04);
// Üst yarı: 1024 ham veri (ince cyan barlar)
if uv.y < 0.5 {
let i = u32(uv.x * f32(N));
let v = data_r[i];
let yLocal = uv.y / 0.5;
let on = step(1.0 - yLocal, v);
return vec4<f32>(mix(bg, fg, on), 1.0);
}
// Alt yarı: 16 partial sum (kalın amber barlar)
let wgIdx = u32(uv.x * 16.0);
let v = data_r[N + wgIdx];
let yLocal = (uv.y - 0.5) / 0.5;
let on = step(1.0 - yLocal, v);
return vec4<f32>(mix(bg, warm * 0.8, on), 1.0);
}