UNPKG

@takram/three-clouds

Version:
1,617 lines (1,495 loc) 127 kB
import { Pass as ze, ShaderPass as q, Resolution as $, Effect as Fe, EffectAttribute as We } from "postprocessing"; import { Vector3 as f, Vector2 as m, Matrix4 as v, Object3D as se, Box3 as Be, Uniform as r, GLSL3 as G, Vector4 as x, RawShaderMaterial as Q, Camera as we, WebGLRenderTarget as Ge, HalfFloatType as Ce, LinearFilter as B, RedFormat as Ve, WebGLArrayRenderTarget as ke, EventDispatcher as je, Matrix3 as Ye, Texture as ce, Data3DTexture as le } from "three"; import { AtmosphereMaterialBase as Ze, AtmosphereParameters as Te, getAltitudeCorrectionOffset as Ke } from "@takram/three-atmosphere"; import { lerp as De, defineInt as L, defineExpression as Ee, define as C, defineFloat as ee, unrollLoops as V, resolveIncludes as H, assertType as qe, Geodetic as $e, definePropertyShorthand as he, defineUniformShorthand as ue } from "@takram/three-geospatial"; import { runtime as de, definitions as pe, common as fe } from "@takram/three-atmosphere/shaders/bruneton"; import { vogelDisk as Xe, interleavedGradientNoise as Je, cascadedShadowMaps as Qe, raySphereIntersection as Ae, generators as et, turbo as _e, math as Pe, depth as tt } from "@takram/three-geospatial/shaders"; class k { constructor(e = 0, t = 0, n = 0, a = 0) { this.expTerm = e, this.exponent = t, this.linearTerm = n, this.constantTerm = a; } set(e = 0, t = 0, n = 0, a = 0) { return this.expTerm = e, this.exponent = t, this.linearTerm = n, this.constantTerm = a, this; } clone() { return new k( this.expTerm, this.exponent, this.linearTerm, this.constantTerm ); } copy(e) { return this.expTerm = e.expTerm, this.exponent = e.exponent, this.linearTerm = e.linearTerm, this.constantTerm = e.constantTerm, this; } } const nt = [ "channel", "altitude", "height", "densityScale", "shapeAmount", "shapeDetailAmount", "weatherExponent", "shapeAlteringBias", "coverageFilterWidth", "shadow", "densityProfile" ]; function at(o, e) { if (e != null) for (const t of nt) { const n = e[t]; n != null && (o[t] instanceof k ? o[t].copy(n) : o[t] = n); } } const b = class b { constructor(e) { this.channel = "r", this.altitude = 0, this.height = 0, this.densityScale = 0.2, this.shapeAmount = 1, this.shapeDetailAmount = 1, this.weatherExponent = 1, this.shapeAlteringBias = 0.35, this.coverageFilterWidth = 0.6, this.densityProfile = new k(0, 0, 0.75, 0.25), this.shadow = !1, this.set(e); } set(e) { return at(this, e), this; } clone() { return new b(this); } copy(e) { return this.channel = e.channel, this.altitude = e.altitude, this.height = e.height, this.densityScale = e.densityScale, this.shapeAmount = e.shapeAmount, this.shapeDetailAmount = e.shapeDetailAmount, this.weatherExponent = e.weatherExponent, this.shapeAlteringBias = e.shapeAlteringBias, this.coverageFilterWidth = e.coverageFilterWidth, this.densityProfile.copy(e.densityProfile), this.shadow = e.shadow, this; } }; b.DEFAULT = /* @__PURE__ */ new b(); let T = b; const R = /* @__PURE__ */ Array.from( { length: 8 }, () => ({ value: 0, flag: 0 }) ), N = /* @__PURE__ */ Array.from( { length: 3 }, () => ({ min: 0, max: 0 }) ); function it(o, e) { return o.value !== e.value ? o.value - e.value : o.flag - e.flag; } const U = class U extends Array { constructor(e) { super( new T(e == null ? void 0 : e[0]), new T(e == null ? void 0 : e[1]), new T(e == null ? void 0 : e[2]), new T(e == null ? void 0 : e[3]) ); } set(e) { return this[0].set(e == null ? void 0 : e[0]), this[1].set(e == null ? void 0 : e[1]), this[2].set(e == null ? void 0 : e[2]), this[3].set(e == null ? void 0 : e[3]), this; } reset() { return this[0].copy(T.DEFAULT), this[1].copy(T.DEFAULT), this[2].copy(T.DEFAULT), this[3].copy(T.DEFAULT), this; } clone() { return new U(this); } copy(e) { return this[0].copy(e[0]), this[1].copy(e[1]), this[2].copy(e[2]), this[3].copy(e[3]), this; } get localWeatherChannels() { return this[0].channel + this[1].channel + this[2].channel + this[3].channel; } packValues(e, t) { return t.set(this[0][e], this[1][e], this[2][e], this[3][e]); } packSums(e, t, n) { return n.set( this[0][e] + this[0][t], this[1][e] + this[1][t], this[2][e] + this[2][t], this[3][e] + this[3][t] ); } packDensityProfiles(e, t) { return t.set( this[0].densityProfile[e], this[1].densityProfile[e], this[2].densityProfile[e], this[3].densityProfile[e] ); } // Redundant, but need to avoid creating garbage here as this runs every frame. packIntervalHeights(e, t) { for (let s = 0; s < 4; ++s) { const c = this[s]; let h = R[s]; h.value = c.altitude, h.flag = 0, h = R[s + 4], h.value = c.altitude + c.height, h.flag = 1; } R.sort(it); let n = 0, a = 0; for (let s = 0; s < R.length; ++s) { const { value: c, flag: h } = R[s]; if (a === 0 && s > 0) { const u = N[n++]; u.min = R[s - 1].value, u.max = c; } a += h === 0 ? 1 : -1; } for (; n < 3; ++n) { const s = N[n]; s.min = 0, s.max = 0; } let i = N[0]; e.x = i.min, t.x = i.max, i = N[1], e.y = i.min, t.y = i.max, i = N[2], e.z = i.min, t.z = i.max; } }; U.DEFAULT = /* @__PURE__ */ new U([ { channel: "r", altitude: 750, height: 650, densityScale: 0.2, shapeAmount: 1, shapeDetailAmount: 1, weatherExponent: 1, shapeAlteringBias: 0.35, coverageFilterWidth: 0.6, shadow: !0 }, { channel: "g", altitude: 1e3, height: 1200, densityScale: 0.2, shapeAmount: 1, shapeDetailAmount: 1, weatherExponent: 1, shapeAlteringBias: 0.35, coverageFilterWidth: 0.6, shadow: !0 }, { channel: "b", altitude: 7500, height: 500, densityScale: 3e-3, shapeAmount: 0.4, shapeDetailAmount: 0, weatherExponent: 1, shapeAlteringBias: 0.35, coverageFilterWidth: 0.5 }, { channel: "a" } ]); let X = U; var rt = "production" === "production", me = "Invariant failed"; function A(o, e) { if (!o) { if (rt) throw new Error(me); var t = me; throw new Error(t); } } class z { constructor(e, t) { this.near = [new f(), new f(), new f(), new f()], this.far = [new f(), new f(), new f(), new f()], e != null && t != null && this.setFromCamera(e, t); } clone() { return new z().copy(this); } copy(e) { for (let t = 0; t < 4; ++t) this.near[t].copy(e.near[t]), this.far[t].copy(e.far[t]); return this; } setFromCamera(e, t) { const n = e.isOrthographicCamera === !0, a = e.projectionMatrixInverse; this.near[0].set(1, 1, -1), this.near[1].set(1, -1, -1), this.near[2].set(-1, -1, -1), this.near[3].set(-1, 1, -1); for (let i = 0; i < 4; ++i) this.near[i].applyMatrix4(a); this.far[0].set(1, 1, 1), this.far[1].set(1, -1, 1), this.far[2].set(-1, -1, 1), this.far[3].set(-1, 1, 1); for (let i = 0; i < 4; ++i) { const s = this.far[i]; s.applyMatrix4(a); const c = Math.abs(s.z); n ? s.z *= Math.min(t / c, 1) : s.multiplyScalar(Math.min(t / c, 1)); } return this; } split(e, t = []) { for (let n = 0; n < e.length; ++n) { const a = t[n] ?? (t[n] = new z()); if (n === 0) for (let i = 0; i < 4; ++i) a.near[i].copy(this.near[i]); else for (let i = 0; i < 4; ++i) a.near[i].lerpVectors( this.near[i], this.far[i], e[n - 1] ); if (n === e.length - 1) for (let i = 0; i < 4; ++i) a.far[i].copy(this.far[i]); else for (let i = 0; i < 4; ++i) a.far[i].lerpVectors( this.near[i], this.far[i], e[n] ); } return t.length = e.length, t; } applyMatrix4(e) { for (let t = 0; t < 4; ++t) this.near[t].applyMatrix4(e), this.far[t].applyMatrix4(e); return this; } } const ot = { uniform: (o, e, t, n, a = []) => { for (let i = 0; i < o; ++i) a[i] = (e + (t - e) * (i + 1) / o) / t; return a.length = o, a; }, logarithmic: (o, e, t, n, a = []) => { for (let i = 0; i < o; ++i) a[i] = e * (t / e) ** ((i + 1) / o) / t; return a.length = o, a; }, practical: (o, e, t, n = 0.5, a = []) => { for (let i = 0; i < o; ++i) { const s = (e + (t - e) * (i + 1) / o) / t, c = e * (t / e) ** ((i + 1) / o) / t; a[i] = De(s, c, n); } return a.length = o, a; } }; function st(o, e, t, n, a, i = []) { return ot[o](e, t, n, a, i); } const ve = /* @__PURE__ */ new f(), ge = /* @__PURE__ */ new f(), ct = /* @__PURE__ */ new v(), Se = /* @__PURE__ */ new v(), lt = /* @__PURE__ */ new z(), ht = /* @__PURE__ */ new Be(), ut = { maxFar: null, farScale: 1, splitMode: "practical", splitLambda: 0.5, margin: 0, fade: !0 }; class dt { constructor(e) { this.cascades = [], this.mapSize = new m(), this.cameraFrustum = new z(), this.frusta = [], this.splits = [], this._far = 0; const { cascadeCount: t, mapSize: n, maxFar: a, farScale: i, splitMode: s, splitLambda: c, margin: h, fade: u } = { ...ut, ...e }; this.cascadeCount = t, this.mapSize.copy(n), this.maxFar = a, this.farScale = i, this.splitMode = s, this.splitLambda = c, this.margin = h, this.fade = u; } get cascadeCount() { return this.cascades.length; } set cascadeCount(e) { var t; if (e !== this.cascadeCount) { for (let n = 0; n < e; ++n) (t = this.cascades)[n] ?? (t[n] = { interval: new m(), matrix: new v(), inverseMatrix: new v(), projectionMatrix: new v(), inverseProjectionMatrix: new v(), viewMatrix: new v(), inverseViewMatrix: new v() }); this.cascades.length = e; } } get far() { return this._far; } updateIntervals(e) { const t = this.cascadeCount, n = this.splits, a = this.far; st( this.splitMode, t, e.near, a, this.splitLambda, n ), this.cameraFrustum.setFromCamera(e, a), this.cameraFrustum.split(n, this.frusta); const i = this.cascades; for (let s = 0; s < t; ++s) i[s].interval.set(n[s - 1] ?? 0, n[s] ?? 0); } getFrustumRadius(e, t) { const n = t.near, a = t.far; let i = Math.max( a[0].distanceTo(a[2]), a[0].distanceTo(n[2]) ); if (this.fade) { const s = e.near, c = this.far, h = a[0].z / (c - s); i += 0.25 * h ** 2 * (c - s); } return i * 0.5; } updateMatrices(e, t, n = 1) { const a = ct.lookAt( ve.setScalar(0), ge.copy(t).multiplyScalar(-1), se.DEFAULT_UP ), i = Se.multiplyMatrices( Se.copy(a).invert(), e.matrixWorld ), s = this.frusta, c = this.cascades; A(s.length === c.length); const h = this.margin, u = this.mapSize; for (let d = 0; d < s.length; ++d) { const p = s[d], g = c[d], S = this.getFrustumRadius(e, s[d]), P = -S, O = S, ne = S, ae = -S; g.projectionMatrix.makeOrthographic( P, O, ne, ae, -this.margin, // near S * 2 + this.margin // far ); const { near: Ue, far: He } = lt.copy(p).applyMatrix4(i), F = ht.makeEmpty(); for (let W = 0; W < 4; W++) F.expandByPoint(Ue[W]), F.expandByPoint(He[W]); const E = F.getCenter(ve); E.z = F.max.z + h; const ie = (O - P) / u.width, re = (ne - ae) / u.height; E.x = Math.round(E.x / ie) * ie, E.y = Math.round(E.y / re) * re, E.applyMatrix4(a); const oe = ge.copy(t).multiplyScalar(n).add(E); g.inverseViewMatrix.lookAt(E, oe, se.DEFAULT_UP).setPosition(oe); } } update(e, t, n) { this._far = this.maxFar != null ? Math.min(this.maxFar, e.far * this.farScale) : e.far * this.farScale, this.updateIntervals(e), this.updateMatrices(e, t, n); const a = this.cascades, i = this.cascadeCount; for (let s = 0; s < i; ++s) { const { matrix: c, inverseMatrix: h, projectionMatrix: u, inverseProjectionMatrix: d, viewMatrix: p, inverseViewMatrix: g } = a[s]; d.copy(u).invert(), p.copy(g).invert(), c.copy(u).multiply(p), h.copy(g).multiply(d); } } } const ye = [ 0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5 ], Oe = /* @__PURE__ */ ye.reduce((o, e, t) => { const n = new m(); for (let a = 0; a < 16; ++a) if (ye[a] === t) { n.set((a % 4 + 0.5) / 4, (Math.floor(a / 4) + 0.5) / 4); break; } return [...o, n]; }, []), pt = { resolutionScale: 1, lightShafts: !0, shapeDetail: !0, turbulence: !0, haze: !0, clouds: { multiScatteringOctaves: 8, accurateSunSkyLight: !0, accuratePhaseFunction: !1, // Primary raymarch maxIterationCount: 500, minStepSize: 50, maxStepSize: 1e3, maxRayDistance: 2e5, perspectiveStepScale: 1.01, minDensity: 1e-5, minExtinction: 1e-5, minTransmittance: 0.01, // Secondary raymarch maxIterationCountToGround: 3, maxIterationCountToSun: 2, minSecondaryStepSize: 100, secondaryStepScale: 2, // Shadow length maxShadowLengthIterationCount: 500, minShadowLengthStepSize: 50, maxShadowLengthRayDistance: 2e5 }, shadow: { cascadeCount: 3, mapSize: /* @__PURE__ */ new m(512, 512), // Primary raymarch maxIterationCount: 50, minStepSize: 100, maxStepSize: 1e3, minDensity: 1e-5, minExtinction: 1e-5, minTransmittance: 1e-4 } }, l = pt, ft = { // TODO: We cloud decrease multi-scattering octaves for lower quality presets, // but it leads to a loss of higher frequency scattering, making it darker // overall, which suggests the need for a fudge factor to scale the radiance. low: { ...l, lightShafts: !1, // Expensive shapeDetail: !1, // Expensive turbulence: !1, // Expensive clouds: { ...l.clouds, accurateSunSkyLight: !1, // Greatly reduces texel reads. maxIterationCount: 200, minStepSize: 100, maxRayDistance: 1e5, minDensity: 1e-4, minExtinction: 1e-4, minTransmittance: 0.1, // Makes the primary march terminate earlier. maxIterationCountToGround: 0, // Expensive maxIterationCountToSun: 1 // Only 1 march makes big difference }, shadow: { ...l.shadow, maxIterationCount: 25, minDensity: 1e-4, minExtinction: 1e-4, minTransmittance: 0.01, // Makes the primary march terminate earlier. cascadeCount: 2, // Obvious mapSize: /* @__PURE__ */ new m(256, 256) // Obvious } }, medium: { ...l, lightShafts: !1, // Expensive turbulence: !1, // Expensive clouds: { ...l.clouds, minDensity: 1e-4, minExtinction: 1e-4, accurateSunSkyLight: !1, maxIterationCountToSun: 2, maxIterationCountToGround: 1 }, shadow: { ...l.shadow, minDensity: 1e-4, minExtinction: 1e-4, mapSize: /* @__PURE__ */ new m(256, 256) } }, high: l, // Consider high quality preset as default. ultra: { ...l, clouds: { ...l.clouds, minStepSize: 10 }, shadow: { ...l.shadow, mapSize: /* @__PURE__ */ new m(1024, 1024) } } }, mt = `precision highp float; precision highp sampler3D; precision highp sampler2DArray; #include <common> #include <packing> #include "core/depth" #include "core/math" #include "core/turbo" #include "core/generators" #include "core/raySphereIntersection" #include "core/cascadedShadowMaps" #include "core/interleavedGradientNoise" #include "core/vogelDisk" #include "atmosphere/bruneton/definitions" uniform AtmosphereParameters ATMOSPHERE; uniform vec3 SUN_SPECTRAL_RADIANCE_TO_LUMINANCE; uniform vec3 SKY_SPECTRAL_RADIANCE_TO_LUMINANCE; uniform sampler2D transmittance_texture; uniform sampler3D scattering_texture; uniform sampler2D irradiance_texture; uniform sampler3D single_mie_scattering_texture; uniform sampler3D higher_order_scattering_texture; #include "atmosphere/bruneton/common" #include "atmosphere/bruneton/runtime" #include "types" #include "parameters" #include "clouds" #if !defined(RECIPROCAL_PI4) #define RECIPROCAL_PI4 0.07957747154594767 #endif // !defined(RECIPROCAL_PI4) uniform sampler2D depthBuffer; uniform mat4 viewMatrix; uniform mat4 reprojectionMatrix; uniform mat4 viewReprojectionMatrix; uniform float cameraNear; uniform float cameraFar; uniform float cameraHeight; uniform vec2 temporalJitter; uniform vec2 targetUvScale; uniform float mipLevelScale; // Scattering const vec2 scatterAnisotropy = vec2(SCATTER_ANISOTROPY_1, SCATTER_ANISOTROPY_2); const float scatterAnisotropyMix = SCATTER_ANISOTROPY_MIX; uniform float skyLightScale; uniform float groundBounceScale; uniform float powderScale; uniform float powderExponent; // Primary raymarch uniform int maxIterationCount; uniform float minStepSize; uniform float maxStepSize; uniform float maxRayDistance; uniform float perspectiveStepScale; // Secondary raymarch uniform int maxIterationCountToSun; uniform int maxIterationCountToGround; uniform float minSecondaryStepSize; uniform float secondaryStepScale; // Beer shadow map uniform sampler2DArray shadowBuffer; uniform vec2 shadowTexelSize; uniform vec2 shadowIntervals[SHADOW_CASCADE_COUNT]; uniform mat4 shadowMatrices[SHADOW_CASCADE_COUNT]; uniform float shadowFar; uniform float maxShadowFilterRadius; // Shadow length #ifdef SHADOW_LENGTH uniform int maxShadowLengthIterationCount; uniform float minShadowLengthStepSize; uniform float maxShadowLengthRayDistance; #endif // SHADOW_LENGTH in vec2 vUv; in vec3 vCameraPosition; in vec3 vCameraDirection; // Direction to the center of screen in vec3 vRayDirection; // Direction to the texel in vec3 vViewPosition; in GroundIrradiance vGroundIrradiance; in CloudsIrradiance vCloudsIrradiance; layout(location = 0) out vec4 outputColor; layout(location = 1) out vec3 outputDepthVelocity; #ifdef SHADOW_LENGTH layout(location = 2) out float outputShadowLength; #endif // SHADOW_LENGTH float readDepth(const vec2 uv) { #if DEPTH_PACKING == 3201 return unpackRGBAToDepth(texture(depthBuffer, uv)); #else // DEPTH_PACKING == 3201 return texture(depthBuffer, uv).r; #endif // DEPTH_PACKING == 3201 } float getViewZ(const float depth) { #ifdef PERSPECTIVE_CAMERA return perspectiveDepthToViewZ(depth, cameraNear, cameraFar); #else // PERSPECTIVE_CAMERA return orthographicDepthToViewZ(depth, cameraNear, cameraFar); #endif // PERSPECTIVE_CAMERA } vec3 ecefToWorld(const vec3 positionECEF) { return (ecefToWorldMatrix * vec4(positionECEF - altitudeCorrection, 1.0)).xyz; } vec2 getShadowUv(const vec3 worldPosition, const int cascadeIndex) { vec4 clip = shadowMatrices[cascadeIndex] * vec4(worldPosition, 1.0); clip /= clip.w; return clip.xy * 0.5 + 0.5; } float getDistanceToShadowTop(const vec3 rayPosition) { // Distance to the top of the shadows along the sun direction, which matches // the ray origin of BSM. return raySphereSecondIntersection( rayPosition, sunDirection, vec3(0.0), bottomRadius + shadowTopHeight ); } #ifdef DEBUG_SHOW_CASCADES const vec3 cascadeColors[4] = vec3[4]( vec3(1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0), vec3(0.0, 0.0, 1.0), vec3(1.0, 1.0, 0.0) ); vec3 getCascadeColor(const vec3 rayPosition) { vec3 worldPosition = ecefToWorld(rayPosition); int cascadeIndex = getCascadeIndex( viewMatrix, worldPosition, shadowIntervals, cameraNear, shadowFar ); vec2 uv = getShadowUv(worldPosition, cascadeIndex); if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) { return vec3(1.0); } return cascadeColors[cascadeIndex]; } vec3 getFadedCascadeColor(const vec3 rayPosition, const float jitter) { vec3 worldPosition = ecefToWorld(rayPosition); int cascadeIndex = getFadedCascadeIndex( viewMatrix, worldPosition, shadowIntervals, cameraNear, shadowFar, jitter ); return cascadeIndex >= 0 ? cascadeColors[cascadeIndex] : vec3(1.0); } #endif // DEBUG_SHOW_CASCADES float readShadowOpticalDepth( const vec2 uv, const float distanceToTop, const float distanceOffset, const int cascadeIndex ) { // r: frontDepth, g: meanExtinction, b: maxOpticalDepth, a: maxOpticalDepthTail // Also see the discussion here: https://x.com/shotamatsuda/status/1885322308908442106 vec4 shadow = texture(shadowBuffer, vec3(uv, float(cascadeIndex))); float distanceToFront = max(0.0, distanceToTop - distanceOffset - shadow.r); return min(shadow.b + shadow.a, shadow.g * distanceToFront); } float sampleShadowOpticalDepthPCF( const vec3 worldPosition, const float distanceToTop, const float distanceOffset, const float radius, const int cascadeIndex ) { vec2 uv = getShadowUv(worldPosition, cascadeIndex); if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) { return 0.0; } if (radius < 0.1) { return readShadowOpticalDepth(uv, distanceToTop, distanceOffset, cascadeIndex); } float sum = 0.0; vec2 offset; #pragma unroll_loop_start for (int i = 0; i < 16; ++i) { #if UNROLLED_LOOP_INDEX < SHADOW_SAMPLE_COUNT offset = vogelDisk( UNROLLED_LOOP_INDEX, SHADOW_SAMPLE_COUNT, interleavedGradientNoise(gl_FragCoord.xy + temporalJitter * resolution) * PI2 ); sum += readShadowOpticalDepth( uv + offset * radius * shadowTexelSize, distanceToTop, distanceOffset, cascadeIndex ); #endif // UNROLLED_LOOP_INDEX < SHADOW_SAMPLE_COUNT } #pragma unroll_loop_end return sum / float(SHADOW_SAMPLE_COUNT); } float sampleShadowOpticalDepth( const vec3 rayPosition, const float distanceOffset, const float radius, const float jitter ) { float distanceToTop = getDistanceToShadowTop(rayPosition); if (distanceToTop <= 0.0) { return 0.0; } vec3 worldPosition = ecefToWorld(rayPosition); int cascadeIndex = getFadedCascadeIndex( viewMatrix, worldPosition, shadowIntervals, cameraNear, shadowFar, jitter ); return cascadeIndex >= 0 ? sampleShadowOpticalDepthPCF( worldPosition, distanceToTop, distanceOffset, radius, cascadeIndex ) : 0.0; } #ifdef DEBUG_SHOW_SHADOW_MAP vec4 getCascadedShadowMaps(vec2 uv) { vec4 coord = vec4(vUv, vUv - 0.5) * 2.0; vec4 shadow = vec4(0.0); if (uv.y > 0.5) { if (uv.x < 0.5) { shadow = texture(shadowBuffer, vec3(coord.xw, 0.0)); } else { #if SHADOW_CASCADE_COUNT > 1 shadow = texture(shadowBuffer, vec3(coord.zw, 1.0)); #endif // SHADOW_CASCADE_COUNT > 1 } } else { if (uv.x < 0.5) { #if SHADOW_CASCADE_COUNT > 2 shadow = texture(shadowBuffer, vec3(coord.xy, 2.0)); #endif // SHADOW_CASCADE_COUNT > 2 } else { #if SHADOW_CASCADE_COUNT > 3 shadow = texture(shadowBuffer, vec3(coord.zy, 3.0)); #endif // SHADOW_CASCADE_COUNT > 3 } } #if !defined(DEBUG_SHOW_SHADOW_MAP_TYPE) #define DEBUG_SHOW_SHADOW_MAP_TYPE 0 #endif // !defined(DEBUG_SHOW_SHADOW_MAP_TYPE const float frontDepthScale = 1e-5; const float meanExtinctionScale = 10.0; const float maxOpticalDepthScale = 0.01; vec3 color; #if DEBUG_SHOW_SHADOW_MAP_TYPE == 1 color = vec3(shadow.r * frontDepthScale); #elif DEBUG_SHOW_SHADOW_MAP_TYPE == 2 color = vec3(shadow.g * meanExtinctionScale); #elif DEBUG_SHOW_SHADOW_MAP_TYPE == 3 color = vec3((shadow.b + shadow.a) * maxOpticalDepthScale); #else // DEBUG_SHOW_SHADOW_MAP_TYPE color = (shadow.rgb + vec3(0.0, 0.0, shadow.a)) * vec3(frontDepthScale, meanExtinctionScale, maxOpticalDepthScale); #endif // DEBUG_SHOW_SHADOW_MAP_TYPE return vec4(color, 1.0); } #endif // DEBUG_SHOW_SHADOW_MAP vec2 henyeyGreenstein(const vec2 g, const float cosTheta) { vec2 g2 = g * g; // prettier-ignore return RECIPROCAL_PI4 * ((1.0 - g2) / max(vec2(1e-7), pow(1.0 + g2 - 2.0 * g * cosTheta, vec2(1.5)))); } #ifdef ACCURATE_PHASE_FUNCTION float draine(float u, float g, float a) { float g2 = g * g; // prettier-ignore return (1.0 - g2) * (1.0 + a * u * u) / (4.0 * (1.0 + a * (1.0 + 2.0 * g2) / 3.0) * PI * pow(1.0 + g2 - 2.0 * g * u, 1.5)); } // Numerically-fitted large particles (d=10) phase function It won't be // plausible without a more precise multiple scattering. // Reference: https://research.nvidia.com/labs/rtr/approximate-mie/ float phaseFunction(const float cosTheta, const float attenuation) { const float gHG = 0.988176691700256; // exp(-0.0990567/(d-1.67154)) const float gD = 0.5556712547839497; // exp(-2.20679/(d+3.91029) - 0.428934) const float alpha = 21.995520856274638; // exp(3.62489 - 8.29288/(d+5.52825)) const float weight = 0.4819554318404214; // exp(-0.599085/(d-0.641583)-0.665888) return mix( henyeyGreenstein(vec2(gHG) * attenuation, cosTheta).x, draine(cosTheta, gD * attenuation, alpha), weight ); } #else // ACCURATE_PHASE_FUNCTION float phaseFunction(const float cosTheta, const float attenuation) { const vec2 g = scatterAnisotropy; const vec2 weights = vec2(1.0 - scatterAnisotropyMix, scatterAnisotropyMix); // A similar approximation is described in the Frostbite's paper, where phase // angle is attenuated instead of anisotropy. return dot(henyeyGreenstein(g * attenuation, cosTheta), weights); } #endif // ACCURATE_PHASE_FUNCTION float phaseFunction(const float cosTheta) { return phaseFunction(cosTheta, 1.0); } float marchOpticalDepth( const vec3 rayOrigin, const vec3 rayDirection, const int maxIterationCount, const float mipLevel, const float jitter, out float rayDistance ) { int iterationCount = int( max(0.0, remap(mipLevel, 0.0, 1.0, float(maxIterationCount + 1), 1.0) - jitter) ); if (iterationCount == 0) { // Fudge factor to approximate the mean optical depth. // TODO: Remove it. return 0.5; } float stepSize = minSecondaryStepSize / float(iterationCount); float nextDistance = stepSize * jitter; float opticalDepth = 0.0; for (int i = 0; i < iterationCount; ++i) { rayDistance = nextDistance; vec3 position = rayDistance * rayDirection + rayOrigin; vec2 uv = getGlobeUv(position); float height = length(position) - bottomRadius; WeatherSample weather = sampleWeather(uv, height, mipLevel); MediaSample media = sampleMedia(weather, position, uv, mipLevel, jitter); opticalDepth += media.extinction * stepSize; nextDistance += stepSize; stepSize *= secondaryStepScale; } return opticalDepth; } float marchOpticalDepth( const vec3 rayOrigin, const vec3 rayDirection, const int maxIterationCount, const float mipLevel, const float jitter ) { float rayDistance; return marchOpticalDepth( rayOrigin, rayDirection, maxIterationCount, mipLevel, jitter, rayDistance ); } float approximateMultipleScattering(const float opticalDepth, const float cosTheta) { // Multiple scattering approximation // See: https://fpsunflower.github.io/ckulla/data/oz_volumes.pdf // a: attenuation, b: contribution, c: phase attenuation vec3 coeffs = vec3(1.0); // [a, b, c] const vec3 attenuation = vec3(0.5, 0.5, 0.5); // Should satisfy a <= b float scattering = 0.0; float beerLambert; #pragma unroll_loop_start for (int i = 0; i < 12; ++i) { #if UNROLLED_LOOP_INDEX < MULTI_SCATTERING_OCTAVES beerLambert = exp(-opticalDepth * coeffs.y); scattering += coeffs.x * beerLambert * phaseFunction(cosTheta, coeffs.z); coeffs *= attenuation; #endif // UNROLLED_LOOP_INDEX < MULTI_SCATTERING_OCTAVES } #pragma unroll_loop_end return scattering; } // TODO: Construct spherical harmonics of degree 2 using 2 sample points // positioned near the horizon occlusion points on the sun direction plane. vec3 getGroundSunSkyIrradiance( const vec3 position, const vec3 surfaceNormal, const float height, out vec3 skyIrradiance ) { #ifdef ACCURATE_SUN_SKY_LIGHT return GetSunAndSkyIrradiance( (position - surfaceNormal * height) * METER_TO_LENGTH_UNIT, surfaceNormal, sunDirection, skyIrradiance ); #else // ACCURATE_SUN_SKY_LIGHT skyIrradiance = vGroundIrradiance.sky; return vGroundIrradiance.sun; #endif // ACCURATE_SUN_SKY_LIGHT } vec3 getCloudsSunSkyIrradiance(const vec3 position, const float height, out vec3 skyIrradiance) { #ifdef ACCURATE_SUN_SKY_LIGHT return GetSunAndSkyScalarIrradiance(position * METER_TO_LENGTH_UNIT, sunDirection, skyIrradiance); #else // ACCURATE_SUN_SKY_LIGHT float alpha = remapClamped(height, minHeight, maxHeight); skyIrradiance = mix(vCloudsIrradiance.minSky, vCloudsIrradiance.maxSky, alpha); return mix(vCloudsIrradiance.minSun, vCloudsIrradiance.maxSun, alpha); #endif // ACCURATE_SUN_SKY_LIGHT } #ifdef GROUND_BOUNCE vec3 approximateRadianceFromGround( const vec3 position, const vec3 surfaceNormal, const float height, const float mipLevel, const float jitter ) { float opticalDepthToGround = marchOpticalDepth( position, -surfaceNormal, maxIterationCountToGround, mipLevel, jitter ); vec3 skyIrradiance; vec3 sunIrradiance = getGroundSunSkyIrradiance(position, surfaceNormal, height, skyIrradiance); const float groundAlbedo = 0.3; vec3 groundIrradiance = skyIrradiance + (1.0 - coverage) * sunIrradiance; vec3 bouncedRadiance = groundAlbedo * RECIPROCAL_PI * groundIrradiance; return bouncedRadiance * exp(-opticalDepthToGround); } #endif // GROUND_BOUNCE vec4 marchClouds( const vec3 rayOrigin, const vec3 rayDirection, const vec2 rayNearFar, const float cosTheta, const float jitter, const float rayStartTexelsPerPixel, out float frontDepth, out ivec3 sampleCount ) { vec3 radianceIntegral = vec3(0.0); float transmittanceIntegral = 1.0; float weightedDistanceSum = 0.0; float transmittanceSum = 0.0; float maxRayDistance = rayNearFar.y - rayNearFar.x; float stepSize = minStepSize + (perspectiveStepScale - 1.0) * rayNearFar.x; // I don't understand why spatial aliasing remains unless doubling the jitter. float rayDistance = stepSize * jitter * 2.0; for (int i = 0; i < maxIterationCount; ++i) { if (rayDistance > maxRayDistance) { break; // Termination } vec3 position = rayDistance * rayDirection + rayOrigin; float height = length(position) - bottomRadius; float mipLevel = log2(max(1.0, rayStartTexelsPerPixel + rayDistance * 1e-5)); #if !defined(DEBUG_MARCH_INTERVALS) if (insideLayerIntervals(height)) { stepSize *= perspectiveStepScale; rayDistance += mix(stepSize, maxStepSize, min(1.0, mipLevel)); continue; } #endif // !defined(DEBUG_MARCH_INTERVALS) // Sample rough weather. vec2 uv = getGlobeUv(position); WeatherSample weather = sampleWeather(uv, height, mipLevel); #ifdef DEBUG_SHOW_SAMPLE_COUNT ++sampleCount.x; #endif // DEBUG_SHOW_SAMPLE_COUNT if (!any(greaterThan(weather.density, vec4(minDensity)))) { // Step longer in empty space. // TODO: This produces banding artifacts. // Possible improvement: Binary search refinement stepSize *= perspectiveStepScale; rayDistance += mix(stepSize, maxStepSize, min(1.0, mipLevel)); continue; } // Sample detailed participating media. MediaSample media = sampleMedia(weather, position, uv, mipLevel, jitter, sampleCount); if (media.extinction > minExtinction) { vec3 skyIrradiance; vec3 sunIrradiance = getCloudsSunSkyIrradiance(position, height, skyIrradiance); vec3 surfaceNormal = normalize(position); // March optical depth to the sun for finer details, which BSM lacks. float sunRayDistance = 0.0; float opticalDepth = marchOpticalDepth( position, sunDirection, maxIterationCountToSun, mipLevel, jitter, sunRayDistance ); if (height < shadowTopHeight) { // Obtain the optical depth from BSM at the ray position. opticalDepth += sampleShadowOpticalDepth( position, // Take account of only positions further than the marched ray // distance. sunRayDistance, // Apply PCF only when the sun is close to the horizon. maxShadowFilterRadius * remapClamped(dot(sunDirection, surfaceNormal), 0.1, 0.0), jitter ); } vec3 radiance = sunIrradiance * approximateMultipleScattering(opticalDepth, cosTheta); #ifdef GROUND_BOUNCE // Fudge factor for the irradiance from ground. if (height < shadowTopHeight && mipLevel < 0.5) { vec3 groundRadiance = approximateRadianceFromGround( position, surfaceNormal, height, mipLevel, jitter ); radiance += groundRadiance * RECIPROCAL_PI4 * groundBounceScale; } #endif // GROUND_BOUNCE // Crude approximation of sky gradient. Better than none in the shadows. float skyGradient = dot(weather.heightFraction * 0.5 + 0.5, media.weight); radiance += skyIrradiance * RECIPROCAL_PI4 * skyGradient * skyLightScale; // Finally multiply by scattering. radiance *= media.scattering; #ifdef POWDER radiance *= 1.0 - powderScale * exp(-media.extinction * powderExponent); #endif // POWDER #ifdef DEBUG_SHOW_CASCADES if (height < shadowTopHeight) { radiance = 1e-3 * getFadedCascadeColor(position, jitter); } #endif // DEBUG_SHOW_CASCADES // Energy-conserving analytical integration of scattered light // See 5.6.3 in https://media.contentapi.ea.com/content/dam/eacom/frostbite/files/s2016-pbs-frostbite-sky-clouds-new.pdf float transmittance = exp(-media.extinction * stepSize); float clampedExtinction = max(media.extinction, 1e-7); vec3 scatteringIntegral = (radiance - radiance * transmittance) / clampedExtinction; radianceIntegral += transmittanceIntegral * scatteringIntegral; transmittanceIntegral *= transmittance; // Aerial perspective affecting clouds // See 5.9.1 in https://media.contentapi.ea.com/content/dam/eacom/frostbite/files/s2016-pbs-frostbite-sky-clouds-new.pdf weightedDistanceSum += rayDistance * transmittanceIntegral; transmittanceSum += transmittanceIntegral; } if (transmittanceIntegral <= minTransmittance) { break; // Early termination } // Take a shorter step because we've already hit the clouds. stepSize *= perspectiveStepScale; rayDistance += stepSize; } // The final product of 5.9.1 and we'll evaluate this in aerial perspective. frontDepth = transmittanceSum > 0.0 ? weightedDistanceSum / transmittanceSum : -1.0; return vec4(radianceIntegral, remapClamped(transmittanceIntegral, 1.0, minTransmittance)); } #ifdef SHADOW_LENGTH float marchShadowLength( const vec3 rayOrigin, const vec3 rayDirection, const vec2 rayNearFar, const float jitter ) { float shadowLength = 0.0; float maxRayDistance = rayNearFar.y - rayNearFar.x; float stepSize = minShadowLengthStepSize; float rayDistance = stepSize * jitter; const float attenuationFactor = 1.0 - 5e-4; float attenuation = 1.0; // TODO: This march is closed, and sample resolution can be much lower. // Refining the termination by binary search will make it much more efficient. for (int i = 0; i < maxShadowLengthIterationCount; ++i) { if (rayDistance > maxRayDistance) { break; // Termination } vec3 position = rayDistance * rayDirection + rayOrigin; float opticalDepth = sampleShadowOpticalDepth(position, 0.0, 0.0, jitter); shadowLength += (1.0 - exp(-opticalDepth)) * stepSize * attenuation; stepSize *= perspectiveStepScale; rayDistance += stepSize; } return shadowLength; } #endif // SHADOW_LENGTH #ifdef HAZE vec4 approximateHaze( const vec3 rayOrigin, const vec3 rayDirection, const float maxRayDistance, const float cosTheta, const float shadowLength ) { float modulation = remapClamped(coverage, 0.2, 0.4); if (cameraHeight * modulation < 0.0) { return vec4(0.0); } float density = modulation * hazeDensityScale * exp(-cameraHeight * hazeExponent); if (density < 1e-7) { return vec4(0.0); // Prevent artifact in views from space } // Blend two normals by the difference in angle so that normal near the // ground becomes that of the origin, and in the sky that of the horizon. vec3 normalAtOrigin = normalize(rayOrigin); vec3 normalAtHorizon = (rayOrigin - dot(rayOrigin, rayDirection) * rayDirection) / bottomRadius; float alpha = remapClamped(dot(normalAtOrigin, normalAtHorizon), 0.9, 1.0); vec3 normal = mix(normalAtOrigin, normalAtHorizon, alpha); // Analytical optical depth where density exponentially decreases with height. // Based on: https://iquilezles.org/articles/fog/ float angle = max(dot(normal, rayDirection), 1e-5); float exponent = angle * hazeExponent; float linearTerm = density / hazeExponent / angle; // Derive the optical depths separately for with and without shadow length. float expTerm = 1.0 - exp(-maxRayDistance * exponent); float shadowExpTerm = 1.0 - exp(-min(maxRayDistance, shadowLength) * exponent); float opticalDepth = expTerm * linearTerm; float shadowOpticalDepth = max((expTerm - shadowExpTerm) * linearTerm, 0.0); float transmittance = saturate(1.0 - exp(-opticalDepth)); float shadowTransmittance = saturate(1.0 - exp(-shadowOpticalDepth)); vec3 skyIrradiance = vGroundIrradiance.sky; vec3 sunIrradiance = vGroundIrradiance.sun; vec3 inscatter = sunIrradiance * phaseFunction(cosTheta) * shadowTransmittance; inscatter += skyIrradiance * RECIPROCAL_PI4 * skyLightScale * transmittance; inscatter *= hazeScatteringCoefficient / (hazeAbsorptionCoefficient + hazeScatteringCoefficient); return vec4(inscatter, transmittance); } #endif // HAZE void applyAerialPerspective( const vec3 cameraPosition, const vec3 frontPosition, const float shadowLength, inout vec4 color ) { vec3 transmittance; vec3 inscatter = GetSkyRadianceToPoint( cameraPosition * METER_TO_LENGTH_UNIT, frontPosition * METER_TO_LENGTH_UNIT, shadowLength * METER_TO_LENGTH_UNIT, sunDirection, transmittance ); color.rgb = color.rgb * transmittance + inscatter * color.a; } bool rayIntersectsGround(const vec3 cameraPosition, const vec3 rayDirection) { float r = length(cameraPosition); float mu = dot(cameraPosition, rayDirection) / r; return mu < 0.0 && r * r * (mu * mu - 1.0) + bottomRadius * bottomRadius >= 0.0; } struct IntersectionResult { bool ground; vec4 first; vec4 second; }; IntersectionResult getIntersections(const vec3 cameraPosition, const vec3 rayDirection) { IntersectionResult intersections; intersections.ground = rayIntersectsGround(cameraPosition, rayDirection); raySphereIntersections( cameraPosition, rayDirection, bottomRadius + vec4(0.0, minHeight, maxHeight, shadowTopHeight), intersections.first, intersections.second ); return intersections; } vec2 getRayNearFar(const IntersectionResult intersections) { vec2 nearFar; if (cameraHeight < minHeight) { // View below the clouds if (intersections.ground) { nearFar = vec2(-1.0); // No clouds to the ground } else { nearFar = vec2(intersections.second.y, intersections.second.z); nearFar.y = min(nearFar.y, maxRayDistance); } } else if (cameraHeight < maxHeight) { // View inside the total cloud layer if (intersections.ground) { nearFar = vec2(cameraNear, intersections.first.y); } else { nearFar = vec2(cameraNear, intersections.second.z); } } else { // View above the clouds nearFar = vec2(intersections.first.z, intersections.second.z); if (intersections.ground) { // Clamp the ray at the min height. nearFar.y = intersections.first.y; } } return nearFar; } #ifdef SHADOW_LENGTH vec2 getShadowRayNearFar(const IntersectionResult intersections) { vec2 nearFar; if (cameraHeight < shadowTopHeight) { if (intersections.ground) { nearFar = vec2(cameraNear, intersections.first.x); } else { nearFar = vec2(cameraNear, intersections.second.w); } } else { nearFar = vec2(intersections.first.w, intersections.second.w); if (intersections.ground) { // Clamp the ray at the ground. nearFar.y = intersections.first.x; } } nearFar.y = min(nearFar.y, maxShadowLengthRayDistance); return nearFar; } #endif // SHADOW_LENGTH #ifdef HAZE vec2 getHazeRayNearFar(const IntersectionResult intersections) { vec2 nearFar; if (cameraHeight < maxHeight) { if (intersections.ground) { nearFar = vec2(cameraNear, intersections.first.x); } else { nearFar = vec2(cameraNear, intersections.second.z); } } else { nearFar = vec2(cameraNear, intersections.second.z); if (intersections.ground) { // Clamp the ray at the ground. nearFar.y = intersections.first.x; } } return nearFar; } #endif // HAZE float getRayDistanceToScene(const vec3 rayDirection, out float viewZ) { float depth = readDepth(vUv * targetUvScale + temporalJitter); if (depth < 1.0 - 1e-7) { depth = reverseLogDepth(depth, cameraNear, cameraFar); viewZ = getViewZ(depth); return -viewZ / dot(rayDirection, vCameraDirection); } viewZ = 0.0; return 0.0; } void main() { #ifdef DEBUG_SHOW_SHADOW_MAP outputColor = getCascadedShadowMaps(vUv); outputDepthVelocity = vec3(0.0); #ifdef SHADOW_LENGTH outputShadowLength = 0.0; #endif // SHADOW_LENGTH return; #endif // DEBUG_SHOW_SHADOW_MAP vec3 cameraPosition = vCameraPosition + altitudeCorrection; vec3 rayDirection = normalize(vRayDirection); float cosTheta = dot(sunDirection, rayDirection); IntersectionResult intersections = getIntersections(cameraPosition, rayDirection); vec2 rayNearFar = getRayNearFar(intersections); #ifdef SHADOW_LENGTH vec2 shadowRayNearFar = getShadowRayNearFar(intersections); #endif // SHADOW_LENGTH #ifdef HAZE vec2 hazeRayNearFar = getHazeRayNearFar(intersections); #endif // HAZE float sceneViewZ; float rayDistanceToScene = getRayDistanceToScene(rayDirection, sceneViewZ); if (rayDistanceToScene > 0.0) { rayNearFar.y = min(rayNearFar.y, rayDistanceToScene); #ifdef SHADOW_LENGTH shadowRayNearFar.y = min(shadowRayNearFar.y, rayDistanceToScene); #endif // SHADOW_LENGTH #ifdef HAZE hazeRayNearFar.y = min(hazeRayNearFar.y, rayDistanceToScene); #endif // HAZE } bool intersectsGround = any(lessThan(rayNearFar, vec2(0.0))); bool intersectsScene = rayNearFar.y < rayNearFar.x; float stbn = getSTBN(); vec4 color = vec4(0.0); float frontDepth = rayNearFar.y; vec3 depthVelocity = vec3(0.0); float shadowLength = 0.0; bool hitClouds = false; if (!intersectsGround && !intersectsScene) { vec3 rayOrigin = rayNearFar.x * rayDirection + cameraPosition; vec2 globeUv = getGlobeUv(rayOrigin); #ifdef DEBUG_SHOW_UV outputColor = vec4(vec3(checker(globeUv, localWeatherRepeat + localWeatherOffset)), 1.0); outputDepthVelocity = vec3(0.0); #ifdef SHADOW_LENGTH outputShadowLength = 0.0; #endif // SHADOW_LENGTH return; #endif // DEBUG_SHOW_UV float mipLevel = getMipLevel(globeUv * localWeatherRepeat) * mipLevelScale; mipLevel = mix(0.0, mipLevel, min(1.0, 0.2 * cameraHeight / maxHeight)); float marchedFrontDepth; ivec3 sampleCount = ivec3(0); color = marchClouds( rayOrigin, rayDirection, rayNearFar, cosTheta, stbn, pow(2.0, mipLevel), marchedFrontDepth, sampleCount ); #ifdef DEBUG_SHOW_SAMPLE_COUNT outputColor = vec4(vec3(sampleCount) / vec3(500.0, 5.0, 5.0), 1.0); outputDepthVelocity = vec3(0.0); #ifdef SHADOW_LENGTH outputShadowLength = 0.0; #endif // SHADOW_LENGTH return; #endif // DEBUG_SHOW_SAMPLE_COUNT // Front depth will be -1.0 when no samples are accumulated. hitClouds = marchedFrontDepth >= 0.0; if (hitClouds) { frontDepth = rayNearFar.x + marchedFrontDepth; #ifdef SHADOW_LENGTH // Clamp the shadow length ray at the clouds. shadowRayNearFar.y = mix( shadowRayNearFar.y, min(frontDepth, shadowRayNearFar.y), color.a // Interpolate by the alpha for smoother edges. ); // Shadow length must be computed before applying aerial perspective. if (all(greaterThanEqual(shadowRayNearFar, vec2(0.0)))) { shadowLength = marchShadowLength( shadowRayNearFar.x * rayDirection + cameraPosition, rayDirection, shadowRayNearFar, stbn ); } #endif // SHADOW_LENGTH #ifdef HAZE // Clamp the haze ray at the clouds. hazeRayNearFar.y = mix( hazeRayNearFar.y, min(frontDepth, hazeRayNearFar.y), color.a // Interpolate by the alpha for smoother edges. ); #endif // HAZE // Apply aerial perspective. vec3 frontPosition = cameraPosition + frontDepth * rayDirection; applyAerialPerspective(cameraPosition, frontPosition, shadowLength, color); // Velocity for temporal resolution. vec3 frontPositionWorld = ecefToWorld(frontPosition); vec4 prevClip = reprojectionMatrix * vec4(frontPositionWorld, 1.0); prevClip /= prevClip.w; vec2 prevUv = prevClip.xy * 0.5 + 0.5; vec2 velocity = vUv - prevUv; depthVelocity = vec3(frontDepth, velocity); } } if (!hitClouds) { #ifdef SHADOW_LENGTH if (all(greaterThanEqual(shadowRayNearFar, vec2(0.0)))) { shadowLength = marchShadowLength( shadowRayNearFar.x * rayDirection + cameraPosition, rayDirection, shadowRayNearFar, stbn ); } #endif // SHADOW_LENGTH // Velocity for temporal resolution. Here reproject in the view space for // greatly reducing the precision errors. frontDepth = sceneViewZ < 0.0 ? -sceneViewZ : cameraFar; vec3 frontView = vViewPosition * frontDepth; vec4 prevClip = viewReprojectionMatrix * vec4(frontView, 1.0); prevClip /= prevClip.w; vec2 prevUv = prevClip.xy * 0.5 + 0.5; vec2 velocity = vUv - prevUv; depthVelocity = vec3(frontDepth, velocity); } #ifdef DEBUG_SHOW_FRONT_DEPTH outputColor = vec4(turbo(frontDepth / maxRayDistance), 1.0); outputDepthVelocity = vec3(0.0); #ifdef SHADOW_LENGTH outputShadowLength = 0.0; #endif // SHADOW_LENGTH return; #endif // DEBUG_SHOW_FRONT_DEPTH #ifdef HAZE vec4 haze = approximateHaze( cameraNear * rayDirection + cameraPosition, rayDirection, hazeRayNearFar.y - hazeRayNearFar.x, cosTheta, shadowLength ); color.rgb = mix(color.rgb, haze.rgb, haze.a); color.a = color.a * (1.0 - haze.a) + haze.a; #endif // HAZE outputColor = color; outputDepthVelocity = depthVelocity; #ifdef SHADOW_LENGTH outputShadowLength = shadowLength * METER_TO_LENGTH_UNIT; #endif // SHADOW_LENGTH } `, Re = `float getSTBN() { ivec3 size = textureSize(stbnTexture, 0); vec3 scale = 1.0 / vec3(size); return texture(stbnTexture, vec3(gl_FragCoord.xy, float(frame % size.z)) * scale).r; } // Straightforward spherical mapping vec2 getSphericalUv(const vec3 position) { vec2 st = normalize(position.yx); float phi = atan(st.x, st.y); float theta = asin(normalize(position).z); return vec2(phi * RECIPROCAL_PI2 + 0.5, theta * RECIPROCAL_PI + 0.5); } vec2 getCubeSphereUv(const vec3 position) { // Cube-sphere relaxation by: http://mathproofs.blogspot.com/2005/07/mapping-cube-to-sphere.html // TODO: Tile and fix seams. // Possible improvements: // https://iquilezles.org/articles/texturerepetition/ // https://gamedev.stackexchange.com/questions/184388/fragment-shader-map-dot-texture-repeatedly-over-the-sphere // https://github.com/mmikk/hextile-demo vec3 n = normalize(position); vec3 f = abs(n); vec3 c = n / max(f.x, max(f.y, f.z)); vec2 m; if (all(greaterThan(f.yy, f.xz))) { m = c.y > 0.0 ? vec2(-n.x, n.z) : n.xz; } else if (all(greaterThan(f.xx, f.yz))) { m = c.x > 0.0 ? n.yz : vec2(-n.y, n.z); } else { m = c.z > 0.0 ? n.xy : vec2(n.x, -n.y); } vec2 m2 = m * m; float q = dot(m2.xy, vec2(-2.0, 2.0)) - 3.0; float q2 = q * q; vec2 uv; uv.x = sqrt(1.5 + m2.x - m2.y - 0.5 * sqrt(-24.0 * m2.x + q2)) * (m.x > 0.0 ? 1.0 : -1.0); uv.y = sqrt(6.0 / (3.0 - uv.x * uv.x)) * m.y; return uv * 0.5 + 0.5; } vec2 getGlobeUv(const vec3 position) { return getCubeSphereUv(position); } float getMipLevel(const vec2 uv) { const float mipLevelScale = 0.1; vec2 coord = uv * resolution; vec2 ddx = dFdx(coord); vec2 ddy = dFdy(coord); float deltaMaxSqr = max(dot(ddx, ddx), dot(ddy, ddy)) * mipLevelScale; return max(0.0, 0.5 * log2(max(1.0, deltaMaxSqr))); } bool insideLayerIntervals(const float height) { bvec3 gt = greaterThan(vec3(height), minIntervalHeights); bvec3 lt = lessThan(vec3(height), maxIntervalHeights); return any(bvec3(gt.x && lt.x, gt.y && lt.y, gt.z && lt.z)); } struct WeatherSample { vec4 heightFraction; // Normalized height of each layer vec4 density; }; vec4 shapeAlteringFunction(const vec4 heightFraction, const vec4 bias) { // Apply a semi-circle transform to round the clouds towards the top. vec4 biased = pow(heightFraction, bias); vec4 x = clamp(biased * 2.0 - 1.0, -1.0, 1.0); return 1.0 - x * x; } WeatherSample sampleWeather(const vec2 uv, const float height, const fl