fix(world): Straße/Stadtfläche-Kontrast, Brückennaht, tote metalnessMap (Fix-Runde 1)

Ruling 55: asphalt_02 maß roh heller (Ø 89.5) als concrete_pavement_03 (Ø 65.9) und
ließ Stadtflächen wie dunkle Flecken auf der Straße wirken. material.color kann eine
Textur nur abdunkeln, also wird die Straße auf 0x999999 abgetönt statt die Stadtfläche
zu verändern. Gemessen im gerenderten Bild an denselben Bildschirmregionen: Straße
93.0 -> 42.5, Stadtfläche 63.2 -> 63.0 (unverändert) — die Straße kommt jetzt deutlich
dunkler heraus als die Stadtfläche, nicht nur als vorher sie selbst.

Ruling 56: Brücken sind in den Daten dasselbe Straßenstück, nur über Wasser
(geometry.js trennt sie einzig über roads.filter(r => r.bridge)) und bekommen jetzt
asphalt_02 und denselben Farbton wie roads statt der Stadtflächentextur, damit an
keinem Straße-Brücke-Übergang mehr eine Farbnaht entsteht. Neuer Test dafür (RED vor
der Änderung, GREEN danach) plus ein erreichtes Brückenstück im Browser (Chunk 11_7)
fotografiert.

Ruling 57: metalnessMap entfernt. metalness steht fest auf 0, three multipliziert
texelMetalness nur noch dazu (metalnessmap_fragment.glsl.js) und könnte das Ergebnis
nie von 0 wegbewegen — reine Kosten ohne Wirkung.

Ruling 58: anisotropy als optionaler Parameter von createMaterials()/loadSet() statt
eines globalen Renderer-Zugriffs; main.js übergibt jetzt
renderer.capabilities.getMaxAnisotropy(). Laufzeit bestätigt: alle drei Texturtypen
auf allen Materialien tragen anisotropy 16 (= getMaxAnisotropy() auf diesem Gerät).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
knsoandClaude Sonnet 5 committed 2026-09-13 00:00:37 +02:00
1 parent fb0d19c2e5
commit 2469d30e76
3 files changed
+35 -7

No files matched your search

+28 -6
View File
@@ -7,13 +7,22 @@ export const TEXTURES = {
park: "leafy_grass", // Grünflächen
urban: "concrete_pavement_03", // versiegelte Stadtfläche
roads: "asphalt_02",
bridges: "concrete_pavement_03",
// Ruling 56: eine Brücke ist in den Daten dasselbe Straßenstück, nur über Wasser
// (geometry.js trennt sie einzig über roads.filter(r => r.bridge)) — dieselbe
// Textur wie roads, sonst zeigt jeder Straße-Brücke-Übergang eine Farbnaht.
bridges: "asphalt_02",
buildings: "white_stucco",
};
const FALLBACK_COLORS = {
ground: 0xa8b878, sand: 0xe8d888, park: 0x8bc34a, urban: 0xd8d8d8,
roads: 0x505050, bridges: 0xb0b0b0, buildings: 0xe8e4dc,
};
// Ruling 55: asphalt_02 misst roh heller (Ø 89.5) als concrete_pavement_03 (Ø 65.9) —
// Stadtflächen wirkten dadurch wie dunkle Flecken auf der Straße, das Straßennetz
// verschwand optisch. material.color kann eine Textur nur abdunkeln, nie aufhellen,
// also wird hier die hellere Straße (und die jetzt identisch texturierte Brücke)
// abgetönt statt die Stadtfläche zu verändern.
const ROAD_TINT = 0x999999;
/** Kachelbare Wellen-Normalmap, weil Poly Haven keine Wassertextur hat. */
export function waterNormalData(size = 128) {
@@ -34,7 +43,7 @@ export function waterNormalData(size = 128) {
return data;
}
async function loadSet(loader, slug, fallback) {
async function loadSet(loader, slug, fallback, { tint, maxAnisotropy } = {}) {
let map, normalMap, arm;
try {
[map, normalMap, arm] = await Promise.all(
@@ -46,16 +55,29 @@ async function loadSet(loader, slug, fallback) {
}
for (const t of [map, normalMap, arm]) {
t.wrapS = t.wrapT = RepeatWrapping;
// Ruling 58: Straßen laufen flach in die Tiefe — dort flimmert Standard-
// filterung zuerst. createMaterials() kennt den Renderer nicht selbst, daher
// als optionaler Parameter von außen; ohne ihn bleibt three beim Standard (1).
if (maxAnisotropy) t.anisotropy = maxAnisotropy;
}
map.colorSpace = SRGBColorSpace;
// arm packt Ambient Occlusion, Rauheit und Metallgrad in R, G und B.
return new MeshStandardMaterial({ map, normalMap, aoMap: arm, roughnessMap: arm, metalnessMap: arm, metalness: 0 });
// arm packt Ambient Occlusion und Rauheit in R und G. metalnessMap fehlt bewusst
// (Ruling 57): metalness steht fest auf 0, three multipliziert texelMetalness nur
// noch dazu (metalnessmap_fragment.glsl.js) und könnte das Ergebnis nie von 0
// wegbewegen — reine Kosten (Texturbindung, Shader-Permutation) ohne Wirkung.
const material = new MeshStandardMaterial({ map, normalMap, aoMap: arm, roughnessMap: arm, metalness: 0 });
// Ruling 55/56: nur der geladene Erfolgsfall bekommt die Tönung — der einfarbige
// Ersatz oben hat schon seinen eigenen, passenden Grundton.
if (tint) material.color.set(tint);
return material;
}
export async function createMaterials() {
export async function createMaterials(maxAnisotropy) {
const loader = new TextureLoader();
const tints = { roads: ROAD_TINT, bridges: ROAD_TINT };
const entries = await Promise.all(
MATERIAL_KEYS.map(async (key) => [key, await loadSet(loader, TEXTURES[key], FALLBACK_COLORS[key])]),
MATERIAL_KEYS.map(async (key) =>
[key, await loadSet(loader, TEXTURES[key], FALLBACK_COLORS[key], { tint: tints[key], maxAnisotropy })]),
);
const materials = Object.fromEntries(entries);
materials.buildings.vertexColors = true; // pastellige Tönung je Gebäude