centralized state

This commit is contained in:
2026-06-13 13:44:02 +02:00
parent 0a8861a5b1
commit 48c499e356
11 changed files with 693 additions and 206 deletions
+57
View File
@@ -0,0 +1,57 @@
// 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) Varyings
// - RGB555-Quantisierung + 4×4-Bayer-Dither im Fragment-Shader
struct Uniforms {
mvp: mat4x4f,
};
@group(0) @binding(0) var<uniform> u: Uniforms;
struct VsOut {
@builtin(position) pos: vec4f,
// linear = affin interpoliert; Vertex-Colors (und später UVs)
// wobbeln dadurch wie auf der PS1.
@location(0) @interpolate(linear) color: vec3f,
};
// Halbe interne Auflösung (320×240). Bei umschaltbarer Auflösung später
// in die Uniforms verschieben.
const HALF_RES = vec2f(160.0, 120.0);
@vertex
fn vs_main(@location(0) pos: vec3f, @location(1) color: vec3f) -> 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 * HALF_RES) / HALF_RES * clip.w, clip.zw);
}
var out: VsOut;
out.pos = clip;
out.color = color;
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 {
// 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(in.color, vec3f(0.0), vec3f(1.0));
return vec4f(floor(c * 31.0 + t) / 31.0, 1.0);
}