Skip to content

scenix v1.8.0 — Terrain, Sky, And Water

Choose a tag to compare

@github-actions github-actions released this 19 Oct 08:13
· 3 commits to main since this release
8276a8c

Scenix v1.8.0 — Terrain, Sky, And Water

Scenix 1.8.0 adds three optional, no_std-capable CPU crates for real-world
environments — scenix-terrain (heightmaps, chunked LOD, splats, collision,
streaming), scenix-sky (procedural atmosphere, sun positions, baked
environment maps), and scenix-water (Gerstner waves, foam, fresnel,
underwater tinting, mirror/reflection helpers) — plus a GPU environment draw
path in scenix-renderer behind its environment feature, material
descriptors in scenix-material, and the schema ids in scenix-core.
Everything is additive and feature-gated; the default facade build behaves
exactly like v1.7.0.

Highlights

  • scenix-terrain is a standalone CPU crate (std/serde): deterministic
    seeded Heightmap::from_noise (raw noise stays in [-1, 1],
    remap_range refits spans), TerrainGrid::generate chunking with
    TerrainConfig (chunks per axis × chunk cells × LodPolicy bands),
    per-LOD chunk_geometry plus chunk_geometry_with_splat that packs
    normalized splat weights into vertex color channels, SplatMap lattice
    math, TerrainCollider ray probes (with_step, with_max_steps,
    height_at, unit-length normal_at), and a deterministic
    ChunkStreamer poll/unload loop over wrapping lattices.
  • scenix-sky profiles produce pure direction-tap colors:
    SkyModel::clear_day/overcast_haze with a turbidity/rayleigh/mie tuning
    surface; SunPosition translates elevation/azimuth into unit-length sun
    directions and drives SunLight/DirectionalLight (warm low sun, neutral
    zenith); bake_equirectangular produces deterministic sky maps with the
    v = 0 zenith baking convention, bake_light_probe projects radiance into
    sh_coefficients taps, and bake_ambient collapses the map into sky/ground
    hemisphere averages; GroundedSkybox fades a dome into a flat ground
    patch under the horizon line.
  • scenix-water simulates on the CPU as the source of truth:
    WaveField presets (ocean_shore, calm) hold up to MAX_WAVES (4)
    stacked GerstnerWaves (wave_number = 2pi/wavelength, deep-water
    phase_speed); WaterSurface::geometry(t) builds displaced meshes whose
    vertices match height_at exactly, with unit-length normals;
    FoamParams::shoreline gates crest energy behind a hard threshold;
    FresnelParams::water follows Schlick reflectance (grazing angles mirror
    fully); UnderwaterView tints with red-first absorption and distance
    murk drift; mirror_transform/reflected_camera build the reflection
    math for planar water reflections.
  • scenix-material adds self-contained TerrainMaterial (four splat
    layers with albedo/roughness/tiling plus optional splat and detail
    textures), SkyMaterial (gradient triple, direction/color/glare sun
    params, fade band, atmosphere flag), and WaterMaterial (deep/shallow
    tints, Schlick base reflectance, optional foam threshold/sharpness,
    optional reflection/refraction textures), plus ShaderKind::Terrain,
    ShaderKind::Sky, ShaderKind::Water variants and the
    FEATURE_SPLAT_MAP (1 << 22), FEATURE_ATMOSPHERE (1 << 23),
    FEATURE_FRESNEL (1 << 24), FEATURE_FOAM (1 << 25), and
    FEATURE_REFRACTION (1 << 26) bits.
  • scenix-renderer (behind renderer + environment) owns an environment
    registry keyed by node material ids: register_environment upserts a slot
    from EnvironmentMaterial::Terrain/Sky/Water (terrain gets pre-packed
    chunk geometry; water gets the WaveField the GPU paver replays), three
    WGSL pipelines run per frame — splat weights from vertex channels or the
    splat texture, a depth-no-write sky dome, and an alpha-blended water
    surface that displaces with the same GPU Gems wave math as the CPU —
    with sky-before-opaque and water-after-transparent draw ordering, an
    environment uniform belt, and dynamic bind groups rebuilt only on
    registration changes.
  • scenix-core adds the TerrainId, SkyId, and WaterId newtypes;
    scenix-scene adds NodeKind::Terrain/Sky/Water nodes whose material
    link drives slot lookup at draw time.
  • scenix-texture adds EquirectSampler (bilinear sampling over an
    equirectangular map in the sky-bake row order) and height_to_normal_map
    (central-difference height-to-normal conversion) for environment
    tooling.
  • scenix-wasm documents the browser fallback plan:
    environment_fallback_plan(WebGlCapabilityLevel) — WebGPU gets full WGSL
    parity with reflection/refraction targets, WebGL 2 renders with static
    per-slot uniforms (screen-space water survives via framebuffers), and
    WebGL 1 falls back to CPU-baked vertex-color tinting without
    screen-space water.
  • The facade gains terrain, sky, water, and environment features
    (terrain and sky re-export lists are curated because the lod/sky/sun
    module names collide with the scene, material, and light glob re-exports).

Small Example

use scenix::{EnvironmentMaterial, MaterialId, Renderer, RendererConfig, SceneGraph, SceneNode};
use scenix::{SkyId, SkyMaterial, TerrainId, TerrainMaterial, WaterId, WaterMaterial};
use scenix::{Heightmap, LodBand, LodPolicy, TerrainConfig, TerrainGrid};

let mut renderer = Renderer::headless(RendererConfig::new(320, 240)).await?;

// 1. Terrain: a small two-chunk heightfield at LOD 0.
let map = Heightmap::from_fn(17, 17, 1.0, |ix, iz| ((ix + iz) as f32) * 0.5);
let policy = LodPolicy::new(&[LodBand::new(40.0, 0)])?;
let chunk = TerrainGrid::generate(&map, TerrainConfig::new(2, 8, policy))
    .chunk_geometry(0, 0)?;

let terrain = TerrainMaterial::default_palette();
renderer.register_environment(
    MaterialId::new(1),
    EnvironmentMaterial::Terrain(&terrain, &chunk),
)?;

// 2. Sky: a procedural dominating dome.
let sky = SkyMaterial::clear_day();
renderer.register_environment(MaterialId::new(2), EnvironmentMaterial::Sky(&sky))?;

// 3. Scene nodes key the slots through the schema.
let mut scene = SceneGraph::new();
scene.add(SceneNode::terrain("hills", TerrainId::new(1), MaterialId::new(1)));
scene.add(SceneNode::sky("backdrop", SkyId::new(1), MaterialId::new(2)));
scene.update_world_transforms();

// stats.scene_environments / stats.visible_environments are reported per
// frame; FrameStats has no post-condition on culling counts.

Feature gates: environment (terrain + sky + water), renderer + environment
for the GPU draw path, material for the descriptors (default-on in the
facade), and scene for the node schema (default-on).