feat(world): Poly-Haven-Materialien, Wasser, Himmel und mitlaufende Schatten
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
289bbbba1d
commit
fb0d19c2e5
25 files changed
+160
-14
No files matched your search
@@ -0,0 +1,72 @@
|
||||
import { DataTexture, MeshStandardMaterial, RepeatWrapping, RGBAFormat, SRGBColorSpace, TextureLoader } from "three";
|
||||
|
||||
export const MATERIAL_KEYS = ["ground", "sand", "park", "urban", "roads", "bridges", "buildings"];
|
||||
export const TEXTURES = {
|
||||
ground: "grass_path_2", // unbebautes Land
|
||||
sand: "sand_01", // Strand
|
||||
park: "leafy_grass", // Grünflächen
|
||||
urban: "concrete_pavement_03", // versiegelte Stadtfläche
|
||||
roads: "asphalt_02",
|
||||
bridges: "concrete_pavement_03",
|
||||
buildings: "white_stucco",
|
||||
};
|
||||
const FALLBACK_COLORS = {
|
||||
ground: 0xa8b878, sand: 0xe8d888, park: 0x8bc34a, urban: 0xd8d8d8,
|
||||
roads: 0x505050, bridges: 0xb0b0b0, buildings: 0xe8e4dc,
|
||||
};
|
||||
|
||||
/** Kachelbare Wellen-Normalmap, weil Poly Haven keine Wassertextur hat. */
|
||||
export function waterNormalData(size = 128) {
|
||||
const data = new Uint8Array(size * size * 4);
|
||||
for (let y = 0; y < size; y++) {
|
||||
for (let x = 0; x < size; x++) {
|
||||
const i = (y * size + x) * 4;
|
||||
const u = (x / size) * Math.PI * 2;
|
||||
const v = (y / size) * Math.PI * 2;
|
||||
const dx = Math.sin(u * 3) * 0.5 + Math.sin(v * 2 + 1.3) * 0.3;
|
||||
const dz = Math.cos(v * 3) * 0.5 + Math.cos(u * 2 + 0.7) * 0.3;
|
||||
data[i] = 128 + dx * 40;
|
||||
data[i + 1] = 128 + dz * 40;
|
||||
data[i + 2] = 245;
|
||||
data[i + 3] = 255;
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
async function loadSet(loader, slug, fallback) {
|
||||
let map, normalMap, arm;
|
||||
try {
|
||||
[map, normalMap, arm] = await Promise.all(
|
||||
["Diffuse", "nor_gl", "arm"].map((m) => loader.loadAsync(`./textures/${slug}_${m}.jpg`)),
|
||||
);
|
||||
} catch (err) {
|
||||
console.warn(`Textur ${slug} fehlt, einfarbiger Ersatz:`, err);
|
||||
return new MeshStandardMaterial({ color: fallback, roughness: 0.9 });
|
||||
}
|
||||
for (const t of [map, normalMap, arm]) {
|
||||
t.wrapS = t.wrapT = RepeatWrapping;
|
||||
}
|
||||
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 });
|
||||
}
|
||||
|
||||
export async function createMaterials() {
|
||||
const loader = new TextureLoader();
|
||||
const entries = await Promise.all(
|
||||
MATERIAL_KEYS.map(async (key) => [key, await loadSet(loader, TEXTURES[key], FALLBACK_COLORS[key])]),
|
||||
);
|
||||
const materials = Object.fromEntries(entries);
|
||||
materials.buildings.vertexColors = true; // pastellige Tönung je Gebäude
|
||||
|
||||
const normal = new DataTexture(waterNormalData(128), 128, 128, RGBAFormat);
|
||||
normal.wrapS = normal.wrapT = RepeatWrapping;
|
||||
normal.repeat.set(60, 60);
|
||||
normal.needsUpdate = true;
|
||||
materials.water = new MeshStandardMaterial({
|
||||
color: 0x2c67a4, normalMap: normal, roughness: 0.15, metalness: 0.2, transparent: true, opacity: 0.92,
|
||||
});
|
||||
materials.seafloor = new MeshStandardMaterial({ color: 0x1d4468, roughness: 1 });
|
||||
return materials;
|
||||
}
|
||||
Reference in new issue
Block a user