153 lines
6.9 KiB
C#
153 lines
6.9 KiB
C#
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
using UnityEngine.Rendering;
|
||
|
||
namespace _Project.Scripts.LevelGenerating
|
||
{
|
||
// =====================================================================
|
||
// Запекает освещение комнаты (Direct Spot/Point + Floor Bounce GI + Room Corner AO)
|
||
// напрямую в RenderTexture каждого меша и выключает реальные компоненты Light!
|
||
// =====================================================================
|
||
public static class RuntimeRoomLightmapBaker
|
||
{
|
||
private static Material _bakeMaterial;
|
||
private static readonly int LightmapTexProp = Shader.PropertyToID("_RuntimeLightmap");
|
||
|
||
private static readonly Vector4[] LightPositions = new Vector4[16];
|
||
private static readonly Vector4[] LightDirections = new Vector4[16];
|
||
private static readonly Vector4[] LightColors = new Vector4[16];
|
||
|
||
public static void BakeRoom(RoomView view, RoomLightConfig config)
|
||
{
|
||
var targets = new List<Renderer>();
|
||
targets.AddRange(view.FloorRenderers);
|
||
targets.AddRange(view.CeilingRenderers);
|
||
targets.AddRange(view.WallRenderers);
|
||
|
||
// Если запекание ВЫКЛЮЧЕНО в конфиге — сбрасываем лайтмапу в чёрный цвет (выключаем фейковый Emission),
|
||
// чтобы ты сразу видел разницу!
|
||
if (config == null || !config.bakeToRuntimeLightmap)
|
||
{
|
||
var resetBlock = new MaterialPropertyBlock();
|
||
foreach (var r in targets)
|
||
{
|
||
if (r == null) continue;
|
||
r.GetPropertyBlock(resetBlock);
|
||
resetBlock.SetTexture(LightmapTexProp, Texture2D.blackTexture);
|
||
r.SetPropertyBlock(resetBlock);
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (_bakeMaterial == null)
|
||
{
|
||
var shader = Shader.Find("Hidden/LevelGenerating/UVRoomBaker");
|
||
if (shader == null)
|
||
{
|
||
Debug.LogError("RuntimeRoomLightmapBaker: не найден шейдер Hidden/LevelGenerating/UVRoomBaker!");
|
||
return;
|
||
}
|
||
_bakeMaterial = new Material(shader);
|
||
}
|
||
|
||
// 1. Собираем все источники света в этой комнате (до 16 штук)
|
||
var lights = view.GetComponentsInChildren<Light>(false);
|
||
int count = Mathf.Min(lights.Length, 16);
|
||
if (count == 0) return;
|
||
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
var l = lights[i];
|
||
Vector3 pos = l.transform.position;
|
||
Vector3 dir = l.transform.forward;
|
||
|
||
LightPositions[i] = new Vector4(pos.x, pos.y, pos.z, l.range);
|
||
|
||
if (l.type == LightType.Spot)
|
||
{
|
||
float cosOuter = Mathf.Cos(l.spotAngle * 0.5f * Mathf.Deg2Rad);
|
||
float innerAngle = l.innerSpotAngle > 0f ? l.innerSpotAngle : l.spotAngle * 0.8f;
|
||
float cosInner = Mathf.Cos(innerAngle * 0.5f * Mathf.Deg2Rad);
|
||
|
||
LightDirections[i] = new Vector4(dir.x, dir.y, dir.z, cosOuter);
|
||
Color c = l.color * l.intensity;
|
||
LightColors[i] = new Vector4(c.r, c.g, c.b, cosInner);
|
||
}
|
||
else // Point Light
|
||
{
|
||
LightDirections[i] = new Vector4(0f, -1f, 0f, -2f); // -2 = флаг Point Light
|
||
Color c = l.color * l.intensity;
|
||
LightColors[i] = new Vector4(c.r, c.g, c.b, -1f);
|
||
}
|
||
}
|
||
|
||
// 2. Передаём параметры комнаты и света в шейдер запекания
|
||
Vector3 rMin = view.OriginMeters;
|
||
Vector3 rMax = rMin + view.SizeMeters;
|
||
|
||
_bakeMaterial.SetInt("_LightCount", count);
|
||
_bakeMaterial.SetVectorArray("_LightPositions", LightPositions);
|
||
_bakeMaterial.SetVectorArray("_LightDirections", LightDirections);
|
||
_bakeMaterial.SetVectorArray("_LightColors", LightColors);
|
||
_bakeMaterial.SetVector("_RoomMin", new Vector4(rMin.x, rMin.y, rMin.z, 0f));
|
||
_bakeMaterial.SetVector("_RoomMax", new Vector4(rMax.x, rMax.y, rMax.z, 0f));
|
||
_bakeMaterial.SetVector("_BounceAndAmbient", new Vector4(
|
||
config.ceilingBounceIntensity,
|
||
config.bakedAmbientIntensity,
|
||
config.cornerAOStrength,
|
||
rMin.y));
|
||
|
||
// 3. Собираем все рендереры комнаты (пол, потолок, стены)
|
||
|
||
|
||
var cmd = new CommandBuffer { name = $"BakeRoom_{view.Index}" };
|
||
var mpb = new MaterialPropertyBlock();
|
||
int res = Mathf.Clamp(config.lightmapResolution, 32, 1024);
|
||
|
||
foreach (var r in targets)
|
||
{
|
||
if (r == null) continue;
|
||
|
||
var rt = new RenderTexture(res, res, 0, RenderTextureFormat.ARGBHalf)
|
||
{
|
||
name = $"LM_{view.name}_{r.name}",
|
||
filterMode = FilterMode.Bilinear,
|
||
wrapMode = TextureWrapMode.Clamp
|
||
};
|
||
rt.Create();
|
||
view.BakedLightmaps.Add(rt);
|
||
|
||
cmd.SetRenderTarget(rt);
|
||
cmd.ClearRenderTarget(false, true, Color.black);
|
||
cmd.DrawRenderer(r, _bakeMaterial, 0, 0);
|
||
|
||
// Назначаем напрямую в инстанс материала (100% работает с любым Shader Graph и SRP Batcher!)
|
||
r.material.SetTexture(LightmapTexProp, rt);
|
||
|
||
r.GetPropertyBlock(mpb);
|
||
mpb.SetTexture(LightmapTexProp, rt);
|
||
r.SetPropertyBlock(mpb);
|
||
}
|
||
|
||
// Выполняем запекание на GPU мгновенно
|
||
Graphics.ExecuteCommandBuffer(cmd);
|
||
cmd.Release();
|
||
|
||
// 4. САМОЕ ГЛАВНОЕ ДЛЯ FPS: Выключаем реальные источники света после запекания!
|
||
int bakedShellMask = LayerMask.GetMask("Ground", "Ceiling", "Wall");
|
||
|
||
foreach (var l in lights)
|
||
{
|
||
// Убираем запечённые стены, пол и потолок из Culling Mask лампы
|
||
l.cullingMask &= ~bakedShellMask;
|
||
|
||
// Если включена галочка оптимизации в конфиге — убираем дорогие тени
|
||
if (config.disableRealtimeLightsAfterBake)
|
||
{
|
||
l.shadows = LightShadows.None;
|
||
}
|
||
|
||
}
|
||
}
|
||
}
|
||
} |