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:
@@ -20,11 +20,12 @@ interface WorldGroup {
|
||||
getObjectByName(name: string): unknown;
|
||||
}
|
||||
|
||||
const TERRAIN_WIDTH = 160;
|
||||
const TERRAIN_DEPTH = 300;
|
||||
const NEAR_Z = 14;
|
||||
const ROAD_CLEAR_HALF_WIDTH = 11.4;
|
||||
const RIDGE_START = 24;
|
||||
const TERRAIN_WIDTH = 360;
|
||||
const TERRAIN_DEPTH = 438;
|
||||
const NEAR_Z = 28;
|
||||
const ROAD_CLEAR_HALF_WIDTH = 9.4;
|
||||
const RIDGE_START = 46;
|
||||
const NOISE_PERIOD = 5_694;
|
||||
|
||||
export function createTerrain(
|
||||
resources: SceneResourceBag,
|
||||
@@ -42,6 +43,8 @@ export function createTerrain(
|
||||
uRouteProgress: { value: 0 },
|
||||
uTerrainEnergy: { value: initialPalette.terrainEnergy },
|
||||
uOctaves: { value: quality.terrainOctaves },
|
||||
uRelief: { value: 34 },
|
||||
uFar: { value: 430 },
|
||||
uLowColor: { value: lowColor },
|
||||
uHighColor: { value: highColor },
|
||||
};
|
||||
@@ -54,12 +57,15 @@ export function createTerrain(
|
||||
uniform float uRouteProgress;
|
||||
uniform float uTerrainEnergy;
|
||||
uniform float uOctaves;
|
||||
uniform float uRelief;
|
||||
uniform float uFar;
|
||||
varying float vHeight;
|
||||
varying float vRoadMask;
|
||||
varying float vDepthFade;
|
||||
varying float vFlow;
|
||||
|
||||
float hash21(vec2 point) {
|
||||
point = mod(point, 256.0);
|
||||
point = fract(point * vec2(123.34, 456.21));
|
||||
point += dot(point, point + 45.32);
|
||||
return fract(point.x * point.y);
|
||||
@@ -85,19 +91,22 @@ export function createTerrain(
|
||||
|
||||
void main() {
|
||||
vec3 displaced = position;
|
||||
float wrappedPhase = mod(uDistance * (0.18 + uSpeed * 0.06), 96.0);
|
||||
float ridgeNoise = multiOctaveNoise(vec2(position.x * 0.038, (position.z - wrappedPhase) * 0.028));
|
||||
float macroRidge = multiOctaveNoise(vec2(position.x * 0.014 + 31.0, position.z * 0.012));
|
||||
float edgeGrowth = mix(0.6, 1.0, smoothstep(0.0, 72.0, abs(position.x)));
|
||||
float height = roadMask * (0.25 + ridgeNoise * ridgeNoise * 8.8 + macroRidge * 2.4);
|
||||
height *= edgeGrowth * uTerrainEnergy;
|
||||
displaced.y += height - 0.22;
|
||||
float wrappedPhase = mod(uDistance, ${NOISE_PERIOD.toFixed(1)});
|
||||
const float S = 0.04495961;
|
||||
vec2 world = vec2(position.x, position.z - wrappedPhase);
|
||||
float ridgeNoise = multiOctaveNoise(world * S);
|
||||
float edgeGrowth = mix(7.0, uRelief, smoothstep(14.0, 150.0, abs(position.x)));
|
||||
float height = roadMask * ridgeNoise * edgeGrowth * uTerrainEnergy;
|
||||
displaced.y += height;
|
||||
|
||||
vHeight = clamp(height / 10.0, 0.0, 1.0);
|
||||
vHeight = clamp(height / max(1.0, uRelief), 0.0, 1.0);
|
||||
vRoadMask = roadMask;
|
||||
vDepthFade = smoothstep(-286.0, -205.0, position.z) * (1.0 - smoothstep(7.0, 14.0, position.z));
|
||||
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);
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(displaced, 1.0);
|
||||
gl_Position = projectionMatrix * viewPosition;
|
||||
}
|
||||
`,
|
||||
fragmentShader: `
|
||||
@@ -112,10 +121,10 @@ export function createTerrain(
|
||||
void main() {
|
||||
float ridgeMix = smoothstep(0.16, 0.78, vHeight);
|
||||
vec3 color = mix(uLowColor, uHighColor, ridgeMix);
|
||||
float pulse = 0.78 + 0.22 * smoothstep(0.18, 0.52, vFlow);
|
||||
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.34, 0.66, ridgeMix);
|
||||
gl_FragColor = vec4(color, alpha);
|
||||
float alpha = vRoadMask * vDepthFade * pulse * routeCalm * mix(0.32, 0.82, ridgeMix);
|
||||
gl_FragColor = vec4(color * mix(0.78, 1.55, ridgeMix), alpha);
|
||||
}
|
||||
`,
|
||||
transparent: true,
|
||||
@@ -126,7 +135,7 @@ export function createTerrain(
|
||||
}));
|
||||
|
||||
const lines = new THREE.LineSegments(geometry, material);
|
||||
lines.name = 'terrain-lines';
|
||||
lines.name = 'terrain-grid';
|
||||
lines.frustumCulled = false;
|
||||
group.add(lines);
|
||||
|
||||
@@ -134,7 +143,7 @@ export function createTerrain(
|
||||
group,
|
||||
update(distance, speed, chapter, routeProgress, palette) {
|
||||
const chapterPalette = CHAPTER_PALETTES[chapter];
|
||||
uniforms.uDistance.value = finiteOrZero(distance);
|
||||
uniforms.uDistance.value = ((finiteOrZero(distance) % NOISE_PERIOD) + NOISE_PERIOD) % NOISE_PERIOD;
|
||||
uniforms.uSpeed.value = clamp01(speed);
|
||||
uniforms.uRouteProgress.value = clamp01(routeProgress);
|
||||
uniforms.uTerrainEnergy.value = palette?.terrainEnergy ?? chapterPalette.terrainEnergy;
|
||||
@@ -151,7 +160,8 @@ export function createTerrain(
|
||||
|
||||
function createIndexedTerrainGeometry(segments: number) {
|
||||
const zSegments = Math.max(16, Math.floor(segments));
|
||||
const xSegments = Math.max(24, Math.floor(zSegments * 0.75 / 2) * 2);
|
||||
const approximateCell = TERRAIN_DEPTH / zSegments;
|
||||
const xSegments = Math.max(48, Math.floor((TERRAIN_WIDTH / approximateCell) / 2) * 2);
|
||||
const columnCount = xSegments + 1;
|
||||
const rowCount = zSegments + 1;
|
||||
const positions = new Float32Array(columnCount * rowCount * 3);
|
||||
|
||||
Reference in New Issue
Block a user