G-Buffer: Normalen aus glTF bis in den Post-Vertrag
Cel-Ramp und ein Sobel, der auch Innenkanten findet, brauchen beide Normalen — die gab es in der Pipeline bisher nirgends. Der Strang geht quer durch alle Schichten, deshalb als eigener Schritt, bevor ein Effekt darauf zugreift. Loader: NORMAL wird gelesen und mit der Kofaktormatrix des 3x3-Teils transformiert, nicht mit der Welt-Matrix. Das ist die inverse Transponierte bis auf den Faktor det, den das Normieren frisst — nur so bleibt eine Normale bei ungleichmäßiger Skalierung senkrecht auf ihrer Fläche, und bei Spiegelungen dreht sie korrekt mit. glTF erlaubt Meshes ohne NORMAL; die rechnet fill_missing_normals aus den angrenzenden Flächen (Nullvektor als Sentinel). Beide Szenen-Modelle bringen eigene Normalen mit, der Fallback ist reine Absicherung. Szenen-Pass schreibt jetzt zwei Ziele: Albedo und n*0.5+0.5. Das Alpha des Normal-Buffers ist für eine Objekt-ID reserviert und noch leer. Die Depth-Textur bekommt TEXTURE_BINDING, und der Post-Vertrag wächst um group 1 (Normale + Tiefe), die jeder Effekt ignorieren darf — dither tut das. Statt eines cel.wgsl-Durchreichers gibt es normals.wgsl, per Default aus: `post normals on` zeigt oben die Normalen, unten die linearisierte Tiefe. Ein Effekt, der nichts tut, wäre von einem kaputten nicht zu unterscheiden; dieser hier trennt später "Loader kaputt" von "Effekt kaputt". Das Standard-Bild bleibt unverändert — die Normalen werden geschrieben, aber von keinem aktiven Effekt gelesen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -42,6 +42,9 @@ pub struct Object {
|
||||
/// UVs parallel zu `verts`, V-Ursprung oben links — glTF- und zugleich
|
||||
/// `tga::Image`-Konvention, direkt sampelbar ohne Flip.
|
||||
pub uvs: Vec<[f32; 2]>,
|
||||
/// Normalen parallel zu `verts`, normiert, Welt-Raum. Fehlen sie im
|
||||
/// Modell, rechnet der Loader sie aus den Flächen.
|
||||
pub normals: Vec<[f32; 3]>,
|
||||
pub tris: Vec<[usize; 3]>,
|
||||
/// Material-Index je Dreieck, parallel zu `tris` (→ `Model::materials`).
|
||||
pub tri_mats: Vec<usize>,
|
||||
@@ -77,6 +80,39 @@ pub const SIGNAL_PROP: &str = "signal";
|
||||
pub const ROLE_PROP: &str = "role";
|
||||
pub const ROLE_SPAWN: &str = "spawn";
|
||||
|
||||
/// Auf Länge 1 bringen; der Nullvektor bleibt der Nullvektor (Sentinel für
|
||||
/// „noch keine Normale" in [`fill_missing_normals`]).
|
||||
pub fn norm(v: [f32; 3]) -> [f32; 3] {
|
||||
let l = (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt();
|
||||
if l > 0.0 { [v[0] / l, v[1] / l, v[2] / l] } else { v }
|
||||
}
|
||||
|
||||
/// Fehlende Normalen (Nullvektoren) aus den angrenzenden Flächen füllen —
|
||||
/// glTF erlaubt Meshes ohne `NORMAL`. Die Flächennormalen werden je Vertex
|
||||
/// aufsummiert, was an Kanten weich mittelt; Alternative wäre, die Vertices
|
||||
/// je Fläche aufzuspalten (hartes Flat-Shading) — dafür ist der Look hier
|
||||
/// nicht empfindlich genug.
|
||||
pub fn fill_missing_normals(o: &mut Object) {
|
||||
if !o.normals.contains(&[0.0; 3]) { return; }
|
||||
let mut acc = vec![[0.0f32; 3]; o.verts.len()];
|
||||
for t in &o.tris {
|
||||
let (a, b, c) = (o.verts[t[0]], o.verts[t[1]], o.verts[t[2]]);
|
||||
let u = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
|
||||
let v = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
|
||||
let n = [
|
||||
u[1] * v[2] - u[2] * v[1],
|
||||
u[2] * v[0] - u[0] * v[2],
|
||||
u[0] * v[1] - u[1] * v[0],
|
||||
];
|
||||
for &i in t {
|
||||
for k in 0..3 { acc[i][k] += n[k]; }
|
||||
}
|
||||
}
|
||||
for (i, n) in o.normals.iter_mut().enumerate() {
|
||||
if *n == [0.0; 3] { *n = norm(acc[i]); }
|
||||
}
|
||||
}
|
||||
|
||||
/// Die Property-Regeln anwenden, sobald `props` gelesen sind (Objekte
|
||||
/// starten sichtbar und ohne Collider): `collide` schaltet den Collider,
|
||||
/// `"proxy"` und Betretens-Zonen sind reine Volumen und werden nie gerendert.
|
||||
@@ -119,6 +155,11 @@ pub(crate) fn test_cube(name: &str, min: [f32; 3], collide: Option<&str>, signal
|
||||
let mut o = Object {
|
||||
name: name.into(),
|
||||
uvs: vec![[0.0, 0.0]; verts.len()],
|
||||
// Vom Mittelpunkt nach außen — die Collider-Tests rendern nie, aber
|
||||
// so ist die Richtung wenigstens plausibel.
|
||||
normals: verts.iter()
|
||||
.map(|v| norm([v[0] - min[0] - 0.5, v[1] - min[1] - 0.5, v[2] - min[2] - 0.5]))
|
||||
.collect(),
|
||||
verts, tris,
|
||||
tri_mats: vec![0; n],
|
||||
visible: true, collider: false, props,
|
||||
|
||||
Reference in New Issue
Block a user