feat: polish nightdrive road sun and signage

This commit is contained in:
2026-09-03 00:29:26 +03:00
parent bc18bc2043
commit a03d4d8dba
44 changed files with 1208 additions and 202 deletions

View File

@@ -3,6 +3,12 @@ import type { EnvironmentChapter } from '@/journey/types';
import type { RenderQualitySettings } from '@/scene/render-quality';
import type { SceneResourceBag } from '@/scene/resources';
import { CHAPTER_PALETTES, type MutableChapterPalette } from '@/scene/world/palette';
import {
HORIZON_FOG_END,
HORIZON_FOG_START,
WORLD_DEPTH,
WORLD_NEAR_Z,
} from '@/scene/world/metrics';
export interface TerrainWorld {
group: WorldGroup;
@@ -21,9 +27,7 @@ interface WorldGroup {
}
const TERRAIN_WIDTH = 360;
const TERRAIN_DEPTH = 438;
const NEAR_Z = 28;
const ROAD_CLEAR_HALF_WIDTH = 9.4;
const ROAD_CLEAR_HALF_WIDTH = 10.3;
const RIDGE_START = 46;
const NOISE_PERIOD = 5_694;
@@ -39,20 +43,24 @@ export function createTerrain(
const highColor = new THREE.Color(initialPalette.terrainHigh);
const uniforms = {
uDistance: { value: 0 },
uFlowDistance: { value: 0 },
uSpeed: { value: 0 },
uRouteProgress: { value: 0 },
uTerrainEnergy: { value: initialPalette.terrainEnergy },
uOctaves: { value: quality.terrainOctaves },
uRelief: { value: 34 },
uFar: { value: 430 },
uFar: { value: HORIZON_FOG_END },
uLowColor: { value: lowColor },
uHighColor: { value: highColor },
uFogColor: { value: new THREE.Color(initialPalette.fog) },
uHazeColor: { value: new THREE.Color(initialPalette.haze) },
};
const material = resources.own('material', new THREE.ShaderMaterial({
uniforms,
vertexShader: `
attribute float roadMask;
uniform float uDistance;
uniform float uFlowDistance;
uniform float uSpeed;
uniform float uRouteProgress;
uniform float uTerrainEnergy;
@@ -104,14 +112,16 @@ export function createTerrain(
vec4 viewPosition = modelViewMatrix * vec4(displaced, 1.0);
float viewDistance = -viewPosition.z;
vDepthFade = smoothstep(4.0, 18.0, viewDistance)
* (1.0 - smoothstep(uFar * 0.32, uFar * 0.88, viewDistance));
vFlow = fract((-position.z + uDistance * (0.9 + uSpeed * 0.25)) / 11.0);
* (1.0 - smoothstep(${(HORIZON_FOG_START + 150).toFixed(1)}, ${HORIZON_FOG_END.toFixed(1)}, viewDistance));
vFlow = fract((-position.z + uFlowDistance) / 11.0);
gl_Position = projectionMatrix * viewPosition;
}
`,
fragmentShader: `
uniform vec3 uLowColor;
uniform vec3 uHighColor;
uniform vec3 uFogColor;
uniform vec3 uHazeColor;
uniform float uRouteProgress;
varying float vHeight;
varying float vRoadMask;
@@ -121,6 +131,7 @@ export function createTerrain(
void main() {
float ridgeMix = smoothstep(0.16, 0.78, vHeight);
vec3 color = mix(uLowColor, uHighColor, ridgeMix);
color = mix(color, mix(uFogColor, uHazeColor, 0.2), 1.0 - vDepthFade);
float pulse = 0.72 + 0.28 * smoothstep(0.16, 0.55, vFlow);
float routeCalm = mix(1.0, 0.9, smoothstep(0.82, 1.0, uRouteProgress));
float alpha = vRoadMask * vDepthFade * pulse * routeCalm * mix(0.32, 0.82, ridgeMix);
@@ -144,12 +155,15 @@ export function createTerrain(
update(distance, speed, chapter, routeProgress, palette) {
const chapterPalette = CHAPTER_PALETTES[chapter];
uniforms.uDistance.value = ((finiteOrZero(distance) % NOISE_PERIOD) + NOISE_PERIOD) % NOISE_PERIOD;
uniforms.uFlowDistance.value = uniforms.uDistance.value;
uniforms.uSpeed.value = clamp01(speed);
uniforms.uRouteProgress.value = clamp01(routeProgress);
uniforms.uTerrainEnergy.value = palette?.terrainEnergy ?? chapterPalette.terrainEnergy;
if (palette) {
lowColor.copy(palette.terrainLow);
highColor.copy(palette.terrainHigh);
uniforms.uFogColor.value.copy(palette.fog);
uniforms.uHazeColor.value.copy(palette.haze);
} else {
lowColor.setHex(chapterPalette.terrainLow);
highColor.setHex(chapterPalette.terrainHigh);
@@ -160,7 +174,7 @@ export function createTerrain(
function createIndexedTerrainGeometry(segments: number) {
const zSegments = Math.max(16, Math.floor(segments));
const approximateCell = TERRAIN_DEPTH / zSegments;
const approximateCell = WORLD_DEPTH / zSegments;
const xSegments = Math.max(48, Math.floor((TERRAIN_WIDTH / approximateCell) / 2) * 2);
const columnCount = xSegments + 1;
const rowCount = zSegments + 1;
@@ -170,7 +184,7 @@ function createIndexedTerrainGeometry(segments: number) {
let maskOffset = 0;
for (let row = 0; row < rowCount; row += 1) {
const z = NEAR_Z - (row / zSegments) * TERRAIN_DEPTH;
const z = WORLD_NEAR_Z - (row / zSegments) * WORLD_DEPTH;
for (let column = 0; column < columnCount; column += 1) {
const x = -TERRAIN_WIDTH / 2 + (column / xSegments) * TERRAIN_WIDTH;
positions[vertexOffset] = x;