源码 / shaders/Ch06/FalloffCompare.glsl
shaders/Ch06/FalloffCompare.glsl
// 案例 6.2 点光/聚光的衰减:URP 的平滑衰减 vs 纯平方反比 vs 线性衰减(GLSL 版,对应 Ch06/FalloffCompare.shader) Shader "Cookbook/Ch06/FalloffCompare" { SubShader { Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" "Queue"="Geometry" } GLSLINCLUDE uniform vec4 _BaseMap_ST; uniform vec4 _BaseColor; uniform float _AmbientStrength; ENDGLSL Pass { Name "ForwardLit" Tags { "LightMode"="UniversalForward" } GLSLPROGRAM #pragma shader_feature_local_fragment _FALLOFF_URP _FALLOFF_INVERSESQUARE _FALLOFF_LINEAR uniform sampler2D _BaseMap; VARYING vec2 uv; VARYING vec3 positionWS; VARYING vec3 normalWS; #ifdef VERTEX void main() { VertexPositionInputs pos = GetVertexPositionInputs(positionOS.xyz); gl_Position = pos.positionCS; positionWS = pos.positionWS; normalWS = TransformObjectToWorldNormal(normalOS); uv = TransformTex(texcoord0.xy, _BaseMap_ST); } #endif #ifdef FRAGMENT // 不用 GetAdditionalLight(),直接读 URP 上传的光源数组,自己算衰减。 // (网页版的光源数组与 URP 14 同名:_AdditionalLightsPosition / Color / Attenuation / SpotDir) vec3 PunctualLight(int loopIndex, vec3 positionWS, vec3 normalWS) { // Forward+:循环变量就是全局光源下标;Forward:先查“本物体的第几盏灯”对应的全局下标。 // HLSL 版用 #if USE_FORWARD_PLUS 区分;网页版的 GetPerObjectLightIndex 在 Forward+ 下原样返回 int i = GetPerObjectLightIndex(loopIndex); vec4 lightPos = _AdditionalLightsPosition[i]; // w = 0 表示方向光 vec3 color = _AdditionalLightsColor[i].rgb; vec4 attenParam = _AdditionalLightsAttenuation[i]; // x = 1/range²,zw = 聚光角度参数 vec4 spotDir = _AdditionalLightsSpotDir[i]; vec3 lightVector = lightPos.xyz - positionWS * lightPos.w; float d2 = max(dot(lightVector, lightVector), HALF_MIN); vec3 L = lightVector * inversesqrt(d2); #if defined(_FALLOFF_INVERSESQUARE) float atten = 1.0 / d2; // 物理上的平方反比,没有窗口 #elif defined(_FALLOFF_LINEAR) float atten = saturate(1.0 - sqrt(d2 * attenParam.x)); // 1 - d/range #else float atten = DistanceAttenuation(d2, attenParam.xy); // URP:平方反比 × 平滑窗口 #endif atten *= AngleAttenuation(spotDir.xyz, L, attenParam.zw); // 聚光锥;点光的参数让它恒为 1 return color * atten * saturate(dot(normalWS, L)); } // ---- 网页版专用:模拟 Forward+ 的分簇剔除(HLSL 版没有这段,由 URP 的 LIGHT_LOOP_BEGIN 自动完成)---- // URP 的 Forward+ 把屏幕切成 8×8 像素的 tile,再按深度分段(z-bin),只有“范围球”碰到当前 tile 与 z-bin 的灯 // 才会进入这个像素的光源循环。网页版没有分簇结构,这里逐像素做同样的判断,好让“纯平方反比”在灯的 Range // 之外被截断、露出 tile 形状的锯齿边——这正是本案例要演示的现象 bool ForwardPlusClusterHasLight(int i, vec3 positionWS) { vec4 lp = _AdditionalLightsPosition[i]; if (lp.w == 0.0) return true; // 方向光不参与分簇 float r = inversesqrt(max(_AdditionalLightsAttenuation[i].x, 1e-8)); vec3 c = TransformWorldToView(lp.xyz); // 观察空间:相机朝 -z // z-bin:像素深度要落在灯的 [深度 - r, 深度 + r] 里 float z = -TransformWorldToView(positionWS).z; if (z < -c.z - r || z > -c.z + r) return false; // tile:把灯心投影到屏幕上,夹到当前 tile 的矩形里,取过这一点的视线; // 灯心到这条视线的距离小于 r,就认为范围球碰到了这个 tile 的视锥 vec2 p = vec2(unity_MatrixP[0][0], unity_MatrixP[1][1]); vec2 t0 = floor(gl_FragCoord.xy / 8.0) * 8.0; vec2 ndcMin = t0 / _ScreenParams.xy * 2.0 - 1.0, ndcMax = (t0 + 8.0) / _ScreenParams.xy * 2.0 - 1.0; vec2 ndcC = c.z < 0.0 ? c.xy * p / -c.z : vec2(0.0); vec3 d = normalize(vec3(clamp(ndcC, ndcMin, ndcMax) / p, -1.0)); vec3 closest = d * max(dot(c, d), 0.0); return length(c - closest) < r; } void main() { vec3 albedo = texture(_BaseMap, uv).rgb * _BaseColor.rgb; vec3 N = normalize(normalWS); InputData inputData = InitInputData(); inputData.positionWS = positionWS; inputData.normalizedScreenSpaceUV = GetNormalizedScreenSpaceUV(gl_FragCoord); Light mainLight = GetMainLight(); vec3 lighting = mainLight.color * mainLight.distanceAttenuation * saturate(dot(N, mainLight.direction)); #if defined(_ADDITIONAL_LIGHTS) // HLSL 版:Forward+ 先循环附加方向光,再 LIGHT_LOOP_BEGIN;网页版统一成普通 for(见案例 6.1) int lightCount = GetAdditionalLightsCount(); for (int lightIndex = 0; lightIndex < lightCount; lightIndex++) { // Forward+(_PerObjectLights = 0)时模拟分簇剔除;Forward 路径按物体选灯,不受 Range 截断 if (_PerObjectLights < 0.5 && !ForwardPlusClusterHasLight(lightIndex, inputData.positionWS)) continue; lighting += PunctualLight(lightIndex, inputData.positionWS, N); } #endif vec3 color = albedo * lighting + albedo * SampleSH(N) * _AmbientStrength; fragColor = vec4(color, 1.0); } #endif ENDGLSL } // ---- 投影与深度:见案例 6.4 与 6.8 ---- Pass { Name "ShadowCaster" Tags { "LightMode"="ShadowCaster" } ZWrite On ZTest LEqual ColorMask 0 GLSLPROGRAM #include "Ch06/ShadowCasterPass.glsl" ENDGLSL } Pass { Name "DepthOnly" Tags { "LightMode"="DepthOnly" } ZWrite On ColorMask R GLSLPROGRAM #include "Ch06/DepthOnlyPass.glsl" ENDGLSL } } }