Shader "Hidden/LevelGenerating/UVRoomBaker" { SubShader { Tags { "RenderType"="Opaque" } Cull Off ZWrite Off ZTest Always Pass { HLSLPROGRAM #pragma vertex vert #pragma fragment frag #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" struct Attributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; float2 uv0 : TEXCOORD0; }; struct Varyings { float4 positionCS : SV_POSITION; float3 positionWS : TEXCOORD0; float3 normalWS : TEXCOORD1; }; int _LightCount; float4 _LightPositions[16]; // xyz = pos, w = range float4 _LightDirections[16]; // xyz = dir, w = cos(outerSpotAngle * 0.5) float4 _LightColors[16]; // rgb = color * intensity, a = cos(innerSpotAngle * 0.5) float4 _RoomMin; float4 _RoomMax; float4 _BounceAndAmbient; // x = bounceIntensity, y = ambientIntensity, z = aoStrength, w = floorY Varyings vert(Attributes input) { Varyings output; float2 uv = input.uv0; float2 clipXY = uv * 2.0 - 1.0; #if UNITY_UV_STARTS_AT_TOP clipXY.y = -clipXY.y; #endif output.positionCS = float4(clipXY, 0.5, 1.0); output.positionWS = TransformObjectToWorld(input.positionOS.xyz); output.normalWS = TransformObjectToWorldNormal(input.normalOS); return output; } // Безопасный расчёт затенения в стыках (никогда не зануляет саму плоскость стены!) float CalculateRoomAO(float3 posWS, float3 normWS) { float3 absN = abs(normWS); float3 dMin = saturate((posWS - _RoomMin.xyz) / 0.7); float3 dMax = saturate((_RoomMax.xyz - posWS) / 0.7); float3 d = min(dMin, dMax); // Игнорируем ось, перпендикулярную самой поверхности (чтобы стена не затеняла сама себя!) float aoX = lerp(d.x, 1.0, step(0.4, absN.x)); float aoY = lerp(d.y, 1.0, step(0.4, absN.y)); float aoZ = lerp(d.z, 1.0, step(0.4, absN.z)); float cavity = aoX * aoY * aoZ; // Минимум 0.25 в самом глубоком углу, чтобы никогда не было абсолютной черноты return lerp(1.0, lerp(0.25, 1.0, smoothstep(0.0, 1.0, cavity)), _BounceAndAmbient.z); } half4 frag(Varyings input) : SV_Target { float3 P = input.positionWS; float3 N = normalize(input.normalWS); float3 totalLight = float3(1, 1, 1) * _BounceAndAmbient.y; float floorY = _BounceAndAmbient.w; float roomCenterY = (_RoomMin.y + _RoomMax.y) * 0.5; float bounceMult = _BounceAndAmbient.x; for (int i = 0; i < _LightCount; i++) { float3 lPos = _LightPositions[i].xyz; float lRange = max(0.5, _LightPositions[i].w); float3 lDir = normalize(_LightDirections[i].xyz); float cosOuter = _LightDirections[i].w; float cosInner = _LightColors[i].w; float3 lColor = _LightColors[i].rgb; // 1. ПРЯМОЙ СВЕТ (Direct Spot / Point Light) float3 toLight = lPos - P; float dist = length(toLight); if (dist < lRange) { float3 L = toLight / max(0.0001, dist); // abs(dot) на случай, если нормаль стены развёрнута, но с приоритетом лицевой стороны float ndotl = saturate(dot(N, L)); float distNorm = saturate(dist / lRange); float atten = (1.0 - distNorm * distNorm); atten *= atten; float spotAtten = 1.0; if (cosOuter > -0.99) { float cosAngle = dot(-L, lDir); spotAtten = smoothstep(cosOuter, max(cosOuter + 0.01, cosInner), cosAngle); } totalLight += lColor * (ndotl * atten * spotAtten); } // 2. ОБЪЁМНЫЙ ОТСКОК СВЕТА НА СТЕНЫ И ПОТОЛОК (Room GI Bounce) // Ставим виртуальное облако рассеянного света в середине высоты комнаты под каждой лампой, // чтобы оно красиво освещало и потолок, и все 4 стены вокруг! if (bounceMult > 0.001) { float3 bouncePos = float3(lPos.x, roomCenterY, lPos.z); float bounceRange = lRange * 1.6; float3 toBounce = bouncePos - P; float bDist = length(toBounce); if (bDist < bounceRange) { float3 bL = toBounce / max(0.0001, bDist); // Мягкое полусферическое освещение (Half-Lambert), чтобы стены освещались плавно float bNdotL = saturate(dot(N, bL) * 0.5 + 0.5); // Для пола убавляем bounce, чтобы пол не засвечивался дважды (на него и так светит Spot) float isFloor = step(0.5, N.y); bNdotL *= lerp(1.0, 0.2, isFloor); float bDistNorm = saturate(bDist / bounceRange); float bAtten = (1.0 - bDistNorm * bDistNorm); totalLight += lColor * (bNdotL * bAtten * bounceMult); } } } // Применяем мягкий AO в углах totalLight *= CalculateRoomAO(P, N); // Мягкий компрессор яркости (Reinhard Tonemap), чтобы пол и потолок НИКОГДА не горели как лампы! totalLight = totalLight / (1.0 + totalLight * 0.6); return half4(totalLight, 1.0); } ENDHLSL } } }