/* ============================================================ 晟算科技 · 太空场景引擎 v3 —— 电影级真实感 真实月面(NASA/SSS) + 真实星野(银河) + ACES调色 + 泛光 撕裂伤口 + 碎石量级谱 + 尘埃云 模块加载(import map), 模式: journey / orbit ============================================================ */ import * as THREE from "three"; import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js"; import { RenderPass } from "three/addons/postprocessing/RenderPass.js"; import { UnrealBloomPass } from "three/addons/postprocessing/UnrealBloomPass.js"; if (!window.SPACE_MODE) { window.SPACE_MODE = document.body.getAttribute("data-space") || "journey"; } (function () { "use strict"; var isMobile = window.innerWidth < 768 || "ontouchstart" in window; var reducedMotion = window.matchMedia("(prefers-reduced-motion: reduced)").matches; var bootEl = document.getElementById("spaceBoot"); var canvasHost = document.getElementById("spaceCanvas"); var isJourney = window.SPACE_MODE === "journey"; function fallback() { if (bootEl) bootEl.remove(); document.body.classList.add("space-fallback", "space-ready"); } var testC = document.createElement("canvas"); if (!(testC.getContext("webgl") || testC.getContext("experimental-webgl"))) { fallback(); return; } /* ============================================================ 工具 ============================================================ */ function glowTexture(inner, mid, size) { size = size || 256; var c = document.createElement("canvas"); c.width = c.height = size; var x = c.getContext("2d"); var g = x.createRadialGradient(size/2, size/2, 0, size/2, size/2, size/2); g.addColorStop(0, inner); g.addColorStop(0.25, inner.replace(/[\d.]+\)$/, "0.5)")); g.addColorStop(0.55, mid.replace(/[\d.]+\)$/, "0.18)")); g.addColorStop(0.8, mid.replace(/[\d.]+\)$/, "0.05)")); g.addColorStop(1, "rgba(0,0,0,0)"); x.fillStyle = g; x.fillRect(0, 0, size, size); return new THREE.CanvasTexture(c); } // Three.js SphereGeometry 的标准 UV 映射 function uvToSphere(u, v, r) { var phi = 2 * Math.PI * u; var theta = Math.PI * v; return new THREE.Vector3( -r * Math.cos(phi) * Math.sin(theta), r * Math.cos(theta), r * Math.sin(phi) * Math.sin(theta) ); } // 冲击点: 朝向 +x+z (首页相机可见侧) var IMPACT = { u: 0.334, v: 0.47 }; /* ============================================================ 场景与渲染器(ACES电影调色) ============================================================ */ var scene = new THREE.Scene(); var camera = new THREE.PerspectiveCamera(55, innerWidth / innerHeight, 0.1, 3000); var rig = new THREE.Group(); rig.add(camera); scene.add(rig); var renderer; try { renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true, powerPreference: "high-performance" }); } catch (e) { fallback(); return; } renderer.setPixelRatio(Math.min(devicePixelRatio, isMobile ? 1.2 : 1.6)); renderer.setSize(innerWidth, innerHeight); renderer.toneMapping = THREE.ACESFilmicToneMapping; // 电影级色调映射 renderer.toneMappingExposure = 1.15; renderer.outputColorSpace = THREE.SRGBColorSpace; canvasHost.appendChild(renderer.domElement); /* ---------- 泛光(只有高亮物体发光: 太阳/裂缝/代码粒子) ---------- */ var composer = null, bloomPass = null; if (!isMobile) { composer = new EffectComposer(renderer); composer.addPass(new RenderPass(scene, camera)); bloomPass = new UnrealBloomPass(new THREE.Vector2(innerWidth, innerHeight), 0.85, 0.55, 0.78); composer.addPass(bloomPass); } /* ============================================================ 资产加载(真实纹理) ============================================================ */ var texLoader = new THREE.TextureLoader(); var loadState = { moon: false, sky: false }; /* ---------- 真实星野天球(银河横贯) ---------- */ var skyMesh = null; texLoader.load("images/starfield.jpg", function (tex) { tex.colorSpace = THREE.SRGBColorSpace; tex.mapping = THREE.EquirectangularReflectionMapping; var mat = new THREE.MeshBasicMaterial({ map: tex, side: THREE.BackSide, depthWrite: false }); mat.fog = false; skyMesh = new THREE.Mesh(new THREE.SphereGeometry(1200, 48, 32), mat); skyMesh.rotation.y = 1.35; // 让银河带横贯可见半球 skyMesh.rotation.x = 0.3; scene.add(skyMesh); loadState.sky = true; }, undefined, function () { loadState.sky = true; }); /* ---------- 近景尘埃(保留视差最快层) ---------- */ function makeDustLayer(count, spread) { var geo = new THREE.BufferGeometry(); var pos = new Float32Array(count * 3); var col = new Float32Array(count * 3); for (var i = 0; i < count; i++) { pos[i*3] = (Math.random()-0.5) * spread; pos[i*3+1] = (Math.random()-0.5) * spread; pos[i*3+2] = (Math.random()-0.5) * spread; var b = 0.5 + Math.random() * 0.5; col[i*3] = b * 0.85; col[i*3+1] = b * 0.95; col[i*3+2] = b; } geo.setAttribute("position", new THREE.BufferAttribute(pos, 3)); geo.setAttribute("color", new THREE.BufferAttribute(col, 3)); var mat = new THREE.PointsMaterial({ size: 1.1, vertexColors: true, transparent: true, opacity: 0.55, sizeAttenuation: true, depthWrite: false }); return new THREE.Points(geo, mat); } var nearDust = makeDustLayer(isMobile ? 100 : 220, 200); rig.add(nearDust); /* ---------- 太阳: 小而烈 + 泛光 + 横向光纹 ---------- */ var sunGroup = new THREE.Group(); // 亮度>1的核心,推过泛光阈值 var sunCore = new THREE.Mesh( new THREE.SphereGeometry(5, 24, 24), new THREE.MeshBasicMaterial({ color: new THREE.Color(2.6, 2.3, 1.9) }) ); sunGroup.add(sunCore); // 柔和光晕(克制) var sunHalo = new THREE.Sprite(new THREE.SpriteMaterial({ map: glowTexture("rgba(255,232,190,0.85)", "rgba(255,170,90,0.6)"), transparent: true, opacity: 0.4, blending: THREE.AdditiveBlending, depthWrite: false })); sunHalo.scale.set(46, 46, 1); sunGroup.add(sunHalo); // 变形宽银幕横向光纹 var streakC = document.createElement("canvas"); streakC.width = 256; streakC.height = 8; var sx = streakC.getContext("2d"); var sg = sx.createLinearGradient(0, 0, 256, 0); sg.addColorStop(0, "rgba(255,215,160,0)"); sg.addColorStop(0.5, "rgba(255,235,200,0.85)"); sg.addColorStop(1, "rgba(255,215,160,0)"); sx.fillStyle = sg; sx.fillRect(0, 0, 256, 8); var streakTex = new THREE.CanvasTexture(streakC); var sunStreak = new THREE.Sprite(new THREE.SpriteMaterial({ map: streakTex, transparent: true, opacity: 0.55, blending: THREE.AdditiveBlending, depthWrite: false })); sunStreak.scale.set(90, 1.6, 1); sunGroup.add(sunStreak); sunGroup.position.set(-150, 70, -260); scene.add(sunGroup); /* ============================================================ 月亮: 真实纹理 + 撕裂伤口 + 发光裂缝 ============================================================ */ var moonGroup = new THREE.Group(); var MOON_R = 10; var moonMat = new THREE.MeshStandardMaterial({ color: 0xc9ccd2, // 冷调青灰(提亮度,纹理可见) roughness: 0.98, metalness: 0.0, bumpScale: 0.9 // 更强环形山起伏 }); var moon = null; // 发光裂缝纹理(emissiveMap): 冲击点+放射裂纹+断面代码 function makeCrackEmissive() { var W = 1024, H = 512; var c = document.createElement("canvas"); c.width = W; c.height = H; var x = c.getContext("2d"); x.fillStyle = "#000"; x.fillRect(0, 0, W, H); var ix = IMPACT.u * W, iy = IMPACT.v * H; // 断面发光代码结构 x.save(); x.beginPath(); x.arc(ix, iy, 58, 0, 7); x.clip(); x.strokeStyle = "rgba(90,255,195,0.5)"; x.lineWidth = 1; for (var g = -58; g <= 58; g += 9) { x.beginPath(); x.moveTo(ix + g, iy - 58); x.lineTo(ix + g, iy + 58); x.stroke(); x.beginPath(); x.moveTo(ix - 58, iy + g); x.lineTo(ix + 58, iy + g); x.stroke(); } var chars = "01{}<>/=;#*"; x.font = "9px monospace"; x.textBaseline = "top"; for (var i = 0; i < 55; i++) { x.fillStyle = "rgba(160,255,215," + (0.4 + Math.random() * 0.6) + ")"; x.fillText(chars[Math.floor(Math.random() * chars.length)], ix - 52 + Math.random() * 104, iy - 52 + Math.random() * 104); } x.restore(); // 冲击核心辉光 var core = x.createRadialGradient(ix, iy, 0, ix, iy, 80); core.addColorStop(0, "rgba(200,255,230,1)"); core.addColorStop(0.3, "rgba(120,255,205,0.5)"); core.addColorStop(1, "rgba(80,230,175,0)"); x.fillStyle = core; x.beginPath(); x.arc(ix, iy, 80, 0, 7); x.fill(); // 放射裂纹(宽淡→窄亮) x.lineCap = "round"; x.lineJoin = "round"; for (var c2 = 0; c2 < 6; c2++) { var ang = (c2 / 6) * Math.PI * 2 + (Math.random() - 0.5) * 0.8; var px = ix, py = iy; var pts = [[px, py]]; var len = 0.16 + Math.random() * 0.2; var steps = 6 + Math.floor(Math.random() * 4); for (var s = 0; s < steps; s++) { ang += (Math.random() - 0.5) * 0.8; px += Math.cos(ang) * (len * W / steps); py += Math.sin(ang) * (len * W / steps) * 0.55; pts.push([px, py]); } [[4, "rgba(70,220,170,0.06)"], [2, "rgba(100,240,190,0.12)"], [0.9, "rgba(200,255,235,0.75)"]].forEach(function (ps) { x.strokeStyle = ps[1]; x.lineWidth = ps[0]; x.beginPath(); x.moveTo(pts[0][0], pts[0][1]); for (var k = 1; k < pts.length; k++) x.lineTo(pts[k][0], pts[k][1]); x.stroke(); }); } return new THREE.CanvasTexture(c); } // 顶点撕裂: 把伤口区域向内撕成参差轮廓 function tearGeometry(geo, woundDir) { var pos = geo.attributes.position; var v = new THREE.Vector3(); for (var i = 0; i < pos.count; i++) { v.fromBufferAttribute(pos, i).normalize(); var ang = v.angleTo(woundDir); if (ang < 0.45) { var fall = 1 - ang / 0.45; // 高频噪声 → 锯齿而非圆坑 var n = 0.5 + 0.5 * Math.sin(v.x * 13.1) * Math.sin(v.y * 9.7) * Math.sin(v.z * 11.3) + 0.25 * Math.sin(v.x * 31.7 + v.y * 23.9); n = Math.max(0, Math.min(1, n)); var depth = fall * (0.5 + 3.4 * n * (0.35 + 0.65 * fall)); if (ang > 0.28) depth += (Math.random() - 0.5) * 0.55; // 撕裂缘参差 var newR = MOON_R - Math.max(0, depth); pos.setXYZ(i, v.x * newR, v.y * newR, v.z * newR); } } geo.computeVertexNormals(); } var woundDirLocal = uvToSphere(IMPACT.u, IMPACT.v, 1).normalize(); var woundPoint = uvToSphere(IMPACT.u, IMPACT.v, MOON_R * 0.98); texLoader.load("images/moon_color.jpg", function (colorTex) { colorTex.colorSpace = THREE.SRGBColorSpace; moonMat.map = colorTex; moonMat.emissiveMap = makeCrackEmissive(); moonMat.emissive = new THREE.Color(0x9fffd8); moonMat.emissiveIntensity = 2.1; // 亮度转凹凸(NASA位移图下载失败时的运行时方案,效果扎实) var img = colorTex.image; var bc = document.createElement("canvas"); bc.width = 1024; bc.height = 512; var bx = bc.getContext("2d"); bx.drawImage(img, 0, 0, 1024, 512); var id = bx.getImageData(0, 0, 1024, 512); var d = id.data; for (var i = 0; i < d.length; i += 4) { var lum = d[i] * 0.299 + d[i+1] * 0.587 + d[i+2] * 0.114; lum = Math.max(0, Math.min(255, (lum - 110) * 1.7 + 100)); d[i] = d[i+1] = d[i+2] = lum; } bx.putImageData(id, 0, 0); moonMat.bumpMap = new THREE.CanvasTexture(bc); var geo = new THREE.SphereGeometry(MOON_R, isMobile ? 64 : 96, isMobile ? 48 : 72); tearGeometry(geo, woundDirLocal); moon = new THREE.Mesh(geo, moonMat); moonGroup.add(moon); loadState.moon = true; }, undefined, function () { loadState.moon = true; }); // 月缘柔光(青灰,克制——别洗白纹理) var moonHalo = new THREE.Sprite(new THREE.SpriteMaterial({ map: glowTexture("rgba(175,200,195,0.5)", "rgba(160,190,185,0.4)"), transparent: true, opacity: 0.16, blending: THREE.AdditiveBlending, depthWrite: false })); moonHalo.scale.set(26, 26, 1); moonGroup.add(moonHalo); // 伤口溢出辉光(活着的事件) var woundGlow = new THREE.Sprite(new THREE.SpriteMaterial({ map: glowTexture("rgba(160,255,220,0.9)", "rgba(90,255,195,0.55)"), transparent: true, opacity: 0.8, blending: THREE.AdditiveBlending, depthWrite: false })); woundGlow.scale.set(11, 11, 1); woundGlow.position.copy(woundPoint).multiplyScalar(1.04); moonGroup.add(woundGlow); /* ---------- 大碎片(2块,顶点扰动,真月面) ---------- */ var fragments = []; var fragSpecs = [ { size: 2.4, scatter: new THREE.Vector3(8.5, 4.5, 4), home: new THREE.Vector3(6.8, 3.6, 2.8), spin: 0.16, period: 14 }, { size: 1.6, scatter: new THREE.Vector3(-6.5, -5, 5), home: new THREE.Vector3(-5, -3.8, 3.8), spin: -0.24, period: 9 } ]; fragSpecs.forEach(function (spec) { var geo = new THREE.IcosahedronGeometry(spec.size, 2); var pa = geo.getAttribute("position"); var vv = new THREE.Vector3(); for (var i = 0; i < pa.count; i++) { vv.fromBufferAttribute(pa, i).normalize(); var j = 0.7 + Math.random() * 0.55; pa.setXYZ(i, vv.x * spec.size * j, vv.y * spec.size * j, vv.z * spec.size * j); } geo.computeVertexNormals(); var mesh = new THREE.Mesh(geo, moonMat); mesh.position.copy(spec.scatter); mesh.rotation.set(Math.random() * 3, Math.random() * 3, Math.random() * 3); moonGroup.add(mesh); fragments.push({ mesh: mesh, spec: spec, phase: Math.random() * Math.PI * 2 }); }); /* ---------- 碎石量级谱(InstancedMesh, 从伤口拖出) ---------- */ var DEBRIS_N = isMobile ? 90 : 220; var debrisData = []; var debrisMesh = null; (function buildDebris() { var rockGeo = new THREE.IcosahedronGeometry(1, 0); var rp = rockGeo.getAttribute("position"); for (var i = 0; i < rp.count; i++) { var j = 0.65 + Math.random() * 0.7; rp.setXYZ(i, rp.getX(i) * j, rp.getY(i) * j, rp.getZ(i) * j); } rockGeo.computeVertexNormals(); debrisMesh = new THREE.InstancedMesh(rockGeo, moonMat, DEBRIS_N); var tangent = new THREE.Vector3().crossVectors(woundDirLocal, new THREE.Vector3(0, 1, 0)).normalize(); var bitan = new THREE.Vector3().crossVectors(woundDirLocal, tangent).normalize(); var dummy = new THREE.Object3D(); for (var d = 0; d < DEBRIS_N; d++) { var along = (Math.random() - 0.3) * 48; // 沿切线拖带(更宽) var out = 3 + Math.random() * 15; // 向外飞散距离(不贴月) var pos = woundPoint.clone() .add(tangent.clone().multiplyScalar(along)) .add(woundDirLocal.clone().multiplyScalar(out)) .add(bitan.clone().multiplyScalar((Math.random() - 0.5) * 5)); var scale = 0.06 + Math.pow(Math.random(), 2.2) * 0.55; // 大量小颗,少数大颗 var vel = woundDirLocal.clone().multiplyScalar(0.1 + Math.random() * 0.25) .add(tangent.clone().multiplyScalar((Math.random() - 0.5) * 0.1)); var axis = new THREE.Vector3(Math.random()-0.5, Math.random()-0.5, Math.random()-0.5).normalize(); debrisData.push({ pos: pos, scale: scale, vel: vel, axis: axis, rotSpeed: (Math.random() - 0.5) * 0.6, phase: Math.random() * Math.PI * 2, wobble: 0.3 + Math.random() * 0.7, period: 8 + Math.random() * 12 }); dummy.position.copy(pos); dummy.scale.setScalar(scale); dummy.updateMatrix(); debrisMesh.setMatrixAt(d, dummy.matrix); } debrisMesh.instanceMatrix.needsUpdate = true; moonGroup.add(debrisMesh); })(); /* ---------- 尘埃云(烟尘,参考图灵魂) ---------- */ var dustClouds = []; var tangent2 = new THREE.Vector3().crossVectors(woundDirLocal, new THREE.Vector3(0, 1, 0)).normalize(); for (var dc = 0; dc < (isMobile ? 3 : 5); dc++) { var dustTex = glowTexture("rgba(150,175,165,0.55)", "rgba(140,170,160,0.35)", 256); var dust = new THREE.Sprite(new THREE.SpriteMaterial({ map: dustTex, transparent: true, opacity: 0.05 + Math.random() * 0.06, blending: THREE.NormalBlending, depthWrite: false })); var sc2 = 22 + Math.random() * 26; dust.scale.set(sc2 * 1.6, sc2, 1); var along2 = 4 + dc * 7 + Math.random() * 4; dust.position.copy(woundPoint) .add(tangent2.clone().multiplyScalar(along2)) .add(woundDirLocal.clone().multiplyScalar(2 + Math.random() * 4)); moonGroup.add(dust); dustClouds.push({ sp: dust, base: dust.position.clone(), phase: Math.random() * Math.PI * 2, along: along2 }); } // 修复光缝 var seams = []; var seamTex = glowTexture("rgba(160,255,220,1)", "rgba(90,255,195,0.6)", 64); fragSpecs.forEach(function (spec) { var seam = new THREE.Sprite(new THREE.SpriteMaterial({ map: seamTex, color: 0x9fffd8, transparent: true, opacity: 0, blending: THREE.AdditiveBlending, depthWrite: false })); seam.scale.set(spec.size * 3.2, spec.size * 1.2, 1); seam.position.copy(spec.home).multiplyScalar(0.94); moonGroup.add(seam); seams.push(seam); }); moonGroup.position.set(6, 1, -26); scene.add(moonGroup); /* ---------- 代码泄漏粒子 ---------- */ var codeChars = "01{}<>/=;#*$fx"; var charCache = {}; function charTex(ch) { if (!charCache[ch]) { var s = 48, c = document.createElement("canvas"); c.width = c.height = s; var x = c.getContext("2d"); x.font = "bold 30px monospace"; x.textAlign = "center"; x.textBaseline = "middle"; x.shadowColor = "#9fffd8"; x.shadowBlur = 9; x.fillStyle = "#d6ffee"; x.fillText(ch, s / 2, s / 2); charCache[ch] = new THREE.CanvasTexture(c); } return charCache[ch]; } var LEAK_N = isMobile ? 20 : 40; var leaks = []; var leakBase = woundPoint.clone().add(moonGroup.position); for (var li = 0; li < LEAK_N; li++) { var sp = new THREE.Sprite(new THREE.SpriteMaterial({ map: charTex(codeChars[Math.floor(Math.random() * codeChars.length)]), transparent: true, opacity: 0, depthWrite: false, blending: THREE.AdditiveBlending })); var sc3 = 0.4 + Math.random() * 0.55; sp.scale.set(sc3, sc3, 1); var emit = leakBase.clone().add(new THREE.Vector3((Math.random()-0.5)*3, (Math.random()-0.5)*3, (Math.random()-0.5)*3)); sp.position.copy(emit); scene.add(sp); leaks.push({ sp: sp, life: Math.random(), speed: 0.08 + Math.random() * 0.14, drift: woundDirLocal.clone().multiplyScalar(0.4 + Math.random() * 0.5) .add(new THREE.Vector3((Math.random()-0.5)*0.4, (Math.random()-0.5)*0.4, (Math.random()-0.5)*0.3)), origin: emit }); } /* ---------- 光照(前左上方主光: 月面左亮右暗,环形山显形) ---------- */ var sunLight = new THREE.DirectionalLight(0xffe3c0, 2.8); sunLight.position.set(-114, 71, 118); // 前左上,照亮相机可见半球的左侧 scene.add(sunLight); var fillCold = new THREE.DirectionalLight(0x5a6fc8, 0.55); // 右前方冷补光,碎石不死黑 fillCold.position.set(120, -30, 100); scene.add(fillCold); scene.add(new THREE.AmbientLight(0x2a3452, 0.6)); var rimLight = new THREE.DirectionalLight(0x8899ff, 0.35); rimLight.position.set(80, -40, 60); scene.add(rimLight); /* ============================================================ 相机编排(V6行为: 呼吸漂移 + 鼠标惯性 + 滚动驱动) ============================================================ */ var mouseX = 0, mouseY = 0; if (!isMobile) { document.addEventListener("mousemove", function (e) { mouseX = (e.clientX / innerWidth - 0.5) * 2; mouseY = (e.clientY / innerHeight - 0.5) * 2; }); } function scrollProgress() { var h = document.documentElement.scrollHeight - innerHeight; return h > 0 ? Math.min(Math.max(scrollY / h, 0), 1) : 0; } function easeInOut(t) { return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2; } function lerp(a, b, t) { return a + (b - a) * t; } var warpT = reducedMotion ? 1 : 0; var clock = new THREE.Clock(); var bootDone = false; /* ---------- BOOT ---------- */ var bootLines = [ { t: "> 接入深空观测网络 ………… ", ok: "[OK]" }, { t: "> 望远镜阵列对焦 …………… ", ok: "[OK]" }, { t: "> 扫描目标: MOON.SYS ……… ", ok: "[FATAL]" }, { t: "> 检测到结构性碎裂 ………… ", ok: "[FRAGS]" }, { t: "> 启动修复协议 ……………… ", ok: "[GO]" } ]; var bootBody = document.getElementById("spaceBootBody"); var bootBar = document.getElementById("spaceBootBar"); function runBoot() { if (!bootEl || reducedMotion) { finishBoot(); return; } var i = 0; (function next() { if (i < bootLines.length) { var line = document.createElement("div"); line.className = "terminal-line"; var isFatal = bootLines[i].ok.indexOf("FATAL") >= 0 || bootLines[i].ok.indexOf("FRAG") >= 0; line.innerHTML = '' + bootLines[i].t + '' + bootLines[i].ok + ""; if (bootBody) bootBody.appendChild(line); if (bootBar) bootBar.style.width = ((i + 1) / bootLines.length * 100) + "%"; i++; setTimeout(next, 340); } else setTimeout(finishBoot, 420); })(); } function finishBoot() { if (bootEl) { bootEl.classList.add("hide"); setTimeout(function () { if (bootEl.parentNode) bootEl.parentNode.removeChild(bootEl); }, 900); } bootDone = true; document.body.classList.add("space-ready"); } runBoot(); setTimeout(finishBoot, 4500); /* ============================================================ 主循环 ============================================================ */ var dummy = new THREE.Object3D(); var tmpQ = new THREE.Quaternion(); function animate() { requestAnimationFrame(animate); var t = clock.getElapsedTime(); var p = isJourney ? scrollProgress() : 0; var ep = easeInOut(p); // warp入场 if (warpT < 1) { warpT = Math.min(1, warpT + 0.008); var w = 1 - Math.pow(1 - warpT, 3); camera.position.z = lerp(220, isJourney ? 46 : 42, w); if (skyMesh) skyMesh.rotation.y += 0.004 * (1 - warpT); } else if (isJourney) { camera.position.z = lerp(46, 26.5, ep); camera.position.y = lerp(0, 3, ep); } else { camera.position.z = 42; } // 呼吸式失重漂移 rig.position.x = Math.sin(t * 0.24) * 0.55; rig.position.y = Math.cos(t * 0.19) * 0.45; rig.rotation.z = Math.sin(t * 0.1) * 0.008; // 鼠标惯性跟随 camera.rotation.y += ((-mouseX * 0.09) - camera.rotation.y) * 0.045; camera.rotation.x += ((-mouseY * 0.06) - camera.rotation.x) * 0.045; // 月亮缓慢自转 + 呼吸 if (moon) moon.rotation.y += 0.0006; moonGroup.position.y = 1 + Math.sin(t * 0.33) * 0.3; moonGroup.rotation.z = Math.sin(t * 0.11) * 0.015; // 伤口辉光脉动 woundGlow.material.opacity = 0.65 + Math.sin(t * 1.6) * 0.2; // 修复进度(V6时序) var repair = isJourney ? Math.min(Math.max((p - 0.42) / 0.46, 0), 1) * 0.85 : 0.3; var rt = repair / 0.85; // 大碎片: 无衰减漂移,各自周期相位 fragments.forEach(function (f) { var target = f.spec.scatter.clone().lerp(f.spec.home, rt * rt); var amp = 0.5 * (1 - rt); var w1 = (t / f.spec.period) * Math.PI * 2 + f.phase; f.mesh.position.set( target.x + Math.sin(w1) * amp, target.y + Math.sin(w1 * 0.83 + 1.2) * amp * 0.8, target.z + Math.cos(w1 * 0.71) * amp * 0.7 ); f.mesh.rotation.x += 0.0016 * f.spec.spin * (1 - rt * 0.8); f.mesh.rotation.y += 0.0022 * f.spec.spin * (1 - rt * 0.8); }); // 碎石场: 各自向外漂移+翻滚+不同步摆动(持续离散中) if (debrisMesh) { for (var d = 0; d < DEBRIS_N; d++) { var dd = debrisData[d]; dd.pos.add(dd.vel.clone().multiplyScalar(0.016 * (1 - rt * 0.6))); var wob = (t / dd.period) * Math.PI * 2 + dd.phase; dummy.position.set( dd.pos.x + Math.sin(wob) * dd.wobble * 0.3, dd.pos.y + Math.sin(wob * 0.83) * dd.wobble * 0.25, dd.pos.z + Math.cos(wob * 0.71) * dd.wobble * 0.25 ); tmpQ.setFromAxisAngle(dd.axis, t * dd.rotSpeed + dd.phase); dummy.quaternion.copy(tmpQ); dummy.scale.setScalar(dd.scale); dummy.updateMatrix(); debrisMesh.setMatrixAt(d, dummy.matrix); } debrisMesh.instanceMatrix.needsUpdate = true; } // 尘埃云缓慢流动 dustClouds.forEach(function (dc2, i) { dc2.sp.position.x = dc2.base.x + Math.sin(t * 0.1 + dc2.phase) * 1.2 + t * 0.05 * (i % 2 ? 1 : -0.7); dc2.sp.position.y = dc2.base.y + Math.cos(t * 0.08 + dc2.phase) * 0.9; dc2.sp.material.opacity = 0.05 + 0.03 * Math.sin(t * 0.23 + dc2.phase) + 0.04; }); // 光缝 seams.forEach(function (s, i) { s.material.opacity = rt * 0.9 + Math.sin(t * 2.2 + i * 1.7) * 0.06 * rt; }); // 代码泄漏 var leakRate = 1 - rt * 0.55; leaks.forEach(function (L) { L.life += L.speed * 0.016; if (L.life > 1) { L.life = 0; L.sp.position.copy(L.origin); L.sp.material.map = charTex(codeChars[Math.floor(Math.random() * codeChars.length)]); } L.sp.position.add(L.drift.clone().multiplyScalar(0.016)); L.sp.material.opacity = Math.sin(L.life * Math.PI) * 0.8 * leakRate; }); // 太阳呼吸 + 光纹微闪 sunHalo.material.opacity = 0.36 + Math.sin(t * 0.7) * 0.06; sunStreak.material.opacity = 0.45 + Math.sin(t * 1.1) * 0.12; // 星野极缓旋转 + 近尘埃视差 if (skyMesh) skyMesh.rotation.y += 0.00008; nearDust.rotation.y += 0.0003; if (composer) composer.render(); else renderer.render(scene, camera); } animate(); addEventListener("resize", function () { camera.aspect = innerWidth / innerHeight; camera.updateProjectionMatrix(); renderer.setSize(innerWidth, innerHeight); if (composer) composer.setSize(innerWidth, innerHeight); }); })();