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 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229
| Shader "Custom/URP/VolumetricLightBeam" { Properties { _BeamColor ("光束颜色", Color) = (1, 0.9, 0.7, 1) _BeamIntensity ("光束强度", Range(0, 10)) = 2.0 _BeamRadius ("光束半径(底部)", Float) = 0.5 _BeamTipRadius ("光束尖端半径(顶部)", Float) = 0.05 _BeamHeight ("光束高度", Float) = 4.0
// 散射参数 _ScatterCoeff ("散射系数(密度)", Range(0, 2)) = 0.5 _AbsorbCoeff ("吸收系数", Range(0, 1)) = 0.1 _MieG ("Mie 散射 g 值(前向散射)", Range(-0.99, 0.99)) = 0.7
// 步进参数 _StepCount ("步进次数", Range(8, 64)) = 32 _NoiseScale ("噪波尺度(光束扰动)", Range(0, 5)) = 1.5 _NoiseStrength ("噪波强度", Range(0, 1)) = 0.3 _AnimSpeed ("动画速度", Range(0, 2)) = 0.3 }
SubShader { Tags { "RenderType" = "Transparent" "RenderPipeline" = "UniversalPipeline" "Queue" = "Transparent" }
Pass { Name "VolumetricBeam" Tags { "LightMode" = "UniversalForward" }
Blend One One // 加法混合(体积光叠加) ZWrite Off Cull Front // 从内部渲染(相机在圆锥外时才正确)
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/DeclareDepthTexture.hlsl"
CBUFFER_START(UnityPerMaterial) float4 _BeamColor; float _BeamIntensity; float _BeamRadius; float _BeamTipRadius; float _BeamHeight; float _ScatterCoeff; float _AbsorbCoeff; float _MieG; int _StepCount; float _NoiseScale; float _NoiseStrength; float _AnimSpeed; CBUFFER_END
struct Attributes { float4 positionOS : POSITION; float2 uv : TEXCOORD0; };
struct Varyings { float4 positionHCS : SV_POSITION; float3 worldPos : TEXCOORD0; float4 screenPos : TEXCOORD1; };
// Henyey-Greenstein 相位函数(Mie 散射近似) float HenyeyGreenstein(float cosTheta, float g) { float g2 = g * g; return (1.0 - g2) / (4.0 * 3.14159 * pow(1.0 + g2 - 2.0 * g * cosTheta, 1.5)); }
// 噪波函数(用于光束扰动,模拟尘埃颗粒) float Hash(float3 p) { p = frac(p * float3(0.1031, 0.1030, 0.0973)); p += dot(p, p.yzx + 33.33); return frac((p.x + p.y) * p.z); }
float Noise3D(float3 p) { float3 i = floor(p); float3 f = frac(p); f = f * f * (3.0 - 2.0 * f);
return lerp( lerp(lerp(Hash(i), Hash(i + float3(1,0,0)), f.x), lerp(Hash(i + float3(0,1,0)), Hash(i + float3(1,1,0)), f.x), f.y), lerp(lerp(Hash(i + float3(0,0,1)), Hash(i + float3(1,0,1)), f.x), lerp(Hash(i + float3(0,1,1)), Hash(i + float3(1,1,1)), f.x), f.y), f.z ); }
// 圆锥 SDF(y 轴方向,底部大顶部小) float ConeIntersect(float3 localRayOrigin, float3 localRayDir, out float tNear, out float tFar) { // 圆锥参数化:底部 y=0 半径 _BeamRadius,顶部 y=_BeamHeight 半径 _BeamTipRadius float rb = _BeamRadius; float rt = _BeamTipRadius; float h = _BeamHeight;
// 圆锥斜率 float slope = (rb - rt) / h;
// 代入圆锥方程求交 float a = localRayDir.x * localRayDir.x + localRayDir.z * localRayDir.z - slope * slope * localRayDir.y * localRayDir.y; float b = 2.0 * (localRayOrigin.x * localRayDir.x + localRayOrigin.z * localRayDir.z - slope * slope * (localRayOrigin.y - rb / slope) * localRayDir.y); float c = localRayOrigin.x * localRayOrigin.x + localRayOrigin.z * localRayOrigin.z - slope * slope * (localRayOrigin.y - rb / slope) * (localRayOrigin.y - rb / slope);
float disc = b * b - 4.0 * a * c; if (disc < 0.0) { tNear = -1; tFar = -1; return -1; }
float sqrtDisc = sqrt(disc); float t1 = (-b - sqrtDisc) / (2.0 * a); float t2 = (-b + sqrtDisc) / (2.0 * a);
tNear = min(t1, t2); tFar = max(t1, t2); return 1.0; }
Varyings vert(Attributes input) { Varyings output; output.positionHCS = TransformObjectToHClip(input.positionOS.xyz); output.worldPos = TransformObjectToWorld(input.positionOS.xyz); output.screenPos = ComputeScreenPos(output.positionHCS); return output; }
half4 frag(Varyings input) : SV_Target { // ---- 设置光线(世界空间 -> 物体空间)---- float3 camWS = GetCameraPositionWS(); float3 rayDir_world = normalize(input.worldPos - camWS);
// 变换到物体空间进行圆锥求交 float3 rayOrigin_obj = TransformWorldToObject(camWS); float3 rayDir_obj = normalize(TransformWorldToObject(input.worldPos) - rayOrigin_obj);
float tNear, tFar; if (ConeIntersect(rayOrigin_obj, rayDir_obj, tNear, tFar) < 0) discard;
// 裁剪 y 范围(只在圆锥有效高度内) float tMin = tNear, tMax = tFar;
// ---- 读取场景深度(用于遮挡测试)---- float2 screenUV = input.screenPos.xy / input.screenPos.w; float sceneDepth = SampleSceneDepth(screenUV);
// 将深度转换为线性距离(从相机到场景表面的距离) float sceneLinearDepth = LinearEyeDepth(sceneDepth, _ZBufferParams); // 光线最大步进距离受深度限制 float rayMaxDist = min(sceneLinearDepth, tMax * length(rayDir_world));
// ---- 光线步进积分 ---- float stepSize = (tMax - tMax * 0.0 - tMin) / float(_StepCount); float t = tMin + stepSize * 0.5; // 步进起始(半步偏移减少条带)
float3 accumColor = float3(0, 0, 0); float transmittance = 1.0; // Beer-Lambert 透射率
// 光源方向(使用主光源) float3 lightDir = _MainLightPosition.xyz; float cosTheta = dot(rayDir_world, lightDir); float phaseVal = HenyeyGreenstein(cosTheta, _MieG);
for (int i = 0; i < _StepCount; i++) { float3 samplePosLocal = rayOrigin_obj + rayDir_obj * t; float3 samplePosWorld = TransformObjectToWorld(samplePosLocal);
// 深度遮挡:当前采样点超过场景深度时停止 float sampleDist = length(samplePosWorld - camWS); if (sampleDist > rayMaxDist) break;
// 噪波扰动密度(模拟光束中的浮尘颗粒) float3 noisePosAnimated = samplePosWorld * _NoiseScale + float3(0, -_Time.y * _AnimSpeed, 0); float noiseDensity = Noise3D(noisePosAnimated);
// 局部密度(中心高边缘低) float radialDist = length(samplePosLocal.xz); float heightFrac = saturate(samplePosLocal.y / _BeamHeight); float beamRadiusAt = lerp(_BeamRadius, _BeamTipRadius, heightFrac); float localDensity = saturate(1.0 - radialDist / beamRadiusAt);
// 叠加噪波 localDensity *= 1.0 + (noiseDensity - 0.5) * _NoiseStrength; localDensity = max(0.0, localDensity);
// Beer-Lambert:当前步的透射率 float extinction = (_ScatterCoeff + _AbsorbCoeff) * localDensity; float stepTransmit = exp(-extinction * stepSize);
// 散射贡献(Frostbite 能量守恒积分) float3 scatter = _BeamColor.rgb * _ScatterCoeff * localDensity * phaseVal; // 解析积分:避免步长依赖 float3 scatterInt = (scatter - scatter * stepTransmit) / max(extinction, 1e-6); accumColor += transmittance * scatterInt;
transmittance *= stepTransmit; t += stepSize;
if (transmittance < 0.01) break; // 早期退出 }
float3 finalColor = accumColor * _BeamIntensity; return half4(finalColor, 1.0); } ENDHLSL } } }
|