Outline-Pass: Kanten aus dem G-Buffer
Erster Effekt, der das Bild verändert. Gesucht werden Kanten der Geometrie, nicht des Bildes — auf dem fertigen Bild zu suchen wäre falsch, weil der Dither Flächen in Muster zerlegt und jede Texturkante als Kante zählte. Drei unabhängige Signale, genommen wird das stärkste: Tiefe (Silhouetten) und Normale (Knicke) über einen 3x3-Sobel, die Objekt-ID über einen Vergleich mit den direkten Nachbarn. Läuft vor dither, damit die Linie mitquantisiert wird wie alles andere. Zwei Vorarbeiten, die nur für diesen Pass existieren und deshalb hier mit drinstehen: Der Post-Vertrag bekommt group 2 mit Uniforms (inv_res, near, far, time). Das räumt near/far aus normals.wgsl heraus, wo ich sie als Literale abgeschrieben hatte — der Debug-View hätte stillschweigend gelogen, sobald jemand die Projektion ändert, ausgerechnet das Werkzeug, dem man beim Debuggen glauben muss. Sie kommen jetzt aus NEAR_FAR in gpu.rs, derselben Konstante wie die Projektion. time ist Vorrat für animierte Effekte; jetzt mitgenommen, weil eine spätere Vertragsänderung alle Bind-Group-Layouts anfasst. Die Objektnummer füllt das reservierte Alpha des Normal-Buffers. Sie läuft fortlaufend statt als Namens-Hash: benachbarte Nummern unterscheiden sich damit garantiert, ein Hash hätte mit ~1/256 still kollidiert. Mesh::append verschiebt die Nummern des Anhängsels, sonst trügen die ersten Objekte zweier .glb dieselbe und die Kante zwischen ihnen fehlte. Ab 1, damit die 0 "kein Objekt" heißt — der Outline-Pass zeichnet auf dem Hintergrund nichts, sonst läge jede Silhouette doppelt. Nicht getrennt committet, weil Uniform und ID für sich nichts leisten: vor diesem Pass liest sie niemand außer dem Debug-View. Strichstärke geht über den Abtastabstand des Kernels (WIDTH, aktuell 2) statt über nachträgliches Aufdicken — das kostet keinen zusätzlichen Texturzugriff, wo Dilatation das Neunfache gebraucht hätte. Die Gradienten werden durch den Abstand geteilt, damit die Schwellwerte beim Breitenwechsel ihre Bedeutung behalten. DEPTH_T und NORMAL_T sind aus der Kernel-Normierung hergeleitet, nicht an der Szene erprobt; Fehlerbilder und Richtung stehen als Kommentar daneben. normals.wgsl zeigt jetzt drei Bänder (Normale, ID, Tiefe). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+12
-2
@@ -32,6 +32,11 @@ pub const INTERNAL_H: u32 = 480;
|
||||
const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth16Unorm;
|
||||
const INTERNAL_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;
|
||||
|
||||
/// Near-/Far-Ebene der Projektion. Auch die Post-Kette bekommt sie (als
|
||||
/// Uniform), damit kein Effekt-Shader die Werte abschreiben muss, um die
|
||||
/// Tiefe zu linearisieren — sonst lügen sie, sobald die Kamera sich ändert.
|
||||
const NEAR_FAR: [f32; 2] = [0.1, 100.0];
|
||||
|
||||
pub struct Gpu {
|
||||
surface: wgpu::Surface<'static>,
|
||||
device: wgpu::Device,
|
||||
@@ -46,6 +51,8 @@ pub struct Gpu {
|
||||
blit_pipeline: wgpu::RenderPipeline,
|
||||
/// Blit-Bind-Group je Post-Target — welche gilt, sagt [`Post::run`].
|
||||
blit_binds: [wgpu::BindGroup; 2],
|
||||
/// Nullpunkt der Shader-Zeit (Post-Uniform).
|
||||
start: std::time::Instant,
|
||||
}
|
||||
|
||||
impl Gpu {
|
||||
@@ -108,7 +115,8 @@ impl Gpu {
|
||||
// Szene: Geometrie + Bilder kommen fertig vom Aufrufer (Decode in
|
||||
// render::run); hier wird nur hochgeladen.
|
||||
let scene = ScenePass::new(&device, &queue, INTERNAL_FORMAT, DEPTH_FORMAT, mesh, images);
|
||||
let post = Post::new(&device, INTERNAL_FORMAT, [INTERNAL_W, INTERNAL_H], &depth_view);
|
||||
let post = Post::new(
|
||||
&device, INTERNAL_FORMAT, [INTERNAL_W, INTERNAL_H], &depth_view, NEAR_FAR);
|
||||
|
||||
// Overlay: eigener Pass hinter der Kette, ohne Tiefe.
|
||||
let sprite = SpritePass::new(
|
||||
@@ -171,6 +179,7 @@ impl Gpu {
|
||||
surface, device, queue, config,
|
||||
depth_view,
|
||||
scene, post, sprite, blit_pipeline, blit_binds,
|
||||
start: std::time::Instant::now(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,10 +205,11 @@ impl Gpu {
|
||||
let proj = Mat4::perspective(
|
||||
60f32.to_radians(),
|
||||
INTERNAL_W as f32 / INTERNAL_H as f32,
|
||||
0.1, 100.0,
|
||||
NEAR_FAR[0], NEAR_FAR[1],
|
||||
);
|
||||
let half_res = [INTERNAL_W as f32 * 0.5, INTERNAL_H as f32 * 0.5];
|
||||
self.scene.prepare(&self.queue, &proj.mul(view), half_res);
|
||||
self.post.prepare(&self.queue, self.start.elapsed().as_secs_f32());
|
||||
self.sprite.prepare(&self.queue, &ui.verts);
|
||||
|
||||
use wgpu::CurrentSurfaceTexture as Cst;
|
||||
|
||||
Reference in New Issue
Block a user