feat: polish nightdrive road sun and signage
This commit is contained in:
@@ -2,10 +2,16 @@ import * as THREE from 'three';
|
||||
import type { SceneResourceBag } from '@/scene/resources';
|
||||
import type { MutableChapterPalette } from '@/scene/world/palette';
|
||||
import { CHAPTER_PALETTES } from '@/scene/world/palette';
|
||||
import { SUN_DISTANCE_Z } from '@/scene/world/metrics';
|
||||
|
||||
export interface SunWorld {
|
||||
group: WorldGroup;
|
||||
update(palette: Readonly<MutableChapterPalette>, routeProgress: number, elapsedSeconds: number): void;
|
||||
update(
|
||||
palette: Readonly<MutableChapterPalette>,
|
||||
routeProgress: number,
|
||||
elapsedSeconds: number,
|
||||
driveEnergy?: number,
|
||||
): void;
|
||||
}
|
||||
|
||||
interface WorldGroup {
|
||||
@@ -20,12 +26,14 @@ export function createSun(resources: SceneResourceBag): SunWorld {
|
||||
const sunColor = new THREE.Color(night.sun);
|
||||
const uniforms = {
|
||||
uColor: { value: sunColor },
|
||||
uHazeColor: { value: new THREE.Color(night.haze) },
|
||||
uScanBands: { value: 11 },
|
||||
uWarmth: { value: night.warmth },
|
||||
uRouteProgress: { value: 0 },
|
||||
uTime: { value: 0 },
|
||||
uDriveEnergy: { value: 0 },
|
||||
};
|
||||
const geometry = resources.own('geometry', new THREE.CircleGeometry(44, 128));
|
||||
const geometry = resources.own('geometry', new THREE.CircleGeometry(74, 128));
|
||||
const material = resources.own('material', new THREE.ShaderMaterial({
|
||||
uniforms,
|
||||
vertexShader: `
|
||||
@@ -38,24 +46,42 @@ export function createSun(resources: SceneResourceBag): SunWorld {
|
||||
`,
|
||||
fragmentShader: `
|
||||
uniform vec3 uColor;
|
||||
uniform vec3 uHazeColor;
|
||||
uniform float uScanBands;
|
||||
uniform float uWarmth;
|
||||
uniform float uRouteProgress;
|
||||
uniform float uTime;
|
||||
uniform float uDriveEnergy;
|
||||
varying vec2 vSunUv;
|
||||
|
||||
void main() {
|
||||
float radius = distance(vSunUv, vec2(0.5));
|
||||
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);
|
||||
vec2 centered = vSunUv - 0.5;
|
||||
float radius = length(centered);
|
||||
float softEdge = 1.0 - smoothstep(0.465, 0.5, radius);
|
||||
float limb = smoothstep(0.31, 0.485, radius);
|
||||
float broadField = 0.5 + 0.5 * sin(
|
||||
vSunUv.x * 9.0
|
||||
+ sin(vSunUv.y * 11.0 + uTime * 0.028) * 1.35
|
||||
);
|
||||
float fineField = 0.5 + 0.5 * sin(
|
||||
vSunUv.x * 23.0 - vSunUv.y * 16.0 + sin(uTime * 0.021) * 0.7
|
||||
);
|
||||
float internalField = broadField * 0.7 + fineField * 0.3;
|
||||
float verticalHeat = mix(0.68, 1.3, smoothstep(0.06, 0.92, vSunUv.y));
|
||||
float lowerDisc = 1.0 - smoothstep(0.28, 0.67, vSunUv.y);
|
||||
float scanPhase = fract(vSunUv.y * uScanBands + sin(uTime * 0.08) * 0.008);
|
||||
float scanBand = smoothstep(0.09, 0.18, scanPhase)
|
||||
* (1.0 - smoothstep(0.72, 0.82, scanPhase));
|
||||
float bandCut = mix(1.0, 0.54 + scanBand * 0.46, lowerDisc * 0.78);
|
||||
float horizonFade = smoothstep(0.015, 0.13, vSunUv.y);
|
||||
float radialCore = 1.0 - smoothstep(0.02, 0.48, radius);
|
||||
float luminance = verticalHeat
|
||||
* (1.08 + uWarmth * 0.16 + uRouteProgress * 0.045 + uDriveEnergy * 0.045);
|
||||
vec3 warmCore = mix(uColor, vec3(1.0, 0.61, 0.43), uWarmth * 0.18 + uRouteProgress * 0.035);
|
||||
vec3 core = mix(uColor * 0.78, warmCore * 1.12, radialCore * 0.58);
|
||||
core = mix(core, core * mix(0.82, 1.13, internalField), 0.54);
|
||||
core = mix(core, uHazeColor * 0.72, limb * 0.22 + lowerDisc * 0.055);
|
||||
gl_FragColor = vec4(core * luminance, softEdge * bandCut * horizonFade);
|
||||
}
|
||||
`,
|
||||
transparent: true,
|
||||
@@ -66,15 +92,51 @@ export function createSun(resources: SceneResourceBag): SunWorld {
|
||||
}));
|
||||
const disc = new THREE.Mesh(geometry, material);
|
||||
disc.name = 'horizon-sun';
|
||||
disc.position.set(0, 28, -410);
|
||||
disc.position.set(0, 48, SUN_DISTANCE_Z);
|
||||
group.add(disc);
|
||||
|
||||
const limbMaterial = resources.own('material', new THREE.ShaderMaterial({
|
||||
uniforms: {
|
||||
uColor: { value: sunColor.clone() },
|
||||
uEnergy: { value: 0 },
|
||||
},
|
||||
vertexShader: `
|
||||
varying vec2 vUv;
|
||||
void main() {
|
||||
vUv = uv;
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||
}
|
||||
`,
|
||||
fragmentShader: `
|
||||
uniform vec3 uColor;
|
||||
uniform float uEnergy;
|
||||
varying vec2 vUv;
|
||||
void main() {
|
||||
float radius = distance(vUv, vec2(0.5));
|
||||
float ring = smoothstep(0.405, 0.475, radius)
|
||||
* (1.0 - smoothstep(0.475, 0.515, radius));
|
||||
gl_FragColor = vec4(uColor * (0.72 + uEnergy * 0.07), ring * (0.22 + uEnergy * 0.025));
|
||||
}
|
||||
`,
|
||||
transparent: true,
|
||||
depthWrite: false,
|
||||
depthTest: true,
|
||||
blending: THREE.AdditiveBlending,
|
||||
toneMapped: true,
|
||||
}));
|
||||
const limb = new THREE.Mesh(geometry, limbMaterial);
|
||||
limb.name = 'sun-limb-glow';
|
||||
limb.position.copy(disc.position);
|
||||
limb.position.z -= 0.3;
|
||||
limb.scale.setScalar(1.075);
|
||||
group.add(limb);
|
||||
|
||||
const haloTexture = resources.own('texture', createHaloTexture());
|
||||
const haloMaterial = resources.own('material', new THREE.SpriteMaterial({
|
||||
map: haloTexture,
|
||||
color: sunColor.clone(),
|
||||
transparent: true,
|
||||
opacity: 0.24,
|
||||
opacity: 0.13,
|
||||
depthWrite: false,
|
||||
depthTest: true,
|
||||
blending: THREE.AdditiveBlending,
|
||||
@@ -83,22 +145,48 @@ export function createSun(resources: SceneResourceBag): SunWorld {
|
||||
const halo = new THREE.Sprite(haloMaterial);
|
||||
halo.name = 'horizon-halo';
|
||||
halo.position.copy(disc.position);
|
||||
halo.position.z -= 0.5;
|
||||
halo.scale.set(220, 138, 1);
|
||||
halo.position.z -= 1.2;
|
||||
halo.scale.set(330, 232, 1);
|
||||
group.add(halo);
|
||||
|
||||
const bleedMaterial = resources.own('material', new THREE.SpriteMaterial({
|
||||
map: haloTexture,
|
||||
color: sunColor.clone(),
|
||||
transparent: true,
|
||||
opacity: 0.075,
|
||||
depthWrite: false,
|
||||
depthTest: true,
|
||||
blending: THREE.AdditiveBlending,
|
||||
toneMapped: true,
|
||||
}));
|
||||
const bleed = new THREE.Sprite(bleedMaterial);
|
||||
bleed.name = 'horizon-light-bleed';
|
||||
bleed.position.set(0, 7, SUN_DISTANCE_Z - 2.2);
|
||||
bleed.scale.set(560, 118, 1);
|
||||
group.add(bleed);
|
||||
|
||||
return {
|
||||
group,
|
||||
update(palette, routeProgress, elapsedSeconds) {
|
||||
update(palette, routeProgress, elapsedSeconds, driveEnergy = 0) {
|
||||
const progress = clamp01(routeProgress);
|
||||
const energy = clamp01(driveEnergy);
|
||||
sunColor.copy(palette.sun);
|
||||
haloMaterial.color.copy(palette.sun);
|
||||
uniforms.uHazeColor.value.copy(palette.haze);
|
||||
haloMaterial.color.copy(palette.sun).lerp(palette.haze, 0.22);
|
||||
bleedMaterial.color.copy(palette.sun).lerp(palette.haze, 0.22);
|
||||
limbMaterial.uniforms.uColor.value.copy(palette.sun);
|
||||
uniforms.uWarmth.value = clamp01(palette.warmth);
|
||||
uniforms.uRouteProgress.value = progress;
|
||||
uniforms.uTime.value = Number.isFinite(elapsedSeconds) ? elapsedSeconds : 0;
|
||||
haloMaterial.opacity = 0.12 + palette.warmth * 0.08;
|
||||
disc.position.y = 28 + palette.warmth * 7;
|
||||
uniforms.uDriveEnergy.value = energy;
|
||||
limbMaterial.uniforms.uEnergy.value = energy;
|
||||
haloMaterial.opacity = 0.105 + palette.warmth * 0.05 + energy * 0.015;
|
||||
bleedMaterial.opacity = 0.055 + palette.warmth * 0.03 + energy * 0.01;
|
||||
halo.scale.set(330 + energy * 18, 232 + energy * 10, 1);
|
||||
disc.position.y = 48 + palette.warmth * 9;
|
||||
limb.position.y = disc.position.y;
|
||||
halo.position.y = disc.position.y;
|
||||
bleed.position.y = 7 + palette.warmth * 3;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user