2026-05-07 10:14:44 +02:00
Shader " Enviro / Standard / RaymarchClouds "
{
Properties
{
}
SubShader
{
Cull Off ZWrite Off ZTest Always
Tags { " RenderType " = " Opaque " }
Pass
{
CGPROGRAM
# pragma vertex vert
# pragma fragment frag
# pragma target 3.0
# pragma exclude_renderers gles d3d9
# pragma multi_compile __ UNITY_COLORSPACE_GAMMA
# pragma multi_compile __ ENVIRO_DEPTHBLENDING
# pragma multi_compile __ ENVIRO_CURLNOISE
# pragma multi_compile __ ENVIRO_HALTONOFFSET
# include " UnityCG . cginc "
2026-05-19 15:29:54 +02:00
# include " . / EnviroFogCore . cginc "
# include " . / EnviroVolumeCloudsCore . cginc "
2026-05-07 10:14:44 +02:00
uniform half4 _MainTex_ST ;
float4x4 _LeftWorldFromView ;
float4x4 _RightWorldFromView ;
float4x4 _LeftViewFromScreen ;
float4x4 _RightViewFromScreen ;
#ifdef ENVIRO_HALTONOFFSET
float _RaymarchOffset ;
float4 _TexelSize ;
#else
#define BAYER_FACTOR (1.0/16.0)
const float bayerFilter [ 16 ] =
{
0.0 * ( 1.0 / 16.0 ) ,
8.0 * ( 1.0 / 16.0 ),
2.0 * ( 1.0 / 16.0 ),
10.0 * ( 1.0 / 16.0 ),
12.0 * ( 1.0 / 16.0 ),
4.0 * ( 1.0 / 16.0 ),
14.0 * ( 1.0 / 16.0 ),
6.0 * ( 1.0 / 16.0 ),
3.0 * ( 1.0 / 16.0 ),
11.0 * ( 1.0 / 16.0 ),
1.0 * ( 1.0 / 16.0 ),
9.0 * ( 1.0 / 16.0 ),
15.0 * ( 1.0 / 16.0 ),
7.0 * ( 1.0 / 16.0 ),
13.0 * ( 1.0 / 16.0 ),
5.0 * ( 1.0 / 16.0 )
};
#endif
float3 ScreenSpaceDither ( float2 vScreenPos , float3 clr )
{
float d = dot ( float2 ( 131.0 , 312.0 ), vScreenPos . xy + _Time . y );
float3 vDither = float3 ( d , d , d );
vDither . rgb = frac ( vDither . rgb / float3 ( 103.0 , 71.0 , 97.0 )) - float3 ( 0.5 , 0.5 , 0.5 );
return ( vDither . rgb / 15.0 ) * 1.0 * Luminance ( clr );
}
struct appdata
{
float4 vertex : POSITION ;
float2 uv : TEXCOORD0 ;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
float4 position : SV_POSITION ;
float2 uv : TEXCOORD0 ;
float3 sky : TEXCOORD1 ;
float4 screenPos : TEXCOORD2 ;
UNITY_VERTEX_OUTPUT_STEREO
};
v2f vert ( appdata_img v )
{
v2f o ;
UNITY_SETUP_INSTANCE_ID ( v );
UNITY_INITIALIZE_OUTPUT ( v2f , o );
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO ( o );
o . position = UnityObjectToClipPos ( v . vertex );
o . uv = v . texcoord ;
o . sky . x = saturate ( _SunDir . y + 0.25 );
o . sky . y = saturate ( clamp ( 1.0 - _SunDir . y , 0.0 , 0.5 ));
o . screenPos = ComputeScreenPos ( o . position );
return o ;
}
float4 frag ( v2f i ) : SV_Target
{
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX ( i );
//return lerp(float4(1, 1, 1, 1), float4(0, 0, 0, 0), unity_StereoEyeIndex);
float4 cameraRay = float4 ( i . uv * 2.0 - 1.0 , 1.0 , 1.0 );
//World Space
float3 EyePosition = _CameraPosition ;
float3 EyePositionDepth = _WorldSpaceCameraPos ;
//Workaround for large scale games where player position will be resetted.
//float3 EyePosition = float3(0.0,_CameraPosition.y, 0.0);
float2 sPos = i . position . xy ;
float3 ray = 0 ;
//#if UNITY_SINGLE_PASS_STEREO
if ( unity_StereoEyeIndex == 0 )
{
cameraRay = mul ( _InverseProjection , cameraRay );
cameraRay = cameraRay / cameraRay . w ;
ray = normalize ( mul (( float3x3 ) _InverseRotation , cameraRay . xyz ));
}
else
{
cameraRay = mul ( _InverseProjection_SP , cameraRay );
cameraRay = cameraRay / cameraRay . w ;
ray = normalize ( mul (( float3x3 ) _InverseRotation_SP , cameraRay . xyz ));
}
//#else
// cameraRay = mul(_InverseProjection, cameraRay);
// cameraRay = cameraRay / cameraRay.w;
// ray = normalize(mul((float3x3)_InverseRotation, cameraRay.xyz));
//#endif
float rawDepth = SAMPLE_DEPTH_TEXTURE ( _CameraDepthTexture , UnityStereoTransformScreenSpaceTex ( i . uv ));
bool depthPresent = rawDepth > 0.0 ;
float dpth = Linear01Depth ( rawDepth );
#ifdef ENVIRO_DEPTHBLENDING
float4x4 proj , eyeToWorld ;
if ( unity_StereoEyeIndex == 0 )
{
proj = _LeftViewFromScreen ;
eyeToWorld = _LeftWorldFromView ;
}
else
{
proj = _RightViewFromScreen ;
eyeToWorld = _RightWorldFromView ;
}
//bit of matrix math to take the screen space coord (u,v,depth) and transform to world space
float2 uvClip = i . uv * 2.0 - 1.0 ;
float clipDepth = rawDepth ; // Fix for OpenGl Core thanks to Lars Bertram
clipDepth = ( UNITY_NEAR_CLIP_VALUE < 0 ) ? clipDepth * 2 - 1 : clipDepth ;
float4 clipPos = float4 ( uvClip , clipDepth , 1.0 );
float4 viewPos = mul ( proj , clipPos ); // inverse projection by clip position
viewPos /= viewPos . w ; // perspective division
float4 wsPos = float4 ( mul ( eyeToWorld , viewPos ). xyz , 1 );
float4 wsDir = wsPos - float4 ( EyePosition , 0 );
float3 viewDir = normalize ( wsDir );
#endif
float4 sky = ComputeScatteringClouds ( ray , i . sky . xy , _gameTime );
float4 color = float4 ( 0 , 0 , 0 , 1 );
float3 LightDirection = _LightDir ;
float3 LightColor = _LightColor . rgb ;
//Switch to Moon Light Color
if ( _CloudDensityScale . w < _CloudDensityScale . z )
LightColor = _MoonLightColor . rgb ;
float pRad = _CloudsParameter . w ;
float3 pCent = float3 ( EyePosition . x , - pRad , EyePosition . z );
float3 startPos ;
float3 endPos ;
// find nearest inner shell point
float2 ih = 0.0 f ;
uint innerShellHits = intersectRaySphere (
EyePosition ,
ray ,
pCent ,
pRad + _CloudsParameter . x ,
ih );
// find nearest outer shell point
float2 oh = 0.0 f ;
uint outerShellHits = intersectRaySphere (
EyePosition ,
ray ,
pCent ,
pRad + _CloudsParameter . y ,
oh );
// world space ray intersections
float3 innerShellHit = EyePositionDepth + ( ray * ih . x );
float3 outerShellHit = EyePositionDepth + ( ray * oh . x );
float2 hitDistance ;
// eye radius from planet center
float ch = length ( EyePosition - pCent ) - _CloudsParameter . w ;
if ( ch < _CloudsParameter . x )
{
#ifdef ENVIRO_DEPTHBLENDING
// exit if there's something in front of the start of the cloud volume
if (( depthPresent && ( distance ( wsPos , EyePositionDepth ) < distance ( innerShellHit , EyePositionDepth ))) || ray . y < - 0.05 ) // shell hits are guaranteed, but the ground may be occluding cloud layer
{
return float4 ( 0.0 f , 0.0 f , 0.0 f , 0.0 f );
}
#else
if ( ray . y < - 0.02 )
return float4 ( 0.0 f , 0.0 f , 0.0 f , 0.0 f );
#endif
endPos = outerShellHit ;
hitDistance = float2 ( ih . x , oh . x );
}
else if ( ch > _CloudsParameter . y )
{
float3 firstShellHit = outerShellHit ;
float3 secondShellHit = outerShellHits == 2 u && innerShellHits == 0 u ? EyePosition + ( ray * oh . y ) : innerShellHit ;
#ifdef ENVIRO_DEPTHBLENDING
if ( outerShellHits == 0 u || depthPresent && ( distance ( wsPos , EyePositionDepth ) <= distance ( firstShellHit , EyePositionDepth )))
{
return float4 ( 0.0 f , 0.0 f , 0.0 f , 0.0 f );
}
#endif
endPos = secondShellHit ;
float hit2 = outerShellHits == 2 u && innerShellHits == 0 u ? oh . y : ih . x ;
hitDistance = float2 ( oh . x , hit2 );
}
else // between shells
{
float3 shellHit = innerShellHits > 0 u ? innerShellHit : outerShellHit ;
float hit = innerShellHits > 0 u ? ih . x : oh . x ;
float height = Remap ( EyePosition . y , _CloudsParameter . x , _CloudsParameter . y , 0 , 1 );
hitDistance = ResolveInside ( EyePosition . xyz , ray , lerp ( 25000 , 100000 , height ));
#ifdef ENVIRO_DEPTHBLENDING
if ( depthPresent && ( distance ( wsPos , EyePositionDepth ) < distance ( shellHit , EyePositionDepth )))
{
shellHit = wsPos ;
hitDistance . y = ( wsPos - EyePositionDepth ) / viewDir ;
}
#endif
endPos = shellHit ;
//float reducedDistance = 500 * (1.0 + 0.0) / (1 * lerp(1.0, 0.015, smoothstep(-0.2, -0.6, 0.1)));
//hit = min(hit, 0.0 + reducedDistance);
//hitDistance = float2(0.0, hit);
}
hitDistance . x = max ( 0.0 , hitDistance . x );
///
int steps = ( int ) lerp ( _Steps . x , _Steps . x , ray . y );
float rayStepLength = ( 1 * ( hitDistance . y - hitDistance . x ) / steps );
float3 rayStep = ray * rayStepLength ;
#ifdef ENVIRO_HALTONOFFSET
const float bayerOffsets [ 3 ][ 3 ] = {
{ 0 , 7 , 3 },
{ 6 , 5 , 2 },
{ 4 , 1 , 8 }
};
float2 screenPos = i . screenPos . xy / i . screenPos . w ;
int2 texelID = int2 ( fmod ( screenPos / _TexelSize , 3.0 )); //Calculate a texel id to index bayer matrix.
float bayerOffset = ( bayerOffsets [ texelID . x ][ texelID . y ]) / 9.0 f ; //bayeroffset between[0,1)
float offset = - fmod ( _RaymarchOffset + bayerOffset , 1.0 f ); //final offset combined. The value will be multiplied by sample step in GetDensity.
float3 pos = ( EyePosition + ( hitDistance . x + offset * rayStepLength ) * ray );
rayStepLength = rayStepLength * offset ;
#else
float3 pos = ( EyePosition + ( hitDistance . x + rayStepLength ) * ray );
uint a = uint ( i . uv . x ) % 4 ;
uint b = uint ( i . uv . y ) % 4 ;
pos += bayerFilter [ a * 4 + b ] * rayStep ;
#endif
float cloud_test = 0.0 ;
int zero_density_sample_count = 0 ;
float sampled_density_previous = - 1.0 ;
float ds = 0.0 ;
float trans = 1.0 ;
float intensity = 0.0 ;
float alpha = 1.0 ;
float eyeToEnd = distance ( EyePosition , endPos );
float lod = saturate (( 0.5 - Remap ( eyeToEnd , 0 , _CloudsParameter . w * 0.1 , 0 , 1.25 ) ) * 1.25 );
float inScatteringAngle = dot ( normalize ( ray ), normalize ( LightDirection ));
#ifndef ENVIRO_DEPTHBLENDING
// Reduce steps when rendering behind objects.
if ( dpth < 1 )
steps *= _stepsInDepth ;
#else
#endif
//Raymarching
[ loop ]
for ( int i = 0 ; i < steps ; i ++ )
{
#ifdef ENVIRO_HALTONOFFSET
pos += rayStep ;
#endif
//Calculate projection height
float height = GetSamplingHeight ( pos , pCent );
//Get out of expensive raymarching
if ( alpha <= 0.01 || height > 1.0 || height < 0.0 || _CloudsCoverageSettings . x <= - 0.9 )
break ;
// Get Weather Data
float3 weather = GetWeather ( pos );
if ( cloud_test > 0.0 )
{
float sampled_density = CalculateCloudDensity ( pos , pCent , weather , 0 , lod , true );
if ( sampled_density == 0.0 && sampled_density_previous == 0.0 )
{
zero_density_sample_count ++ ;
}
if ( zero_density_sample_count < 11 && sampled_density != 0.0 )
{
float dl = GetDensityAlongRay ( pos , pCent , LightDirection , weather , lod );
ds += saturate ( sampled_density );
float extinction = _CloudDensityScale . x * sampled_density ;
float transmittance = exp ( - extinction );
float hg = max ( HenryGreenstein ( inScatteringAngle , _CloudsLighting . y ) * 0.5 , _CloudsLighting . z * 2 * HenryGreenstein ( inScatteringAngle , 0.99 - _CloudsLighting . w ));
float luminance = GetLightEnergy ( pos , height , dl , ds , hg , inScatteringAngle , rayStepLength , _CloudsLighting . x , weather );
float integScatt = ( luminance - luminance * transmittance );
intensity += trans * integScatt ;
trans *= transmittance ;
alpha *= max ( trans , 0.0 );
float3 sunLight = pow ( LightColor , 2 ) * _LightIntensity ;
sunLight . rgb = sunLight . rgb * intensity * saturate ( alpha );
color . rgb += sunLight . rgb ;
if ( alpha <= _CloudsCoverageSettings . z )
alpha = 0.0 ;
}
// if not, then set cloud_test to zero so that we go back to the cheap sample case
else
{
cloud_test = 0.0 ;
zero_density_sample_count = 0 ;
}
sampled_density_previous = sampled_density ;
}
else
{
// sample density the cheap way, only using the low frequency noise
cloud_test = CalculateCloudDensity ( pos , pCent , weather , 0 , lod , false );
if ( cloud_test == 0.0 )
{
pos += rayStep * 2 ;
}
else //take a step back and capture area we skipped.
{
pos -= rayStep ;
}
}
#ifndef ENVIRO_HALTONOFFSET
pos += rayStep ;
#endif
}
color . a = saturate ( 1 - alpha );
// Ambient Lighting
float3 ambientColor = ( sky . rgb * _AmbientLightColor . rgb );
color = color + float4 ( ambientColor * _AmbientSkyColorIntensity * _CloudsLightingExtended . y , 0 ) * saturate ( 1 - ( color ));
//Tonemapping
if ( _CloudsLightingExtended . z == 0 )
{
color . rgb = tonemapACES ( color . rgb , _CloudsLightingExtended . w );
}
//Dithering
color . rgb += ScreenSpaceDither ( sPos , color . rgb );
#if defined(UNITY_COLORSPACE_GAMMA)
color . rgb = LinearToGammaSpace ( color . rgb );
#endif
return color ;
}
ENDCG
}
}
}