Variable: globeClouds
constglobeClouds:object
Defined in: modules/lighting/globe-clouds/globe-clouds.ts:15
Spherical cloud shell using the existing cloud density, noise and sunlight model. Camera and rays are globe-centered, with the planet's surface at unit radius. The shared clouds uniforms retain metre/second units; sunDirection is globe-centered XYZ.
Type Declaration
defaultUniforms
readonlydefaultUniforms:object
defaultUniforms.planetRadius
readonlyplanetRadius:6370972=6370972
dependencies
readonlydependencies: [{defaultUniforms:CloudUniforms;dependencies: [{fs: "\nfloat valueNoise_hash(vec2 cell) {\n vec3 value = fract(vec3(cell.x, cell.y, cell.x) * 0.1031);\n value += dot(value, value.yzx + 33.33);\n return fract((value.x + value.y) * value.z);\n}\nfloat valueNoise_noise(vec2 position) {\n vec2 cell = floor(position);\n vec2 fraction = fract(position);\n vec2 blend = fraction * fraction * (3.0 - 2.0 * fraction);\n return mix(mix(valueNoise_hash(cell), valueNoise_hash(cell + vec2(1.0, 0.0)), blend.x),\n mix(valueNoise_hash(cell + vec2(0.0, 1.0)), valueNoise_hash(cell + vec2(1.0)), blend.x), blend.y);\n}\n\nfloat valueNoise_hash3(vec3 cell) {\n vec3 value = fract(cell * 0.1031);\n value += dot(value, value.yzx + 33.33);\n return fract((value.x + value.y) * value.z);\n}\nfloat valueNoise_noise3(vec3 position) {\n vec3 cell = floor(position);\n vec3 fraction = fract(position);\n vec3 blend = fraction * fraction * (3.0 - 2.0 * fraction);\n float bottom = mix(mix(valueNoise_hash3(cell), valueNoise_hash3(cell + vec3(1.0, 0.0, 0.0)), blend.x),\n mix(valueNoise_hash3(cell + vec3(0.0, 1.0, 0.0)), valueNoise_hash3(cell + vec3(1.0, 1.0, 0.0)), blend.x), blend.y);\n float top = mix(mix(valueNoise_hash3(cell + vec3(0.0, 0.0, 1.0)), valueNoise_hash3(cell + vec3(1.0, 0.0, 1.0)), blend.x),\n mix(valueNoise_hash3(cell + vec3(0.0, 1.0, 1.0)), valueNoise_hash3(cell + vec3(1.0)), blend.x), blend.y);\n return mix(bottom, top, blend.z);\n}\n";name:"valueNoise";source: "\nfn valueNoise_hash(cell: vec2f) -> f32 {\n var value = fract(vec3f(cell.x, cell.y, cell.x) * 0.1031);\n value += vec3f(dot(value, value.yzx + vec3f(33.33)));\n return fract((value.x + value.y) * value.z);\n}\nfn valueNoise_noise(position: vec2f) -> f32 {\n let cell = floor(position);\n let fraction = fract(position);\n let blend = fraction * fraction * (vec2f(3.0) - 2.0 * fraction);\n return mix(mix(valueNoise_hash(cell), valueNoise_hash(cell + vec2f(1.0, 0.0)), blend.x),\n mix(valueNoise_hash(cell + vec2f(0.0, 1.0)), valueNoise_hash(cell + vec2f(1.0)), blend.x), blend.y);\n}\n\nfn valueNoise_hash3(cell: vec3f) -> f32 {\n var value = fract(cell * 0.1031);\n value += vec3f(dot(value, value.yzx + vec3f(33.33)));\n return fract((value.x + value.y) * value.z);\n}\nfn valueNoise_noise3(position: vec3f) -> f32 {\n let cell = floor(position);\n let fraction = fract(position);\n let blend = fraction * fraction * (vec3f(3.0) - 2.0 * fraction);\n let bottom = mix(mix(valueNoise_hash3(cell), valueNoise_hash3(cell + vec3f(1.0, 0.0, 0.0)), blend.x),\n mix(valueNoise_hash3(cell + vec3f(0.0, 1.0, 0.0)), valueNoise_hash3(cell + vec3f(1.0, 1.0, 0.0)), blend.x), blend.y);\n let top = mix(mix(valueNoise_hash3(cell + vec3f(0.0, 0.0, 1.0)), valueNoise_hash3(cell + vec3f(1.0, 0.0, 1.0)), blend.x),\n mix(valueNoise_hash3(cell + vec3f(0.0, 1.0, 1.0)), valueNoise_hash3(cell + vec3f(1.0)), blend.x), blend.y);\n return mix(bottom, top, blend.z);\n}\n"; }];fs: "\nlayout(std140) uniform cloudsUniforms {\n float cover;\n float altitude;\n float thickness;\n float scale;\n float density;\n float time;\n vec2 velocity;\n vec3 sunDirection;\n vec3 sunColor;\n} clouds;\n// Shared normalized density field for planar slabs and spherical shells.\nfloat clouds_getDensityAt(vec3 coordinate, float height) {\n float envelope = smoothstep(0.0, 0.2, height) * (1.0 - smoothstep(0.65, 1.0, height));\n coordinate += vec3(8.7, 3.2, clouds.time * 0.003);\n float broad = valueNoise_noise3(coordinate);\n float detail = valueNoise_noise3(coordinate * 2.07 + vec3(13.1, 7.3, 2.8));\n float fine = valueNoise_noise3(coordinate * 4.23 + vec3(5.4, 11.7, 8.1));\n float threshold = mix(0.78, 0.18, clamp(clouds.cover, 0.0, 1.0));\n return envelope * smoothstep(threshold, threshold + 0.16, broad * 0.72 + detail * 0.2 + fine * 0.08);\n}\n\nfloat clouds_getDensity(vec3 position) {\n float height = (position.z - clouds.altitude) / max(clouds.thickness, 1.0);\n return clouds_getDensityAt(vec3((position.xy - clouds.velocity * clouds.time) / max(clouds.scale, 1.0), height * 0.8), height);\n}\nvec3 clouds_getLighting(float density, float sunDensity, float daylight, float silver) {\n vec3 ambient = mix(vec3(0.035, 0.05, 0.085), vec3(0.28, 0.34, 0.42), daylight);\n float sunlight = exp(-(density + sunDensity) * 2.0);\n return ambient + clouds.sunColor * daylight * sunlight * (0.5 + silver * 0.3);\n}\n\n// Beer-Lambert extinction toward the sun through the same density used for sky rendering.\nfloat clouds_getTransmittance(vec3 position) {\n if (clouds.cover <= 0.0 || clouds.density <= 0.0) return 1.0;\n vec3 direction = normalize(clouds.sunDirection);\n if (direction.z <= 0.02) return 1.0;\n float start = max((clouds.altitude - position.z) / direction.z, 0.0);\n float end = min((clouds.altitude + clouds.thickness - position.z) / direction.z, 40000.0);\n if (end <= start) return 1.0;\n float stepLength = (end - start) / 16.0;\n float opticalDepth = 0.0;\n for (int sampleIndex = 0; sampleIndex < 16; sampleIndex++) {\n opticalDepth += clouds_getDensity(position + direction * (start + (float(sampleIndex) + 0.5) * stepLength)) * stepLength;\n }\n return exp(-opticalDepth * max(clouds.density, 0.0));\n}\n\nvec4 clouds_getColor(vec3 camera, vec3 rayDirection) {\n if (clouds.cover <= 0.0 || clouds.density <= 0.0 || abs(rayDirection.z) < 0.0001) return vec4(0.0);\n float bottom = (clouds.altitude - camera.z) / rayDirection.z;\n float top = (clouds.altitude + max(clouds.thickness, 1.0) - camera.z) / rayDirection.z;\n float start = max(min(bottom, top), 0.0);\n float end = min(max(bottom, top), 20000.0);\n if (end <= start) return vec4(0.0);\n float jitter = valueNoise_hash(rayDirection.xy * 4096.0);\n float stepLength = (end - start) / 64.0;\n vec3 sunDirection = normalize(clouds.sunDirection);\n float daylight = smoothstep(-0.08, 0.18, sunDirection.z);\n float silver = pow(max(dot(rayDirection, sunDirection), 0.0), 12.0);\n vec3 radiance = vec3(0.0);\n float transmittance = 1.0;\n for (int sampleIndex = 0; sampleIndex < 64; sampleIndex++) {\n vec3 position = camera + rayDirection * (start + (float(sampleIndex) + jitter) * stepLength);\n float density = clouds_getDensity(position);\n float sunDensity = clouds_getDensity(position + sunDirection * clouds.thickness * 0.28);\n float alpha = 1.0 - exp(-density * max(clouds.density, 0.0) * stepLength);\n vec3 color = clouds_getLighting(density, sunDensity, daylight, silver);\n radiance += transmittance * alpha * color;\n transmittance *= 1.0 - alpha;\n }\n float distanceFade = 1.0 - smoothstep(10000.0, 20000.0, start);\n return vec4(radiance, 1.0 - transmittance) * distanceFade;\n}\n";name:"clouds";source: "\nstruct cloudsUniforms {\n cover: f32,\n altitude: f32,\n thickness: f32,\n scale: f32,\n density: f32,\n time: f32,\n velocity: vec2f,\n sunDirection: vec3f,\n sunColor: vec3f,\n};\n@group(3) @binding(auto) var<uniform> clouds: cloudsUniforms;\nfn clouds_getDensityAt(inputCoordinate: vec3f, height: f32) -> f32 {\n let envelope = smoothstep(0.0, 0.2, height) * (1.0 - smoothstep(0.65, 1.0, height));\n var coordinate = inputCoordinate;\n coordinate += vec3f(8.7, 3.2, clouds.time * 0.003);\n let broad = valueNoise_noise3(coordinate);\n let detail = valueNoise_noise3(coordinate * 2.07 + vec3f(13.1, 7.3, 2.8));\n let fine = valueNoise_noise3(coordinate * 4.23 + vec3f(5.4, 11.7, 8.1));\n let threshold = mix(0.78, 0.18, clamp(clouds.cover, 0.0, 1.0));\n return envelope * smoothstep(threshold, threshold + 0.16, broad * 0.72 + detail * 0.2 + fine * 0.08);\n}\n\nfn clouds_getDensity(position: vec3f) -> f32 {\n let height = (position.z - clouds.altitude) / max(clouds.thickness, 1.0);\n return clouds_getDensityAt(vec3f((position.xy - clouds.velocity * clouds.time) / max(clouds.scale, 1.0), height * 0.8), height);\n}\nfn clouds_getLighting(density: f32, sunDensity: f32, daylight: f32, silver: f32) -> vec3f {\n let ambient = mix(vec3f(0.035, 0.05, 0.085), vec3f(0.28, 0.34, 0.42), daylight);\n let sunlight = exp(-(density + sunDensity) * 2.0);\n return ambient + clouds.sunColor * daylight * sunlight * (0.5 + silver * 0.3);\n}\n\n// Beer-Lambert extinction toward the sun through the same density used for sky rendering.\nfn clouds_getTransmittance(position: vec3f) -> f32 {\n if (clouds.cover <= 0.0 || clouds.density <= 0.0) { return 1.0; }\n var direction: vec3f = normalize(clouds.sunDirection);\n if (direction.z <= 0.02) { return 1.0; }\n var start: f32 = max((clouds.altitude - position.z) / direction.z, 0.0);\n var end: f32 = min((clouds.altitude + clouds.thickness - position.z) / direction.z, 40000.0);\n if (end <= start) { return 1.0; }\n var stepLength: f32 = (end - start) / 16.0;\n var opticalDepth: f32 = 0.0;\n for (var sampleIndex: i32 = 0; sampleIndex < 16; sampleIndex++) {\n opticalDepth += clouds_getDensity(position + direction * (start + (f32(sampleIndex) + 0.5) * stepLength)) * stepLength;\n }\n return exp(-opticalDepth * max(clouds.density, 0.0));\n}\n\nfn clouds_getColor(camera: vec3f, rayDirection: vec3f) -> vec4f {\n if (clouds.cover <= 0.0 || clouds.density <= 0.0 || abs(rayDirection.z) < 0.0001) { return vec4f(0.0); }\n let bottom = (clouds.altitude - camera.z) / rayDirection.z;\n let top = (clouds.altitude + max(clouds.thickness, 1.0) - camera.z) / rayDirection.z;\n let start = max(min(bottom, top), 0.0);\n let end = min(max(bottom, top), 20000.0);\n if (end <= start) { return vec4f(0.0); }\n let jitter = valueNoise_hash(rayDirection.xy * 4096.0);\n let stepLength = (end - start) / 64.0;\n let sunDirection = normalize(clouds.sunDirection);\n let daylight = smoothstep(-0.08, 0.18, sunDirection.z);\n let silver = pow(max(dot(rayDirection, sunDirection), 0.0), 12.0);\n var radiance = vec3f(0.0);\n var transmittance = 1.0;\n for (var sampleIndex = 0; sampleIndex < 64; sampleIndex++) {\n let position = camera + rayDirection * (start + (f32(sampleIndex) + jitter) * stepLength);\n let density = clouds_getDensity(position);\n let sunDensity = clouds_getDensity(position + sunDirection * clouds.thickness * 0.28);\n let alpha = 1.0 - exp(-density * max(clouds.density, 0.0) * stepLength);\n let color = clouds_getLighting(density, sunDensity, daylight, silver);\n radiance += transmittance * alpha * color;\n transmittance *= 1.0 - alpha;\n }\n let distanceFade = 1.0 - smoothstep(10000.0, 20000.0, start);\n return vec4f(radiance, 1.0 - transmittance) * distanceFade;\n}\n";uniformTypes: {altitude:"f32";cover:"f32";density:"f32";scale:"f32";sunColor:"vec3<f32>";sunDirection:"vec3<f32>";thickness:"f32";time:"f32";velocity:"vec2<f32>"; };getUniforms: {altitude:number;cover:number;density:number;scale:number;sunColor: [number,number,number];sunDirection: [number,number,number];thickness:number;time:number;velocity: [number,number]; }; }]
fs
readonlyfs: "\nlayout(std140) uniform globeCloudsUniforms { float planetRadius; } globeClouds;\nvec2 globeClouds_intersectSphere(vec3 camera, vec3 direction, float radius) {\n float projected = dot(camera, direction);\n float discriminant = projected * projected - dot(camera, camera) + radius * radius;\n if (discriminant < 0.0) return vec2(-1.0);\n float root = sqrt(discriminant);\n return vec2(-projected - root, -projected + root);\n}\nfloat globeClouds_getDensity(vec3 position) {\n float height = ((length(position) - 1.0) * globeClouds.planetRadius - clouds.altitude) / max(clouds.thickness, 1.0);\n // Rotate a 3D field rather than wrapping longitude UVs: no seams or pinched poles.\n vec2 angle = clouds.velocity * clouds.time / globeClouds.planetRadius;\n vec3 rotated = vec3(cos(angle.x) * position.x + sin(angle.x) * position.y,\n -sin(angle.x) * position.x + cos(angle.x) * position.y, position.z);\n rotated = vec3(rotated.x, cos(angle.y) * rotated.y + sin(angle.y) * rotated.z,\n -sin(angle.y) * rotated.y + cos(angle.y) * rotated.z);\n return clouds_getDensityAt(rotated * globeClouds.planetRadius / max(clouds.scale, 1.0), height);\n}\nvec4 globeClouds_getColor(vec3 camera, vec3 direction) {\n if (clouds.cover <= 0.0 || clouds.density <= 0.0) return vec4(0.0);\n float bottom = 1.0 + clouds.altitude / globeClouds.planetRadius;\n float top = bottom + max(clouds.thickness, 1.0) / globeClouds.planetRadius;\n vec2 outer = globeClouds_intersectSphere(camera, direction, top);\n if (outer.y <= 0.0) return vec4(0.0);\n float start = max(outer.x, 0.0);\n float end = outer.y;\n vec2 inner = globeClouds_intersectSphere(camera, direction, bottom);\n if (length(camera) < bottom) start = max(start, inner.y);\n else if (inner.x >= start) end = min(end, inner.x);\n vec2 ground = globeClouds_intersectSphere(camera, direction, 1.0);\n if (ground.x >= 0.0) end = min(end, ground.x);\n if (end <= start) return vec4(0.0);\n float stepLength = (end - start) / 32.0;\n float jitter = valueNoise_hash(direction.xy * 4096.0);\n vec3 sunlightDirection = normalize(clouds.sunDirection);\n float silver = pow(max(dot(direction, sunlightDirection), 0.0), 12.0);\n vec3 radiance = vec3(0.0);\n float transmittance = 1.0;\n for (int sampleIndex = 0; sampleIndex < 32; sampleIndex++) {\n vec3 position = camera + direction * (start + (float(sampleIndex) + jitter) * stepLength);\n float density = globeClouds_getDensity(position);\n float sunDensity = globeClouds_getDensity(position + sunlightDirection * clouds.thickness * 0.28 / globeClouds.planetRadius);\n float daylight = smoothstep(-0.08, 0.18, dot(normalize(position), sunlightDirection));\n float alpha = 1.0 - exp(-density * clouds.density * stepLength * globeClouds.planetRadius);\n radiance += transmittance * alpha * clouds_getLighting(density * 0.25, sunDensity * 0.25, daylight, silver);\n transmittance *= 1.0 - alpha;\n if (transmittance < 0.01) break;\n }\n return vec4(radiance, 1.0 - transmittance);\n}\n"
name
readonlyname:"globeClouds"='globeClouds'
source
readonlysource: "\nstruct globeCloudsUniforms { planetRadius: f32 };\n@group(3) @binding(auto) var<uniform> globeClouds: globeCloudsUniforms;\nfn globeClouds_intersectSphere(camera: vec3f, direction: vec3f, radius: f32) -> vec2f {\n let projected = dot(camera, direction);\n let discriminant = projected * projected - dot(camera, camera) + radius * radius;\n if (discriminant < 0.0) { return vec2f(-1.0); }\n let root = sqrt(discriminant);\n return vec2f(-projected - root, -projected + root);\n}\nfn globeClouds_getDensity(position: vec3f) -> f32 {\n let height = ((length(position) - 1.0) * globeClouds.planetRadius - clouds.altitude) / max(clouds.thickness, 1.0);\n let angle = clouds.velocity * clouds.time / globeClouds.planetRadius;\n var rotated = vec3f(cos(angle.x) * position.x + sin(angle.x) * position.y,\n -sin(angle.x) * position.x + cos(angle.x) * position.y, position.z);\n rotated = vec3f(rotated.x, cos(angle.y) * rotated.y + sin(angle.y) * rotated.z,\n -sin(angle.y) * rotated.y + cos(angle.y) * rotated.z);\n return clouds_getDensityAt(rotated * globeClouds.planetRadius / max(clouds.scale, 1.0), height);\n}\nfn globeClouds_getColor(camera: vec3f, direction: vec3f) -> vec4f {\n if (clouds.cover <= 0.0 || clouds.density <= 0.0) { return vec4f(0.0); }\n let bottom = 1.0 + clouds.altitude / globeClouds.planetRadius;\n let top = bottom + max(clouds.thickness, 1.0) / globeClouds.planetRadius;\n let outer = globeClouds_intersectSphere(camera, direction, top);\n if (outer.y <= 0.0) { return vec4f(0.0); }\n var start = max(outer.x, 0.0);\n var end = outer.y;\n let inner = globeClouds_intersectSphere(camera, direction, bottom);\n if (length(camera) < bottom) { start = max(start, inner.y); }\n else if (inner.x >= start) { end = min(end, inner.x); }\n let ground = globeClouds_intersectSphere(camera, direction, 1.0);\n if (ground.x >= 0.0) { end = min(end, ground.x); }\n if (end <= start) { return vec4f(0.0); }\n let stepLength = (end - start) / 32.0;\n let jitter = valueNoise_hash(direction.xy * 4096.0);\n let sunlightDirection = normalize(clouds.sunDirection);\n let silver = pow(max(dot(direction, sunlightDirection), 0.0), 12.0);\n var radiance = vec3f(0.0);\n var transmittance = 1.0;\n for (var sampleIndex = 0; sampleIndex < 32; sampleIndex++) {\n let position = camera + direction * (start + (f32(sampleIndex) + jitter) * stepLength);\n let density = globeClouds_getDensity(position);\n let sunDensity = globeClouds_getDensity(position + sunlightDirection * clouds.thickness * 0.28 / globeClouds.planetRadius);\n let daylight = smoothstep(-0.08, 0.18, dot(normalize(position), sunlightDirection));\n let alpha = 1.0 - exp(-density * clouds.density * stepLength * globeClouds.planetRadius);\n radiance += transmittance * alpha * clouds_getLighting(density * 0.25, sunDensity * 0.25, daylight, silver);\n transmittance *= 1.0 - alpha;\n if (transmittance < 0.01) { break; }\n }\n return vec4f(radiance, 1.0 - transmittance);\n}\n"
uniformTypes
readonlyuniformTypes:object
uniformTypes.planetRadius
readonlyplanetRadius:"f32"='f32'