Files
jelito/Assets/Jbl_Shaders/Fog/Volumetric Fog_Hybrid.shader
2025-10-14 16:02:35 +02:00

199 lines
9.3 KiB
GLSL
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Shader "Custom/VolumetricFog_Hybrid"
{
Properties
{
_ColorLow("Fog Color Low", Color) = (0.6, 0.6, 0.7, 1)
_ColorHigh("Fog Color High", Color) = (0.8, 0.8, 0.9, 1)
_AmbientFogColor("Ambient Fog Color", Color) = (0.3, 0.3, 0.35, 1)
_MaxDistance("Max Distance", Float) = 100
_StepSize("Step Size", Range(0.1, 20)) = 1
_MaxSteps("Max Steps", Range(8, 256)) = 64
_DensityMultiplier("Density Multiplier", Range(0, 10)) = 1
_DensityThreshold("Density Threshold", Range(0, 1)) = 0.1
_HeightFalloff("Height Falloff", Range(0, 2)) = 0.2
_NoiseOffset("Noise Offset", Float) = 0
_NoiseTiling("Noise Tiling", Float) = 1
_FogNoise("Fog Noise", 3D) = "white" {}
_NoiseScrollSpeed("Noise Scroll Speed (XYZ)", Vector) = (0.02, 0, 0.015, 0)
_NoisePulseSpeed("Noise Pulse Speed", Float) = 0.2
_NoisePulseAmount("Noise Pulse Amount", Float) = 0.1
[HDR]_LightContribution("Light Contribution", Color) = (1, 1, 1, 1)
_LightScattering("Light Scattering (g)", Range(-1, 1)) = 0.2
_DepthFade("Depth Fade Strength", Range(0,1)) = 0.3
_NearStart("Near Fade Start", Float) = 1.0
_NearEnd("Near Fade End", Float) = 3.0
_DayColor("Day Fog Tint", Color) = (0.7,0.75,0.8,1)
_NightColor("Night Fog Tint", Color) = (0.3,0.35,0.4,1)
_DayFactor("Day/Night Blend", Range(0,1)) = 0.5
_GodRayStrength("God Ray Strength", Range(0,1)) = 0.2
_DitherStrength("Dither Strength", Range(0,1)) = 0.3
_ColorSteps("Color Steps", Range(2,32)) = 8
// Sterowanie zależnością od wysokości
_HeightModeBlend("Height Mode Blend (0=World,1=Camera)", Range(0,1)) = 1
}
SubShader
{
Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" }
Pass
{
HLSLPROGRAM
#pragma vertex Vert
#pragma fragment frag
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE _MAIN_LIGHT_SHADOWS_SCREEN
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
#include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"
float4 _ColorLow, _ColorHigh, _AmbientFogColor;
float _MaxDistance, _StepSize; int _MaxSteps;
float _DensityMultiplier, _DensityThreshold, _HeightFalloff;
float _NoiseOffset, _NoiseTiling; TEXTURE3D(_FogNoise);
float3 _NoiseScrollSpeed; float _NoisePulseSpeed, _NoisePulseAmount;
float4 _LightContribution; float _LightScattering;
float _DepthFade;
float _NearStart, _NearEnd;
float4 _DayColor, _NightColor; float _DayFactor, _GodRayStrength;
float _DitherStrength; float _ColorSteps;
int _CustomAdditionalLightCount;
float4 _CustomAdditionalLightPos[16];
float4 _CustomAdditionalLightColor[16];
float _CustomAdditionalLightRange[16];
float _HeightModeBlend;
// ——— Helpers ———
float henyey_greenstein(float cosTheta, float g)
{
float g2 = g * g;
return (1.0 - g2) / (4.0 * PI * pow(1.0 + g2 - 2.0 * g * cosTheta, 1.5));
}
// Hybrydowa gęstość mgły: miks world height i camera height + relY
float get_density(float3 worldPos)
{
// 3D noise coords pozostawiamy w world space (topologia nadal „rzeźbi” mgłę),
// ale wysokościowy spadek gęstości kontrolujemy hybrydą.
float pulse = 1.0 + sin(_Time.y * _NoisePulseSpeed) * _NoisePulseAmount;
float3 noiseCoords = worldPos * 0.01 * _NoiseTiling * pulse;
noiseCoords += _NoiseScrollSpeed * _Time.y;
float4 noise = _FogNoise.SampleLevel(sampler_TrilinearRepeat, noiseCoords, 0);
float density = noise.r;
density = saturate(density - _DensityThreshold) * _DensityMultiplier;
// World vs Camera height falloff
float camY = _WorldSpaceCameraPos.y;
float relY = worldPos.y - camY; // wysokość względem kamery
float worldFactor = exp(-max(0, worldPos.y) * _HeightFalloff);
float camFactor = exp(-max(0, camY) * _HeightFalloff); // stały względem raya
float relFactor = exp(-abs(relY) * _HeightFalloff); // „chmura” wokół kamery
// Hybryda: najpierw łączymy world i relY (wygląda kamerowo), potem mieszamy z camY stałym
float camLike = lerp(worldFactor, relFactor, _HeightModeBlend);
float hybrid = lerp(worldFactor, camLike, _HeightModeBlend); // przy 1.0 = relY (kamerowo), przy 0.0 = world
density *= hybrid;
return density;
}
half4 frag(Varyings IN) : SV_Target
{
float4 col = SAMPLE_TEXTURE2D(_BlitTexture, sampler_LinearClamp, IN.texcoord);
float depth = SampleSceneDepth(IN.texcoord);
float3 worldPos = ComputeWorldSpacePosition(IN.texcoord, depth, UNITY_MATRIX_I_VP);
float3 entryPoint = _WorldSpaceCameraPos;
float3 viewDir = worldPos - _WorldSpaceCameraPos;
float viewLength = length(viewDir);
float3 rayDir = normalize(viewDir);
float2 pixelCoords = IN.texcoord * _BlitTexture_TexelSize.zw;
float distLimit = min(viewLength, _MaxDistance);
float distTravelled = InterleavedGradientNoise(pixelCoords, (int)(_Time.y / max(HALF_EPS, unity_DeltaTime.x))) * _NoiseOffset;
float transmittance = 1.0;
float3 fogAccum = 0;
[loop]
for (int i = 0; i < _MaxSteps && distTravelled < distLimit; i++)
{
float3 rayPos = entryPoint + rayDir * distTravelled;
float density = get_density(rayPos);
if (density > 0)
{
// Lighting
Light mainLight = GetMainLight(TransformWorldToShadowCoord(rayPos));
float phase = henyey_greenstein(dot(rayDir, mainLight.direction), _LightScattering);
float3 lightCol = mainLight.color.rgb * _LightContribution.rgb * phase * mainLight.shadowAttenuation;
for (int li = 0; li < _CustomAdditionalLightCount; li++)
{
float3 toLight = _CustomAdditionalLightPos[li].xyz - rayPos;
float dist = length(toLight);
float3 dir = normalize(toLight);
float atten = saturate(1.0 - dist / _CustomAdditionalLightRange[li]);
float phaseAdd = henyey_greenstein(dot(rayDir, dir), _LightScattering);
lightCol += _CustomAdditionalLightColor[li].rgb * _LightContribution.rgb * phaseAdd * atten;
}
// Fog color: przestajemy wiązać gradient z worldPos.y — używamy wysokości względnej do kamery
float camY = _WorldSpaceCameraPos.y;
float relY = rayPos.y - camY;
float worldColorF = saturate(rayPos.y * 0.01);
float camColorF = saturate(relY * 0.01); // ten daje „stały” wygląd niezależnie od poziomu mapy
float colorF = lerp(worldColorF, camColorF, _HeightModeBlend);
float3 fogColor = lerp(_ColorLow.rgb, _ColorHigh.rgb, colorF);
float3 dayNightTint = lerp(_NightColor.rgb, _DayColor.rgb, _DayFactor);
fogColor = lerp(fogColor, dayNightTint, 0.5);
fogColor = lerp(fogColor, _AmbientFogColor.rgb, 0.25);
fogColor += lightCol * _GodRayStrength;
// Fading
float fade = exp(-distTravelled * _DepthFade);
float nearFactor = saturate((distTravelled - _NearStart) / (_NearEnd - _NearStart));
density *= fade * nearFactor;
fogAccum += (fogColor + lightCol) * density * _StepSize;
transmittance *= exp(-density * _StepSize);
if (transmittance < 0.01) break;
}
distTravelled += _StepSize;
}
float3 finalFog = fogAccum * transmittance;
// Dither + quantize
float dither = frac(sin(dot(pixelCoords, float2(12.9898,78.233))) * 43758.5453);
finalFog += (dither - 0.5) * _DitherStrength.xxx;
finalFog = floor(finalFog * _ColorSteps) / _ColorSteps;
return float4(lerp(col.rgb, finalFog, 1.0 - saturate(transmittance)), 1.0);
}
ENDHLSL
}
}
}