diff --git a/index.html b/index.html
index b3c6a97..2450dba 100644
--- a/index.html
+++ b/index.html
@@ -32,15 +32,10 @@
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'; // 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";
+ 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";
// --- Szene / Kamera / Renderer ---
const scene = new THREE.Scene();
@@ -53,20 +48,18 @@
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 } // 0 = aus, 1 = voll
+ 'opacity': { value: 1.0 }
},
vertexShader: `
varying vec2 vUv;
@@ -83,81 +76,27 @@
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);
- foliageOverlayPass.uniforms['tFoliage'].value = foliageTexture;
- foliageOverlayPass.uniforms['opacity'].value = 1.0; // ggf. anpassen
-
-
- // post processing stack
+ // Postprocessing
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(ssaoPass);
- composer.addPass(bloomPass);
- //composer.addPass(brightnessContrastPass);
+ 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(vignettePass);
- composer.addPass(rgbShiftPass);
- //composer.addPass(filmPass);
- composer.addPass(gammaPass);
-
-
- // end of postprocessing
-
+ composer.addPass(new ShaderPass(GammaCorrectionShader));
+ // Resize-Handler
function onResize(){
const W=container.clientWidth, H=container.clientHeight, winA=W/H;
let vw,vh,vx,vy;
@@ -171,10 +110,9 @@
renderer.setScissor(vx,vy,vw,vh);
renderer.setScissorTest(true);
renderer.toneMapping = THREE.ACESFilmicToneMapping;
- renderer.toneMappingExposure = 1.2; // Belichtung erhöhen/niedriger testen!
+ renderer.toneMappingExposure = 1.2;
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);
@@ -186,7 +124,7 @@
// --- HDRI Environment ---
const texLoader = new THREE.TextureLoader();
const pmremGen = new THREE.PMREMGenerator(renderer);
- texLoader.load('assets/hdri/environment.png', tex => {
+ texLoader.load('assets/hdri/environment.jpg', tex => {
const envRT = pmremGen.fromEquirectangular(tex).texture;
scene.environment = envRT;
scene.background = envRT;
@@ -195,11 +133,8 @@
});
// --- 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;
@@ -229,7 +164,7 @@
.map(name=>`assets/models/spirits/${name}`);
}
- // --- GLBs laden (kein Axis Swap) ---
+ // --- GLBs laden ---
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]);
@@ -239,7 +174,7 @@
THREE.MathUtils.degToRad(rotDeg[2])
);
obj.traverse(c => {
- c.visible = visible; // MUSS true sein, sonst kein Schatten!
+ c.visible = visible;
if (c.isMesh) {
c.castShadow = castShadow;
c.receiveShadow = receiveShadow;
@@ -254,8 +189,16 @@
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, environment, shadowTree;
+ let spinnerRed, spinnerBlue, torigate, landscape, shadowTree;
const clock = new THREE.Clock();
let lastSpawn = 0;
const SPAWN_INT = 5;
@@ -266,217 +209,77 @@
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);
-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);
+ }
- const L2 = new THREE.PointLight(0xFFA230, 5, 30);
- L2.castShadow = true;
- counterRotatingLights.push(L2);
- scene.add(L2);
-}
+ // --- 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);
- // Teleport-Flash-Material
- const flashMaterial = new THREE.MeshBasicMaterial({
- color: 0xffffcc,
- transparent: true,
- opacity: 0.95,
- blending: THREE.AdditiveBlending,
- depthWrite: false
- });
+ // 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);
+ });
+ }
- // 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);
+ 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;
+ }
+ }
}
- });
- 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-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
- }
- }
- }
-
- // === 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);
+ this.grp.position.y -= MOVE_SPEED * dt;
+ if (t > 15) {
+ scene.remove(this.grp);
return false;
}
return true;
- };
- scene.add(pl);
- spawned.push(pl);
- */
+ }
}
// --- Spinner (mit Transparenz) ---
@@ -493,10 +296,8 @@ class Spirit {
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; // experimentier ruhig mit höheren Werten!
- // <- Optional: VertexColors aktivieren, falls du sie willst
+ c.material.emissiveIntensity = 3.0;
c.material.vertexColors = true;
c.castShadow = true;
}
@@ -508,53 +309,42 @@ class Spirit {
// --- Haupt-Flow ---
(async()=>{
await fetchSpirits();
-
- // Environment (empfängt Schatten)
- environment = await loadGLB(
- 'assets/models/environment.glb',
+ landscape = await loadGLB(
+ 'assets/models/landscape.glb',
[0,0,0], [90,0,0],
{receiveShadow:true, castShadow:true}
);
-
- // Tori Gate
torigate = await loadGLB(
- 'assets/models/torigate.glb',
+ 'assets/models/tori.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: Rotation & Schweben
- const bob = Math.sin(t*1.2)*0.5; // sanftes Schweben
+ // Spinner Animation
+ const bob = Math.sin(t*1.2)*0.5;
const baseY = 16.55 + bob;
spinnerRed.position.y = baseY + 0.8;
spinnerBlue.position.y = baseY;
-
- spinnerRed.rotation.y -= 1.2 * dt; // Uhrzeigersinn
- spinnerBlue.rotation.y += 1.2 * dt; // Gegen-Uhrzeigersinn
+ spinnerRed.rotation.y -= 1.2 * dt;
+ spinnerBlue.rotation.y += 1.2 * dt;
// Rotierende Lichter
const center = new THREE.Vector3(0, 16.55, 1.5);
- const lightZ = center.z; // Fix auf Z wie Spinner
- // Normale Richtung (wie bisher)
+ const lightZ = center.z;
for(let i=0; i SPAWN_INT) {
lastSpawn = t;
- spawnPair();
+ spawned.push(new Spirit(spinnerRed.position.clone().add(new THREE.Vector3(0,-1.5,0))));
}
// update & cleanup