diff options
Diffstat (limited to 'viewer/js/atmosphere.js')
| -rw-r--r-- | viewer/js/atmosphere.js | 65 |
1 files changed, 65 insertions, 0 deletions
diff --git a/viewer/js/atmosphere.js b/viewer/js/atmosphere.js new file mode 100644 index 0000000..98e5947 --- /dev/null +++ b/viewer/js/atmosphere.js @@ -0,0 +1,65 @@ +// atmosphere.js — sky and distance haze near the ground: colours from camera height and sun (pure) + the sky pass +import * as THREE from 'three'; + +export const SKY_FULL_KM = 20; // full sky below this eye altitude … +export const SKY_NONE_KM = 100; // … fading to space above this +export const HAZE_KM = 80; // e-folding distance of the haze +export const HAZE_MAX = 0.85; +const DAY = { horizon: [0.74, 0.83, 0.93], zenith: [0.2, 0.4, 0.74] }; +const NIGHT = { horizon: [0.05, 0.06, 0.1], zenith: [0.01, 0.015, 0.04] }; +const smooth = (a, b, x) => { const t = Math.min(1, Math.max(0, (x - a) / (b - a))); return t * t * (3 - 2 * t); }; +const mix = (a, b, t) => a.map((v, i) => v + (b[i] - v) * t); + +export function atmosphere(altKm, sunUp = null) { // sunUp: cos of the sun's zenith angle at the eye; null = Sky off (day) + const day = sunUp === null ? 1 : smooth(-0.15, 0.1, sunUp); + return { amount: 1 - smooth(SKY_FULL_KM, SKY_NONE_KM, altKm), + horizon: mix(NIGHT.horizon, DAY.horizon, day), zenith: mix(NIGHT.zenith, DAY.zenith, day) }; +} + +const VERT = /* glsl */ ` +varying vec2 vNdc; +void main() { vNdc = position.xy; gl_Position = vec4(position.xy, 1.0, 1.0); }`; + +const FRAG = /* glsl */ ` +uniform mat4 invProj; +uniform mat4 camWorld; +uniform vec3 up; +uniform vec3 horizon; +uniform vec3 zenith; +uniform vec3 space; +uniform float amount; +varying vec2 vNdc; +void main() { + vec4 v = invProj * vec4(vNdc, 1.0, 1.0); + vec3 dir = normalize(mat3(camWorld) * (v.xyz / v.w)); + vec3 sky = mix(horizon, zenith, smoothstep(0.0, 0.6, dot(dir, up))); + gl_FragColor = vec4(mix(space, sky, amount), 1.0); +}`; + +export class SkyDome { // one screen-filling triangle drawn first: sky where no ground covers it + constructor(scene, spaceHex) { + const g = new THREE.BufferGeometry(); + g.setAttribute('position', new THREE.BufferAttribute(new Float32Array([-1, -1, 0, 3, -1, 0, -1, 3, 0]), 3)); + const s = [(spaceHex >> 16) & 255, (spaceHex >> 8) & 255, spaceHex & 255].map(v => v / 255); + this.uniforms = { invProj: { value: new THREE.Matrix4() }, camWorld: { value: new THREE.Matrix4() }, + up: { value: new THREE.Vector3(0, 1, 0) }, horizon: { value: new THREE.Vector3() }, zenith: { value: new THREE.Vector3() }, + space: { value: new THREE.Vector3(...s) }, amount: { value: 0 } }; + this.mesh = new THREE.Mesh(g, new THREE.ShaderMaterial({ vertexShader: VERT, fragmentShader: FRAG, uniforms: this.uniforms, + depthTest: false, depthWrite: false })); + this.mesh.frustumCulled = false; + this.mesh.renderOrder = -1000; + this.mesh.visible = false; + scene.add(this.mesh); + } + + update(camera, up, air) { + const u = this.uniforms; + u.invProj.value.copy(camera.projectionMatrixInverse); + u.camWorld.value.copy(camera.matrixWorld); + u.up.value.copy(up); + u.horizon.value.set(...air.horizon); + u.zenith.value.set(...air.zenith); + u.amount.value = air.amount; + this.mesh.visible = air.amount > 0; + } +} |
