源码 / Ch28/VolumetricLight.shader
Ch28/VolumetricLight.shader
// 案例 28.5:体积光(主光方向光的光束 / God Rays)。 // Pass 0:半分辨率光线步进,逐步采样主光阴影图;Pass 1/2:可分离模糊;Pass 3:合成到相机颜色;Pass 4:只看散射项 Shader "Cookbook/Ch28/VolumetricLight" { Properties { _Density ("Density (1/m)", Float) = 0.06 _Anisotropy ("HG Anisotropy g", Range(-0.9, 0.95)) = 0.6 _MaxDistance ("Max March Distance", Float) = 30 _Steps ("Steps", Range(4, 64)) = 24 _Intensity ("Intensity", Float) = 1 [Toggle] _Jitter ("Jitter Start", Float) = 1 _Tint ("Tint", Color) = (1, 0.92, 0.8, 1) } SubShader { Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" } ZWrite Off ZTest Always Cull Off Blend Off HLSLINCLUDE #include "Assets/Cookbook/Ch28/ScreenSpaceCommon.hlsl" #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Random.hlsl" CBUFFER_START(UnityPerMaterial) float _Density, _Anisotropy, _MaxDistance, _Steps, _Intensity, _Jitter; half4 _Tint; CBUFFER_END float4 _VolTexelSize; // 体积纹理的 (1/w, 1/h, w, h) TEXTURE2D_X(_VolumetricResult); // 合成时读取(r = 散射,g = 透射率) half4 Blur9(float2 uv, float2 dir) { static const float w[5] = { 0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216 }; half4 sum = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv) * w[0]; for (int i = 1; i < 5; i++) { sum += SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv + dir * i) * w[i]; sum += SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv - dir * i) * w[i]; } return sum; } ENDHLSL Pass { Name "RayMarch" HLSLPROGRAM #pragma vertex Vert #pragma fragment FragMarch // 主光阴影的关键字由 URP 在渲染阴影图后全局开启;全屏 Pass 只要声明同样的变体即可。 // 不需要 _MAIN_LIGHT_SHADOWS_SCREEN:屏幕空间阴影只有“表面”上的结果,体积里的点用不上。 #pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE // Shadows.hlsl 用到 CommonMaterial.hlsl 里的 LerpWhiteTo,但自己没 include(Lit 走 Lighting.hlsl 时会间接带上) #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonMaterial.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/RealtimeLights.hlsl" // Henyey-Greenstein 相位函数:g>0 时向前散射为主,迎着光看最亮 float PhaseHG(float cosTheta, float g) { float g2 = g * g; return INV_FOUR_PI * (1 - g2) / pow(max(1 + g2 - 2 * g * cosTheta, 1e-4), 1.5); } half4 FragMarch(Varyings input) : SV_Target { UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input); float2 uv = input.texcoord; float depth = SampleDeviceDepth(uv); float3 endWS = IsSkyDepth(depth) ? ComputeWorldSpacePosition(uv, UNITY_RAW_FAR_CLIP_VALUE, UNITY_MATRIX_I_VP) : ReconstructPositionWS(uv, depth); float3 rayWS = endWS - _WorldSpaceCameraPos; float rayLength = min(length(rayWS), _MaxDistance); float3 dir = rayWS / max(length(rayWS), 1e-4); Light mainLight = GetMainLight(); float phase = PhaseHG(dot(dir, mainLight.direction), _Anisotropy); int steps = (int)_Steps; float stepLength = rayLength / steps; // 抖动起点:把“条带”换成高频噪声,再交给模糊去掉 float jitter = _Jitter > 0.5 ? InterleavedGradientNoise(input.positionCS.xy, 0) : 0.5; float scatter = 0; float transmittance = 1; float stepT = exp(-_Density * stepLength); // 每步的透射率(均匀介质) for (int i = 0; i < steps; i++) { float t = (i + jitter) * stepLength; float3 p = _WorldSpaceCameraPos + dir * t; // 关键两行:世界坐标 → 阴影坐标(自动选级联)→ 比较阴影图 float4 shadowCoord = TransformWorldToShadowCoord(p); float visibility = MainLightRealtimeShadow(shadowCoord); // 这一小段介质散射到相机方向的光,再乘上它到相机之间的透射率 scatter += visibility * transmittance * (1 - stepT); transmittance *= stepT; } scatter *= phase * _Intensity * FOUR_PI; // ×4π 让 g=0 时的强度约为 1,便于调参 return half4(scatter, transmittance, 0, 1); } ENDHLSL } Pass { Name "BlurH" HLSLPROGRAM #pragma vertex Vert #pragma fragment Frag half4 Frag(Varyings input) : SV_Target { return Blur9(input.texcoord, float2(_VolTexelSize.x, 0)); } ENDHLSL } Pass { Name "BlurV" HLSLPROGRAM #pragma vertex Vert #pragma fragment Frag half4 Frag(Varyings input) : SV_Target { return Blur9(input.texcoord, float2(0, _VolTexelSize.y)); } ENDHLSL } Pass { Name "Composite" HLSLPROGRAM #pragma vertex Vert #pragma fragment Frag // Shadows.hlsl 用到 CommonMaterial.hlsl 里的 LerpWhiteTo,但自己没 include(Lit 走 Lighting.hlsl 时会间接带上) #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonMaterial.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/RealtimeLights.hlsl" half4 Frag(Varyings input) : SV_Target { half4 color = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_PointClamp, input.texcoord); half2 vol = SAMPLE_TEXTURE2D_X(_VolumetricResult, sampler_LinearClamp, input.texcoord).rg; half3 lightColor = GetMainLight().color * _Tint.rgb; // 场景被介质衰减(×透射率),再加上介质散射进视线的光 color.rgb = color.rgb * vol.g + lightColor * vol.r; return color; } ENDHLSL } Pass { Name "ShowScatter" HLSLPROGRAM #pragma vertex Vert #pragma fragment Frag half4 Frag(Varyings input) : SV_Target { half s = SAMPLE_TEXTURE2D_X(_VolumetricResult, sampler_LinearClamp, input.texcoord).r; return half4(s.xxx, 1); } ENDHLSL } } }