Skip to content

Shaders & matériaux

La lib expose un seul matériau procédural par type de corps via la classe BodyMaterial. Quatre fragment shaders (rocky, metallic, gas, star) partagent un vertex shader commun et un système de paramètres unifié.

Vue d'ensemble

ts
import {
  BodyMaterial,
  BODY_PARAMS,
  getDefaultParams,
  SHADER_RANGES,
} from '@cedric-pouilleux/stellexjs/core'

const params   = getDefaultParams('rocky')
const material = new BodyMaterial('rocky', params)
SymboleRôle
BodyMaterialClasse — wrap un THREE.ShaderMaterial typé par type de corps
BODY_PARAMSSchéma des paramètres par type (type, min/max/step, default, optionCount) — sans labels d'affichage
getDefaultParamsRenvoie les valeurs par défaut pour un type donné
SHADER_RANGESBornes min/max/step des sliders
GodRaysShaderPass post-processing god rays

Display labels & UI groups. La lib n'embarque pas de labels d'affichage ni de regroupement par section — i18n et organisation UI sont à la charge du caller. Le playground maintient un dictionnaire local paramLabels.ts (labels anglais + groupes + libellés des select) que tu peux reprendre comme référence.

Pousser des paramètres en temps réel

BodyMaterial.setParams() met à jour les uniforms sans reconstruire le matériau — adapté à un panneau de contrôle :

ts
material.setParams({
  roughness:    0.85,
  craterCount:  7,
  crackAmount:  0.7,
  lavaAmount:   0.4,
  lavaColor:   '#ff5520',
})

Les types numériques sont convertis en float, les chaînes hex ('#rrggbb') sont parsées en THREE.Color. Les paramètres inconnus pour le type courant sont silencieusement ignorés — vous pouvez maintenir un dictionnaire global et laisser chaque matériau ne consommer que ce qui le concerne.

Construire une UI à partir du schéma

BODY_PARAMS[type] liste tous les paramètres consommés avec leur type, leurs bornes et leur valeur par défaut. Les labels affichés et le groupement par section sont à toi — le playground maintient un dictionnaire local pour son propre panneau.

ts
import { BODY_PARAMS, SHADER_RANGES } from '@cedric-pouilleux/stellexjs/core'

// Caller-side label dictionary — adapt to your i18n / UX.
const LABELS: Record<string, string> = {
  roughness:   'Roughness',
  crackAmount: 'Cracks',
  // …
}

for (const [key, def] of Object.entries(BODY_PARAMS.rocky)) {
  const label = LABELS[key] ?? key
  // def.type, def.default, def.min, def.max, def.step, def.optionCount…
}

playground/src/components/ShaderControls.vue + playground/src/lib/paramLabels.ts montrent un exemple complet de panneau auto-généré à partir du schéma.

Variations & identité visuelle par planète

La lib expose plusieurs leviers de variabilité automatique (déterministes du name du body) ou pilotables via le panneau shader.

Relief terrain — archetypes (rocky, metallic)

terrainArchetype (entier 0–3) sélectionne la forme du FBm utilisé pour le relief :

IndexArchétypeEffet
0Lisse (FBM)Bosses douces classiques (défaut)
1Crêtes (Ridged)Arêtes nettes, chaînes de montagnes
2Dunes (Billow)Mounds arrondis, dunes
3HybridePlaines en dunes + sommets en crêtes

L'archetype est consommé à la fois par le vertex shader (displacement géométrique) et le fragment (motif coloré), donc la silhouette suit le pattern.

ts
material.setParams({ terrainArchetype: 1 })  // crêtes

En complément, le shader rocky module l'amplitude du domain-warp via hash1(uSeed) — chaque planète a une « tortuosité » distincte sans aucune action utilisateur.

Atmosphère — halo et nuages (rocky, metallic)

Le bloc Atmosphère centralise tout ce qui pilote l'atmo shell live (sans rebuild) :

ParamEffet
atmoTintCouleur du halo — Mars rouille, Vénus jaune, Pluton bleu glacé
atmoOpacityOpacité globale du halo
atmoColorMixMix entre tint procédural et couleurs des tuiles peintes
waveAmount / waveColor / waveScale / waveSpeedCouche nuages (couverture, teinte, fréquence, vitesse de drift)
cloudPatternPreset structurel : Dispersé / Cyclones / Voile

cloudPattern configure simultanément bandiness, turbulence, storms et bandFreq de l'atmo shell pour produire des identités atmosphériques distinctes — Terre cycloned, Vénus voilée, etc.

Tempêtes — vortex (gaseous)

Les géantes gazeuses portent jusqu'à 3 vortex ovales (taches type Jupiter) dont la position, la taille et le sens de rotation sont dérivés du seed du body. Pilotables via :

ParamEffet
stormStrengthVisibilité globale (0 = vortex désactivés)
stormColorCouleur dédiée des taches (indépendante du palette gaz)
stormSizeMultiplicateur de rayon (0.3 = mini ovales, 2.5 = grandes bandes)
stormEyeStrengthIntensité de l'œil sombre central

Chaque vortex a une structure à 3 zones (couronne extérieure, cœur saturé avec spirale animée, œil sombre) et bende localement les bandes du gaz.

Étoiles : conversion Kelvin → couleur

Pour les corps stellaires, trois utilitaires convertissent une température (en Kelvin) en couleur :

ts
import { kelvinToRGB, kelvinToThreeColor, kelvinLabel } from '@cedric-pouilleux/stellexjs/core'

kelvinToRGB(5778)         // { r: 1, g: 0.97, b: 0.92 } — soleil G
kelvinToThreeColor(3500)  // THREE.Color — étoile M (rouge)
kelvinLabel(10000)        // 'B' — classification spectrale

Cf. SPECTRAL_TABLE pour la table complète O–M.

God rays (post-processing)

ts
import { godRaysFromStar, GodRaysShader } from '@cedric-pouilleux/stellexjs/core'

const pass = godRaysFromStar({
  star,            // mesh étoile
  scene,
  camera,
  renderer,
  // params optionnels : density, weight, decay, exposure
})

// dans la boucle :
pass.render()

Voir God rays stellaires pour un exemple complet avec EffectComposer.

Personnaliser un fragment shader

Si vous avez besoin d'un look custom, BodyMaterial accepte un fragmentOverride qui remplace le shader par défaut. Vous gardez les uniforms standards (uTime, uLightDir, uPalette, …) ; à vous de définir ce que vous en faites. La liste exhaustive des uniforms exposés est documentée sur BodyMaterialOptions.

Le PRNG (pour les implémenteurs cross-stack)

Toute la génération aléatoire de la lib passe par un seul PRNG : seededPrng(seed: string) (internal/prng.ts). C'est :

  • FNV-1a 32-bit sur la string seed → état initial 32-bit
  • SplitMix32 comme générateur (h += 0x9e3779b9, mix bit-shifts)
  • Sortie en float ∈ [0, 1) après division par 2^32
ts
// Pseudocode — l'implémentation exacte est dans internal/prng.ts
let h = FNV1a(seed)
return () => {
  h = (h + 0x9e3779b9) >>> 0
  let z = h
  z = mul32(z ^ (z >>> 16), 0x85ebca6b)
  z = mul32(z ^ (z >>> 13), 0xc2b2ae35)
  return ((z ^ (z >>> 16)) >>> 0) / 4294967296
}

Pourquoi SplitMix32 et pas Math.random() (Mulberry32, xoshiro, etc.) :

  • Passes BigCrush et PractRand — pas de motifs faibles sur les bits de poids faible (xorshift32 a ce défaut).
  • Pas d'état multi-mot — un seul uint32, donc trivial à porter.
  • Vitesse comparable à Math.random côté JS, et déterministe entre runtimes (V8, JSC, server, browser).

Si votre back est en Rust / Go / Python et doit reproduire les mêmes valeurs que la lib, réimplémentez exactement ces deux étapes : la séquence de bytes en entrée doit produire les mêmes 32 bits en sortie. Les noiseSeed et autres random offsets visibles dans BodyVariation reposent sur cette équivalence.

Pas de Math.random() nu

La règle s'applique aussi au code que vous ajoutez côté caller si vous voulez du déterminisme. seededPrng est exposé par l'entry sim (import { seededPrng } from '@cedric-pouilleux/stellexjs/sim') ; nommez vos seeds par scope (seededPrng(name + ':resources'), seededPrng(name + ':factions'), …) pour éviter que deux générateurs partagent leur état.

Distribué sous la licence indiquée dans le dépôt.