feat: layer the nightdrive sky and atmosphere
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import * as THREE from 'three';
|
||||
import type { EnvironmentChapter, JourneySnapshot } from '@/journey/types';
|
||||
import type { JourneySnapshot } from '@/journey/types';
|
||||
import type { SceneResourceBag } from '@/scene/resources';
|
||||
import type { SceneBuildContext, SceneGeneration } from '@/scene/types';
|
||||
import { renderQualityFor } from '@/scene/render-quality';
|
||||
@@ -8,21 +8,18 @@ import { createTerrain } from '@/scene/world/terrain';
|
||||
import { createSun } from '@/scene/world/sun';
|
||||
import { createCar } from '@/scene/world/car';
|
||||
import { createSign } from '@/scene/world/sign';
|
||||
import { createParticles } from '@/scene/world/particles';
|
||||
import { createAtmosphere } from '@/scene/world/atmosphere';
|
||||
import { createPostProcessor } from '@/scene/post-processing';
|
||||
import {
|
||||
createMutableChapterPalette,
|
||||
writeChapterPalette,
|
||||
type MutableChapterPalette,
|
||||
} from '@/scene/world/palette';
|
||||
import { isTestFault, recordTestFaultEvent } from '@/testing/fault-hooks';
|
||||
|
||||
declare const __PORTFOLIO_TEST_BUILD__: boolean;
|
||||
const TEST_BUILD = typeof __PORTFOLIO_TEST_BUILD__ !== 'undefined' && __PORTFOLIO_TEST_BUILD__;
|
||||
|
||||
const skyColors: Readonly<Record<EnvironmentChapter, number>> = {
|
||||
'deep-night': 0x040713,
|
||||
systems: 0x060918,
|
||||
infrastructure: 0x080b1d,
|
||||
builds: 0x0d1025,
|
||||
'pre-dawn': 0x18152c,
|
||||
destination: 0x2b2035,
|
||||
};
|
||||
|
||||
export function createChaseCamera(aspect = 1) {
|
||||
const camera = new THREE.PerspectiveCamera(52, aspect, 0.1, 320);
|
||||
camera.position.set(0, 4.8, 12.5);
|
||||
@@ -62,9 +59,11 @@ export async function createProceduralSceneGeneration(
|
||||
renderer.toneMappingExposure = 1.05;
|
||||
|
||||
const scene = new THREE.Scene();
|
||||
const background = new THREE.Color(skyColors['deep-night']);
|
||||
const currentPalette = createMutableChapterPalette('deep-night');
|
||||
const targetPalette = createMutableChapterPalette('deep-night');
|
||||
const background = currentPalette.sky.clone();
|
||||
scene.background = background;
|
||||
scene.fog = new THREE.FogExp2(0x080c18, 0.012);
|
||||
scene.fog = new THREE.FogExp2(currentPalette.fog.getHex(), 0.012);
|
||||
const camera = createChaseCamera();
|
||||
|
||||
const ambient = new THREE.HemisphereLight(0x6aa9c5, 0x100812, 1.25);
|
||||
@@ -78,43 +77,19 @@ export async function createProceduralSceneGeneration(
|
||||
const sun = createSun(context.resources);
|
||||
const car = createCar(context.resources);
|
||||
const sign = createSign(context.resources);
|
||||
const particles = createParticles(context.resources, quality.particleCount);
|
||||
scene.add(road.group, terrain.group, sun.mesh, car.group, sign.group, particles.points);
|
||||
const atmosphere = createAtmosphere(context.resources, quality);
|
||||
scene.add(
|
||||
atmosphere.group,
|
||||
road.group,
|
||||
terrain.group,
|
||||
sun.group,
|
||||
car.group,
|
||||
sign.group,
|
||||
);
|
||||
|
||||
const postTarget = quality.postEffects
|
||||
? context.resources.own('target', new THREE.WebGLRenderTarget(1, 1, {
|
||||
depthBuffer: true,
|
||||
minFilter: THREE.LinearFilter,
|
||||
magFilter: THREE.LinearFilter,
|
||||
}))
|
||||
const postProcessor = quality.postEffects
|
||||
? createPostProcessor(context.resources, renderer)
|
||||
: null;
|
||||
const postScene = postTarget ? new THREE.Scene() : null;
|
||||
const postCamera = postTarget ? new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1) : null;
|
||||
if (postScene && postTarget) {
|
||||
const postGeometry = context.resources.own('geometry', new THREE.PlaneGeometry(2, 2));
|
||||
const postMaterial = context.resources.own('material', new THREE.ShaderMaterial({
|
||||
uniforms: { source: { value: postTarget.texture } },
|
||||
vertexShader: `
|
||||
varying vec2 vUv;
|
||||
void main() {
|
||||
vUv = uv;
|
||||
gl_Position = vec4(position.xy, 0.0, 1.0);
|
||||
}
|
||||
`,
|
||||
fragmentShader: `
|
||||
uniform sampler2D source;
|
||||
varying vec2 vUv;
|
||||
void main() {
|
||||
vec4 color = texture2D(source, vUv);
|
||||
float vignette = smoothstep(0.92, 0.24, distance(vUv, vec2(0.5)));
|
||||
gl_FragColor = vec4(color.rgb * mix(0.82, 1.0, vignette), color.a);
|
||||
}
|
||||
`,
|
||||
depthTest: false,
|
||||
depthWrite: false,
|
||||
}));
|
||||
postScene.add(new THREE.Mesh(postGeometry, postMaterial));
|
||||
}
|
||||
let width = 0;
|
||||
let height = 0;
|
||||
let driveDistance = 0;
|
||||
@@ -132,10 +107,7 @@ export async function createProceduralSceneGeneration(
|
||||
renderer.setSize(width, height, false);
|
||||
camera.aspect = width / height;
|
||||
camera.updateProjectionMatrix();
|
||||
postTarget?.setSize(
|
||||
Math.max(1, Math.floor(width * quality.pixelRatio)),
|
||||
Math.max(1, Math.floor(height * quality.pixelRatio)),
|
||||
);
|
||||
postProcessor?.resize(width, height, quality.pixelRatio * quality.postScale);
|
||||
if (TEST_BUILD && context.quality !== 'medium') {
|
||||
window.__PORTFOLIO_PERFORMANCE__?.recordRuntimeAllocation(context.resources.gpuBudgetSnapshot({
|
||||
tier: context.quality,
|
||||
@@ -176,6 +148,8 @@ export async function createProceduralSceneGeneration(
|
||||
resize();
|
||||
const deltaSeconds = lastNow === null ? 0 : Math.min(0.1, Math.max(0, (now - lastNow) / 1000));
|
||||
lastNow = now;
|
||||
writeChapterPalette(targetPalette, snapshot.chapter, snapshot.chapter, 0);
|
||||
dampPalette(currentPalette, targetPalette, deltaSeconds);
|
||||
driveDistance += snapshot.driveSpeed * deltaSeconds * 18;
|
||||
road.update(driveDistance, snapshot.driveSpeed, snapshot.acceleration);
|
||||
terrain.update(
|
||||
@@ -183,22 +157,27 @@ export async function createProceduralSceneGeneration(
|
||||
snapshot.driveSpeed,
|
||||
snapshot.chapter,
|
||||
snapshot.routeProgress,
|
||||
currentPalette,
|
||||
);
|
||||
sun.update(snapshot.chapter, snapshot.routeProgress);
|
||||
sun.update(currentPalette, snapshot.routeProgress);
|
||||
car.update(snapshot.driveSpeed, snapshot.acceleration, now / 1000);
|
||||
sign.update(snapshot.sign);
|
||||
particles.update(driveDistance, snapshot.driveSpeed, snapshot.acceleration);
|
||||
background.setHex(skyColors[snapshot.chapter]);
|
||||
atmosphere.update(
|
||||
driveDistance,
|
||||
snapshot.driveSpeed,
|
||||
snapshot.acceleration,
|
||||
currentPalette,
|
||||
);
|
||||
background.copy(currentPalette.sky);
|
||||
scene.fog.color.copy(currentPalette.fog);
|
||||
scene.fog.density = 0.0105 + (1 - currentPalette.warmth) * 0.0025;
|
||||
const fov = 52 + Math.min(7, snapshot.acceleration * 4.5);
|
||||
if (camera.fov !== fov) {
|
||||
camera.fov = fov;
|
||||
camera.updateProjectionMatrix();
|
||||
}
|
||||
if (postTarget) {
|
||||
renderer.setRenderTarget(postTarget);
|
||||
renderer.render(scene, camera);
|
||||
renderer.setRenderTarget(null);
|
||||
renderer.render(postScene, postCamera);
|
||||
if (postProcessor) {
|
||||
postProcessor.render(scene, camera);
|
||||
} else {
|
||||
renderer.render(scene, camera);
|
||||
}
|
||||
@@ -211,3 +190,21 @@ export async function createProceduralSceneGeneration(
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function dampPalette(
|
||||
current: MutableChapterPalette,
|
||||
target: Readonly<MutableChapterPalette>,
|
||||
deltaSeconds: number,
|
||||
): void {
|
||||
const mix = 1 - Math.exp(-Math.max(0, deltaSeconds) * 2.8);
|
||||
current.sky.lerp(target.sky, mix);
|
||||
current.fog.lerp(target.fog, mix);
|
||||
current.terrainLow.lerp(target.terrainLow, mix);
|
||||
current.terrainHigh.lerp(target.terrainHigh, mix);
|
||||
current.sun.lerp(target.sun, mix);
|
||||
current.haze.lerp(target.haze, mix);
|
||||
current.warmth = THREE.MathUtils.lerp(current.warmth, target.warmth, mix);
|
||||
current.terrainEnergy = THREE.MathUtils.lerp(current.terrainEnergy, target.terrainEnergy, mix);
|
||||
current.starVisibility = THREE.MathUtils.lerp(current.starVisibility, target.starVisibility, mix);
|
||||
current.streakEnergy = THREE.MathUtils.lerp(current.streakEnergy, target.streakEnergy, mix);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user