← Workbench

Polar clouds for RFL

The JSON pack encodes a spherical weather field and four cloud decks. It includes two RGBA8 textures, the current Rossby/Ekman parameters, polar phases, and live oval storms. This is an appearance model. Coverage and radial thickness are synthesized, not simulated cloud microphysics.

Download loader, GLSL and integration example

What is encoded

FieldMeaning
format / versionrfl-polar-clouds / 1
density.dataBase64 RGBA8. R, G, B, A hold decks 0–3. Divide bytes by 255 for coverage.
wind.dataBase64 RGBA8. Decode tangent XYZ as (RGB bytes − 128) × wind.scale. Alpha is unused.
layersRadius, full radial thickness, wind multiplier and coverage channel.
flow_stateRo, E, regime, wavenumber, animation time, both pole phases and captured oval storms.

Coordinates and scale

Use RFL body-local coordinates: +Y is north, longitude zero is +X, positive longitude turns toward +Z. Remove body translation and rotation before sampling. Longitude wraps. Image row zero is north; the first and last rows include the exact poles. Sample longitudes at cell centers. The supplied GLSL handles the half-texel latitude correction.

The four deck radii are 1, 0.955, 0.910 and 0.865 times RFL’s gas object radius. Full thickness defaults to 0.025 times that radius, an artistic choice. The deep opaque boundary remains RFL’s responsibility. RFL can use the same coverage for distant dithered shells and nearby volumes.

Motion and fine detail

The wind is unit-sphere displacement per animation second, not metres per second. Start with animation time = game seconds × 0.05. The baked field is frozen at capture. Two overlapping advection phases loop smoothly over 16 animation seconds; they do not evolve the weather physically or spawn future storms. The original analytic field and its phases are retained in the recipe for a future evolving simulation.

Coverage is produced by backtracing seeded spherical noise through the same field used by the globe. Shared coarse noise links the decks; independent fine noise and layer shear give variety. Use RFL’s existing billow/erosion noise inside each radial profile. Do not also apply RFL’s existing gasFlow warp to the imported coverage: that would distort it twice.

RFL connection

The loader is supplied but is not yet installed in RFL. Its current gas renderer is dist/rfl-renderer.js. Include polarCloudGLSL in its fragment shader, bind the two maps, and use polarCoverage in place of procedural coverage when a body has a pack. Keep the existing path for bodies without one.

import {decodePolarClouds, uploadPolarCloudTextures,
        polarCloudGLSL} from './polar-clouds.js';
const response = await fetch('rfl-polar-clouds-v1.json');
if (!response.ok) throw Error('Cloud pack unavailable');
const decoded = decodePolarClouds(await response.json());
const gpu = uploadPolarCloudTextures(gl, decoded);
// Compile the gas shader with polarCloudGLSL included.
// With that program current, use two FREE texture units:
gpu.bind(gasProgram, densityUnit, windUnit);
// Fragment shader, after ray/shell intersection:
vec3 bodyNormal = transpose(uObjectRotation) * n;
float cover = polarCoverage(bodyNormal, layer, uCloudTime * .05);
if (cover < airDither()) continue;
// For a nearby volume, sample an actual 3D position:
vec3 localOverR = transpose(uObjectRotation) * (point-center) / R;
float density = polarVolume(localOverR, uCloudTime * .05);
// Modulate density with RFL's fine cloud noise and integrate opacity.

Textures use linear RGBA8, not sRGB: LINEAR filtering, REPEAT in S, CLAMP_TO_EDGE in T, no vertical upload flip and no mipmaps. The loader sets these. Call gpu.dispose() when replacing or unloading a pack. Re-upload after WebGL context restoration.

Size

128 × 64 uses 64 KiB for both decoded textures and roughly 87 KiB of JSON. 256 × 128 uses 256 KiB decoded and roughly 343 KiB of JSON. No voxel volume or particle trail history is stored.