#ifndef SKY_CORE_INCLUDED #define SKY_CORE_INCLUDED // ===================================================================================== // Sky Core - logique du ciel stylisé PARTAGÉE (skybox procédurale). // Pas de CBUFFER ici : que des fonctions pures + les globals poussés par C#. // À inclure APRÈS Core.hlsl. Réutilise le bruit de SeasonCore.hlsl. // // Tout le ciel est une fonction pure de _TimeOfDay (0..1, minuit -> minuit), donc deux // clients qui partagent la même valeur de temps voient exactement le même ciel. // ===================================================================================== #include "SeasonCore.hlsl" // Heure du jour 0..1 (0 = minuit, 0.25 = aube, 0.5 = midi, 0.75 = crépuscule). // Poussée par le SkyTimeManager via Shader.SetGlobalFloat("_TimeOfDay", ...). float _TimeOfDay; // Direction MONDE du soleil (vecteur unitaire pointant DEPUIS la surface VERS le soleil). // Poussée par le SkyTimeManager (-sunLight.forward) pour que le disque solaire colle // exactement à la lumière directionnelle qui éclaire la scène. float3 _SunDirection; // Temps continu SYNCHRONISÉ en secondes (écoulé depuis l'époque serveur), poussé par le // SkyTimeManager. On l'utilise pour TOUTE l'animation (nuages, scintillement, étoiles // filantes) au lieu de _Time.y, qui est local à chaque machine et désynchroniserait les // évènements visibles comme les étoiles filantes entre les joueurs. float _SkyTime; // ------------------------------------------------------------------------------------- // Facteurs temporels dérivés de _TimeOfDay. // ------------------------------------------------------------------------------------- // Hauteur du soleil [-1..1] : 1 au zénith, 0 à l'horizon, négatif sous l'horizon (nuit). float SkySunHeight() { return _SunDirection.y; } // Poids de la nuit 0..1 : 0 en plein jour, 1 quand le soleil est bien sous l'horizon. float SkyNightFactor() { return saturate(-SkySunHeight() * 4.0 + 0.15); } // Poids du crépuscule/aube 0..1 : pic quand le soleil frôle l'horizon (teintes chaudes). float SkyTwilightFactor() { float h = SkySunHeight(); return saturate(1.0 - abs(h) * 6.0); } // ------------------------------------------------------------------------------------- // Dégradé de fond horizon -> zénith, teinté par l'heure du jour. // ------------------------------------------------------------------------------------- // Mélange les couleurs zénith/horizon selon l'élévation de la vue, puis bascule entre // palette jour, palette crépuscule (chaude) et palette nuit (sombre/bleutée) selon le soleil. float3 SkyGradient(float3 viewDir, float3 dayZenith, float3 dayHorizon, float3 nightZenith, float3 nightHorizon, float3 twilightColor, float horizonSharpness) { float up = saturate(viewDir.y); float t = pow(1.0 - up, max(horizonSharpness, 0.001)); float3 dayCol = lerp(dayZenith, dayHorizon, t); float3 nightCol = lerp(nightZenith, nightHorizon, t); float night = SkyNightFactor(); float twilight = SkyTwilightFactor(); float3 col = lerp(dayCol, nightCol, night); // Le crépuscule rougeoie surtout près de l'horizon, du côté du soleil. float sunSide = saturate(dot(normalize(viewDir.xz + 1e-4), normalize(_SunDirection.xz + 1e-4)) * 0.5 + 0.5); col = lerp(col, twilightColor, twilight * t * sunSide); return col; } // ------------------------------------------------------------------------------------- // Soleil : disque net + halo doux. Disparaît proprement sous l'horizon. // ------------------------------------------------------------------------------------- // Disque solaire dur (smoothstep sur l'angle vue/soleil) qui s'éteint quand le soleil // passe sous l'horizon, pour ne pas laisser un disque flotter la nuit. float3 SunDisk(float3 viewDir, float3 sunColor, float sunSize) { float d = dot(viewDir, _SunDirection); float disk = smoothstep(1.0 - sunSize, 1.0 - sunSize * 0.5, d); float horizonFade = saturate(SkySunHeight() * 8.0 + 0.2); return sunColor * disk * horizonFade; } // Halo radial autour du soleil, plus large et plus doux que le disque. float3 SunGlow(float3 viewDir, float3 glowColor, float glowSize, float glowStrength) { float d = dot(viewDir, _SunDirection); float glow = pow(saturate(d), max(glowSize, 0.001)); float horizonFade = saturate(SkySunHeight() * 6.0 + 0.3); return glowColor * glow * glowStrength * horizonFade; } // ------------------------------------------------------------------------------------- // Nuages procéduraux : FBM sur le gradient noise, projeté sur la voûte, défilant. // ------------------------------------------------------------------------------------- // FBM (somme d'octaves de SeasonGradientNoise) -> densité de nuage 0..1. float SkyFbm(float2 uv) { float sum = 0.0; float amp = 0.5; float freq = 1.0; [unroll] for (int i = 0; i < 5; i++) { sum += SeasonGradientNoise(uv * freq) * amp; freq *= 2.0; amp *= 0.5; } return sum; } // Projette la direction de vue sur un plan "voûte céleste", anime le défilement avec le // temps et éclaire le nuage par la direction du soleil : le bord face au soleil est lumineux, // le ventre du nuage reste sombre. Renvoie rgb prémultiplié + couverture dans .a. float4 Clouds(float3 viewDir, float time, float3 cloudColor, float3 cloudShadowColor, float coverage, float softness, float scale, float2 windDir) { // Au-dessous de l'horizon : pas de nuages. float aboveHorizon = saturate(viewDir.y * 4.0); if (aboveHorizon <= 0.0) return float4(0, 0, 0, 0); // Projection planaire : on divise par y pour étirer vers l'horizon (effet de voûte). float2 uv = viewDir.xz / (viewDir.y + 0.15) * scale; uv += windDir * time; float density = SkyFbm(uv); float clouds = smoothstep(coverage, coverage + softness, density); // Éclairage : décale l'échantillon vers le soleil pour estimer l'épaisseur traversée. float2 lightOffset = _SunDirection.xz * 0.15 * scale; float densityToward = SkyFbm(uv - lightOffset); float lit = saturate((density - densityToward) * 3.0 + 0.5); float3 col = lerp(cloudShadowColor, cloudColor, lit); // La nuit, les nuages s'assombrissent (gardent juste un reste de lune/ciel). col *= lerp(1.0, 0.25, SkyNightFactor()); float alpha = clouds * aboveHorizon; return float4(col * alpha, alpha); } // ------------------------------------------------------------------------------------- // Étoiles : champ haché seuillé, apparaissant la nuit avec un léger scintillement. // ------------------------------------------------------------------------------------- // Points épars (hash sur une grille fine de la direction de vue) qui ne s'allument que // la nuit (nightFactor) et scintillent doucement via le temps. float3 Stars(float3 viewDir, float density, float twinkleSpeed, float time) { float aboveHorizon = saturate(viewDir.y * 3.0); if (aboveHorizon <= 0.0) return float3(0, 0, 0); float2 uv = viewDir.xz / (viewDir.y + 0.2); float2 cell = floor(uv * 120.0); float2 h = SeasonHash22(cell) * 0.5 + 0.5; float star = step(1.0 - density, h.x); float twinkle = 0.6 + 0.4 * sin(time * twinkleSpeed + h.y * 6.2831); return star * twinkle * aboveHorizon * SkyNightFactor(); } // ------------------------------------------------------------------------------------- // Étoiles filantes : traînées procédurales animées, rares, uniquement la nuit. // ------------------------------------------------------------------------------------- // Génère des stries fines qui balaient le ciel. Chaque "fenêtre temporelle" tire une // position/direction hachée ; la strie n'est visible qu'une fraction de la fenêtre, ce qui // rend l'évènement rare et déterministe (donc identique sur tous les clients). float3 ShootingStars(float3 viewDir, float time, float3 color, float rate) { float aboveHorizon = saturate(viewDir.y * 2.0); if (aboveHorizon <= 0.0) return float3(0, 0, 0); float window = floor(time * rate); float local = frac(time * rate); float2 seed = SeasonHash22(float2(window, 17.0)) * 0.5 + 0.5; float2 dir = normalize(SeasonHash22(float2(window, 91.0))); float2 uv = viewDir.xz / (viewDir.y + 0.2); float2 start = (seed - 0.5) * 4.0; float2 head = start + dir * (local * 6.0 - 1.0); // Distance perpendiculaire à la traînée -> finesse de la strie. float2 rel = uv - head; float along = dot(rel, dir); float perp = length(rel - dir * along); float streak = smoothstep(0.04, 0.0, perp) * smoothstep(0.0, -0.6, along) * smoothstep(0.6, 0.0, -along); float life = smoothstep(0.0, 0.1, local) * smoothstep(1.0, 0.4, local); return color * streak * life * aboveHorizon * SkyNightFactor(); } #endif // SKY_CORE_INCLUDED