Ombres planète ↔ anneaux
Deux ombres distinctes sont en jeu :
- Planète → anneau : le shader d'anneau (
buildBodyRings) rayonne du fragment vers le soleil et atténue si la planète bouche le rayon. Auto-câblé :<BodyRings>(etbuildBodyRingsen vanille) auto-discover la lumière dominante viafindDominantLightWorldPoschaque frame, ou utilise unesunLightexplicite si le caller en passe une. - Anneau → planète : le shader planète reçoit un patch GLSL via
injectRingShadow(auto-appelé paruseBodyquandhasRings: true). Il rayonne du fragment vers le soleil et échantillonne le profil radial de l'anneau si le rayon le traverse. Le caller doit pousser la position du soleil dansuRingSunWorldPos— la lib n'a pas de hook auto pour cet uniform.
Câbler uRingSunWorldPos
Pour une scène statique (lumière fixe), un seul push à l'init suffit :
const planetMat = (body as any).planetMaterial?.material as THREE.ShaderMaterial
if (planetMat?.uniforms.uRingSunWorldPos) {
// Light directionnelle → projeter le vecteur loin pour simuler une source à l'infini.
planetMat.uniforms.uRingSunWorldPos.value
.copy(sun.position).normalize().multiplyScalar(1e4)
}Pour une scène où le soleil bouge (orbite, jour/nuit), refresh chaque frame :
import { findDominantLightWorldPos, findSceneRoot } from '@cedric-pouilleux/stellexjs/core'
// dans la boucle :
findDominantLightWorldPos(
findSceneRoot(body.group),
planetMat.uniforms.uRingSunWorldPos.value,
)Pourquoi pas auto ?
L'auto-discovery est volontairement absente côté planète : sur une scène multi-corps avec plusieurs étoiles (système binaire, lunes éclairées par leur primaire), le « soleil dominant » n'a pas de sens unique. Le caller décide quelle source projette l'ombre — typiquement la même que celle utilisée par <BodyRings>, mais ce n'est pas obligatoire.
Anatomie du shader
L'ombre anneau→planète est calculée par-pixel dans le fragment du matériau planète :
- Rayon : du fragment courant vers
uRingSunWorldPos, projeté sur le plan équatorial de la planète (normale+Ylocale). - Intersection : si le rayon traverse le disque entre
uRingInnerRetuRingOuterR, on calcule la coordonnée radialet ∈ [0, 1]. - Opacité : le profil radial de l'anneau (
uRingProfileA/B— 8 samples) est lerpé pour obtenir la densité macro, puis modulé par un FBM 1D pour la micro-banding. - Atténuation :
gl_FragColor.rgb *= 1 - shadeoùshade = density × edge × opacity × 0.85.
L'angle de vue ne joue pas : l'ombre est physiquement correcte indépendamment de la caméra.
Désactiver
L'ombre anneau→planète est intrinsèque au patch shader — pas de flag d'off direct. Pour la couper visuellement, le plus simple est de laisser uRingSunWorldPos à (0, 0, 0) (l'origine). Le test _rfDenom > 1e-4 reste vrai mais la distance au soleil devient nulle, donc le rayon ne « voyage » jamais et aucun fragment n'est ombré.