1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
| Shader "Custom/URP/VolumetricFog" { Properties { _FogColor ("Fog Color", Color) = (0.5, 0.8, 0.5, 1.0) _FogDensity ("Fog Density", Range(0.0, 2.0)) = 0.5 _FogHeight ("Max Height", Float) = 3.0 _FogCenter ("Fog Center (WS)", Vector) = (0, 0, 0, 0) _FogRadius ("Fog Sphere Radius",Float) = 5.0 _RaySteps ("Ray March Steps", Range(8, 32)) = 16 _StepSize ("Ray Step Size", Range(0.1, 1.0)) = 0.4 // 噪声纹理(用于扰动雾密度) _NoiseTex ("Noise Texture", 2D) = "white" {} _NoiseScale ("Noise Scale", Range(0.1, 2.0)) = 0.5 _NoiseSpeed ("Noise Speed", Range(0.0, 1.0)) = 0.1 }
SubShader { Tags { "Queue" = "Transparent" "RenderType" = "Transparent" "RenderPipeline" = "UniversalPipeline" }
Blend SrcAlpha OneMinusSrcAlpha ZWrite Off // 只渲染背面(从内部看雾,避免正面遮挡) Cull Front
Pass { Name "VolumetricFogPass" Tags { "LightMode" = "UniversalForward" }
HLSLPROGRAM #pragma vertex vert #pragma fragment frag
#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.universal/ShaderLibrary/DeclareDepthTexture.hlsl"
TEXTURE2D(_NoiseTex); SAMPLER(sampler_NoiseTex);
CBUFFER_START(UnityPerMaterial) float4 _NoiseTex_ST; float4 _FogColor; float4 _FogCenter; // xyz: 世界坐标中心 float _FogDensity; float _FogHeight; float _FogRadius; float _RaySteps; float _StepSize; float _NoiseScale; float _NoiseSpeed; CBUFFER_END
struct Attributes { float4 positionOS : POSITION; UNITY_VERTEX_INPUT_INSTANCE_ID };
struct Varyings { float4 positionHCS : SV_POSITION; float3 positionWS : TEXCOORD0; // 世界空间位置(用于光线方向) float4 screenPos : TEXCOORD1; // 屏幕坐标(深度采样) UNITY_VERTEX_OUTPUT_STEREO };
// ======== SDF 形状函数(世界空间)========
// 球形 SDF(局部雾的边界) float sdSphere(float3 p, float3 center, float radius) { return length(p - center) - radius; }
// 高度限制(雾不超过指定高度) float sdHeightLimit(float3 p, float yMin, float yMax) { return max(yMin - p.y, p.y - yMax); }
// 综合雾 SDF(球形 + 高度限制的交集) float fogSDF(float3 p) { float dSphere = sdSphere(p, _FogCenter.xyz, _FogRadius); float dHeight = sdHeightLimit(p, _FogCenter.y - 0.5, _FogCenter.y + _FogHeight); // 交集:取两者的最大值(在球形内且在高度范围内) return max(dSphere, dHeight); }
// ======== 雾密度函数 ======== float sampleFogDensity(float3 posWS, float noiseTime) { // SDF 值:负值 = 在雾内部,正值 = 外部 float sdfVal = fogSDF(posWS); if (sdfVal > 0.0) return 0.0; // 在雾外直接跳过
// 基础密度(从边缘到中心线性增加) float baseDensity = saturate(-sdfVal / (_FogRadius * 0.5));
// 高度衰减(底部密,顶部稀) float heightFade = 1.0 - saturate((posWS.y - _FogCenter.y) / _FogHeight); heightFade = pow(heightFade, 1.5);
// 噪声扰动(让雾看起来有体积感) float2 noiseUV = posWS.xz * _NoiseScale + noiseTime; float noise = SAMPLE_TEXTURE2D_LOD(_NoiseTex, sampler_NoiseTex, noiseUV, 2.0).r; // 噪声叠加(减弱部分密度,产生云朵状孔洞) float noiseMask = lerp(0.5, 1.5, noise);
return baseDensity * heightFade * noiseMask * _FogDensity; }
Varyings vert(Attributes IN) { Varyings OUT; UNITY_SETUP_INSTANCE_ID(IN); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT); OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz); OUT.positionWS = TransformObjectToWorld(IN.positionOS.xyz); OUT.screenPos = ComputeScreenPos(OUT.positionHCS); return OUT; }
half4 frag(Varyings IN) : SV_Target { float2 screenUV = IN.screenPos.xy / IN.screenPos.w; float noiseTime = _Time.y * _NoiseSpeed;
// ===== 重建相机光线 ===== float3 cameraPosWS = GetCameraPositionWS(); float3 rayDir = normalize(IN.positionWS - cameraPosWS);
// ===== 场景深度限制(光线不穿越不透明物体)===== float sceneDepth = LinearEyeDepth( SampleSceneDepth(screenUV), _ZBufferParams ); // 将场景深度转换为光线行进的最大距离 float maxRayDist = sceneDepth;
// ===== 光线步进(Ray March Through Fog)===== float totalDensity = 0.0; float3 totalColor = float3(0, 0, 0); float rayT = 0.01; // 起始偏移(避免自相交) int steps = (int)_RaySteps;
for (int i = 0; i < steps; i++) { if (rayT > maxRayDist) break; // 超过场景深度停止
float3 samplePos = cameraPosWS + rayDir * rayT; float density = sampleFogDensity(samplePos, noiseTime);
if (density > 0.001) { // 获取主光源(在雾内采样光照) Light mainLight = GetMainLight(); // 光照对雾颜色的影响 float NdotL = saturate(dot(float3(0, 1, 0), mainLight.direction)); float3 litFogColor = _FogColor.rgb * lerp(0.4, 1.0, NdotL) * mainLight.color;
// 累积(Beer-Lambert 吸收模型) float absorption = density * _StepSize; totalColor += litFogColor * absorption * (1.0 - totalDensity); totalDensity += absorption * (1.0 - totalDensity); }
// 如果已经完全不透明则提前退出 if (totalDensity >= 0.99) break;
rayT += _StepSize; }
totalDensity = saturate(totalDensity); return half4(totalColor / max(totalDensity, 0.001), totalDensity); } ENDHLSL } } }
|