Files
wds/notes/renderer-plan.md
T
2026-06-13 02:48:03 +02:00

5.1 KiB
Raw Blame History

wds — Renderer-Plan

Stand: 2026-06-12. Entscheidungen aus der irl3d-Analyse, festgehalten bevor der Projekt-Einstieg über die Kern-Systeme (Signals/KV, CLI-Phase) läuft.

Ziel

PS1-artiger Look, von vornherein nativ und simpel implementiert statt emuliert: affines Texture-Mapping, Integer-Vertex-Koordinaten, 16-Bit-Farbe. Niedrige interne Auflösung (320×240), hochskaliert aufs Fenster.

Referenz ist ../irl3d: gleiche Engine-Idee (datengetrieben, eine OBJ-Szene, Signals/KV/Ink), aber dort als 8-Bit-Palette-CPU-Softwarerenderer in Q16.16. wds ersetzt den Grafikpfad durch echtes GPU-Rastern.

Hardware-Target

Mittelklasse-Laptop von 2015 (Klasse: i5-5200U + Intel HD 5500). Konsequenzen:

  • Zwei schwache CPU-Kerne → Transformation gehört auf die GPU, die CPU bleibt für Physik und Spiellogik frei.
  • Per-Frame-Vertex-Streaming (CPU-Transform, GTE-Stil) wäre auf alten iGPU-Treibern der riskante Pfad (Buffer-Upload-Stalls) → statische Vertex-Buffer, einmal hochgeladen.
  • wgpu-Backend: Vulkan primär (ANV unterstützt Gen8+), GL als Fallback einplanen. Keine Features jenseits von Baseline-WebGPU benutzen.

Entscheidungen

  1. wgpu direkt, kein pbio. pbio ist eine Abstraktion für Software-Renderer (8-Bit-Framebuffer + Palette-Blit) — für echtes GPU-Rastern Verschwendung. winit + wgpu direkt im Projekt.

  2. Vertex-Shader-Transform mit Pixel-Snapping. Model→View→Projektion im Vertex-Shader. Danach xy/w auf das Pixelraster der internen Auflösung runden und wieder mit w multiplizieren → Vertex-Jitter wie auf der PS1, authentisch gerechnet, nicht nachgestellt.

  3. Affines Texture-Mapping via @interpolate(linear). WGSL kann die perspektivische Korrektur pro Varying abschalten. UVs (und ggf. Vertex-Colors) linear interpolieren → das Textur-Wobbeln kommt gratis. Gegenmittel bleibt wie auf der PS1 das Unterteilen großer Flächen im Asset, nicht Shader-Tricks.

  4. RGB555 + Ordered Dithering im Fragment-Shader. wgpu hat kein 555-Render-Target-Format; Render-Target bleibt RGBA8 bei 320×240, aber der Fragment-Shader quantisiert auf 5 Bit pro Kanal mit 4×4-Bayer-Dither. Die Farbreduktion ist echt, nur das Speicherformat ist modern. Das 16. Bit entspricht dem PS1-Maskbit → bei uns 1-Bit-Alpha (Alpha-Test, kein Blending) — Fortführung der Index-0-Transparenz aus irl3d.

  5. Z-Buffer: ja. Ordering-Tables wären nur eine technische Hürde, kein ästhetischer Gewinn. Standard-Depth-Buffer auf dem internen Target.

  6. UVs aus dem OBJ (vt). irl3d ignoriert vt und mappt planar über Welt-Koordinaten; wds liest echte UVs. PS1-Spiele hatten handgemachte UVs — das gehört zum Look.

  7. Licht: gebacken zuerst. Nur Gouraud-Shading wäre zu einseitig. Plan: Lichteffekte in die Szene backen (Blender → Vertex-Colors), im Shader mit der Textur multiplizieren und mitquantisieren. Dynamische per-Vertex-Punktlichter (im Vertex-Shader, additiv auf die gebackene Farbe) als spätere Stufe. Kein Per-Pixel-Lighting.

Pipeline-Skizze

statische VBOs (Pos i16/f32, UV, Vertex-Color)
  → Vertex-Shader: Model·View·Proj, dann Snap auf 320×240-Raster
  → Raster (Hardware), UV/Color @interpolate(linear)
  → Fragment: Texel-Fetch (nearest), × Vertex-Color,
              Alpha-Test (1 Bit), RGB555-Quantisierung + 4×4-Bayer
  → RGBA8-Offscreen 320×240 + Depth
  → Blit-Pass: Nearest-Upscale mit Letterbox (4:3) aufs Fenster

Draw-Granularität wie irl3d: pro Instance ein Draw (Uniform: Pos + Yaw), Material-Batching später, wenn es weh tut.

Offene Punkte

  • Asset-Pipeline: Texture-Painting in Blender ist mühsam. Kandidaten für Textur-pro-Fläche-Workflows: TrenchBroom (Map-Format-Import nötig), Crocotile 3D, Blockbench. Entscheidung wenn der Szenen-Loader ansteht.
  • Texturformat: direkte True-Color-Texturen statt 8-Bit-CLUT-TGA — wir arbeiten durchgehend mit 15-Bit-True-Color, es gibt keine globale Palette mehr (das load_palette-Verb ist entfernt). Offen bleibt nur das Dateiformat (TGA/PNG) und ob Quantisierung beim Import oder erst im Shader passiert.
  • Fog / Distance-Fade: irl3d hat hartes Pop-In bei CULL_DIST — in wds von Anfang an als Fade Richtung Sky-Farbe einplanen (im Shader trivial, passt zur PS1).
  • Skybox: Cubemap wie irl3d oder schlicht Far-Plane-Farbe + Fog.
  • Interne Auflösung umschaltbar lassen (QVGA/VGA wie irl3d).

Aus irl3d portierbar (renderer-unabhängig)

signals, kv, ink, story_ctrl, game (ActionCtx-Idee), physics, pick, camera, obj-Loader (um vt erweitern), tga, wav, audio, assets, fix/math (Q16.16 bleibt für Spiellogik/Physik sinnvoll; der Renderer selbst rechnet f32 — die GPU kennt ohnehin nur Floats).

Projektphasen

  1. CLI-Phase (läuft): KV-Store, Signal/Action-Dispatcher, Ink-Integration, Konsolen-REPL als erstes Frontend. Deferred Actions als Daten (hide_object, play_sound), damit spätere Subsysteme sie konsumieren, ohne dass der Kern sie kennt.
  2. Datenmodell (Mesh/Instance/Material/Scene + TOML-Formate) headless.
  3. Renderer nach diesem Plan, gegen das stehende Datenmodell.
  4. Physik, Audio, Panel-UI andocken (Ports aus irl3d).