Files
wds/src/render/scene.wgsl
T
2026-06-13 18:18:17 +02:00

63 lines
2.1 KiB
WebGPU Shading Language
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Szenen-Pipeline: die PS1-Eigenheiten aus dem Renderer-Plan, authentisch
// gerechnet statt nachgestellt:
// - Pixel-Snap im Vertex-Shader → Vertex-Jitter
// - @interpolate(linear) → affine (nicht perspektivkorrigierte) UVs
// - RGB555-Quantisierung + 4×4-Bayer-Dither im Fragment-Shader
// - 1-Bit-Alpha-Test (Cutouts), kein Blending
struct Uniforms {
mvp: mat4x4f,
// Halbe interne Auflösung (NDC läuft -1..1, Spanne 2 → Skalierung mit
// der halben Auflösung trifft das Pixelraster). Kommt aus dem Renderer.
half_res: vec2f,
};
@group(0) @binding(0) var<uniform> u: Uniforms;
@group(1) @binding(0) var tex: texture_2d<f32>;
@group(1) @binding(1) var smp: sampler;
struct VsOut {
@builtin(position) pos: vec4f,
// linear = affin interpoliert → das Textur-Wobbeln der PS1.
@location(0) @interpolate(linear) uv: vec2f,
};
@vertex
fn vs_main(@location(0) pos: vec3f, @location(1) uv: vec2f) -> VsOut {
var clip = u.mvp * vec4f(pos, 1.0);
// Pixel-Snap: xy nach der Projektion aufs interne Raster runden und
// zurück in den Clip-Raum. Nur vor der Kamera (w>0) — dahinter würde
// die Division Unsinn liefern, das Hardware-Clipping übernimmt.
if clip.w > 0.0 {
let ndc = clip.xy / clip.w;
clip = vec4f(round(ndc * u.half_res) / u.half_res * clip.w, clip.zw);
}
var out: VsOut;
out.pos = clip;
out.uv = uv;
return out;
}
// 4×4-Bayer-Matrix (Werte 0..15) für Ordered Dithering.
fn bayer4(px: vec2u) -> f32 {
var m = array<u32, 16>(
0u, 8u, 2u, 10u,
12u, 4u, 14u, 6u,
3u, 11u, 1u, 9u,
15u, 7u, 13u, 5u,
);
return f32(m[(px.y % 4u) * 4u + (px.x % 4u)]);
}
@fragment
fn fs_main(in: VsOut) -> @location(0) vec4f {
let texel = textureSample(tex, smp, in.uv);
if texel.a < 0.5 { discard; } // 1-Bit-Alpha (Cutouts), noch ungenutzt
// RGB555: 31 Stufen pro Kanal. Bayer-Schwelle vor dem Abrunden →
// Verläufe zerfallen ins typische Dither-Muster.
let t = (bayer4(vec2u(in.pos.xy)) + 0.5) / 16.0;
let c = clamp(texel.rgb, vec3f(0.0), vec3f(1.0));
return vec4f(floor(c * 31.0 + t) / 31.0, 1.0);
}