Souljack/Assets/_Project/Scripts/LevelGenerating/RuntimeRoomLightmapBaker.cs

153 lines
6.9 KiB
C#
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.

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