feat: immerse the portfolio in a chromatic synthwave night

Carry rose, violet, coral, electric blue and cyan through the interface, road, terrain, signs and atmosphere. Add a Blender-authored paint edition and matching runtime fallback captures. Interpolate atmospheric color on a small sky lattice and enrich existing speed effects without another rendering pass.

Preserve approved copy, project media, metadata, navigation contracts and adaptive audio. Verified with typecheck, client/SSR builds, validated prerendering, artifact and transfer checks, responsive Chrome interaction, fallback checks and Intel-backed performance sampling.
This commit is contained in:
2026-09-05 13:17:53 +03:00
parent 43e510341e
commit 5d5935a1f8
24 changed files with 693 additions and 130 deletions

View File

@@ -22,6 +22,8 @@ export interface AtmosphereWorld {
speed: number,
acceleration: number,
palette: Readonly<MutableChapterPalette>,
elapsedSeconds?: number,
overdrive?: number,
): void;
}
@@ -38,7 +40,12 @@ export function createAtmosphere(
group.name = 'atmosphere-world';
const night = CHAPTER_PALETTES['deep-night'];
const skyGeometry = resources.own('geometry', new THREE.PlaneGeometry(2, 2));
// The sky contains only broad gradients: interpolate their color over a
// small lattice instead of evaluating trigonometry for every screen pixel.
const skyGeometry = resources.own(
'geometry',
new THREE.PlaneGeometry(2, 2, 48, 32),
);
const skyMaterial = resources.own(
'material',
new THREE.ShaderMaterial({
@@ -48,37 +55,53 @@ export function createAtmosphere(
uHazeColor: { value: new THREE.Color(night.haze) },
uWarmth: { value: night.warmth },
uAspect: { value: 1 },
uTime: { value: 0 },
uOverdrive: { value: 0 },
},
vertexShader: `
varying vec2 vSkyUv;
void main() {
vSkyUv = uv;
gl_Position = vec4(position.xy, 1.0, 1.0);
}
`,
fragmentShader: `
uniform vec3 uSkyColor;
uniform vec3 uFogColor;
uniform vec3 uHazeColor;
uniform float uWarmth;
uniform float uAspect;
varying vec2 vSkyUv;
uniform float uTime;
uniform float uOverdrive;
varying vec3 vSkyColor;
void main() {
float verticalSky = smoothstep(0.08, 0.66, vSkyUv.y);
float verticalSky = smoothstep(0.08, 0.66, uv.y);
vec3 lowerField = mix(uSkyColor, uFogColor, 0.34);
vec3 color = mix(lowerField * 0.72, uSkyColor * 1.2, verticalSky);
float horizonBand = exp(-pow(abs(vSkyUv.y - 0.47) * 6.2, 2.0));
float normalizedX = abs(vSkyUv.x - 0.5) * 2.0 * min(uAspect, 2.35) / 2.35;
vec3 color = mix(lowerField * 0.8, uSkyColor * 1.35, verticalSky);
float horizonBand = exp(-pow(abs(uv.y - 0.47) * 6.2, 2.0));
float normalizedX = abs(uv.x - 0.5) * 2.0 * min(uAspect, 2.35) / 2.35;
float centerGlow = 1.0 - smoothstep(0.18, 1.16, normalizedX);
color = mix(
color,
uHazeColor * (0.72 + uWarmth * 0.14),
horizonBand * mix(0.18, 0.4, centerGlow)
horizonBand * mix(0.23, 0.48, centerGlow)
);
float topFalloff = smoothstep(0.54, 1.0, vSkyUv.y);
// Broad auroral veils share the existing sky pass. Slow, unequal
// periods let the sky breathe without scrolling or camera movement.
float drift = sin(uv.x * 5.2 + uTime * 0.025) * 0.035;
float arc = uv.y - (0.66 + 0.19 * sin(uv.x * 2.8 - 0.35) + drift);
float veil = exp(-arc * arc * 82.0);
float innerVeil = exp(-pow((arc + 0.055) * 17.0, 2.0));
float side = smoothstep(0.15, 0.7, uv.x);
vec3 violet = vec3(0.23, 0.018, 0.50);
vec3 rose = vec3(0.50, 0.018, 0.17);
vec3 aurora = mix(violet, rose, 0.5 + 0.5 * sin(uv.x * 4.2 + uTime * 0.018));
color += aurora * veil * (0.10 + side * 0.11) * (1.0 + uOverdrive * 0.36);
color += vec3(0.018, 0.075, 0.29) * innerVeil * side * 0.12;
float topFalloff = smoothstep(0.54, 1.0, uv.y);
float vignette = smoothstep(0.36, 1.18, normalizedX);
color *= 1.0 - topFalloff * 0.28 - vignette * 0.08;
gl_FragColor = vec4(color, 1.0);
vSkyColor = color;
gl_Position = vec4(position.xy, 1.0, 1.0);
}
`,
fragmentShader: `
varying vec3 vSkyColor;
void main() {
gl_FragColor = vec4(vSkyColor, 1.0);
#include <tonemapping_fragment>
#include <colorspace_fragment>
}
@@ -165,7 +188,8 @@ export function createAtmosphere(
void main() {
float radius = distance(gl_PointCoord, vec2(0.5));
float point = 1.0 - smoothstep(0.2, 0.5, radius);
gl_FragColor = vec4(uColor, point * vBrightness * uVisibility);
vec3 starColor = mix(uColor, vec3(1.0, 0.40, 0.58), smoothstep(0.65, 1.0, vBrightness));
gl_FragColor = vec4(starColor, point * vBrightness * uVisibility);
#include <tonemapping_fragment>
#include <colorspace_fragment>
}
@@ -200,6 +224,7 @@ export function createAtmosphere(
uniform float uDistance;
uniform float uAcceleration;
varying float vTail;
varying vec3 vStreakColor;
void main() {
vec3 streak = position;
float depthCycle = mod(
@@ -208,6 +233,7 @@ export function createAtmosphere(
) + ${WORLD_FAR_Z.toFixed(1)};
streak.z = depthCycle - endFactor * (1.4 + uAcceleration * 31.0);
vTail = endFactor;
vStreakColor = mix(vec3(1.0, 0.065, 0.39), vec3(0.035, 0.66, 1.0), smoothstep(-80.0, 80.0, position.x));
gl_Position = projectionMatrix * modelViewMatrix * vec4(streak, 1.0);
}
`,
@@ -215,8 +241,9 @@ export function createAtmosphere(
uniform vec3 uColor;
uniform float uVisibility;
varying float vTail;
varying vec3 vStreakColor;
void main() {
gl_FragColor = vec4(uColor * (1.0 + uVisibility * 0.45), uVisibility * mix(0.08, 0.62, vTail));
gl_FragColor = vec4(vStreakColor * (1.0 + uVisibility * 0.45), uVisibility * mix(0.08, 0.62, vTail));
#include <tonemapping_fragment>
#include <colorspace_fragment>
}
@@ -241,7 +268,14 @@ export function createAtmosphere(
: 1;
skyMaterial.uniforms.uAspect.value = safeAspect;
},
update(distance, speed, acceleration, palette) {
update(
distance,
speed,
acceleration,
palette,
elapsedSeconds = 0,
overdrive = 0,
) {
const safeAcceleration = clamp01(acceleration);
const visibility =
safeAcceleration *
@@ -257,6 +291,8 @@ export function createAtmosphere(
skyMaterial.uniforms.uFogColor.value.copy(palette.fog);
skyMaterial.uniforms.uHazeColor.value.copy(palette.haze);
skyMaterial.uniforms.uWarmth.value = palette.warmth;
skyMaterial.uniforms.uTime.value = finiteOrZero(elapsedSeconds);
skyMaterial.uniforms.uOverdrive.value = clamp01(overdrive);
for (let index = 0; index < silhouetteMaterials.length; index += 1) {
silhouetteMaterials[index]?.color
.copy(palette.fog)

View File

@@ -1,6 +1,6 @@
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import grandTourerUrl from '@/assets/nightdrive-grand-tourer-expressway.glb?url';
import grandTourerUrl from '@/assets/nightdrive-grand-tourer-chromatic.glb?url';
import type { SceneResourceBag } from '@/scene/resources';
import type { SceneDetailMode } from '@/scene/types';
@@ -94,8 +94,8 @@ export function createCar(
const bodyMaterial = resources.own(
'material',
new THREE.MeshStandardMaterial({
color: 0x304e68,
emissive: 0x091725,
color: 0xa72c73,
emissive: 0x2c0822,
emissiveIntensity: 0.42,
roughness: 0.36,
metalness: 0.68,
@@ -104,8 +104,8 @@ export function createCar(
const shoulderMaterial = resources.own(
'material',
new THREE.MeshStandardMaterial({
color: 0x365b77,
emissive: 0x0a1b2a,
color: 0x793fb2,
emissive: 0x211032,
emissiveIntensity: 0.36,
roughness: 0.3,
metalness: 0.74,
@@ -475,11 +475,11 @@ function createAuthoredCar(
material.isMeshStandardMaterial &&
(object.name === 'car-body' || object.name === 'body-haunches')
) {
material.emissive?.setHex(0x0b2738);
material.emissiveIntensity = 0.26;
material.emissive?.setHex(0x491034);
material.emissiveIntensity = 0.16;
}
if (material.isMeshStandardMaterial && object.name === 'rear-glass') {
material.emissive?.setHex(0x041725);
material.emissive?.setHex(0x0f143d);
material.emissiveIntensity = 0.31;
}
if (object.name === 'rear-light-bar' && material.isMeshStandardMaterial) {

View File

@@ -6,13 +6,13 @@ import type { SceneResourceBag } from '@/scene/resources';
*/
export function createCoachworkEnvironment(resources: SceneResourceBag, renderer: unknown) {
const studio = new THREE.Scene();
studio.background = new THREE.Color(0x172738);
studio.background = new THREE.Color(0x251435);
const geometry = new THREE.PlaneGeometry(1, 1);
const cards = [
{ color: 0xb6dce8, power: 3, at: [-8, 7, 2], size: [4, 14] },
{ color: 0x607c91, power: 1.5, at: [6, 5, -3], size: [5, 12] },
{ color: 0xcbd8df, power: 2.2, at: [0, 12, 0], size: [14, 6] },
{ color: 0xbc7278, power: 1.2, at: [0, 3, -12], size: [20, 4] },
{ color: 0x86d9ff, power: 3, at: [-8, 7, 2], size: [4, 14] },
{ color: 0xff68b7, power: 1.5, at: [6, 5, -3], size: [5, 12] },
{ color: 0xffdcce, power: 2.2, at: [0, 12, 0], size: [14, 6] },
{ color: 0xff8b66, power: 1.2, at: [0, 3, -12], size: [20, 4] },
];
const materials = cards.map((card) => {
const material = new THREE.MeshBasicMaterial({ color: new THREE.Color(card.color).multiplyScalar(card.power), side: THREE.DoubleSide });

View File

@@ -317,6 +317,8 @@ export async function createProceduralSceneGeneration(
visualSpeed,
response.streakEnergy,
currentPalette,
atmosphericTime,
overdrive,
);
background.copy(currentPalette.sky);
scene.fog.color.copy(currentPalette.fog);
@@ -325,9 +327,9 @@ export async function createProceduralSceneGeneration(
.lerp(currentPalette.terrainHigh, 0.26);
ambient.groundColor.copy(currentPalette.fog);
ambient.intensity = 1.05 + currentPalette.warmth * 0.18;
carKeyLight.color.setHex(0xb9e3ef);
carKeyLight.color.setHex(0xb2d5ff);
carKeyLight.intensity = 32 + response.bloomEnergy * 3.6 + overdrive * 8;
carFillLight.color.setHex(0x809ec1);
carFillLight.color.setHex(0xff62ad);
carFillLight.intensity = 12.5 + currentPalette.warmth * 2.8;
rearLight.intensity = 4.2 + response.suspensionEnergy * 1.4;
const wideComposition = camera.aspect > 1.18;

View File

@@ -51,10 +51,10 @@ export function createExpressway(
void main() {
float farFade = 1.0 - smoothstep(380.0, 660.0, vDepth);
float nearFade = smoothstep(0.0, 9.0, vDepth);
vec3 cyan = vec3(0.15, 0.69, 0.76);
vec3 cyan = mix(vec3(1.0, 0.025, 0.34), vec3(0.02, 0.78, 0.93), step(0.0, vWorld.x));
vec3 coral = vec3(1.0, 0.18, 0.095);
float facing = max(0.0, dot(normalize(vNormal), normalize(vec3(-0.5, 0.9, 0.6))));
vec3 color = ${luminous ? `mix(${coral ? 'coral' : 'cyan'}, coral, uWarmth * 0.12) * (1.3 + uEnergy * 0.5)` : 'vec3(0.015, 0.027, 0.044) + vec3(0.055, 0.10, 0.135) * pow(facing, 2.0)'};
vec3 color = ${luminous ? `mix(${coral ? 'coral' : 'cyan'}, coral, uWarmth * 0.12) * (1.3 + uEnergy * 0.5)` : 'vec3(0.028, 0.012, 0.046) + vec3(0.09, 0.055, 0.14) * pow(facing, 2.0)'};
${!luminous ? 'color += cyan * pow(max(0.0, vNormal.x), 2.0) * 0.028;' : ''}
if(farFade * nearFade < 0.02) discard;
gl_FragColor = vec4(color, farFade * nearFade);

View File

@@ -29,8 +29,10 @@ export function createHorizonAir(resources: SceneResourceBag) {
float ribbon=exp(-pow((vUv.y-0.45-wave)*5.6,2.0));
float thin=exp(-pow((vUv.y-0.51-wave)*37.0,2.0));
float horizontal=pow(max(0.0,sin(vUv.x*3.14159)),2.0);
vec3 color=mix(uColor,vec3(0.21,0.49,0.59),vUv.y*0.25);
gl_FragColor=vec4(color,(ribbon*0.12+thin*0.026)*horizontal*(0.6+uWarmth*0.4));
float colorWave=0.5+0.5*sin(vUv.x*7.0+uTime*0.035);
vec3 color=mix(uColor,vec3(0.27,0.045,0.75),colorWave*0.55);
color=mix(color,vec3(0.05,0.52,0.75),smoothstep(0.54,0.82,vUv.y)*0.3);
gl_FragColor=vec4(color,(ribbon*0.16+thin*0.032)*horizontal*(0.7+uWarmth*0.3));
#include <tonemapping_fragment>
#include <colorspace_fragment>
}

View File

@@ -38,72 +38,72 @@ export interface MutableColor {
export const CHAPTER_PALETTES: Readonly<Record<EnvironmentChapter, ChapterPaletteDefinition>> = {
'deep-night': {
sky: 0x040713,
fog: 0x07101c,
terrainLow: 0x388596,
terrainHigh: 0xa85e87,
sun: 0xe97985,
haze: 0x69425b,
sky: 0x19092e,
fog: 0x291236,
terrainLow: 0x48cbe0,
terrainHigh: 0xf16cbd,
sun: 0xff649d,
haze: 0xaf287d,
warmth: 0.12,
terrainEnergy: 0.78,
starVisibility: 1,
streakEnergy: 0.72,
},
systems: {
sky: 0x050918,
fog: 0x081622,
terrainLow: 0x168aa0,
terrainHigh: 0xa83f79,
sun: 0xd05282,
haze: 0x6d2968,
sky: 0x140b34,
fog: 0x221c48,
terrainLow: 0x59bdeb,
terrainHigh: 0xe06fff,
sun: 0xff569d,
haze: 0x902dad,
warmth: 0.22,
terrainEnergy: 0.9,
starVisibility: 0.94,
streakEnergy: 0.86,
},
infrastructure: {
sky: 0x060a1b,
fog: 0x091a2a,
terrainLow: 0x197f98,
terrainHigh: 0xa13d7a,
sun: 0xd45b82,
haze: 0x6f2d70,
sky: 0x0e123b,
fog: 0x252148,
terrainLow: 0x3fe5dc,
terrainHigh: 0xd179ff,
sun: 0xff688e,
haze: 0x773bb1,
warmth: 0.3,
terrainEnergy: 0.88,
starVisibility: 0.86,
streakEnergy: 0.82,
},
builds: {
sky: 0x0b0d22,
fog: 0x111d2d,
terrainLow: 0x1a8ba0,
terrainHigh: 0xb04479,
sun: 0xe16d7a,
haze: 0x7f356c,
sky: 0x260b37,
fog: 0x391a43,
terrainLow: 0x6eb8ff,
terrainHigh: 0xff70ac,
sun: 0xff8269,
haze: 0xcb3a84,
warmth: 0.46,
terrainEnergy: 1,
starVisibility: 0.72,
streakEnergy: 1,
},
'pre-dawn': {
sky: 0x15142a,
fog: 0x1c2433,
terrainLow: 0x277f91,
terrainHigh: 0x9c4b78,
sun: 0xf09667,
haze: 0xa45468,
sky: 0x261337,
fog: 0x3a2648,
terrainLow: 0x79c7ee,
terrainHigh: 0xef81b7,
sun: 0xffa567,
haze: 0xbc536f,
warmth: 0.76,
terrainEnergy: 0.72,
starVisibility: 0.38,
streakEnergy: 0.52,
},
destination: {
sky: 0x241b31,
fog: 0x2b2b38,
terrainLow: 0x386f79,
terrainHigh: 0x87506e,
sun: 0xf4b06d,
haze: 0xbb6b65,
sky: 0x311635,
fog: 0x4d2a42,
terrainLow: 0x77d3d7,
terrainHigh: 0xe293c3,
sun: 0xffc078,
haze: 0xd96d74,
warmth: 1,
terrainEnergy: 0.54,
starVisibility: 0.16,

View File

@@ -47,10 +47,10 @@ export function createRoad(
uDistance: { value: 0 },
uSpeed: { value: 0 },
uAcceleration: { value: 0 },
uAsphalt: { value: new THREE.Color(0x03070f) },
uAsphaltLift: { value: new THREE.Color(0x162535) },
uEdgeColor: { value: new THREE.Color(0x64d9e7) },
uLaneColor: { value: new THREE.Color(0xf1a35e) },
uAsphalt: { value: new THREE.Color(0x090511) },
uAsphaltLift: { value: new THREE.Color(0x231936) },
uEdgeColor: { value: new THREE.Color(0x47e4f0) },
uLaneColor: { value: new THREE.Color(0xffb568) },
uFogColor: { value: new THREE.Color(initialPalette.fog) },
uHazeColor: { value: new THREE.Color(initialPalette.haze) },
};
@@ -110,6 +110,7 @@ export function createRoad(
void main() {
float roadX = abs(vRoadX);
vec3 edgeColor = mix(vec3(1.0, 0.035, 0.34), uEdgeColor, smoothstep(-1.0, 1.0, vRoadX));
float travelZ = -vWorldZ + mod(uDistance, ${FLOW_PERIOD.toFixed(1)});
float asphaltMask = 1.0 - smoothstep(7.78, 8.2, roadX);
float shoulderMask = smoothstep(7.72, 8.08, roadX);
@@ -137,8 +138,8 @@ export function createRoad(
vec3 shoulder = mix(uAsphalt * 0.54, uAsphaltLift * 0.7, broadVariation);
shoulder *= shoulderBreakup;
vec3 color = mix(shoulder, asphalt, asphaltMask);
color += uEdgeColor * outerEdge * (0.78 + uAcceleration * 0.34);
color += uEdgeColor * shoulderMask * grain * 0.026;
color += edgeColor * outerEdge * (0.78 + uAcceleration * 0.34);
color += edgeColor * shoulderMask * grain * 0.026;
color += uHazeColor * shoulderSeam * (0.035 + aggregate * 0.025);
color += uLaneColor * centerLane * (0.86 + uSpeed * 0.18);
@@ -148,12 +149,12 @@ export function createRoad(
float streak = valueNoise(vec2(vRoadX * 16.0, travelZ * 0.19));
float film = mix(0.36, 1.0, streak) * (0.74 + 0.26 * fine);
float grazing = smoothstep(8.0, 120.0, vViewDistance);
color += vec3(0.027, 0.047, 0.063) * film * asphaltMask * (0.35 + grazing * 0.65);
color += vec3(0.037, 0.022, 0.069) * film * asphaltMask * (0.35 + grazing * 0.65);
float channel = exp(-pow((roadX - 7.98) * 2.4, 2.0));
color += uEdgeColor * channel * (0.06 + 0.10 * film);
color += edgeColor * channel * (0.06 + 0.10 * film);
float shoulderRibs = step(0.77, fract(travelZ / 1.4))
* smoothstep(8.28, 8.36, roadX) * (1.0 - smoothstep(8.85, 9.02, roadX));
color += vec3(0.065, 0.10, 0.12) * shoulderRibs;
color += vec3(0.11, 0.065, 0.17) * shoulderRibs;
float joint = 1.0 - smoothstep(0.02, 0.075, abs(mod(travelZ + 36.0, 72.0) - 36.0));
color *= 1.0 - joint * 0.5;
float studs = (1.0 - smoothstep(0.07, 0.14, abs(roadX - 7.53)))

View File

@@ -113,8 +113,8 @@ export function createSign(
const housingMaterial = resources.own(
'material',
new THREE.MeshStandardMaterial({
color: 0x334956,
emissive: 0x23404e,
color: 0x654169,
emissive: 0x512547,
emissiveIntensity: 0.6,
roughness: 0.28,
metalness: 0.7,
@@ -126,7 +126,7 @@ export function createSign(
const backplateMaterial = resources.own(
'material',
new THREE.MeshStandardMaterial({
color: 0x172e3c,
color: 0x301f47,
roughness: 0.72,
metalness: 0.38,
transparent: true,
@@ -137,7 +137,7 @@ export function createSign(
const facePanelMaterial = resources.own(
'material',
new THREE.MeshStandardMaterial({
color: 0x061018,
color: 0x11091d,
roughness: 0.9,
metalness: 0.12,
transparent: true,
@@ -148,8 +148,8 @@ export function createSign(
const supportMaterial = resources.own(
'material',
new THREE.MeshStandardMaterial({
color: 0x21343e,
emissive: 0x163845,
color: 0x3c2752,
emissive: 0x38204e,
emissiveIntensity: 0.4,
roughness: 0.56,
metalness: 0.68,
@@ -161,8 +161,8 @@ export function createSign(
const edgeMaterial = resources.own(
'material',
new THREE.MeshStandardMaterial({
color: 0x39747a,
emissive: 0x55b9c0,
color: 0xa3488d,
emissive: 0xff79c0,
emissiveIntensity: 1.1,
roughness: 0.42,
metalness: 0.5,
@@ -174,8 +174,8 @@ export function createSign(
const routeMaterial = resources.own(
'material',
new THREE.MeshStandardMaterial({
color: 0x8e6446,
emissive: 0xb9723f,
color: 0xbc744d,
emissive: 0xffae6b,
emissiveIntensity: 0.8,
roughness: 0.48,
metalness: 0.42,
@@ -706,7 +706,7 @@ function drawLabel(
context.imageSmoothingQuality = 'high';
context.globalAlpha = 0.16;
context.fillStyle = '#8aa1a6';
context.fillStyle = '#cfabcd';
for (const [x, y, scratchWidth] of [
[312, 84, 142],
[742, 424, 96],
@@ -717,25 +717,25 @@ function drawLabel(
}
context.globalAlpha = 1;
context.fillStyle = '#52bbc2';
context.fillStyle = '#ff95cd';
context.fillRect(82, 72, 10, 368);
context.fillStyle = '#91a6a9';
context.fillStyle = '#bbaae0';
context.font = SIGN_MARKER_FONT;
context.textAlign = 'left';
context.textBaseline = 'middle';
context.fillText(`ROUTE ${route.marker} / NEXT`, 142, 120);
context.globalAlpha = 0.6;
context.fillStyle = '#2f4650';
context.fillStyle = '#61416d';
context.fillRect(142, 174, 1_610, 2);
context.globalAlpha = 1;
context.fillStyle = '#e2f7f4';
context.fillStyle = '#fff2df';
const labelSize = fittedLabelSize(context, route.label, 1_520);
context.font = `650 ${labelSize}px "Geist Variable", sans-serif`;
context.fillText(route.label, 142, 326);
context.strokeStyle = '#cb9160';
context.strokeStyle = '#ffb879';
context.lineWidth = 10;
context.lineCap = 'square';
context.lineJoin = 'miter';

View File

@@ -60,8 +60,8 @@ export function createSpeedWake(resources: SceneResourceBag) {
float crossFade=pow(max(0.0,1.0-abs(vUv.x*2.0-1.0)),1.3);
float tip=pow(1.0-vUv.y,1.4)*smoothstep(0.0,0.055,vUv.y);
float filament=0.76+0.24*sin(vUv.y*35.0-uTime*15.0);
vec3 color=mix(vec3(0.45,0.91,1.0),vec3(0.66,0.12,0.64),vUv.y);
color+=vec3(0.7,0.85,0.9)*pow(crossFade,5.0)*0.7;
vec3 color=mix(vec3(0.08,0.86,1.0),vec3(1.0,0.02,0.44),vUv.y);
color+=vec3(1.0,0.46,0.21)*pow(crossFade,5.0)*0.36;
gl_FragColor=vec4(color*1.8,crossFade*tip*filament*uPower*0.8);
#include <tonemapping_fragment>
#include <colorspace_fragment>
@@ -95,12 +95,13 @@ export function createSpeedWake(resources: SceneResourceBag) {
'material',
new THREE.ShaderMaterial({
uniforms,
vertexShader: `uniform float uPower;uniform float uTime;varying float vAlpha;
vertexShader: `uniform float uPower;uniform float uTime;varying float vAlpha;varying vec3 vColor;
void main(){float phase=fract(uTime*0.66+position.y*0.5);
vec3 p=vec3(position.x*(0.85+phase*0.85),0.04,position.z+phase*15.0);
vColor=mix(vec3(1.0,0.06,0.4),vec3(0.04,0.85,1.0),position.y);
vAlpha=sin(phase*3.14159)*(1.0-phase)*uPower*uPower;
gl_Position=projectionMatrix*modelViewMatrix*vec4(p,1.0);}`,
fragmentShader: `varying float vAlpha;void main(){gl_FragColor=vec4(0.3,0.85,1.0,vAlpha*0.48);}`,
fragmentShader: `varying float vAlpha;varying vec3 vColor;void main(){gl_FragColor=vec4(vColor,vAlpha*0.48);}`,
transparent: true,
depthWrite: false,
depthTest: true,

View File

@@ -73,9 +73,9 @@ export function createSun(resources: SceneResourceBag): SunWorld {
float lip = exp(-abs(phase - gap - 0.025) * 90.0) * step(solar.y, 0.65);
float convection = sin(solar.x * 19.0 + sin(solar.y * 23.0 + uTime * 0.055))
* sin(solar.y * 31.0 - uTime * 0.032);
vec3 vermilion = vec3(0.91, 0.045, 0.075);
vec3 coral = mix(uColor, vec3(1.0, 0.19, 0.10), 0.6);
vec3 crown = vec3(1.0, 0.54, 0.25);
vec3 vermilion = vec3(0.85, 0.018, 0.28);
vec3 coral = mix(uColor, vec3(1.0, 0.16, 0.13), 0.48);
vec3 crown = vec3(1.0, 0.63, 0.24);
vec3 core = mix(vermilion, coral, smoothstep(0.0, 0.58, solar.y));
core = mix(core, crown, smoothstep(0.47, 1.0, solar.y));
float filament = pow(abs(sin(solar.x * 39.0 + solar.y * 25.0 + convection * 2.4)), 16.0);
@@ -199,7 +199,7 @@ export function createSun(resources: SceneResourceBag): SunWorld {
: 0;
uniforms.uDriveEnergy.value = energy;
limbMaterial.uniforms.uEnergy.value = energy;
haloMaterial.opacity = 0.075 + palette.warmth * 0.03 + energy * 0.015;
haloMaterial.opacity = 0.095 + palette.warmth * 0.03 + energy * 0.015;
bleedMaterial.opacity = 0.055 + palette.warmth * 0.03 + energy * 0.01;
halo.scale.set(480 + energy * 18, 360 + energy * 10, 1);
disc.position.y = 79 + palette.warmth * 14;

View File

@@ -90,12 +90,13 @@ export function createTerrain(
// produces a color seam. Compute it per vertex, not per pixel.
vec3 gridSpectrum(float phase) {
float p = fract(phase);
vec3 cyan = vec3(0.025, 0.70, 0.90);
vec3 color = mix(cyan, vec3(0.075, 0.18, 0.80), smoothstep(0.04, 0.22, p));
color = mix(color, vec3(0.39, 0.085, 0.80), smoothstep(0.22, 0.40, p));
color = mix(color, vec3(0.88, 0.035, 0.28), smoothstep(0.40, 0.60, p));
color = mix(color, vec3(0.95, 0.21, 0.08), smoothstep(0.60, 0.78, p));
return mix(color, cyan, smoothstep(0.78, 1.0, p));
vec3 cyan = vec3(0.015, 0.82, 0.94);
vec3 color = mix(cyan, vec3(0.025, 0.12, 1.0), smoothstep(0.04, 0.22, p));
color = mix(color, vec3(0.48, 0.025, 1.0), smoothstep(0.22, 0.40, p));
color = mix(color, vec3(1.0, 0.02, 0.42), smoothstep(0.40, 0.60, p));
color = mix(color, vec3(1.0, 0.22, 0.045), smoothstep(0.60, 0.78, p));
color = mix(color, vec3(1.0, 0.55, 0.10), smoothstep(0.78, 0.87, p));
return mix(color, cyan, smoothstep(0.87, 1.0, p));
}
float hash21(vec2 point) {
@@ -174,7 +175,7 @@ export function createTerrain(
vec3 color = mix(vGridColor, uLowColor, 0.06);
// Speed warms passing accents, while the full palette stays present.
float hotAccent = uOverdrive * smoothstep(0.62, 1.0, vFlow) * 0.36;
color = mix(color, vec3(1.0, 0.28, 0.10), hotAccent);
color = mix(color, vec3(1.0, 0.20, 0.25), hotAccent);
color = mix(color, uHighColor, ridgeMix * 0.055);
color = mix(color, mix(uFogColor, uHazeColor, 0.2), 1.0 - vDepthFade);
float pulse = 0.88 + 0.12 * smoothstep(0.16, 0.55, vFlow);
@@ -184,7 +185,7 @@ export function createTerrain(
float surfaceStrength = mix(0.72, 0.12 + uOverdrive * 0.3, road);
float alpha = vDepthFade * pulse * routeCalm * mix(surfaceStrength, 0.91, ridgeMix);
float charge = uOverdrive * (0.35 + vFlow * 0.8) * (0.5 + apron * 0.5);
gl_FragColor = vec4(color * (1.3 + ridgeMix * 0.5 + charge), alpha);
gl_FragColor = vec4(color * (1.46 + ridgeMix * 0.54 + charge), alpha);
#include <tonemapping_fragment>
#include <colorspace_fragment>
}
@@ -223,10 +224,11 @@ export function createTerrain(
varying float vHeight;
varying float vDepthFade;
varying vec3 vTerrainPosition;
varying vec3 vGridColor;
void main() {
vec3 normal = normalize(cross(dFdx(vTerrainPosition), dFdy(vTerrainPosition)));
float light = abs(dot(normal, normalize(vec3(-0.5, 0.8, -0.3))));
vec3 color = mix(vec3(0.002, 0.008, 0.016), uLowColor * 0.11, light);
vec3 color = mix(vec3(0.012, 0.003, 0.024), vGridColor * 0.065, light);
color += uHighColor * pow(vHeight, 2.0) * light * 0.06;
color = mix(uFogColor * 0.55, color, vDepthFade);
gl_FragColor = vec4(color, 1.0);