Files
Emberwild/Assets/GAME/Shaders/Sky/StylizedSky.shader
T
2026-07-07 16:43:51 +02:00

283 lines
11 KiB
GLSL

Shader "GAME/StylizedSky"
{
// Fully procedural stylized skybox: vertical sky gradient, sun + glow, moon, twinkling
// stars and FBM clouds — all animated. Static art-direction lives in these Properties;
// everything that changes with the time of day is pushed as GLOBALS by TimeOfDayManager
// (mirrors how StylizedGrass reads the global "_Season"). No textures, no geometry.
Properties
{
[Header(Clouds static tuning)][Space]
_CloudScale("Cloud Scale", Float) = 1.0
_CloudSoftness("Cloud Softness", Range(0.01, 1)) = 0.08
_CloudShadingStrength("Cloud Shading Strength", Range(0, 4)) = 2.5
_CloudHorizonFade("Cloud Horizon Fade", Range(0.02, 0.6)) = 0.05
_CloudDetail("Cloud Detail (billow lumps)", Range(0, 1)) = 0.7
[Header(Sun static tuning)][Space]
_SunSize("Sun Size", Range(0.002, 0.2)) = 0.04
_SunEdgeSoftness("Sun Edge Softness", Range(0.001, 0.1)) = 0.015
_SunGlowFalloff("Sun Glow Falloff", Range(1, 2000)) = 400
_SunGlowStrength("Sun Glow Strength", Range(0, 3)) = 0.6
[Header(Moon static tuning)][Space]
_MoonSize("Moon Size", Range(0.002, 0.2)) = 0.05
_MoonEdgeSoftness("Moon Edge Softness", Range(0.001, 0.1)) = 0.012
[Header(Stars static tuning)][Space]
_StarDensity("Star Density", Float) = 60
_StarThreshold("Star Threshold", Range(0.8, 0.999)) = 0.93
_StarSize("Star Size", Range(0.01, 0.5)) = 0.09
_StarTwinkleSpeed("Star Twinkle Speed", Float) = 3
[Header(Gradient shaping)][Space]
_HorizonSharpness("Horizon Sharpness", Range(0.2, 8)) = 2.5
_GroundSharpness("Ground Sharpness", Range(0.2, 8)) = 3.0
}
SubShader
{
Tags
{
"RenderType" = "Background"
"Queue" = "Background"
"RenderPipeline" = "UniversalPipeline"
"PreviewType" = "Skybox"
}
Pass
{
Name "StylizedSky"
Cull Off
ZWrite Off
ZTest LEqual
HLSLPROGRAM
#pragma target 3.0
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
// ---------------------------------------------------------------------------------
// Static per-material tuning
// ---------------------------------------------------------------------------------
CBUFFER_START(UnityPerMaterial)
float _CloudScale;
float _CloudSoftness;
float _CloudShadingStrength;
float _CloudHorizonFade;
float _CloudDetail;
float _SunSize;
float _SunEdgeSoftness;
float _SunGlowFalloff;
float _SunGlowStrength;
float _MoonSize;
float _MoonEdgeSoftness;
float _StarDensity;
float _StarThreshold;
float _StarSize;
float _StarTwinkleSpeed;
float _HorizonSharpness;
float _GroundSharpness;
CBUFFER_END
// ---------------------------------------------------------------------------------
// Time-of-day globals (written every frame by TimeOfDayManager via SetGlobalX).
// Declared OUTSIDE the CBUFFER so they resolve to the global values, exactly like
// StylizedGrass reads "_Season".
// ---------------------------------------------------------------------------------
float4 _SkyZenithColor;
float4 _SkyHorizonColor;
float4 _SkyGroundColor;
float4 _SkySunDirection; // xyz = world direction TOWARD the sun (normalized)
float4 _SkySunColor;
float4 _SkyMoonDirection; // xyz = world direction TOWARD the moon (normalized)
float4 _SkyMoonColor;
float4 _SkyCloudColor;
float4 _SkyCloudShadowColor;
float4 _SkyCloudParams; // x = coverage threshold, y = opacity, zw = wind offset
float _SkyStarOpacity;
// ---------------------------------------------------------------------------------
// Noise
// ---------------------------------------------------------------------------------
float hash21(float2 p)
{
p = frac(p * float2(123.34, 345.45));
p += dot(p, p + 34.345);
return frac(p.x * p.y);
}
float valueNoise(float2 p)
{
float2 i = floor(p);
float2 f = frac(p);
float2 u = f * f * (3.0 - 2.0 * f);
float a = hash21(i);
float b = hash21(i + float2(1, 0));
float c = hash21(i + float2(0, 1));
float d = hash21(i + float2(1, 1));
return lerp(lerp(a, b, u.x), lerp(c, d, u.x), u.y);
}
float fbm(float2 p)
{
float v = 0.0;
float amp = 0.5;
[unroll]
for (int i = 0; i < 5; i++)
{
v += amp * valueNoise(p);
p *= 2.02;
amp *= 0.5;
}
return v;
}
// Billow (turbulence) FBM: abs() folds each octave into rounded lumps, producing the
// cauliflower/cumulus structure that plain value-noise FBM cannot.
float billowFbm(float2 p)
{
float v = 0.0;
float amp = 0.5;
[unroll]
for (int i = 0; i < 5; i++)
{
v += amp * abs(2.0 * valueNoise(p) - 1.0);
p *= 2.03;
amp *= 0.5;
}
return v;
}
// ---------------------------------------------------------------------------------
// Sky pieces
// ---------------------------------------------------------------------------------
// Vertical zenith -> horizon -> ground ramp driven by view height (dir.y in [-1,1]).
half3 SampleSkyGradient(float height)
{
float up = pow(saturate(height), 1.0 / _HorizonSharpness);
half3 sky = lerp(_SkyHorizonColor.rgb, _SkyZenithColor.rgb, up);
float down = pow(saturate(-height), 1.0 / _GroundSharpness);
sky = lerp(sky, _SkyGroundColor.rgb, down);
return sky;
}
// Planar projection of the sky onto a cloud sheet. Adding a constant to the height
// (instead of dividing by a clamped near-zero) removes the hard horizon singularity
// that smeared clouds into streaks, keeping puffs readable all the way down.
float2 CloudPlaneUV(float3 dir)
{
float2 wind = _SkyCloudParams.zw;
return (dir.xz / (dir.y + 0.28)) * _CloudScale + wind;
}
// Returns cloud coverage in .x and a fake self-shadow lighting term in .y. Big soft
// masses (value FBM) are broken into rounded cumulus lumps by billow FBM, then a tight
// smoothstep gives defined edges. The lighting term darkens the sun-shadowed underside
// so the puffs read as volumetric rather than flat.
float2 SampleClouds(float3 dir)
{
float2 uv = CloudPlaneUV(dir);
float shape = fbm(uv * 0.6);
float billow = billowFbm(uv * 1.9);
float density = lerp(shape, shape * (0.5 + billow), _CloudDetail);
float coverage = _SkyCloudParams.x;
float cloud = smoothstep(coverage, coverage + _CloudSoftness, density);
float2 lightDir = normalize(_SkySunDirection.xz + 1e-4);
float lit = fbm((uv + lightDir * 0.22) * 0.6);
float lighting = saturate((shape - lit) * _CloudShadingStrength + 0.55);
return float2(cloud, lighting);
}
// Sparse twinkling star field, faded below the horizon and multiplied by night opacity.
float SampleStars(float3 dir)
{
if (dir.y <= 0.0) return 0.0;
float2 uv = (dir.xz / (dir.y + 0.35)) * _StarDensity;
float2 id = floor(uv);
float2 gv = frac(uv) - 0.5;
float n = hash21(id);
if (n < _StarThreshold) return 0.0;
float twinkle = 0.6 + 0.4 * sin(_Time.y * _StarTwinkleSpeed + n * 100.0);
float spark = smoothstep(_StarSize, 0.0, length(gv));
float horizon = smoothstep(0.0, 0.15, dir.y);
return spark * twinkle * horizon;
}
// Sharp disc + soft power-falloff glow for a celestial body.
void SampleBody(float3 dir, float3 bodyDir, float size, float edge,
out float disc, out float glow)
{
float d = dot(dir, bodyDir);
float inner = cos(size);
float outer = cos(size + edge);
disc = smoothstep(outer, inner, d);
glow = pow(saturate(d), _SunGlowFalloff);
}
// ---------------------------------------------------------------------------------
// Vertex / Fragment
// ---------------------------------------------------------------------------------
struct Attributes
{
float4 positionOS : POSITION;
};
struct Varyings
{
float4 positionCS : SV_POSITION;
float3 dirWS : TEXCOORD0;
};
Varyings vert(Attributes IN)
{
Varyings OUT;
OUT.positionCS = TransformObjectToHClip(IN.positionOS.xyz);
OUT.dirWS = TransformObjectToWorldDir(IN.positionOS.xyz, false);
return OUT;
}
half4 frag(Varyings IN) : SV_Target
{
float3 dir = normalize(IN.dirWS);
half3 col = SampleSkyGradient(dir.y);
col += SampleStars(dir) * _SkyStarOpacity;
float moonDisc, moonGlowUnused;
SampleBody(dir, _SkyMoonDirection.xyz, _MoonSize, _MoonEdgeSoftness, moonDisc, moonGlowUnused);
col += _SkyMoonColor.rgb * moonDisc;
float sunDisc, sunGlow;
SampleBody(dir, _SkySunDirection.xyz, _SunSize, _SunEdgeSoftness, sunDisc, sunGlow);
col += _SkySunColor.rgb * (sunGlow * _SunGlowStrength);
col += _SkySunColor.rgb * sunDisc;
float2 clouds = SampleClouds(dir);
float horizonFade = smoothstep(0.0, _CloudHorizonFade, dir.y);
float cloudAlpha = clouds.x * horizonFade * _SkyCloudParams.y;
half3 cloudCol = lerp(_SkyCloudShadowColor.rgb, _SkyCloudColor.rgb, clouds.y);
col = lerp(col, cloudCol, cloudAlpha);
return half4(col, 1.0);
}
ENDHLSL
}
}
Fallback Off
}