feat: polish nightdrive road sun and signage
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user