Files
Emberwild/Assets/GAME/Shaders/StylizedGrassCommon.hlsl
2026-06-22 16:18:34 +02:00

187 lines
9.2 KiB
HLSL

#ifndef STYLIZED_GRASS_COMMON_INCLUDED
#define STYLIZED_GRASS_COMMON_INCLUDED
// =====================================================================================
// Stylized Grass - shared logic
// Inclus par TOUTES les passes (ForwardLit, ShadowCaster, DepthOnly, DepthNormals, Meta)
// pour que le balancement du vent soit STRICTEMENT identique partout
// (sinon les ombres / le depth ne suivent pas l'herbe qui bouge).
// =====================================================================================
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
// -------------------------------------------------------------------------------------
// Textures
// -------------------------------------------------------------------------------------
TEXTURE2D(_MainTex); SAMPLER(sampler_MainTex);
TEXTURE2D(_NormalMap); SAMPLER(sampler_NormalMap);
TEXTURE2D(_MaskMap); SAMPLER(sampler_MaskMap); // R: Metallic, G: Occlusion, A: Smoothness
// -------------------------------------------------------------------------------------
// Material constants (SRP Batcher compatible)
// -------------------------------------------------------------------------------------
CBUFFER_START(UnityPerMaterial)
float4 _MainTex_ST;
float4 _NormalMap_ST;
// Couleurs PAR SAISON (3 saisons : Été/vert, Automne, Hiver).
// TopA / TopB = sommet du brin (2 teintes -> variation par brin)
// Bottom = base du brin
// Gust = couleur de la rafale de vent qui passe sur l'herbe
// Far = teinte atmosphérique au loin (fondu distance)
half4 _SummerTopA; half4 _SummerTopB; half4 _SummerBottom; half4 _SummerGust; half4 _SummerFar;
half4 _AutumnTopA; half4 _AutumnTopB; half4 _AutumnBottom; half4 _AutumnGust; half4 _AutumnFar;
half4 _WinterTopA; half4 _WinterTopB; half4 _WinterBottom; half4 _WinterGust; half4 _WinterFar;
float _SeasonVariation; // échelle du bruit de variation par brin
float _HeightBlend; // hauteur (objet) du dégradé bas -> haut
float _TextureColorInfluence; // 0 = texture en détail N&B (saison pilote la couleur), 1 = couleur texture
// Fondu atmosphérique de distance (la teinte vient de la saison ci-dessus, _xxxFar)
float2 _NearFarRange; // x = début, y = fin du fondu
float _FarBlend; // 0 = aucun fondu distance, 1 = plein
// Givre / hiver
half4 _FrostColor; // HDR, givre/neige d'hiver
float _FrostAmount; // quantité de givre en hiver (sur faces vers le ciel)
float _FrostDesaturation; // désaturation en hiver, 0..1
float _WindWinterStiffness; // multiplie le vent en hiver (1 = inchangé, <1 = brins raides)
// Surface PBR
float _Smoothness;
float _Metallic;
float _Occlusion;
float _NormalStrength;
float _NormalUpBlend; // 0 = normale du mesh, 1 = world-up (éclairage uniforme stylisé)
float _Cutoff;
// Éclairage stylisé
float _ShadowLift; // plancher d'ombre : empêche l'herbe à l'ombre de virer au noir
half4 _TranslucencyColor; // teinte de la lumière qui traverse les brins (fausse SSS)
float _TranslucencyStrength; // force de la translucidité
float _TranslucencyPower; // netteté du halo de contre-jour
// Vent - balancement des sommets (procédural)
float4 _WindDirection; // .xy = direction du vent en monde (XZ)
float _WindStrength; // amplitude du déplacement
float _WindSpeed; // vitesse de l'oscillation principale
float _WindFrequency; // densité spatiale des vagues
float _WindTurbulence; // quantité d'oscillation secondaire (chaos)
float _WindHeight; // hauteur objet à laquelle le sommet bouge à 100%
float _WindGustStrength; // force de la modulation "rafale"
float _WindGustFreq; // fréquence des rafales
// Vent - overlay "rafale" stylisé (fragment ; la couleur vient de la saison, _xxxGust)
float _GustIntensity; // 0 = invisible
float _GustScale; // échelle du bruit de rafale
float _GustSpeed; // vitesse de défilement
float2 _GustContrast; // smoothstep(x, y, noise)
CBUFFER_END
// Logique de saison + bruit partagés (global _Season, SeasonBlend, SeasonWinterWeight, bruit).
#include "SeasonCore.hlsl"
// -------------------------------------------------------------------------------------
// Vent : déplacement procédural en espace monde.
// positionWS : position monde non animée
// heightMask01 : 0 à la base du brin, 1 au sommet (déjà calculé depuis positionOS.y)
// Retourne la position monde animée.
// -------------------------------------------------------------------------------------
float3 GrassApplyWind(float3 positionWS, float heightMask01)
{
float t = _Time.y;
float2 windDir = _WindDirection.xy;
windDir = (dot(windDir, windDir) > 1e-5) ? normalize(windDir) : float2(1.0, 0.0);
// Phase spatiale : les vagues se propagent dans le sens du vent.
float phase = dot(positionWS.xz, windDir) * _WindFrequency + t * _WindSpeed;
// Oscillation principale + turbulence (harmonique décalée).
float sway = sin(phase);
sway += sin(phase * 2.37 + 1.7) * 0.5 * _WindTurbulence;
// Rafales : enveloppe lente qui module l'amplitude globale.
float gust = sin(t * _WindGustFreq + dot(positionWS.xz, windDir) * 0.15) * 0.5 + 0.5;
float amplitude = _WindStrength * (1.0 + gust * _WindGustStrength);
// Hiver : brins plus raides (vent réduit).
amplitude *= lerp(1.0, _WindWinterStiffness, SeasonWinterWeight());
float bend = sway * amplitude * heightMask01;
float3 offset = float3(windDir.x, 0.0, windDir.y) * bend;
// Conserve approximativement la longueur du brin : on baisse légèrement le sommet.
offset.y -= abs(bend) * 0.25 * heightMask01;
return positionWS + offset;
}
// Masque de hauteur normalisé depuis la position objet (base y=0 -> sommet).
float GrassHeightMask(float3 positionOS)
{
return saturate(positionOS.y / max(_WindHeight, 1e-4));
}
// -------------------------------------------------------------------------------------
// Couleur de surface stylisée, PILOTÉE PAR LA SAISON.
// Chaque saison définit son dégradé (Top A/B + Bottom). La texture sert de détail
// (luminance) par défaut -> la couleur vient vraiment de la saison.
// positionWS : position monde animée (fondu distance + variation par brin)
// positionOS : position objet (dégradé vertical)
// normalWS : normale finale (givre d'hiver sur les faces vers le ciel)
// -------------------------------------------------------------------------------------
half3 GrassSurfaceColor(float2 uv, float3 positionWS, float3 positionOS, float3 normalWS)
{
half3 tex = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv).rgb;
int i0, i1; float f;
SeasonBlend(i0, i1, f);
half3 topA[3] = { _SummerTopA.rgb, _AutumnTopA.rgb, _WinterTopA.rgb };
half3 topB[3] = { _SummerTopB.rgb, _AutumnTopB.rgb, _WinterTopB.rgb };
half3 bottom[3] = { _SummerBottom.rgb, _AutumnBottom.rgb, _WinterBottom.rgb };
half3 gust[3] = { _SummerGust.rgb, _AutumnGust.rgb, _WinterGust.rgb };
half3 far[3] = { _SummerFar.rgb, _AutumnFar.rgb, _WinterFar.rgb };
// Couleurs de la saison interpolée (transition fluide entre 2 saisons).
half3 topAcur = lerp(topA[i0], topA[i1], f);
half3 topBcur = lerp(topB[i0], topB[i1], f);
half3 bottomCur = lerp(bottom[i0], bottom[i1], f);
half3 gustCol = lerp(gust[i0], gust[i1], f);
half3 farCol = lerp(far[i0], far[i1], f);
// Variation par brin entre Top A et Top B (bruit world-space).
float v = SeasonGradientNoise(positionWS.xz * _SeasonVariation);
half3 topCol = lerp(topAcur, topBcur, v);
// Dégradé vertical bas -> haut.
float heightT = saturate(positionOS.y / max(_HeightBlend, 1e-4));
half3 grad = lerp(bottomCur, topCol, heightT);
// Texture : détail N&B (préserve la luminosité) -> couleur texture, selon influence.
float texLum = dot(tex, half3(0.299, 0.587, 0.114));
half3 texMod = lerp(saturate(texLum * 2.0).xxx, tex, _TextureColorInfluence);
half3 col = grad * texMod;
// Fondu atmosphérique de distance (teinte saisonnière, neutre si _FarBlend = 0).
float dist = distance(positionWS, _WorldSpaceCameraPos);
float farT = smoothstep(_NearFarRange.x, _NearFarRange.y, dist) * _FarBlend;
col = lerp(col, col * farCol, saturate(farT));
// Overlay rafale colorée saisonnière (le vent qu'on "voit" passer sur l'herbe).
float2 gustUV = positionWS.xz * _GustScale + _WindDirection.xy * (_Time.y * _GustSpeed);
float gustNoise = SeasonGradientNoise(gustUV);
float gustMask = smoothstep(_GustContrast.x, _GustContrast.y, gustNoise) * _GustIntensity;
col = lerp(col, gustCol, saturate(gustMask));
// Hiver : désaturation globale + givre/neige sur les faces vers le ciel.
float winter = SeasonWinterWeight();
float lum = dot(col, half3(0.299, 0.587, 0.114));
col = lerp(col, lum.xxx, winter * _FrostDesaturation);
float frost = winter * _FrostAmount * saturate(normalWS.y);
col = lerp(col, _FrostColor.rgb, saturate(frost));
return col;
}
#endif // STYLIZED_GRASS_COMMON_INCLUDED