Souljack/Assets/_Project/Shaders/UVRoomBaker.shader

154 lines
6.9 KiB
GLSL
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}
}
}