feat: refine nightdrive world and authored car

Integrate the Blender grand tourer with a cohesive shader-driven road, terrain, atmosphere, lighting, and post-processing pass. Refine responsive drive dynamics and sign lifecycle behavior, and add durable visual, accessibility, and performance verification evidence.
This commit is contained in:
2026-09-02 23:16:25 +03:00
parent 7dae835ac5
commit bc18bc2043
73 changed files with 1555 additions and 293 deletions

View File

@@ -5,7 +5,7 @@ import { CHAPTER_PALETTES } from '@/scene/world/palette';
export interface SunWorld {
group: WorldGroup;
update(palette: Readonly<MutableChapterPalette>, routeProgress: number): void;
update(palette: Readonly<MutableChapterPalette>, routeProgress: number, elapsedSeconds: number): void;
}
interface WorldGroup {
@@ -20,11 +20,12 @@ export function createSun(resources: SceneResourceBag): SunWorld {
const sunColor = new THREE.Color(night.sun);
const uniforms = {
uColor: { value: sunColor },
uScanBands: { value: 14 },
uScanBands: { value: 11 },
uWarmth: { value: night.warmth },
uRouteProgress: { value: 0 },
uTime: { value: 0 },
};
const geometry = resources.own('geometry', new THREE.CircleGeometry(20, 96));
const geometry = resources.own('geometry', new THREE.CircleGeometry(44, 128));
const material = resources.own('material', new THREE.ShaderMaterial({
uniforms,
vertexShader: `
@@ -40,17 +41,21 @@ export function createSun(resources: SceneResourceBag): SunWorld {
uniform float uScanBands;
uniform float uWarmth;
uniform float uRouteProgress;
uniform float uTime;
varying vec2 vSunUv;
void main() {
float radius = distance(vSunUv, vec2(0.5));
float softEdge = 1.0 - smoothstep(0.435, 0.5, radius);
float lowerDisc = 1.0 - smoothstep(0.42, 0.76, vSunUv.y);
float scanBand = smoothstep(0.16, 0.28, fract(vSunUv.y * uScanBands));
float bandCut = mix(1.0, 0.32 + scanBand * 0.68, lowerDisc * 0.82);
float horizonFade = smoothstep(0.02, 0.14, vSunUv.y);
float luminance = 0.72 + uWarmth * 0.18 + uRouteProgress * 0.06;
gl_FragColor = vec4(uColor * luminance, softEdge * bandCut * horizonFade * 0.9);
float softEdge = 1.0 - smoothstep(0.475, 0.5, radius);
float lowerDisc = 1.0 - smoothstep(0.46, 0.84, vSunUv.y);
float scanPhase = fract(vSunUv.y * uScanBands + sin(uTime * 0.12) * 0.012);
float scanBand = smoothstep(0.12, 0.2, scanPhase)
* (1.0 - smoothstep(0.58, 0.72, scanPhase));
float bandCut = mix(1.0, 0.18 + scanBand * 0.82, lowerDisc * 0.9);
float horizonFade = smoothstep(0.01, 0.11, vSunUv.y);
float verticalHeat = mix(0.78, 1.3, smoothstep(0.1, 0.88, vSunUv.y));
float luminance = verticalHeat * (1.28 + uWarmth * 0.24 + uRouteProgress * 0.08);
gl_FragColor = vec4(uColor * luminance, softEdge * bandCut * horizonFade);
}
`,
transparent: true,
@@ -60,8 +65,8 @@ export function createSun(resources: SceneResourceBag): SunWorld {
toneMapped: true,
}));
const disc = new THREE.Mesh(geometry, material);
disc.name = 'sun-disc';
disc.position.set(0, 11.5, -142);
disc.name = 'horizon-sun';
disc.position.set(0, 28, -410);
group.add(disc);
const haloTexture = resources.own('texture', createHaloTexture());
@@ -76,22 +81,23 @@ export function createSun(resources: SceneResourceBag): SunWorld {
toneMapped: true,
}));
const halo = new THREE.Sprite(haloMaterial);
halo.name = 'sun-halo';
halo.name = 'horizon-halo';
halo.position.copy(disc.position);
halo.position.z -= 0.5;
halo.scale.set(57, 43, 1);
halo.scale.set(220, 138, 1);
group.add(halo);
return {
group,
update(palette, routeProgress) {
update(palette, routeProgress, elapsedSeconds) {
const progress = clamp01(routeProgress);
sunColor.copy(palette.sun);
haloMaterial.color.copy(palette.sun);
uniforms.uWarmth.value = clamp01(palette.warmth);
uniforms.uRouteProgress.value = progress;
haloMaterial.opacity = 0.18 + palette.warmth * 0.12;
disc.position.y = 11.5 + palette.warmth * 4.8;
uniforms.uTime.value = Number.isFinite(elapsedSeconds) ? elapsedSeconds : 0;
haloMaterial.opacity = 0.12 + palette.warmth * 0.08;
disc.position.y = 28 + palette.warmth * 7;
halo.position.y = disc.position.y;
},
};