154 lines
6.9 KiB
GLSL
154 lines
6.9 KiB
GLSL
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
|
||
}
|
||
}
|
||
} |