Files
2026-09-29 15:35:09 +02:00

176 lines
6.9 KiB
HLSL

#ifndef FOG_INCLUDED
#define FOG_INCLUDED
// Animated fog: the exact noise pattern doesn't matter, use the cheaper hash
#define NOISES_FAST_HASH
#include "Assets/_Content/Shaders/Include/Noises.hlsl"
/*
Performance notes (output is identical to the previous version):
- The alpha mask (ground / spikes / bars) is evaluated first. Where it is exactly 0
the pixel is fully transparent, so the fog density and color noises are skipped.
- Spikes are exactly 0 when height01 >= 0.7 (spikeField is clamped to 0.7 and the
edge warp is >= 0), bars are exactly 0 when height01 >= 1 (barField <= 1):
their noises are skipped there.
*/
void Fog_float
(
float4 ColorA,
float4 ColorB,
float4 ColorC,
float4 ColorD,
float DepthAtten,
float3 PositionWS,
float3 PositionOS,
float3 PivotWS,
float3 Scale,
float SpikeAmount,
float SpikeScale,
float SpikeTilt,
float SpikeSpeed,
float PaintSteps,
float GrainAmount,
float FogStartTime,
out float3 Output,
out float Alpha
)
{
float3 camPos = _WorldSpaceCameraPos.xyz;
float depth = distance(camPos, PositionWS);
float t = (_Time.y - FogStartTime) * .7;
float s = lerp(Scale.x, 1, .3);
// --- Ground glow ---
float height01 = saturate(PositionOS.y / 0.3);
float groundMask = pow(1 - height01, 2);
// --- Reference vector from pivot ---
float2 radial = PositionWS.xz - PivotWS.xz;
// Smooth left/right factor, zero exactly at the front/back seams of the circle
float sideSign = clamp(radial.x / (Scale.x * 0.3 + 0.001), -1, 1);
// Tangential direction (= "around the circle")
float2 tangentDir = normalize(float2(-radial.y, radial.x));
// --- Spikes: lean + drift tangentially, direction flips per side ---
float spikeMask = 0;
float veins = 0;
[branch] if (height01 < 0.7)
{
float2 spikeXZ = radial + tangentDir * (SpikeTilt * height01 * sideSign);
// Tangential animation, same sign as the lean -> each half rotates outward, opposite directions
spikeXZ += tangentDir * (t * SpikeSpeed * sideSign);
// Horizontal-only animation, drifting outward (same direction as the tilt)
spikeXZ.y -= t * SpikeSpeed * sideSign;
// Low frequency field defines the spike's overall height/silhouette
// (low freq -> the lean doesn't break the shape into zigzags)
float spikeField = saturate(FBMNoise3D(float3(spikeXZ.x, spikeXZ.y, 0), SpikeScale * 0.3, 3, 2, .5));
spikeField = pow(spikeField, 4) * SpikeAmount;
spikeField = min(spikeField, 0.7); // never reaches the sky
// High frequency detail -> subtle jagged/organic edges only
float edgeWarp = RidgedNoise3D(float3(spikeXZ * 4, t * 0.1), .1, 3, 2, .5);
veins = RidgedNoise3D(float3(spikeXZ * 12, t * 0.05), .1, 4, 2.2, .6);
spikeField *= lerp(0.75, 1.0, veins);
spikeMask = smoothstep(spikeField, spikeField - 0.04, height01 + edgeWarp * 0.02);
}
// --- Tall organic bars reaching toward the sky ---
float barMask = 0;
[branch] if (height01 < 1)
{
float2 barXZ = radial + float2(SpikeTilt * 0.3 * height01 * sideSign, 0);
float phase = FBMNoise3D(float3(radial * 0.2, t * 0.02), 1, 2, 2, .5) * 6.2831;
float wobble = sin(height01 * 6.2831 * 1.5 + phase) * 2.5 * height01;
barXZ.x += wobble;
float barField = saturate(FBMNoise3D(float3(barXZ * 0.4, t * 0.02), 1, 3, 2, .5));
barField = saturate(pow(barField, 3) * 1.1);
barMask = smoothstep(barField, barField - 0.2, height01);
}
// --- Combine ---
float combinedMask = saturate(max(groundMask, max(spikeMask, barMask)));
// Fully transparent: nothing else can change the blended result
[branch] if (combinedMask <= 0)
{
Output = 0;
Alpha = 0;
return;
}
// Base sampling position, scrolling over time
float3 p = PositionWS;
p += float3(t, t * .2, t);
// Domain warp for organic fog shapes
float noise = RidgedNoise3D(p - PivotWS * .9, .1, 5, 1.6, 1);
p += noise * 3;
// Base fog density
float fog = saturate(FBMNoise3D((p - PivotWS * .5), 10 / s, 3, 1.5, .5));
// --- Painterly grain ---
float grain = FBMNoise3D(PositionWS * 25, 4, 2, 2, .5);
grain = grain * 2 - 1;
// --- Color bands ---
// Transitions narrowed around the same centers (.2 / .5 / .8) as before: each
// quantized PaintSteps level now resolves closer to a pure A/B/C/D color instead
// of a muddier blend, giving a cleaner, more graphic/poster-like read.
float colorMask = saturate(RidgedNoise3D((p - PivotWS * .9) * float3(1, 1.6, 1), 3 / s, 3, 1.6, 1));
float paintMask = saturate(colorMask + grain * 0.03);
paintMask = floor(paintMask * PaintSteps) / max(PaintSteps - 1, 1);
float4 col = lerp(ColorA, ColorB, smoothstep(0.16, 0.24, paintMask));
col = lerp(col, ColorC, smoothstep(0.46, 0.54, paintMask));
col = lerp(col, ColorD, smoothstep(0.76, 0.84, paintMask));
float luma = dot(col.rgb, float3(0.299, 0.587, 0.114));
col.rgb = lerp(col.rgb, luma.xxx, 0.2); // slightly more desaturated toward gray -> muted, graphic-novel mood
col.rgb *= (1 + grain * GrainAmount);
// Spikes read as red glowing veins; bars keep the painted color bands
col.rgb = lerp(col.rgb, ColorA.rgb, spikeMask);
// --- Heavy black ink outline + scattered grime/dirt (Enki Bilal style) ---
// mask*(1-mask) peaks exactly where a smoothstep is mid-transition, so the line always
// lands right on the silhouette boundary no matter how small/large spikeField or barField
// happen to be -- no fragile inset constants, no screen-space derivatives needed.
// A low power + a gain pass fattens that peak into a bold, mostly-solid core instead of
// a thin soft gradient line; the existing noise fields break it up so it reads as a rough
// ink/charcoal stroke rather than a clean vector ring.
float spikeRim = saturate(pow(4 * spikeMask * (1 - spikeMask), 0.4));
spikeRim = saturate(spikeRim * 1.6);
spikeRim *= lerp(0.4, 1.0, veins);
float barRim = saturate(pow(4 * barMask * (1 - barMask), 0.4));
barRim = saturate(barRim * 1.6);
barRim *= lerp(0.45, 1.0, saturate(grain * 0.5 + 0.5));
float inkRim = saturate(spikeRim + barRim);
col.rgb = lerp(col.rgb, float3(0, 0, 0), inkRim); // genuinely black, not a tinted wash
// Grime: independent dark smudges scattered across the whole surface, not tied to any silhouette
float grimeNoise = saturate(FBMNoise3D(PositionWS * 4 + float3(t * 0.05, 0, 0), 5, 3, 2, .5));
float grimeMask = smoothstep(0.5, 0.58, grimeNoise) * (1 - smoothstep(0.8, 0.9, grimeNoise));
col.rgb = lerp(col.rgb, float3(0, 0, 0), grimeMask * 0.75);
// --- Final output ---
Output = saturate(col.rgb);
Alpha = saturate(fog * combinedMask * lerp(.2, 1, smoothstep(0, 15, depth)) * col.a);
}
#endif