Fotografierte Materialien fuer die fotorealistische Ansicht
Deploy App / deploy (push) Successful in 29s
Deploy App / deploy (push) Successful in 29s
Zwoelf CC0-Materialsaetze von ambientCG, neu komprimiert auf 11 MB (public/materialien, Quellenliste in QUELLEN.md). Sie werden erst geladen, wenn auf "Fotorealistisch" geschaltet wird. - Travertin (Travertine009, 2K): 80x80-Platten und Stellplatten werden aus Fotoausschnitten zusammengesetzt - jede Platte ein eigener Ausschnitt, dazu Fugen, Fasen und Fugenschatten. Lose Platten, Stufen, Stelen und Mauern bekommen das Foto direkt. - Keramik 60x60 aus Concrete017, ebenfalls als Plattenraster. - Tegula, Verbundstein, Dekorkies, Schotterwiese, Spielsand, Beete, Rasen, Wiese, Holzschalung, Beton und Sockel direkt als Fotomaterial. - Rauheit fuer Gras, Erde und Stein angehoben: poliert fotografierter Stein und Gras spiegelten draussen sonst den Himmel grau. - Boden der Umgebungskarte als warmes Rueckstrahllicht - Decken und Unterseiten lagen vorher fast schwarz im Schatten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+174
-5
@@ -8,9 +8,10 @@
|
||||
*/
|
||||
import {
|
||||
CanvasTexture, RepeatWrapping, SRGBColorSpace, LinearMipmapLinearFilter,
|
||||
LinearSRGBColorSpace, Vector2,
|
||||
LinearSRGBColorSpace, Texture, Vector2,
|
||||
} from 'three';
|
||||
import { isFoto } from './quality';
|
||||
import { bild, fotoBereit, FOTO_M, type Satz } from './fotomaterial';
|
||||
|
||||
/**
|
||||
* Die Zeichenfunktionen rechnen mit einer logischen Kantenlaenge von SIZE.
|
||||
@@ -115,12 +116,12 @@ function slabs(
|
||||
}
|
||||
|
||||
export interface Belag {
|
||||
map: CanvasTexture;
|
||||
map: Texture;
|
||||
/** Nur fotorealistisch: Rauheit und Fugenschatten aus dem Hoehenbild. */
|
||||
roughnessMap?: CanvasTexture;
|
||||
aoMap?: CanvasTexture;
|
||||
roughnessMap?: Texture;
|
||||
aoMap?: Texture;
|
||||
/** Aus dem Fugenbild abgeleitete Normalmap - gibt den Belaegen Tiefe. */
|
||||
normalMap?: CanvasTexture;
|
||||
normalMap?: Texture;
|
||||
normalScale?: Vector2;
|
||||
tile: number;
|
||||
roughness: number;
|
||||
@@ -130,6 +131,8 @@ export interface Belag {
|
||||
const cache = new Map<string, Belag>();
|
||||
|
||||
function make(key: string, tile: number, roughness: number, draw: (ctx: CanvasRenderingContext2D) => void): Belag {
|
||||
const foto = fotoVariante(key);
|
||||
if (foto) return foto;
|
||||
const k = keyFor(key);
|
||||
const hit = cache.get(k);
|
||||
if (hit) return hit;
|
||||
@@ -219,6 +222,8 @@ function makeRelief(
|
||||
draw: (ctx: CanvasRenderingContext2D) => void,
|
||||
drawHeight: (ctx: CanvasRenderingContext2D) => void,
|
||||
): Belag {
|
||||
const foto = fotoVariante(key);
|
||||
if (foto) return foto;
|
||||
const k = keyFor(key);
|
||||
const hit = cache.get(k);
|
||||
if (hit) return hit;
|
||||
@@ -752,3 +757,167 @@ export const travertinStele = () => makeRelief('travertinStele', 0.5, 0.62, 1.7,
|
||||
}
|
||||
noiseTile(ctx, 95, 120, 0.06);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Fotografierte Materialien (nur fotorealistische Darstellung)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
interface Direkt {
|
||||
satz: Satz;
|
||||
/** Rauheit anheben (0..1): polierter Stein wirkt draussen sonst lackiert. */
|
||||
rauh?: number;
|
||||
/** Helligkeit anheben (0..1) oder absenken (negativ). */
|
||||
hell?: number;
|
||||
}
|
||||
|
||||
interface Platten extends Direkt {
|
||||
tile: number; slabW: number; slabH: number; fuge: number; fugenfarbe: string;
|
||||
/** Helligkeitsschwankung zwischen den einzelnen Platten */
|
||||
streuung: number;
|
||||
fase: number; relief: number;
|
||||
/** Leinwandgroesse in Pixeln */
|
||||
px: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Welcher gezeichnete Belag im Fotomodus durch welches Foto ersetzt wird.
|
||||
* Plattenbelaege werden aus Fotoausschnitten zusammengesetzt - jede Platte
|
||||
* bekommt einen eigenen Ausschnitt des Steins, dazu echte Fugen und Fasen.
|
||||
*/
|
||||
const FOTO_DIREKT: Record<string, Direkt> = {
|
||||
travertinSlab: { satz: 'travertin', rauh: 0.38 },
|
||||
travertinMauer: { satz: 'travertin', rauh: 0.45, hell: -0.04 },
|
||||
travertinStele: { satz: 'travertin', rauh: 0.4 },
|
||||
tegula: { satz: 'tegula', hell: 0.08 },
|
||||
verbundstein: { satz: 'verbundstein' },
|
||||
dekorkies: { satz: 'kies' },
|
||||
spritzschutz: { satz: 'kies', hell: -0.1 },
|
||||
spielsand: { satz: 'sand' },
|
||||
beet: { satz: 'erde', rauh: 0.3 },
|
||||
// Gras glaenzt nicht - ohne angehobene Rauheit spiegelt es den Himmel grau.
|
||||
rasen: { satz: 'rasen', rauh: 0.6 },
|
||||
schotterwiese: { satz: 'schotter', rauh: 0.3 },
|
||||
rohboden: { satz: 'rasen', rauh: 0.6 },
|
||||
beton: { satz: 'beton' },
|
||||
sockel: { satz: 'beton', hell: -0.05 },
|
||||
fassadeHolz: { satz: 'holz', hell: 0.04 },
|
||||
};
|
||||
const FOTO_PLATTEN: Record<string, Platten> = {
|
||||
travertin80: {
|
||||
satz: 'travertin', tile: 3.2, slabW: 0.8, slabH: 0.8, fuge: 0.008, fugenfarbe: 'rgb(176,168,150)',
|
||||
streuung: 0.06, fase: 0.012, relief: 3.2, px: 2048, rauh: 0.38,
|
||||
},
|
||||
travertinstell: {
|
||||
satz: 'travertin', tile: 2.4, slabW: 0.4, slabH: 0.4, fuge: 0.01, fugenfarbe: 'rgb(170,162,146)',
|
||||
streuung: 0.06, fase: 0.01, relief: 3.0, px: 1024, rauh: 0.38,
|
||||
},
|
||||
keramik: {
|
||||
satz: 'keramik', tile: 2.4, slabW: 0.6, slabH: 0.6, fuge: 0.006, fugenfarbe: 'rgb(84,84,82)',
|
||||
streuung: 0.04, fase: 0.006, relief: 2.6, px: 1024, hell: 0.22, rauh: 0.1,
|
||||
},
|
||||
};
|
||||
|
||||
function fotoVariante(key: string): Belag | null {
|
||||
if (!isFoto() || !fotoBereit()) return null;
|
||||
const k = key + ':foto';
|
||||
const hit = cache.get(k);
|
||||
if (hit) return hit;
|
||||
let belag: Belag | null = null;
|
||||
if (FOTO_PLATTEN[key]) belag = fotoPlatten(key, FOTO_PLATTEN[key]);
|
||||
else if (FOTO_DIREKT[key]) belag = fotoDirekt(FOTO_DIREKT[key]);
|
||||
if (belag) cache.set(k, belag);
|
||||
return belag;
|
||||
}
|
||||
|
||||
function bildTextur(src: HTMLImageElement | HTMLCanvasElement, farbe: boolean): Texture {
|
||||
const tx = src instanceof HTMLCanvasElement ? new CanvasTexture(src) : new Texture(src);
|
||||
tx.wrapS = tx.wrapT = RepeatWrapping;
|
||||
tx.colorSpace = farbe ? SRGBColorSpace : LinearSRGBColorSpace;
|
||||
tx.anisotropy = 8;
|
||||
tx.minFilter = LinearMipmapLinearFilter;
|
||||
tx.generateMipmaps = true;
|
||||
tx.needsUpdate = true;
|
||||
return tx;
|
||||
}
|
||||
|
||||
/** Bild auf eine eigene Leinwand kopieren und dabei auf- oder abhellen. */
|
||||
function nachbearbeitet(img: HTMLImageElement, hell: number): HTMLCanvasElement {
|
||||
const c = document.createElement('canvas');
|
||||
c.width = img.width; c.height = img.height;
|
||||
const ctx = c.getContext('2d')!;
|
||||
ctx.drawImage(img, 0, 0);
|
||||
if (hell) {
|
||||
ctx.fillStyle = hell > 0 ? `rgba(255,255,255,${hell})` : `rgba(0,0,0,${-hell})`;
|
||||
ctx.fillRect(0, 0, c.width, c.height);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
function fotoDirekt(d: Direkt): Belag {
|
||||
const color = bild(d.satz, 'color')!;
|
||||
const normal = bild(d.satz, 'normal');
|
||||
const rough = bild(d.satz, 'rough');
|
||||
const ao = bild(d.satz, 'ao');
|
||||
return {
|
||||
map: bildTextur(d.hell ? nachbearbeitet(color, d.hell) : color, true),
|
||||
normalMap: normal ? bildTextur(normal, false) : undefined,
|
||||
normalScale: new Vector2(1, 1),
|
||||
roughnessMap: rough ? bildTextur(d.rauh ? nachbearbeitet(rough, d.rauh) : rough, false) : undefined,
|
||||
aoMap: ao ? bildTextur(ao, false) : undefined,
|
||||
tile: FOTO_M[d.satz],
|
||||
roughness: 1,
|
||||
};
|
||||
}
|
||||
|
||||
function fotoPlatten(key: string, d: Platten): Belag {
|
||||
const color = bild(d.satz, 'color')!;
|
||||
const rough = bild(d.satz, 'rough');
|
||||
PHYS = d.px;
|
||||
const [c, ctx] = canvas();
|
||||
const [rc, rctx] = canvas();
|
||||
const [hc, hctx] = canvas();
|
||||
const pxm = SIZE / d.tile;
|
||||
ctx.fillStyle = d.fugenfarbe;
|
||||
ctx.fillRect(0, 0, SIZE, SIZE);
|
||||
rctx.fillStyle = 'rgb(250,250,250)';
|
||||
rctx.fillRect(0, 0, SIZE, SIZE);
|
||||
let seed = 7;
|
||||
for (let i = 0; i < key.length; i++) seed = (seed * 31 + key.charCodeAt(i)) >>> 0;
|
||||
const rnd = mulberry32(seed);
|
||||
const cols = Math.round(d.tile / d.slabW), rows = Math.round(d.tile / d.slabH);
|
||||
const j = d.fuge * pxm;
|
||||
const physM = FOTO_M[d.satz];
|
||||
for (let row = 0; row < rows; row++) {
|
||||
for (let col = 0; col < cols; col++) {
|
||||
const x = col * d.slabW * pxm + j / 2, y = row * d.slabH * pxm + j / 2;
|
||||
const w = d.slabW * pxm - j, h = d.slabH * pxm - j;
|
||||
// eigener Ausschnitt des Steins je Platte
|
||||
const sw = (d.slabW / physM) * color.width, sh = (d.slabH / physM) * color.height;
|
||||
const sx = rnd() * Math.max(0, color.width - sw), sy = rnd() * Math.max(0, color.height - sh);
|
||||
ctx.drawImage(color, sx, sy, sw, sh, x, y, w, h);
|
||||
const v = (rnd() - 0.5) * 2 * d.streuung + (d.hell ?? 0);
|
||||
if (v) {
|
||||
ctx.fillStyle = v > 0 ? `rgba(255,250,240,${v})` : `rgba(40,34,28,${-v})`;
|
||||
ctx.fillRect(x, y, w, h);
|
||||
}
|
||||
if (rough) {
|
||||
const k = rough.width / color.width;
|
||||
rctx.drawImage(rough, sx * k, sy * k, sw * k, sh * k, x, y, w, h);
|
||||
if (d.rauh) {
|
||||
rctx.fillStyle = `rgba(255,255,255,${d.rauh})`;
|
||||
rctx.fillRect(x, y, w, h);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
slabHeight(hctx, d.tile, d.slabW, d.slabH, d.fuge, d.fase);
|
||||
return {
|
||||
map: bildTextur(c, true),
|
||||
normalMap: normalFromHeight(hc, d.relief * (PHYS / SIZE)),
|
||||
normalScale: new Vector2(1, 1),
|
||||
roughnessMap: bildTextur(rc, false),
|
||||
aoMap: derivedMap(hc, (hh) => 0.35 + Math.min(1, hh * 1.25) * 0.65),
|
||||
tile: d.tile,
|
||||
roughness: 1,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user