初版成品
This commit is contained in:
+121
-27
@@ -1,7 +1,7 @@
|
||||
export const APP_CONFIG = {
|
||||
// Rendering and canvas constraints.
|
||||
scene: {
|
||||
background: '#030712',
|
||||
background: '#000000',
|
||||
maxPixelRatio: 1.75,
|
||||
},
|
||||
// Default perspective camera setup.
|
||||
@@ -13,13 +13,13 @@ export const APP_CONFIG = {
|
||||
},
|
||||
// Shared motion timing and presets for the travel sequence.
|
||||
animation: {
|
||||
speedMin: 0.1,
|
||||
speedMin: 0.05,
|
||||
speedMax: 3,
|
||||
defaultSpeed: 1,
|
||||
defaultSpeed: 0.55,
|
||||
speedPresets: {
|
||||
slow: 0.55,
|
||||
steady: 1,
|
||||
deep: 1.65,
|
||||
slow: 0.2,
|
||||
steady: 0.55,
|
||||
deep: 1.0,
|
||||
},
|
||||
},
|
||||
// Starfield behavior tuned for center-area spawning and parallax depth.
|
||||
@@ -35,20 +35,20 @@ export const APP_CONFIG = {
|
||||
seedProgressExponent: 0.72,
|
||||
baseSizeMin: 1.3,
|
||||
baseSizeMax: 3.6,
|
||||
baseAlphaMin: 0.62,
|
||||
baseAlphaMax: 1,
|
||||
baseAlphaMin: 2.0,
|
||||
baseAlphaMax: 2.5,
|
||||
pointScale: 52,
|
||||
pointMinSize: 2.8,
|
||||
pointMaxSize: 76,
|
||||
accelerationXY: 0.22,
|
||||
accelerationZ: 0.1,
|
||||
nearFieldBoost: 1.8,
|
||||
edgeBoost: 7.2,
|
||||
edgeBurst: 3.4,
|
||||
trailStartProgress: 0.58,
|
||||
trailStrength: 1,
|
||||
trailLength: 0.38,
|
||||
trailWidth: 0.16,
|
||||
nearFieldBoost: 2.25,
|
||||
edgeBoost: 8.8,
|
||||
edgeBurst: 4.4,
|
||||
trailStartProgress: 0.38,
|
||||
trailStrength: 1.8,
|
||||
trailLength: 0.85,
|
||||
trailWidth: 0.12,
|
||||
},
|
||||
// Smoke layers create volumetric light fog passing through the camera.
|
||||
smoke: {
|
||||
@@ -64,19 +64,113 @@ export const APP_CONFIG = {
|
||||
pointerSmoothing: 0.08,
|
||||
maxOffset: 0.4,
|
||||
},
|
||||
// Galaxy parameters for background spiral galaxy layer.
|
||||
// Background galaxy rendered as layered shader fog instead of dense point clouds.
|
||||
galaxy: {
|
||||
count: 50000,
|
||||
radius: 15,
|
||||
branches: 20,
|
||||
spin: 0.3,
|
||||
randomness: 0.15,
|
||||
randomnessPower: 6,
|
||||
size: 0.008,
|
||||
rotationSpeed: 0.001,
|
||||
zPosition: -35,
|
||||
insideColor: '#ff6030',
|
||||
outsideColor: '#3861cbff',
|
||||
zPosition: -40,
|
||||
width: 72,
|
||||
height: 48,
|
||||
tiltX: Math.PI * 0.35,
|
||||
tiltY: Math.PI * 0.15,
|
||||
palette: {
|
||||
coreWhite: '#ffffff',
|
||||
coreGold: '#ffd36b',
|
||||
warmOrange: '#ff9850',
|
||||
warmMagenta: '#7b4a86',
|
||||
outerBlue: '#38d9ff',
|
||||
deepBlue: '#103f97',
|
||||
},
|
||||
layers: [
|
||||
{
|
||||
scale: 1.05,
|
||||
opacity: 0.42,
|
||||
blendMode: 'normal',
|
||||
rotationSpeed: 0.0024,
|
||||
armCount: 2,
|
||||
twist: 12,
|
||||
armSharpness: 0.6,
|
||||
noiseScale: 2.7,
|
||||
noiseStrength: 0.54,
|
||||
coreRadius: 0.2,
|
||||
coreGlow: 3.8,
|
||||
glowStrength: 1.06,
|
||||
zOffset: 0,
|
||||
innerColor: '#fff5de',
|
||||
outerColor: '#63d8ff',
|
||||
dustColor: '#1e3e78',
|
||||
},
|
||||
{
|
||||
scale: 1.25,
|
||||
opacity: 0.38,
|
||||
blendMode: 'normal',
|
||||
rotationSpeed: 0.0016,
|
||||
armCount: 3,
|
||||
twist: 10.4,
|
||||
armSharpness: 0.52,
|
||||
noiseScale: 2.1,
|
||||
noiseStrength: 0.68,
|
||||
coreRadius: 0.24,
|
||||
coreGlow: 3.5,
|
||||
glowStrength: 0.82,
|
||||
zOffset: -0.4,
|
||||
innerColor: '#ffd8c2',
|
||||
outerColor: '#6c89ff',
|
||||
dustColor: '#45206b',
|
||||
},
|
||||
{
|
||||
scale: 1.45,
|
||||
opacity: 0.52,
|
||||
blendMode: 'additive',
|
||||
rotationSpeed: 0.0009,
|
||||
armCount: 4,
|
||||
twist: 9.2,
|
||||
armSharpness: 0.58,
|
||||
noiseScale: 1.55,
|
||||
noiseStrength: 0.76,
|
||||
coreRadius: 0.28,
|
||||
coreGlow: 2.8,
|
||||
glowStrength: 1.08,
|
||||
zOffset: -0.8,
|
||||
innerColor: '#d8f4ff',
|
||||
outerColor: '#25c7ef',
|
||||
dustColor: '#0c2558',
|
||||
},
|
||||
{
|
||||
scale: 1.75,
|
||||
opacity: 0.25,
|
||||
blendMode: 'normal',
|
||||
rotationSpeed: 0.00036,
|
||||
armCount: 2,
|
||||
twist: 6.8,
|
||||
armSharpness: 0.28,
|
||||
noiseScale: 1.05,
|
||||
noiseStrength: 0.62,
|
||||
coreRadius: 0.32,
|
||||
coreGlow: 3.8,
|
||||
glowStrength: 0.34,
|
||||
zOffset: -1.3,
|
||||
innerColor: '#b7e9ff',
|
||||
outerColor: '#4ed5ef',
|
||||
dustColor: '#10356b',
|
||||
},
|
||||
{
|
||||
scale: 2.1,
|
||||
opacity: 0.22,
|
||||
blendMode: 'normal',
|
||||
rotationSpeed: 0.00018,
|
||||
armCount: 2,
|
||||
twist: 4.2,
|
||||
armSharpness: 0.16,
|
||||
noiseScale: 0.78,
|
||||
noiseStrength: 0.56,
|
||||
coreRadius: 0.42,
|
||||
coreGlow: 3.9,
|
||||
glowStrength: 0.2,
|
||||
zOffset: -1.9,
|
||||
innerColor: '#8fddff',
|
||||
outerColor: '#53bfd8',
|
||||
dustColor: '#143f73',
|
||||
},
|
||||
],
|
||||
},
|
||||
// Shared UI timings used by overlays and toolbars.
|
||||
ui: {
|
||||
|
||||
+11
-3
@@ -297,8 +297,8 @@ onBeforeUnmount(() => {
|
||||
|
||||
<section class="hud-layer" :class="{ 'hud-layer--active': started }">
|
||||
<transition name="controls-fade">
|
||||
<div v-if="started && controlsVisible" class="hud-bottom-container">
|
||||
<div
|
||||
v-if="started && controlsVisible"
|
||||
class="glass-toolbar"
|
||||
@pointerdown.stop="markActivity"
|
||||
@pointermove.stop="markActivity"
|
||||
@@ -321,7 +321,7 @@ onBeforeUnmount(() => {
|
||||
</div>
|
||||
|
||||
<label class="toolbar-group toolbar-group--stretch">
|
||||
<span class="toolbar-label">BGM</span>
|
||||
<span class="toolbar-label">背景音乐</span>
|
||||
<select class="glass-select" :value="currentTrack" @change="handleTrackChange">
|
||||
<option v-for="trackName in trackOptions" :key="trackName" :value="trackName">
|
||||
{{ trackName }}
|
||||
@@ -330,7 +330,8 @@ onBeforeUnmount(() => {
|
||||
</label>
|
||||
|
||||
<label class="toolbar-group toolbar-group--stretch">
|
||||
<span class="toolbar-label">音量 {{ volumePercent }}</span>
|
||||
<span class="toolbar-label">音量</span>
|
||||
<div class="range-wrapper" :style="{ '--volume-percent': `${volume * 100}%` }">
|
||||
<input
|
||||
class="glass-range"
|
||||
type="range"
|
||||
@@ -340,6 +341,8 @@ onBeforeUnmount(() => {
|
||||
:value="volume"
|
||||
@input="handleVolumeChange"
|
||||
/>
|
||||
<span class="range-value">{{ volumePercent }}</span>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<div class="toolbar-group toolbar-group--compact">
|
||||
@@ -355,6 +358,11 @@ onBeforeUnmount(() => {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-credit">
|
||||
Created by <a href="https://airnan.cn/" target="_blank" rel="noopener noreferrer">@码农阿楠</a> with AI.
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
+147
-18
@@ -13,7 +13,7 @@
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
--bg-primary: #030712;
|
||||
--bg-primary: #000000;
|
||||
--bg-secondary: rgba(7, 15, 30, 0.72);
|
||||
--border-soft: rgba(148, 163, 184, 0.24);
|
||||
--text-primary: #e2e8f0;
|
||||
@@ -37,9 +37,7 @@ body,
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
background:
|
||||
radial-gradient(circle at top, rgba(14, 165, 233, 0.18), transparent 32%),
|
||||
linear-gradient(180deg, #020617 0%, #030712 45%, #02030a 100%);
|
||||
background: #000000;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
@@ -63,7 +61,7 @@ canvas {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
overflow: hidden;
|
||||
background: radial-gradient(circle at top, rgba(56, 189, 248, 0.08), transparent 34%);
|
||||
background: #000000;
|
||||
}
|
||||
|
||||
.scene-layer {
|
||||
@@ -80,8 +78,7 @@ canvas {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background:
|
||||
radial-gradient(circle at center, transparent 0%, rgba(2, 6, 23, 0.08) 48%, rgba(2, 6, 23, 0.54) 100%),
|
||||
linear-gradient(180deg, rgba(2, 6, 23, 0.16) 0%, rgba(2, 6, 23, 0.48) 100%);
|
||||
radial-gradient(circle at center, transparent 30%, rgba(0, 0, 0, 0.4) 70%, rgba(0, 0, 0, 0.9) 100%);
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
@@ -182,14 +179,23 @@ canvas {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.glass-toolbar {
|
||||
.hud-bottom-container {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 28px;
|
||||
width: min(860px, calc(100% - 24px));
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.glass-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 18px;
|
||||
width: min(860px, calc(100% - 24px));
|
||||
width: 100%;
|
||||
padding: 18px;
|
||||
border: 1px solid rgba(148, 163, 184, 0.22);
|
||||
border-radius: 28px;
|
||||
@@ -198,7 +204,23 @@ canvas {
|
||||
backdrop-filter: blur(20px);
|
||||
box-shadow: 0 18px 56px rgba(2, 6, 23, 0.48);
|
||||
pointer-events: auto;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.footer-credit {
|
||||
font-size: 12px;
|
||||
color: rgba(226, 232, 240, 0.5);
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.footer-credit a {
|
||||
color: rgba(226, 232, 240, 0.8);
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
transition: text-decoration 160ms ease;
|
||||
}
|
||||
|
||||
.footer-credit a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.toolbar-group {
|
||||
@@ -226,10 +248,20 @@ canvas {
|
||||
|
||||
.segment-control {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 46px;
|
||||
padding: 4px;
|
||||
border: 1px solid rgba(148, 163, 184, 0.16);
|
||||
border-radius: 999px;
|
||||
background: rgba(2, 6, 23, 0.34);
|
||||
transition:
|
||||
border-color 160ms ease,
|
||||
background 160ms ease;
|
||||
}
|
||||
|
||||
.segment-control:hover {
|
||||
border-color: rgba(125, 211, 252, 0.32);
|
||||
background: rgba(15, 23, 42, 0.56);
|
||||
}
|
||||
|
||||
.segment-button {
|
||||
@@ -238,7 +270,11 @@ canvas {
|
||||
background: transparent;
|
||||
color: rgba(226, 232, 240, 0.78);
|
||||
cursor: pointer;
|
||||
padding: 10px 16px;
|
||||
height: 100%;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 16px;
|
||||
transition:
|
||||
background 160ms ease,
|
||||
color 160ms ease;
|
||||
@@ -250,13 +286,17 @@ canvas {
|
||||
}
|
||||
|
||||
.toolbar-action {
|
||||
height: 46px;
|
||||
min-height: 46px;
|
||||
border: 1px solid rgba(148, 163, 184, 0.18);
|
||||
border-radius: 16px;
|
||||
border-radius: 999px;
|
||||
background: rgba(2, 6, 23, 0.36);
|
||||
color: var(--text-primary);
|
||||
cursor: pointer;
|
||||
padding: 0 16px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition:
|
||||
border-color 160ms ease,
|
||||
background 160ms ease,
|
||||
@@ -288,17 +328,102 @@ canvas {
|
||||
|
||||
.glass-select {
|
||||
border: 1px solid rgba(148, 163, 184, 0.18);
|
||||
border-radius: 16px;
|
||||
background: rgba(2, 6, 23, 0.36);
|
||||
border-radius: 999px;
|
||||
background-color: rgba(2, 6, 23, 0.36);
|
||||
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='rgba(226, 232, 240, 0.82)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 14px center;
|
||||
background-size: 16px;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
color: var(--text-primary);
|
||||
height: 46px;
|
||||
min-height: 46px;
|
||||
outline: none;
|
||||
padding: 0 14px;
|
||||
padding: 0 36px 0 16px;
|
||||
transition:
|
||||
border-color 160ms ease,
|
||||
background-color 160ms ease;
|
||||
}
|
||||
|
||||
.glass-select:hover {
|
||||
border-color: rgba(125, 211, 252, 0.32);
|
||||
background-color: rgba(15, 23, 42, 0.56);
|
||||
}
|
||||
|
||||
.range-wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 46px;
|
||||
border: 1px solid rgba(148, 163, 184, 0.18);
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(to right, rgba(56, 189, 248, 0.16) var(--volume-percent, 0%), rgba(2, 6, 23, 0.36) var(--volume-percent, 0%));
|
||||
background-clip: padding-box;
|
||||
transition: border-color 160ms ease;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.range-wrapper:hover {
|
||||
border-color: rgba(125, 211, 252, 0.32);
|
||||
}
|
||||
|
||||
.range-value {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
font-size: 14px;
|
||||
color: rgba(226, 232, 240, 0.9);
|
||||
font-weight: 500;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.glass-range {
|
||||
accent-color: var(--accent);
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
background: transparent;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.glass-range::-webkit-slider-runnable-track {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.glass-range::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 0;
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.glass-range::-moz-range-track {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.glass-range::-moz-range-thumb {
|
||||
width: 0;
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.overlay-fade-enter-active,
|
||||
@@ -340,12 +465,16 @@ canvas {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.glass-toolbar {
|
||||
.hud-bottom-container {
|
||||
bottom: 16px;
|
||||
width: min(520px, calc(100% - 16px));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.glass-toolbar {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 14px;
|
||||
width: min(520px, calc(100% - 16px));
|
||||
border-radius: 24px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
+304
-74
@@ -61,7 +61,13 @@ const SMOKE_RESET_Z = APP_CONFIG.smoke.resetZ
|
||||
const SMOKE_BOUNDS_X = APP_CONFIG.smoke.boundsX
|
||||
const SMOKE_BOUNDS_Y = APP_CONFIG.smoke.boundsY
|
||||
|
||||
const STAR_COLORS = ['#ffffff', '#f3f8ff', '#e8f4ff', '#d7eeff', '#fff7ea'] as const
|
||||
const STAR_COLORS = [
|
||||
'#00e5ff', // 亮青
|
||||
'#ff00ea', // 亮粉
|
||||
'#ffcc00', // 亮金
|
||||
'#00ff66', // 亮绿
|
||||
'#ff3333', // 亮红
|
||||
] as const
|
||||
const SMOKE_COLORS = ['#8dd8ff', '#adc4ff', '#d6b5ff', '#ffe7ff'] as const
|
||||
|
||||
const STAR_VERTEX_SHADER = `
|
||||
@@ -78,11 +84,13 @@ varying vec2 vDir;
|
||||
void main() {
|
||||
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
|
||||
float scaledProgress = max(aProgress, 0.0);
|
||||
float pointSize = aSizeBase * (0.95 + scaledProgress * 2.85) * ${APP_CONFIG.starfield.pointScale.toFixed(1)} / max(1.0, -mvPosition.z);
|
||||
// 放大精灵尺寸以容纳更长的拖尾
|
||||
float pointSize = aSizeBase * (0.95 + scaledProgress * 2.85) * ${APP_CONFIG.starfield.pointScale.toFixed(1)} / max(1.0, -mvPosition.z) * 2.5;
|
||||
gl_Position = projectionMatrix * mvPosition;
|
||||
gl_PointSize = clamp(pointSize, ${APP_CONFIG.starfield.pointMinSize.toFixed(1)}, ${APP_CONFIG.starfield.pointMaxSize.toFixed(1)});
|
||||
gl_PointSize = clamp(pointSize, ${APP_CONFIG.starfield.pointMinSize.toFixed(1)} * 2.5, ${APP_CONFIG.starfield.pointMaxSize.toFixed(1)} * 2.5);
|
||||
vColor = color;
|
||||
vAlpha = aAlphaBase * (0.32 + scaledProgress * 0.68);
|
||||
// 取消原本透明度的距离衰减,直接用最高的 baseAlpha 甚至更高,保持全局最亮
|
||||
vAlpha = aAlphaBase * 1.5;
|
||||
vTail = aTail;
|
||||
vDir = aDir;
|
||||
}
|
||||
@@ -93,27 +101,246 @@ varying vec3 vColor;
|
||||
varying float vAlpha;
|
||||
varying float vTail;
|
||||
varying vec2 vDir;
|
||||
uniform float uSpeed;
|
||||
|
||||
void main() {
|
||||
vec2 centered = gl_PointCoord - vec2(0.5);
|
||||
// 放大坐标系,这样中心点画得相对较小,留出极大空间画长尾巴
|
||||
vec2 centered = (gl_PointCoord - vec2(0.5)) * 2.5;
|
||||
float distanceToCenter = length(centered);
|
||||
float core = 1.0 - smoothstep(0.0, 0.22, distanceToCenter);
|
||||
float halo = 1.0 - smoothstep(0.12, 0.5, distanceToCenter);
|
||||
vec2 dir = length(vDir) > 0.0001 ? normalize(vDir) : vec2(0.0, 1.0);
|
||||
float hotspot = 1.0 - smoothstep(0.0, 0.06, distanceToCenter);
|
||||
hotspot = pow(hotspot, 0.32);
|
||||
float core = 1.0 - smoothstep(0.0, 0.13, distanceToCenter);
|
||||
core = pow(core, 0.44);
|
||||
float halo = 1.0 - smoothstep(0.015, 0.22, distanceToCenter);
|
||||
halo = pow(halo, 1.95);
|
||||
|
||||
// 翻转 Y 轴以匹配 WebGL 纹理坐标系
|
||||
vec2 dir = length(vDir) > 0.0001 ? normalize(vec2(vDir.x, -vDir.y)) : vec2(0.0, -1.0);
|
||||
vec2 side = vec2(-dir.y, dir.x);
|
||||
float along = dot(centered, dir);
|
||||
float across = dot(centered, side);
|
||||
float tailLong = 1.0 - smoothstep(-${(0.18 + APP_CONFIG.starfield.trailLength).toFixed(2)}, 0.05, along);
|
||||
float tailThin = 1.0 - smoothstep(${(APP_CONFIG.starfield.trailWidth * 0.35).toFixed(2)}, ${(APP_CONFIG.starfield.trailWidth).toFixed(2)}, abs(across));
|
||||
float tailFade = smoothstep(0.01, 0.52, -along);
|
||||
float tail = tailLong * tailThin * tailFade * vTail;
|
||||
float alpha = (core * 0.95 + halo * 0.45 + tail * 0.85) * vAlpha;
|
||||
|
||||
float back = -along;
|
||||
// 拖尾长度根据当前速度动态变化
|
||||
float speedFactor = max(uSpeed, 0.1);
|
||||
float tailLength = clamp(${(APP_CONFIG.starfield.trailLength).toFixed(2)} * 2.5 * speedFactor, 0.05, 2.5);
|
||||
float tailFade = 1.0 - smoothstep(0.0, tailLength, back);
|
||||
float tailMask = smoothstep(-0.02, 0.05, back);
|
||||
|
||||
float maxWidth = ${(APP_CONFIG.starfield.trailWidth).toFixed(2)};
|
||||
float width = maxWidth * mix(0.1, 1.0, pow(tailFade, 0.8));
|
||||
|
||||
float tailProfile = 1.0 - smoothstep(0.0, width, abs(across));
|
||||
float tailHotspot = 1.0 - smoothstep(0.0, width * 0.3, abs(across));
|
||||
// 速度越快,尾巴亮度也适当增强
|
||||
float tail = (tailProfile * 0.8 + tailHotspot * 1.5) * tailFade * tailMask * vTail * (0.4 + 0.6 * speedFactor);
|
||||
|
||||
// 极高的能量累加,不丢弃任何 alpha,强制发光
|
||||
float alpha = (hotspot * 3.5 + core * 2.5 + halo * 0.5 + tail * 2.8) * vAlpha;
|
||||
|
||||
if (alpha < 0.01) {
|
||||
discard;
|
||||
}
|
||||
|
||||
gl_FragColor = vec4(vColor, alpha);
|
||||
vec3 color = vColor;
|
||||
// 让高光点保持自身颜色的同时极度明亮,而不仅是变白
|
||||
vec3 hotColor = mix(vColor, vec3(1.0), 0.5);
|
||||
color = mix(color, hotColor, hotspot * 0.8 + core * 0.4);
|
||||
color += hotColor * hotspot * 2.0;
|
||||
color += vColor * core * 1.5;
|
||||
color += vColor * tail * 1.8;
|
||||
|
||||
// 用极其暴力的颜色叠加
|
||||
gl_FragColor = vec4(min(color, vec3(1.0)), min(alpha, 1.0));
|
||||
}
|
||||
`
|
||||
|
||||
const GALAXY_VERTEX_SHADER = `
|
||||
varying vec2 vUv;
|
||||
|
||||
void main() {
|
||||
vUv = uv;
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||
}
|
||||
`
|
||||
|
||||
const GALAXY_FRAGMENT_SHADER = `
|
||||
varying vec2 vUv;
|
||||
|
||||
uniform float uTime;
|
||||
uniform float uOpacity;
|
||||
uniform float uArms;
|
||||
uniform float uTwist;
|
||||
uniform float uArmSharpness;
|
||||
uniform float uNoiseScale;
|
||||
uniform float uNoiseStrength;
|
||||
uniform float uCoreRadius;
|
||||
uniform float uCoreGlow;
|
||||
uniform float uGlowStrength;
|
||||
uniform float uSeed;
|
||||
uniform vec3 uCoreWhite;
|
||||
uniform vec3 uCoreGold;
|
||||
uniform vec3 uWarmOrange;
|
||||
uniform vec3 uWarmMagenta;
|
||||
uniform vec3 uOuterBlue;
|
||||
uniform vec3 uDeepBlue;
|
||||
uniform vec3 uInnerColor;
|
||||
uniform vec3 uOuterColor;
|
||||
uniform vec3 uDustColor;
|
||||
|
||||
float hash(vec2 p) {
|
||||
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);
|
||||
}
|
||||
|
||||
float noise(vec2 p) {
|
||||
vec2 i = floor(p);
|
||||
vec2 f = fract(p);
|
||||
vec2 u = f * f * (3.0 - 2.0 * f);
|
||||
|
||||
return mix(
|
||||
mix(hash(i + vec2(0.0, 0.0)), hash(i + vec2(1.0, 0.0)), u.x),
|
||||
mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), u.x),
|
||||
u.y
|
||||
);
|
||||
}
|
||||
|
||||
float fbm(vec2 p) {
|
||||
float value = 0.0;
|
||||
float amplitude = 0.5;
|
||||
|
||||
for (int i = 0; i < 5; i += 1) {
|
||||
value += amplitude * noise(p);
|
||||
p = p * 2.03 + vec2(17.3, 9.2);
|
||||
amplitude *= 0.5;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
vec3 sampleGalaxyRamp(float t) {
|
||||
float value = clamp(t, 0.0, 1.0);
|
||||
vec3 color = uCoreWhite;
|
||||
|
||||
color = mix(color, uCoreGold, smoothstep(0.03, 0.18, value));
|
||||
color = mix(color, uWarmOrange, smoothstep(0.12, 0.32, value));
|
||||
color = mix(color, uWarmMagenta, smoothstep(0.24, 0.58, value));
|
||||
color = mix(color, uOuterBlue, smoothstep(0.5, 0.84, value));
|
||||
color = mix(color, uDeepBlue, smoothstep(0.76, 1.0, value));
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 p = vUv * 2.0 - 1.0;
|
||||
p.x *= 1.08;
|
||||
|
||||
float radius = length(p);
|
||||
float angle = atan(p.y, p.x);
|
||||
float time = uTime * 0.045;
|
||||
vec2 flowUv = p * uNoiseScale;
|
||||
|
||||
float lowNoise = fbm(flowUv + vec2(time * 0.35 + uSeed * 5.1, -time * 0.18));
|
||||
float highNoise = fbm(flowUv * 2.1 - vec2(time * 0.42, time * 0.26 + uSeed * 3.7));
|
||||
float veilNoise = fbm(p * (uNoiseScale * 0.55) + vec2(-time * 0.12, time * 0.08 + uSeed * 1.9));
|
||||
float filamentNoise = fbm(flowUv * 3.4 + vec2(time * 0.18 - uSeed * 2.7, time * 0.24));
|
||||
float clusterNoise = fbm(flowUv * 5.1 + vec2(-time * 0.32 + uSeed * 8.4, time * 0.21));
|
||||
float grainNoise = noise(flowUv * 13.5 + vec2(time * 1.8 + uSeed * 14.0, -time * 1.3));
|
||||
float fineNoise = fbm(flowUv * 8.2 - vec2(time * 0.65 - uSeed * 6.2, time * 0.52));
|
||||
float warpedAngle = angle - radius * uTwist + (lowNoise - 0.5) * uNoiseStrength * 3.2;
|
||||
vec2 swirlDir = vec2(cos(warpedAngle), sin(warpedAngle));
|
||||
vec2 swirlUv = vec2(dot(p, swirlDir), dot(p, vec2(-swirlDir.y, swirlDir.x)));
|
||||
float streakNoise = fbm(swirlUv * vec2(24.0, 3.2) + vec2(time * 0.12 + uSeed * 4.6, -time * 0.06));
|
||||
float mottledNoise = fbm(swirlUv * vec2(10.0, 6.5) - vec2(time * 0.08 - uSeed * 5.2, time * 0.04));
|
||||
float fineFiberNoise = fbm(swirlUv * vec2(46.0, 1.8) + vec2(time * 0.06 + uSeed * 11.4, -time * 0.03));
|
||||
float occlusionNoise = fbm(swirlUv * vec2(5.4, 4.1) - vec2(time * 0.04 - uSeed * 2.6, time * 0.02));
|
||||
float spiralWave = cos(warpedAngle * uArms);
|
||||
float armBase = spiralWave * 0.5 + 0.5;
|
||||
float armMask = smoothstep(1.0 - uArmSharpness, 1.0, armBase);
|
||||
float softArms = smoothstep(0.2, 0.92, armBase + (lowNoise - 0.5) * 0.18);
|
||||
float halo = 1.0 - smoothstep(0.12, 1.45, radius);
|
||||
float outerHalo = 1.0 - smoothstep(0.4, 1.6, radius);
|
||||
float core = 1.0 - smoothstep(0.0, uCoreRadius, radius);
|
||||
float coreBloom = 1.0 - smoothstep(0.0, uCoreRadius * 1.6, radius);
|
||||
float hotCore = 1.0 - smoothstep(0.0, uCoreRadius * 0.95, radius);
|
||||
float dust = smoothstep(0.24, 0.9, highNoise);
|
||||
float wisps = smoothstep(0.44, 0.9, lowNoise) * (1.0 - smoothstep(0.38, 1.08, radius));
|
||||
float outerVeil = smoothstep(0.34, 0.88, veilNoise) * outerHalo;
|
||||
float rim = smoothstep(0.3, 0.98, radius) * (1.0 - smoothstep(0.92, 1.3, radius));
|
||||
float armRidge = pow(armMask, 1.45) * halo;
|
||||
float lanePattern = 1.0 - smoothstep(0.2, 0.72, abs(sin(warpedAngle * (uArms * 0.55) + filamentNoise * 4.4)));
|
||||
float dustLane = lanePattern * smoothstep(0.12, 0.88, radius) * halo * (0.35 + (1.0 - armMask) * 0.9);
|
||||
float clumps = smoothstep(0.62, 0.9, filamentNoise) * softArms * rim;
|
||||
float pulse = 0.5 + 0.5 * sin(uTime * 1.6 + uSeed * 9.0);
|
||||
float shockRadius = uCoreRadius * (1.08 + pulse * 0.42);
|
||||
float shockRing = smoothstep(shockRadius - 0.17, shockRadius - 0.04, radius) *
|
||||
(1.0 - smoothstep(shockRadius + 0.03, shockRadius + 0.11, radius)) *
|
||||
mix(0.55, 1.0, armMask);
|
||||
float clusterMask = smoothstep(0.72, 0.9, clusterNoise) * pow(softArms, 1.45) * rim;
|
||||
float clusterTwinkle = 0.55 + 0.45 * sin(uTime * 2.6 + angle * uArms * 3.2 + radius * 16.0 + uSeed * 12.0);
|
||||
float armSpark = clusterMask * clusterTwinkle;
|
||||
float edgeBreak = smoothstep(0.72, 0.95, veilNoise) * smoothstep(0.55, 1.08, radius) * outerHalo;
|
||||
float grainMask = smoothstep(0.36, 0.84, grainNoise) * mix(0.32, 1.0, softArms) * halo;
|
||||
float dustTexture = smoothstep(0.28, 0.78, fineNoise) * halo * (0.45 + (1.0 - armMask) * 0.8);
|
||||
float fiberBands = smoothstep(0.48, 0.88, streakNoise) * halo * smoothstep(0.12, 0.92, radius);
|
||||
float mottledDust = smoothstep(0.34, 0.82, mottledNoise) * halo;
|
||||
float fineFibers = smoothstep(0.54, 0.9, fineFiberNoise) * halo * smoothstep(0.1, 0.96, radius);
|
||||
float deepOcclusion = smoothstep(0.38, 0.8, occlusionNoise) * halo * smoothstep(0.18, 1.0, radius);
|
||||
float dustFilaments = fiberBands * (0.4 + (1.0 - armMask) * 0.9) * (0.6 + dustLane * 0.8);
|
||||
float outerSparseFibers = smoothstep(0.44, 0.88, streakNoise) *
|
||||
smoothstep(0.48, 1.08, radius) *
|
||||
outerHalo *
|
||||
(0.45 + veilNoise * 0.8);
|
||||
float microSpeckles = smoothstep(0.955, 0.992, grainNoise) * (0.18 + clusterTwinkle * 0.32) * softArms * rim;
|
||||
float density = halo * mix(0.44, 1.0, armMask) * mix(0.88, 1.12, dust);
|
||||
density = mix(density, halo * 0.96, 0.34);
|
||||
float coreGlow = pow(core, uCoreGlow) * uGlowStrength;
|
||||
float bloomGlow = pow(coreBloom, 1.8) * (uGlowStrength * 0.92);
|
||||
float pulseCore = pow(coreBloom, 1.28) * (0.24 + pulse * 0.34);
|
||||
float innerCloud = pow(core, 1.15) * 0.58 + pow(coreBloom, 1.3) * 0.38;
|
||||
float starDust = smoothstep(0.9, 0.985, highNoise) * softArms * rim * 0.07;
|
||||
|
||||
density += wisps * 0.4 + outerVeil * 0.42 + innerCloud + coreGlow + bloomGlow + starDust;
|
||||
density += armRidge * 0.2 + clumps * 0.24 + edgeBreak * 0.18 + shockRing * 0.28 + armSpark * 0.1 + pulseCore;
|
||||
density += grainMask * 0.04 + microSpeckles * 0.018;
|
||||
density *= 1.0 - dustLane * 0.34;
|
||||
density *= 0.94 + grainNoise * 0.11;
|
||||
density *= 1.0 - dustTexture * 0.1;
|
||||
density *= 1.0 - dustFilaments * 0.26;
|
||||
density *= 1.0 - fineFibers * 0.18;
|
||||
density *= 1.0 - outerSparseFibers * 0.13;
|
||||
density *= 1.0 - deepOcclusion * (0.12 + dustLane * 0.1);
|
||||
density *= 0.9 + (1.0 - mottledDust) * 0.12;
|
||||
density *= exp(-radius * 0.85);
|
||||
|
||||
float rampT = clamp(pow(radius, 0.72) + dustLane * 0.05 - coreBloom * 0.14 - armRidge * 0.03 + (1.0 - softArms) * 0.04, 0.0, 1.0);
|
||||
vec3 rampColor = sampleGalaxyRamp(rampT);
|
||||
vec3 layerTint = mix(uOuterColor, uInnerColor, clamp(core * 0.75 + armRidge * 0.24 + softArms * 0.12, 0.0, 1.0));
|
||||
vec3 color = mix(rampColor, layerTint, 0.25); // 增加 layerTint 的比重,提亮
|
||||
|
||||
// 提亮灰尘的颜色,防止在纯黑背景下太暗
|
||||
vec3 brightDust = mix(uDustColor, vec3(0.3, 0.5, 0.8), 0.3);
|
||||
color = mix(color, brightDust, wisps * 0.12 + dustLane * 0.5 + (1.0 - softArms) * 0.1);
|
||||
color = mix(color, brightDust, dustTexture * 0.15 + dustFilaments * 0.25 + fineFibers * 0.15 + mottledDust * 0.12 + deepOcclusion * 0.1);
|
||||
color = mix(color, uWarmOrange, armRidge * 0.16 + shockRing * 0.1);
|
||||
color = mix(color, uWarmMagenta, smoothstep(0.18, 0.62, radius) * (1.0 - armMask) * 0.24);
|
||||
color = mix(color, uOuterBlue, outerVeil * 0.18 + edgeBreak * 0.12);
|
||||
color = mix(color, mix(uOuterBlue, uDustColor, 0.4), outerSparseFibers * 0.36);
|
||||
color += mix(rampColor, vec3(1.0, 0.98, 0.95), 0.4) * grainMask * 0.018;
|
||||
color += mix(uCoreGold, vec3(1.0, 0.96, 0.86), 0.45) * armRidge * 0.18;
|
||||
color += mix(uOuterBlue, vec3(0.9, 0.97, 1.0), 0.35) * clumps * 0.12;
|
||||
color += mix(uCoreGold, uWarmOrange, 0.45) * shockRing * 0.34;
|
||||
color += mix(uCoreWhite, uOuterBlue, 0.28) * armSpark * 0.12;
|
||||
color += mix(uCoreWhite, uCoreGold, 0.4) * microSpeckles * 0.04;
|
||||
color += uCoreWhite * pow(coreBloom, 1.8) * 0.65;
|
||||
color += mix(uCoreWhite, uCoreGold, 0.22) * pow(hotCore, 1.2) * 1.0;
|
||||
|
||||
float alpha = density * uOpacity * (1.0 - smoothstep(1.25, 1.6, radius));
|
||||
|
||||
if (alpha < 0.01) {
|
||||
discard;
|
||||
}
|
||||
|
||||
gl_FragColor = vec4(color, clamp(alpha, 0.0, 1.0));
|
||||
}
|
||||
`
|
||||
|
||||
@@ -134,6 +361,10 @@ function randomRange(min: number, max: number) {
|
||||
return min + Math.random() * (max - min)
|
||||
}
|
||||
|
||||
function getGalaxyBlending(blendMode: 'normal' | 'additive') {
|
||||
return blendMode === 'additive' ? THREE.AdditiveBlending : THREE.NormalBlending
|
||||
}
|
||||
|
||||
function sampleDisk(radius: number) {
|
||||
const angle = Math.random() * Math.PI * 2
|
||||
const distance = Math.pow(Math.random(), APP_CONFIG.starfield.radialExponent) * radius
|
||||
@@ -236,14 +467,13 @@ export function createTravelScene(container: HTMLElement): TravelSceneController
|
||||
|
||||
const galaxyGroup = new THREE.Group()
|
||||
galaxyGroup.position.z = APP_CONFIG.galaxy.zPosition
|
||||
// Tilt the galaxy to give it a perspective view
|
||||
galaxyGroup.rotation.x = Math.PI * 0.35
|
||||
galaxyGroup.rotation.y = Math.PI * 0.15
|
||||
galaxyGroup.rotation.x = APP_CONFIG.galaxy.tiltX
|
||||
galaxyGroup.rotation.y = APP_CONFIG.galaxy.tiltY
|
||||
scene.add(galaxyGroup)
|
||||
|
||||
// Create an inner group for the galaxy rotation to preserve the tilt
|
||||
const galaxyInnerGroup = new THREE.Group()
|
||||
galaxyGroup.add(galaxyInnerGroup)
|
||||
const galaxyGeometry = new THREE.PlaneGeometry(APP_CONFIG.galaxy.width, APP_CONFIG.galaxy.height, 1, 1)
|
||||
const galaxyLayers: THREE.Mesh<THREE.PlaneGeometry, THREE.ShaderMaterial>[] = []
|
||||
const galaxyMaterials: THREE.ShaderMaterial[] = []
|
||||
|
||||
const starsGeometry = new THREE.BufferGeometry()
|
||||
const starPositions = new Float32Array(STAR_COUNT * 3)
|
||||
@@ -264,7 +494,7 @@ export function createTravelScene(container: HTMLElement): TravelSceneController
|
||||
const pointerCurrent = { x: 0, y: 0 }
|
||||
|
||||
let started = false
|
||||
let targetSpeed = 1
|
||||
let targetSpeed: number = APP_CONFIG.animation.defaultSpeed
|
||||
let currentSpeed = 0
|
||||
let animationFrameId = 0
|
||||
let disposed = false
|
||||
@@ -276,6 +506,9 @@ export function createTravelScene(container: HTMLElement): TravelSceneController
|
||||
depthWrite: false,
|
||||
vertexColors: true,
|
||||
blending: THREE.AdditiveBlending,
|
||||
uniforms: {
|
||||
uSpeed: { value: 1.0 },
|
||||
},
|
||||
})
|
||||
|
||||
function spawnStar(index: number, seededProgress?: number) {
|
||||
@@ -314,7 +547,11 @@ export function createTravelScene(container: HTMLElement): TravelSceneController
|
||||
starDirections[index * 2] = direction.x
|
||||
starDirections[index * 2 + 1] = direction.y
|
||||
|
||||
if (Math.random() < 0.9) {
|
||||
starColor.set('#ffffff')
|
||||
} else {
|
||||
starColor.set(STAR_COLORS[Math.floor(Math.random() * STAR_COLORS.length)])
|
||||
}
|
||||
starColors[index * 3] = starColor.r
|
||||
starColors[index * 3 + 1] = starColor.g
|
||||
starColors[index * 3 + 2] = starColor.b
|
||||
@@ -393,60 +630,46 @@ export function createTravelScene(container: HTMLElement): TravelSceneController
|
||||
travelRoot.add(state.mesh)
|
||||
}
|
||||
|
||||
const galaxyGeometry = new THREE.BufferGeometry()
|
||||
const galaxyPositions = new Float32Array(APP_CONFIG.galaxy.count * 3)
|
||||
const galaxyColors = new Float32Array(APP_CONFIG.galaxy.count * 3)
|
||||
const colorInside = new THREE.Color(APP_CONFIG.galaxy.insideColor)
|
||||
const colorOutside = new THREE.Color(APP_CONFIG.galaxy.outsideColor)
|
||||
|
||||
for (let i = 0; i < APP_CONFIG.galaxy.count; i += 1) {
|
||||
const i3 = i * 3
|
||||
const radius = Math.random() * APP_CONFIG.galaxy.radius
|
||||
const branchesAngle =
|
||||
((i % APP_CONFIG.galaxy.branches) / APP_CONFIG.galaxy.branches) * Math.PI * 2
|
||||
const spinAngle = radius * APP_CONFIG.galaxy.spin
|
||||
|
||||
const mixedColor = colorInside.clone()
|
||||
mixedColor.lerp(colorOutside, radius / APP_CONFIG.galaxy.radius)
|
||||
|
||||
const randomX =
|
||||
Math.random() ** APP_CONFIG.galaxy.randomnessPower *
|
||||
(Math.random() < 0.5 ? 1 : -1) *
|
||||
APP_CONFIG.galaxy.randomness *
|
||||
radius
|
||||
const randomY =
|
||||
Math.random() ** APP_CONFIG.galaxy.randomnessPower *
|
||||
(Math.random() < 0.5 ? 1 : -1) *
|
||||
APP_CONFIG.galaxy.randomness *
|
||||
radius
|
||||
const randomZ =
|
||||
Math.random() ** APP_CONFIG.galaxy.randomnessPower *
|
||||
(Math.random() < 0.5 ? 1 : -1) *
|
||||
APP_CONFIG.galaxy.randomness *
|
||||
radius
|
||||
|
||||
galaxyPositions[i3] = Math.cos(branchesAngle + spinAngle) * radius + randomX
|
||||
galaxyPositions[i3 + 1] = Math.sin(branchesAngle + spinAngle) * radius + randomY
|
||||
galaxyPositions[i3 + 2] = randomZ
|
||||
|
||||
galaxyColors[i3] = mixedColor.r
|
||||
galaxyColors[i3 + 1] = mixedColor.g
|
||||
galaxyColors[i3 + 2] = mixedColor.b
|
||||
}
|
||||
|
||||
galaxyGeometry.setAttribute('position', new THREE.BufferAttribute(galaxyPositions, 3))
|
||||
galaxyGeometry.setAttribute('color', new THREE.BufferAttribute(galaxyColors, 3))
|
||||
|
||||
const galaxyMaterial = new THREE.PointsMaterial({
|
||||
size: APP_CONFIG.galaxy.size,
|
||||
sizeAttenuation: true,
|
||||
APP_CONFIG.galaxy.layers.forEach((layerConfig, index) => {
|
||||
const galaxyMaterial = new THREE.ShaderMaterial({
|
||||
vertexShader: GALAXY_VERTEX_SHADER,
|
||||
fragmentShader: GALAXY_FRAGMENT_SHADER,
|
||||
transparent: true,
|
||||
depthWrite: false,
|
||||
blending: THREE.AdditiveBlending,
|
||||
vertexColors: true,
|
||||
side: THREE.DoubleSide,
|
||||
blending: getGalaxyBlending(layerConfig.blendMode),
|
||||
uniforms: {
|
||||
uTime: { value: 0 },
|
||||
uOpacity: { value: layerConfig.opacity },
|
||||
uArms: { value: layerConfig.armCount },
|
||||
uTwist: { value: layerConfig.twist },
|
||||
uArmSharpness: { value: layerConfig.armSharpness },
|
||||
uNoiseScale: { value: layerConfig.noiseScale },
|
||||
uNoiseStrength: { value: layerConfig.noiseStrength },
|
||||
uCoreRadius: { value: layerConfig.coreRadius },
|
||||
uCoreGlow: { value: layerConfig.coreGlow },
|
||||
uGlowStrength: { value: layerConfig.glowStrength },
|
||||
uSeed: { value: index * 0.173 + 0.31 },
|
||||
uCoreWhite: { value: new THREE.Color(APP_CONFIG.galaxy.palette.coreWhite) },
|
||||
uCoreGold: { value: new THREE.Color(APP_CONFIG.galaxy.palette.coreGold) },
|
||||
uWarmOrange: { value: new THREE.Color(APP_CONFIG.galaxy.palette.warmOrange) },
|
||||
uWarmMagenta: { value: new THREE.Color(APP_CONFIG.galaxy.palette.warmMagenta) },
|
||||
uOuterBlue: { value: new THREE.Color(APP_CONFIG.galaxy.palette.outerBlue) },
|
||||
uDeepBlue: { value: new THREE.Color(APP_CONFIG.galaxy.palette.deepBlue) },
|
||||
uInnerColor: { value: new THREE.Color(layerConfig.innerColor) },
|
||||
uOuterColor: { value: new THREE.Color(layerConfig.outerColor) },
|
||||
uDustColor: { value: new THREE.Color(layerConfig.dustColor) },
|
||||
},
|
||||
})
|
||||
|
||||
const galaxyPoints = new THREE.Points(galaxyGeometry, galaxyMaterial)
|
||||
galaxyInnerGroup.add(galaxyPoints)
|
||||
const galaxyMesh = new THREE.Mesh(galaxyGeometry, galaxyMaterial)
|
||||
galaxyMesh.scale.setScalar(layerConfig.scale)
|
||||
galaxyMesh.position.z = layerConfig.zOffset
|
||||
galaxyMesh.rotation.z = index * 0.8
|
||||
galaxyGroup.add(galaxyMesh)
|
||||
galaxyLayers.push(galaxyMesh)
|
||||
galaxyMaterials.push(galaxyMaterial)
|
||||
})
|
||||
|
||||
const clock = new THREE.Clock()
|
||||
|
||||
@@ -589,7 +812,14 @@ export function createTravelScene(container: HTMLElement): TravelSceneController
|
||||
updateStars(motionDelta)
|
||||
updateSmoke(motionDelta * 0.62, elapsed)
|
||||
|
||||
galaxyInnerGroup.rotation.z += APP_CONFIG.galaxy.rotationSpeed * currentSpeed
|
||||
starMaterial.uniforms.uSpeed.value = currentSpeed
|
||||
|
||||
galaxyLayers.forEach((galaxyLayer, index) => {
|
||||
galaxyLayer.rotation.z -= APP_CONFIG.galaxy.layers[index].rotationSpeed * currentSpeed
|
||||
})
|
||||
galaxyMaterials.forEach((material, index) => {
|
||||
material.uniforms.uTime.value = elapsed + index * 0.37
|
||||
})
|
||||
|
||||
travelRoot.rotation.z = pointerCurrent.x * 0.03
|
||||
travelRoot.position.x = -pointerCurrent.x * 0.8
|
||||
@@ -631,8 +861,8 @@ export function createTravelScene(container: HTMLElement): TravelSceneController
|
||||
travelRoot.remove(smokeState.mesh)
|
||||
}
|
||||
|
||||
galaxyMaterials.forEach((material) => material.dispose())
|
||||
galaxyGeometry.dispose()
|
||||
galaxyMaterial.dispose()
|
||||
|
||||
starsGeometry.dispose()
|
||||
starMaterial.dispose()
|
||||
|
||||
Reference in New Issue
Block a user