diff --git a/index.html b/index.html
index 2450dba..b3c6a97 100644
--- a/index.html
+++ b/index.html
@@ -32,10 +32,15 @@
import { DRACOLoader } from 'DRACOLoader';
import { EffectComposer } from 'https://cdn.jsdelivr.net/npm/three@0.155.0/examples/jsm/postprocessing/EffectComposer.js';
import { RenderPass } from 'https://cdn.jsdelivr.net/npm/three@0.155.0/examples/jsm/postprocessing/RenderPass.js';
- import { ShaderPass } from 'https://cdn.jsdelivr.net/npm/three@0.155.0/examples/jsm/postprocessing/ShaderPass.js';
- import { UnrealBloomPass }from 'https://cdn.jsdelivr.net/npm/three@0.155.0/examples/jsm/postprocessing/UnrealBloomPass.js';
- import { GammaCorrectionShader } from 'https://cdn.jsdelivr.net/npm/three@0.155.0/examples/jsm/shaders/GammaCorrectionShader.js';
- import { VignetteShader } from "https://cdn.jsdelivr.net/npm/three@0.155.0/examples/jsm/shaders/VignetteShader.js";
+ import { ShaderPass } from 'https://cdn.jsdelivr.net/npm/three@0.155.0/examples/jsm/postprocessing/ShaderPass.js'; // damit kann man auch easy webgl shader hier reinschreiben
+ import { UnrealBloomPass } from 'https://cdn.jsdelivr.net/npm/three@0.155.0/examples/jsm/postprocessing/UnrealBloomPass.js'; // EPICCC ..
+ import { BrightnessContrastShader } from 'https://cdn.jsdelivr.net/npm/three@0.155.0/examples/jsm/shaders/BrightnessContrastShader.js';
+ import { GammaCorrectionShader } from 'https://cdn.jsdelivr.net/npm/three@0.155.0/examples/jsm/shaders/GammaCorrectionShader.js';
+ import { SSAOPass } from 'https://cdn.jsdelivr.net/npm/three@0.155.0/examples/jsm/postprocessing/SSAOPass.js';
+
+ import { FilmPass } from "https://cdn.jsdelivr.net/npm/three@0.155.0/examples/jsm/postprocessing/FilmPass.js";
+ import { VignetteShader } from "https://cdn.jsdelivr.net/npm/three@0.155.0/examples/jsm/shaders/VignetteShader.js";
+ import { RGBShiftShader } from "https://cdn.jsdelivr.net/npm/three@0.155.0/examples/jsm/shaders/RGBShiftShader.js";
// --- Szene / Kamera / Renderer ---
const scene = new THREE.Scene();
@@ -48,18 +53,20 @@
const container = document.getElementById('viewer');
const renderer = new THREE.WebGLRenderer({ antialias:true });
renderer.outputColorSpace = THREE.SRGBColorSpace;
+
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
container.appendChild(renderer.domElement);
- // Foliage-Shader
+ //Foliage-Shader
const foliageTexture = new THREE.TextureLoader().load('assets/sprites/foliage.png');
foliageTexture.colorSpace = THREE.SRGBColorSpace;
+
const FoliageOverlayShader = {
uniforms: {
'tDiffuse': { value: null },
'tFoliage': { value: foliageTexture },
- 'opacity': { value: 1.0 }
+ 'opacity': { value: 1.0 } // 0 = aus, 1 = voll
},
vertexShader: `
varying vec2 vUv;
@@ -76,27 +83,81 @@
void main() {
vec4 base = texture2D(tDiffuse, vUv);
vec4 foliage = texture2D(tFoliage, vUv);
+ // Normales Overlay: Foliage-Alpha mischt drüber
gl_FragColor = mix(base, vec4(foliage.rgb, base.a), foliage.a * opacity);
}
`
};
- const foliageOverlayPass = new ShaderPass(FoliageOverlayShader);
- // Postprocessing
+ const foliageOverlayPass = new ShaderPass(FoliageOverlayShader);
+ foliageOverlayPass.uniforms['tFoliage'].value = foliageTexture;
+ foliageOverlayPass.uniforms['opacity'].value = 1.0; // ggf. anpassen
+
+
+
+ // post processing stack
const composer = new EffectComposer(renderer);
+
+ //SSAO
+ const ssaoPass = new SSAOPass(scene, camera, container.clientWidth, container.clientHeight);
+ ssaoPass.kernelRadius = 16; // Radius des AO-Effekts (6-16 experimentieren!)
+ ssaoPass.minDistance = 0.05; // Wie nah am Geometriepunkt AO startet
+ ssaoPass.maxDistance = 0.2; // Maximalweite (0.1–0.3 sieht am realistischsten aus)
+ ssaoPass.output = SSAOPass.OUTPUT.Default; // Normal/Default
+
+ ssaoPass.bias = 0.05; // "Füllung" (0.01–0.1)
+ ssaoPass.aoClamp = 0.5; // Kontrast/Abdunklung (0.1–1.0)
+ ssaoPass.lumInfluence = 1; // Wie stark das Umgebungslicht AO beeinflusst
+ ssaoPass.intensity = 1.7; // Hauptstärke des Effekts (1.0–3.0!)
+
+ //BLOOM
+ const bloomPass = new UnrealBloomPass(
+ new THREE.Vector2(container.clientWidth, container.clientHeight),
+ 0.8, // strength (1.0 ist gut, 0.3-2.0 experimentieren!)
+ 0.2, // radius (0.5-1.0)
+ 0.4 // threshold (alles, was heller als 0.0 ist, kann blühen)
+ );
+
+ //BRIGHTNESS-CONTRAST
+ const brightnessContrastPass = new ShaderPass(BrightnessContrastShader);
+ brightnessContrastPass.uniforms['brightness'].value = 0.25; // -1 bis +1
+ brightnessContrastPass.uniforms['contrast'].value = 0.51; // -1 bis +1 (0.2-0.7 sieht oft gut aus!)
+
+ //VIGNETTE-PASS
+ const vignettePass = new ShaderPass(VignetteShader);
+ vignettePass.uniforms['offset'].value = 0.3; // 1.0-2.0, experimentieren!
+ vignettePass.uniforms['darkness'].value = 1.35; // 1.0-2.5, je nach Mood
+
+ //RBG SHIFT (Chromatic Aberration)
+ const rgbShiftPass = new ShaderPass(RGBShiftShader);
+ rgbShiftPass.uniforms['amount'].value = 0.001; // Sehr subtil! (0.001–0.003)
+
+ //FILMPASS (Scanlines)
+ const filmPass = new FilmPass(
+ 1.15, // noise intensity
+ 0.125, // scanline intensity
+ 600, // scanline count
+ false // grayscale
+ );
+
+
+ const gammaPass = new ShaderPass(GammaCorrectionShader);
+
+
composer.addPass(new RenderPass(scene, camera));
composer.addPass(foliageOverlayPass);
- composer.addPass(new UnrealBloomPass(
- new THREE.Vector2(container.clientWidth, container.clientHeight),
- 0.8, 0.2, 0.4
- ));
- const vignettePass = new ShaderPass(VignetteShader);
- vignettePass.uniforms['offset'].value = 0.3;
- vignettePass.uniforms['darkness'].value = 1.35;
+ //composer.addPass(ssaoPass);
+ composer.addPass(bloomPass);
+ //composer.addPass(brightnessContrastPass);
composer.addPass(vignettePass);
- composer.addPass(new ShaderPass(GammaCorrectionShader));
+ composer.addPass(rgbShiftPass);
+ //composer.addPass(filmPass);
+ composer.addPass(gammaPass);
+
+
+ // end of postprocessing
+
- // Resize-Handler
function onResize(){
const W=container.clientWidth, H=container.clientHeight, winA=W/H;
let vw,vh,vx,vy;
@@ -110,9 +171,10 @@
renderer.setScissor(vx,vy,vw,vh);
renderer.setScissorTest(true);
renderer.toneMapping = THREE.ACESFilmicToneMapping;
- renderer.toneMappingExposure = 1.2;
+ renderer.toneMappingExposure = 1.2; // Belichtung erhöhen/niedriger testen!
renderer.outputColorSpace = THREE.SRGBColorSpace;
composer.setSize(W, H);
+ if (ssaoPass) ssaoPass.setSize(W, H);
const dpr = window.devicePixelRatio || 1;
renderer.setPixelRatio(dpr);
composer.setPixelRatio(dpr);
@@ -124,7 +186,7 @@
// --- HDRI Environment ---
const texLoader = new THREE.TextureLoader();
const pmremGen = new THREE.PMREMGenerator(renderer);
- texLoader.load('assets/hdri/environment.jpg', tex => {
+ texLoader.load('assets/hdri/environment.png', tex => {
const envRT = pmremGen.fromEquirectangular(tex).texture;
scene.environment = envRT;
scene.background = envRT;
@@ -133,8 +195,11 @@
});
// --- Schatten-gebendes Licht ---
+ //scene.add(new THREE.AmbientLight(0xFFA230, 0.25));
const sun = new THREE.DirectionalLight(0xFFA230, 2);
sun.position.set(21, -25, 30);
+ //sun.position.set(10, -10, 20);
+
sun.castShadow = true;
sun.shadow.mapSize.width = 2048;
sun.shadow.mapSize.height = 2048;
@@ -164,7 +229,7 @@
.map(name=>`assets/models/spirits/${name}`);
}
- // --- GLBs laden ---
+ // --- GLBs laden (kein Axis Swap) ---
async function loadGLB(path, pos, rotDeg, {receiveShadow=false, castShadow=false, emissive=null, visible=true, shadowOnly=false} = {}) {
const { scene: obj } = await gltfLoader.loadAsync(path);
obj.position.set(pos[0], pos[1], pos[2]);
@@ -174,7 +239,7 @@
THREE.MathUtils.degToRad(rotDeg[2])
);
obj.traverse(c => {
- c.visible = visible;
+ c.visible = visible; // MUSS true sein, sonst kein Schatten!
if (c.isMesh) {
c.castShadow = castShadow;
c.receiveShadow = receiveShadow;
@@ -189,16 +254,8 @@
return obj;
}
- // --- Shadow-Only-Material ---
- const shadowOnlyMaterial = new THREE.MeshBasicMaterial({
- color: 0x000000,
- opacity: 0.01,
- transparent: true,
- depthWrite: false
- });
-
// --- Variablen für Animation ---
- let spinnerRed, spinnerBlue, torigate, landscape, shadowTree;
+ let spinnerRed, spinnerBlue, torigate, environment, shadowTree;
const clock = new THREE.Clock();
let lastSpawn = 0;
const SPAWN_INT = 5;
@@ -209,77 +266,217 @@
const rotatingLights = [];
const counterRotatingLights = [];
const LIGHT_RADIUS = 1;
- for(let i=0;i<3;i++){
- const L = new THREE.PointLight(0xFFA230, 5, 30);
- L.castShadow = true;
- rotatingLights.push(L);
- scene.add(L);
- const L2 = new THREE.PointLight(0xFFA230, 5, 30);
- L2.castShadow = true;
- counterRotatingLights.push(L2);
- scene.add(L2);
+for(let i=0;i<3;i++){
+ const L = new THREE.PointLight(0xFFA230, 5, 30); // Intensität runter, Reichweite hoch
+ L.castShadow = true;
+ rotatingLights.push(L);
+ scene.add(L);
+
+ const L2 = new THREE.PointLight(0xFFA230, 5, 30);
+ L2.castShadow = true;
+ counterRotatingLights.push(L2);
+ scene.add(L2);
+}
+
+ // Teleport-Flash-Material
+ const flashMaterial = new THREE.MeshBasicMaterial({
+ color: 0xffffcc,
+ transparent: true,
+ opacity: 0.95,
+ blending: THREE.AdditiveBlending,
+ depthWrite: false
+ });
+
+ // Shadow-Only-Material
+ const shadowOnlyMaterial = new THREE.MeshBasicMaterial({
+ color: 0x000000,
+ opacity: 0.01,
+ transparent: true,
+ depthWrite: false
+ });
+
+
+// === Partikel-Textur vorbereiten (einfacher weißer Kreis) ===
+const particleTexture = new THREE.TextureLoader().load('assets/sprites/particle.png');
+particleTexture.colorSpace = THREE.SRGBColorSpace;
+// === Audio vorbereiten ===
+/*
+const listener = new THREE.AudioListener();
+camera.add(listener);
+const soundBufferPromise = fetch('assets/sound/spawn.ogg')
+ .then(r=>r.arrayBuffer())
+ .then(buf=>new Promise(res=>{
+ const audio = new AudioContext();
+ audio.decodeAudioData(buf, res);
+ }));
+*/
+
+// === Neue Spirit-Klasse ===
+class Spirit {
+ constructor(position) {
+ this.clock = new THREE.Clock();
+ this.grp = new THREE.Group();
+ this.spiritMeshes = [];
+ this.isFading = true; // Für den Material-Überblend
+ // Positionieren (auf Boden, dann +1m für Partikeleffekt)
+ this.grp.position.copy(position);
+ this.grp.position.z -= 0.6;
+ scene.add(this.grp);
+
+ // Partikel-Spawn
+ this.particles = this.makeParticles();
+ this.particles.position.y = 1.0; // Mitte Spirit
+ this.grp.add(this.particles);
+
+ // Sound abspielen (async, aber egal)
+ /*
+ soundBufferPromise.then(buffer => {
+ const sfx = new THREE.Audio(listener);
+ sfx.setBuffer(buffer);
+ sfx.setVolume(0.7);
+ sfx.play();
+ // Sound ist "virtuell" an Spirit-Gruppe gebunden, keine Entfernungsmischung
+ });
+ */
+
+ // Laden Spirit-GLB
+ const path = spiritModels[Math.floor(Math.random()*spiritModels.length)];
+ gltfLoader.load(path, ({ scene: s }) => {
+ s.traverse(mesh => {
+ if(mesh.isMesh){
+ mesh.castShadow = true;
+ mesh.receiveShadow = true;
+ // Originalmaterial speichern
+ mesh.userData.originalMaterial = mesh.material.clone();
+ mesh.material = mesh.material.clone();
+ // Direkt auf Flash-Farbe setzen, transparent!
+ mesh.material.color.set(0xffffcc);
+ //mesh.material.transparent = true;
+ mesh.material.opacity = 0.0;
+ mesh.material.emissive?.set(0xffffcc); // Falls vorhanden
+ mesh.material.emissiveIntensity = 2.0;
+ this.spiritMeshes.push(mesh);
+ }
+ });
+ s.rotation.x = -Math.PI;
+ this.grp.add(s);
+ });
+ }
+
+ // === Partikel-Effekt erstellen ===
+ makeParticles() {
+ const N = 36; // Partikelanzahl
+ const geom = new THREE.BufferGeometry();
+ const positions = [];
+ const velocities = [];
+ for(let i=0; i this.particles.userData.lifetime){
+ this.grp.remove(this.particles);
+ this.particles.geometry.dispose();
+ this.particles.material.dispose();
+ this.particles = null;
+ }
}
- // --- Spirit-Klasse ohne Partikel & Sound ---
- class Spirit {
- constructor(position) {
- this.clock = new THREE.Clock();
- this.grp = new THREE.Group();
- this.spiritMeshes = [];
- this.isFading = true;
- this.grp.position.copy(position);
- this.grp.position.z -= 0.6;
- scene.add(this.grp);
-
- // Laden Spirit-GLB
- const path = spiritModels[Math.floor(Math.random()*spiritModels.length)];
- gltfLoader.load(path, ({ scene: s }) => {
- s.traverse(mesh => {
- if(mesh.isMesh){
- mesh.castShadow = true;
- mesh.receiveShadow = true;
- mesh.userData.originalMaterial = mesh.material.clone();
- mesh.material = mesh.material.clone();
- mesh.material.color.set(0xffffcc);
- mesh.material.opacity = 0.0;
- mesh.material.emissive?.set(0xffffcc);
- mesh.material.emissiveIntensity = 2.0;
- this.spiritMeshes.push(mesh);
- }
- });
- s.rotation.x = -Math.PI;
- this.grp.add(s);
- });
- }
-
- update(dt) {
- const t = this.clock.getElapsedTime();
- if(this.spiritMeshes && this.isFading){
- for(const mesh of this.spiritMeshes){
- if(t < 0.5){
- mesh.material.opacity = 1;
- mesh.material.color.lerp(mesh.userData.originalMaterial.color, t/0.5);
- if(mesh.material.emissive)
- mesh.material.emissive.lerp(mesh.userData.originalMaterial.emissive || new THREE.Color(0x000000), t/0.5);
- mesh.material.emissiveIntensity = 2.0 * (1-t/0.5) + (mesh.userData.originalMaterial.emissiveIntensity||1.0)*(t/0.5);
- } else {
- mesh.material.opacity = mesh.userData.originalMaterial.opacity ?? 1.0;
- mesh.material.color.copy(mesh.userData.originalMaterial.color);
- if(mesh.material.emissive)
- mesh.material.emissive.copy(mesh.userData.originalMaterial.emissive || new THREE.Color(0x000000));
- mesh.material.emissiveIntensity = mesh.userData.originalMaterial.emissiveIntensity ?? 1.0;
- this.isFading = false;
- }
- }
+ // === Spirit-Meshes aus dem Licht in Textur/Originalfarbe blenden ===
+ if(this.spiritMeshes && this.isFading){
+ for(const mesh of this.spiritMeshes){
+ if(t < 0.5){
+ mesh.material.opacity = 1;
+ mesh.material.color.lerp(mesh.userData.originalMaterial.color, t/0.5);
+ if(mesh.material.emissive)
+ mesh.material.emissive.lerp(mesh.userData.originalMaterial.emissive || new THREE.Color(0x000000), t/0.5);
+ mesh.material.emissiveIntensity = 2.0 * (1-t/0.5) + (mesh.userData.originalMaterial.emissiveIntensity||1.0)*(t/0.5);
+ } else {
+ mesh.material.opacity = mesh.userData.originalMaterial.opacity ?? 1.0;
+ mesh.material.color.copy(mesh.userData.originalMaterial.color);
+ if(mesh.material.emissive)
+ mesh.material.emissive.copy(mesh.userData.originalMaterial.emissive || new THREE.Color(0x000000));
+ mesh.material.emissiveIntensity = mesh.userData.originalMaterial.emissiveIntensity ?? 1.0;
+ this.isFading = false; // fertig
}
- this.grp.position.y -= MOVE_SPEED * dt;
- if (t > 15) {
- scene.remove(this.grp);
+ }
+ }
+
+ // === Bewegung nach unten ===
+ this.grp.position.y -= MOVE_SPEED * dt;
+ if (t > 15) {
+ scene.remove(this.grp);
+ return false;
+ }
+ return true;
+ }
+}
+
+
+
+
+ function spawnPair(){
+ // Spirit
+ const sp = new Spirit(spinnerRed.position.clone().add(new THREE.Vector3(0,-1.5,0)));
+ spawned.push(sp);
+ // Light
+ /*
+ const pl = new THREE.PointLight(0xffe0b3,0,0);
+ pl.position.copy(spinnerRed.position).add(new THREE.Vector3(0,-2,0.5));
+ pl.clock = new THREE.Clock();
+ pl.update = function(dt){
+ this.position.y -= MOVE_SPEED*dt;
+ if(this.clock.getElapsedTime()>20){
+ scene.remove(this);
return false;
}
return true;
- }
+ };
+ scene.add(pl);
+ spawned.push(pl);
+ */
}
// --- Spinner (mit Transparenz) ---
@@ -296,8 +493,10 @@
if (c.isMesh && c.material && c.material.isMeshStandardMaterial) {
c.material.transparent = true;
c.material.opacity = opacity;
+ // <- Das hier ist NEU!
c.material.emissive = new THREE.Color(color);
- c.material.emissiveIntensity = 3.0;
+ c.material.emissiveIntensity = 3.0; // experimentier ruhig mit höheren Werten!
+ // <- Optional: VertexColors aktivieren, falls du sie willst
c.material.vertexColors = true;
c.castShadow = true;
}
@@ -309,42 +508,53 @@
// --- Haupt-Flow ---
(async()=>{
await fetchSpirits();
- landscape = await loadGLB(
- 'assets/models/landscape.glb',
+
+ // Environment (empfängt Schatten)
+ environment = await loadGLB(
+ 'assets/models/environment.glb',
[0,0,0], [90,0,0],
{receiveShadow:true, castShadow:true}
);
+
+ // Tori Gate
torigate = await loadGLB(
- 'assets/models/tori.glb',
+ 'assets/models/torigate.glb',
[0,6.59,0.375], [90,0,0],
{receiveShadow:true, castShadow:true}
);
+
+ // Spinner rot (z.B. models/spinner_red.glb) & Spinner blau (z.B. spinner_blue.glb)
spinnerRed = await loadSpinner(
'assets/models/spinner_red.glb', [0,16.55,0.88], [90,0,0], "#ff3333", 0.2
);
spinnerBlue = await loadSpinner(
'assets/models/spinner_blue.glb', [0,16.55,0.88], [90,0,0], "#3380ff", 0.2
);
+
+ // --- Low Poly Bäume als unsichtbare Schattenobjekte einbauen:
shadowTree = await loadGLB(
'assets/models/tree_low.glb',
[0.0,0.0,0.0], [90,0,0],
{receiveShadow:false, castShadow:true, shadowOnly: true}
);
+ // --- Animation / Render-Loop ---
function animate(){
const dt = clock.getDelta(), t = clock.getElapsedTime();
- // Spinner Animation
- const bob = Math.sin(t*1.2)*0.5;
+ // Spinner Animation: Rotation & Schweben
+ const bob = Math.sin(t*1.2)*0.5; // sanftes Schweben
const baseY = 16.55 + bob;
spinnerRed.position.y = baseY + 0.8;
spinnerBlue.position.y = baseY;
- spinnerRed.rotation.y -= 1.2 * dt;
- spinnerBlue.rotation.y += 1.2 * dt;
+
+ spinnerRed.rotation.y -= 1.2 * dt; // Uhrzeigersinn
+ spinnerBlue.rotation.y += 1.2 * dt; // Gegen-Uhrzeigersinn
// Rotierende Lichter
const center = new THREE.Vector3(0, 16.55, 1.5);
- const lightZ = center.z;
+ const lightZ = center.z; // Fix auf Z wie Spinner
+ // Normale Richtung (wie bisher)
for(let i=0; i SPAWN_INT) {
lastSpawn = t;
- spawned.push(new Spirit(spinnerRed.position.clone().add(new THREE.Vector3(0,-1.5,0))));
+ spawnPair();
}
// update & cleanup