NLDClient-yudde/ProjectNLD/Assets/Editor/Scene/PostProcesser/PostProcessor.cs

1313 lines
48 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Rendering;
using UnityEngine.SceneManagement;
using System.IO;
using Object = UnityEngine.Object;
public abstract class PostProcessor
{
public const string SCENE_POST_PROCESS_NAME = "PostProcess";
private const string SCENE_SURFACE_FOLDER_NAME = "SurfaceData";
private const string SCENE_COMBINED_NAME = "Combined";
private const string GROUND_SPLIT_NAME = "GroundSplit";
private const string LIGHT_BAKE_NAME = "LightMaps";
protected const string SCENE_POSTPROCESS_DIR = "Assets/Art_Out/AutoGen/PostProcessScenes";
protected const string PREFAB_POSTPROCESS_DIR = "Assets/Art_Out/AutoGen/PostProcessPrefabs";
protected const string SURFACE_SHADER_PATH = "Assets/Code/Shaders/Scene/NLD_Scene_Surface.shader";
protected const string SCENE_ADDRESSABLE_GROUP = "PostProcessScenes";
protected const string PREFAB_ADDRESSABLE_GROUP = "PostProcessPrefabs";
// 区分剧情场景前缀
protected const string STORY_SCENE_PREFIX = "story_";
private const int GROUND_SPLIT_WIDTH = 1024;
private const int GROUND_SPLIT_HEIGHT = 1024;
private const int UPP = 100;
// 存储渲染器和它们的原始材质映射
private Dictionary<Renderer, Material[]> originalMaterials = new Dictionary<Renderer, Material[]>();
// 存储需要进行材质恢复的渲染器列表
private List<Renderer> renderersToRestore = new List<Renderer>();
private readonly List<string> sceneRoots = new()
{
"Main Camera", "TerrainGridSystem", "Art", "SceneManager", "MapEditor", "LevelEditor", "TGSPreview",
"InputManager (Singleton)"
};
/// <summary>
/// 需要烘焙的节点
/// </summary>
private readonly List<string> bakeRoot = new()
{
"Trees", "Buildings", "CombinedRoot"
};
/// <summary>
/// 需要烘焙和关闭阴影的地面节点
/// </summary>
private readonly List<string> groundRoot = new()
{
"Ground_2"
};
protected string ProcessedResourceDir => $"{ResourcesBaseDir}/{PostProcessFolderName}";
protected abstract string ResourcesBaseDir { get; }
private string LightBakeDir => $"{ProcessedResourceDir}/{LIGHT_BAKE_NAME}";
private string SurfaceDir => $"{ProcessedResourceDir}/{SCENE_SURFACE_FOLDER_NAME}";
private string CombinedDir => $"{ProcessedResourceDir}/{SCENE_COMBINED_NAME}";
private string GroundSplitDir => $"{ProcessedResourceDir}/{GROUND_SPLIT_NAME}";
protected abstract string PostProcessFolderName { get; }
protected PostProcessInstructor _processInstructor;
private PostProcessInstructor _clonedProcessInstructor;
//private NLDSceneManager NLDSceneManager { get; set; }
//private NLDSceneManager _postProcessSceneManager;
private bool HasClone { get; set; }
protected abstract Object AssetToPostProcess { get; }
protected string OriginAssetPath => AssetDatabase.GetAssetPath(AssetToPostProcess);
protected abstract string OutputAssetPath { get; }
private PostProcessInstructor PostProcessInstructor => HasClone ? _clonedProcessInstructor : _processInstructor;
private Transform CombinedObjectsRoot
{
get
{
if (PostProcessInstructor == null || PostProcessInstructor.combinedObjectsRoot == null)
{
Debug.LogError("找不到Art节点!");
return PostProcessInstructor.transform.parent;
}
return PostProcessInstructor.combinedObjectsRoot;
}
}
private bool CheckPostProcessInstructor(PostProcessInstructor postProcessInstructor)
{
if (postProcessInstructor == null)
{
Debug.LogError("postProcessInstructor is null");
return false;
}
if (postProcessInstructor.processPrefab &&
(!PrefabUtility.IsPartOfAnyPrefab(postProcessInstructor) ||
EditorUtility.IsPersistent(postProcessInstructor)))
{
Debug.LogError("postProcessInstructor不在任何预制体内或者其不是预制体实例");
return false;
}
var currentScene = postProcessInstructor.gameObject.scene;
var objs = currentScene.GetRootGameObjects();
foreach (var obj in objs)
{
if (!sceneRoots.Contains(obj.name))
{
DebugUtil.LogWarning("{1} 场景有其他节点:{0},请检查节点规范", obj.name, currentScene.name);
}
}
return true;
}
protected virtual void Init(PostProcessInstructor postProcessInstructor)
{
_processInstructor = postProcessInstructor;
}
public void PostProcess(PostProcessInstructor postProcessInstructor, bool destroyAfterPostProcess = false,
bool defaultCameraParam = true)
{
if (!CheckPostProcessInstructor(postProcessInstructor))
return;
Init(postProcessInstructor);
HasClone = false;
EditorSceneManager.SaveOpenScenes();
PrepareFolders();
//NLDSceneManager = CommonUtils.GetSceneRootComponent<NLDSceneManager>(Scene);
//暂时先不渲染成面片(地面阴影还没法解决,以及一些与笔刷工具的耦合)
//BakeSurfaceData();
_clonedProcessInstructor = Clone();
HasClone = true;
if (_clonedProcessInstructor == null)
{
Debug.LogError("_clonedProcessInstructor is null!");
return;
}
BakeCombinedGameObject();
BakeLightmaps();
// SplitGroundTexture();
SetFogPosition();
if (!defaultCameraParam)
{
SetCameraParam();
}
RemoveRedundant();
DestroyUselessObject();
AfterAll(destroyAfterPostProcess);
PostProcessData.EditorInstance.AddPostProcessData(AssetToPostProcess, OutputAssetPath);
Debug.Log($"执行后处理成功,后处理资源源路径:{OriginAssetPath} 后处理后路径:{OutputAssetPath}");
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
protected abstract void AfterAll(bool destroyAfterPostProcess);
public static PostProcessInstructor FindInstructorInScene(Scene scene)
{
var instructors = Object.FindObjectsOfType<PostProcessInstructor>();
return instructors.FirstOrDefault(pi => pi.gameObject.scene == scene);
}
// private void DeleteSurfaceData(SceneSurfaceData surfaceData)
// {
// var mat = AssetDatabase.LoadAssetAtPath<Material>(surfaceData.materialPath);
// if (mat == null)
// {
// return;
// }
//
// AssetDatabase.DeleteAsset(AssetDatabase.GetAssetPath(mat.mainTexture));
// AssetDatabase.DeleteAsset(surfaceData.materialPath);
// }
private void PrepareFolders()
{
AssetDatabase.DeleteAsset(ProcessedResourceDir);
AssetDatabase.CreateFolder(ResourcesBaseDir, PostProcessFolderName);
AssetDatabase.CreateFolder(ProcessedResourceDir, SCENE_SURFACE_FOLDER_NAME);
AssetDatabase.CreateFolder(ProcessedResourceDir, SCENE_COMBINED_NAME);
AssetDatabase.CreateFolder(ProcessedResourceDir, GROUND_SPLIT_NAME);
}
protected abstract PostProcessInstructor Clone();
//烘焙面片数据
// private void BakeSurfaceData()
// {
// if (NLDSceneManager == null)
// return;
// NLDSceneManager.allSceneSurfaceData.Clear();
// List<SceneObjectRenderInfo> renderInfos = new();
// var camera = Camera.main;
// if (camera == null)
// {
// Debug.LogError("找不到主相机!请设置主相机的tag");
// return;
// }
//
// foreach (var sObj in NLDSceneManager.sceneObjects)
// {
// if (sObj && sObj.renderInfo && !renderInfos.Contains(sObj.renderInfo))
// {
// var brush = SceneBrushHelper.GetSceneBrushByRenderInfo(sObj.renderInfo);
// if (!brush.SupportSurface)
// continue;
// renderInfos.Add(sObj.renderInfo);
// var gameObject = brush.prefab;
// var texPath =
// EditorUtil.GetAssetUniquePath($"{SurfaceDir}/{gameObject.name}_surface.jpg", out var name);
// Vector3 offset = Vector3.zero;
// Vector3 scale = Vector3.one;
// Quaternion rotation = Quaternion.identity;
// var texture = gameObject.RenderToTexture(camera, texPath, ref offset, ref scale, ref rotation);
// if (texture != null)
// {
// var material = new Material(AssetDatabase.LoadAssetAtPath<Shader>(SURFACE_SHADER_PATH))
// {
// mainTexture = texture
// };
// material.enableInstancing = true;
// var matPath = $"{SurfaceDir}/{gameObject.name}_surface.mat".Replace("\\", "/");
// EditorUtil.CreateAnUniqueAsset(material, matPath);
// SceneSurfaceData surfaceData = new()
// {
// renderInfo = sObj.renderInfo,
// materialPath = matPath,
// cachedOffset = offset,
// eulerAngleID = gameObject.transform.eulerAngles,
// cachedRotation = rotation.eulerAngles,
// cachedScale = scale,
// valid = true,
// };
// NLDSceneManager.allSceneSurfaceData.Add(surfaceData);
// }
// }
// }
//
// //MarkSceneDirty();
// }
//合并网格,贴图
private void BakeCombinedGameObject()
{
var art = CombinedObjectsRoot;
// 不再自动执行模型切割,由外部单独调用
// 收集渲染器
var renderers = CollectRenderers();
Debug.Log($"合批前收集到 {renderers.Count} 个渲染器");
var prefix = GetScenePrefix();
var combinedRoot = new GameObject("CombinedRoot");
combinedRoot.transform.parent = art.transform;
try
{
if (PostProcessInstructor.enableBatchGrouping)
{
// 按照距离对渲染器进行分组
Dictionary<string, List<Renderer>> rendererGroups =
GroupRenderersByCircularDistance(renderers, PostProcessInstructor.batchGroupDistance);
SceneCombineUtil.Instance.CombineGameObjectsByGroups(rendererGroups, combinedRoot.transform,
CombinedDir,
(int)PostProcessInstructor.postProcessTextureUnitSize,
PostProcessInstructor.sampleMaterials, prefix);
}
else
{
// 使用原有的合批方式
SceneCombineUtil.Instance.CombineGameObjects(renderers, combinedRoot.transform, CombinedDir,
(int)PostProcessInstructor.postProcessTextureUnitSize,
PostProcessInstructor.sampleMaterials, prefix);
}
// 如果是剧情场景,重命名生成的所有物体添加前缀
if (!string.IsNullOrEmpty(prefix))
{
var combined = combinedRoot.GetComponentsInChildren<Transform>();
foreach (var trans in combined)
{
if (trans != combinedRoot.transform && !trans.name.StartsWith(prefix))
{
trans.name = prefix + trans.name;
}
}
}
foreach (var renderer in renderers)
{
if (renderer != null && renderer.gameObject != null)
{
Object.DestroyImmediate(renderer.gameObject);
}
}
}
catch (System.Exception ex)
{
Debug.LogError($"合批过程中发生错误: {ex.Message}\n{ex.StackTrace}");
// 如果合批失败,保留原始物体
if (combinedRoot != null)
{
Object.DestroyImmediate(combinedRoot);
}
}
}
// 根据距离对渲染器进行分组
private Dictionary<string, List<Renderer>> GroupRenderersByCircularDistance(List<Renderer> renderers,
float maxDistance)
{
Dictionary<string, List<Renderer>> groups = new Dictionary<string, List<Renderer>>();
int groupIndex = 0;
// 已分组的渲染器
HashSet<Renderer> processedRenderers = new HashSet<Renderer>();
// 对所有渲染器进行处理
while (processedRenderers.Count < renderers.Count)
{
// 找到第一个未处理的渲染器作为新组的起点
Renderer startRenderer = null;
foreach (var renderer in renderers)
{
if (!processedRenderers.Contains(renderer))
{
startRenderer = renderer;
break;
}
}
if (startRenderer == null)
break;
// 创建新组
List<Renderer> currentGroup = new List<Renderer>();
currentGroup.Add(startRenderer);
processedRenderers.Add(startRenderer);
bool addedNew = true;
while (addedNew)
{
addedNew = false;
// 计算当前组的中心点
Vector3 groupCenter = CalculateGroupCenter(currentGroup);
// 查找距离中心点在阈值内的未处理渲染器
foreach (var renderer in renderers)
{
if (processedRenderers.Contains(renderer))
continue;
float distance = Vector3.Distance(groupCenter, renderer.transform.position);
if (distance <= maxDistance)
{
currentGroup.Add(renderer);
processedRenderers.Add(renderer);
addedNew = true;
}
}
}
// 保存当前组
groups.Add($"Group_{groupIndex}", currentGroup);
groupIndex++;
}
Debug.Log($"基于圆形区域分组:将{renderers.Count}个渲染器分成{groups.Count}个组,半径为{maxDistance}");
return groups;
}
private Vector3 CalculateGroupCenter(List<Renderer> group)
{
if (group.Count == 0)
return Vector3.zero;
Vector3 sum = Vector3.zero;
foreach (var renderer in group)
{
sum += renderer.transform.position;
}
return sum / group.Count;
}
// 原先基于顶点数的分组方法(保留作为备用)
private Dictionary<string, List<Renderer>> GroupRenderers(List<Renderer> renderers, int groupSize)
{
Dictionary<string, List<Renderer>> groups = new Dictionary<string, List<Renderer>>();
int totalVertexCount = 0;
int groupIndex = 0;
List<Renderer> currentGroup = new List<Renderer>();
foreach (var renderer in renderers)
{
var meshFilter = renderer.GetComponent<MeshFilter>();
if (meshFilter == null || meshFilter.sharedMesh == null)
continue;
// 获取当前渲染器的顶点数
int vertexCount = meshFilter.sharedMesh.vertexCount;
// 如果当前组顶点数已经超过限制或这个模型本身太大,创建新组
if ((totalVertexCount + vertexCount > groupSize && currentGroup.Count > 0) || vertexCount > groupSize)
{
// 保存当前组
if (currentGroup.Count > 0)
{
groups.Add($"Group_{groupIndex}", new List<Renderer>(currentGroup));
currentGroup.Clear();
totalVertexCount = 0;
groupIndex++;
}
// 如果单个模型超过大小限制,单独放一组
if (vertexCount > groupSize)
{
groups.Add($"Group_{groupIndex}_LargeModel_{renderer.name}", new List<Renderer> { renderer });
groupIndex++;
continue;
}
}
// 添加到当前组
currentGroup.Add(renderer);
totalVertexCount += vertexCount;
}
// 确保最后一组也被添加
if (currentGroup.Count > 0)
{
groups.Add($"Group_{groupIndex}", new List<Renderer>(currentGroup));
}
Debug.Log($"分组合批:将{renderers.Count}个渲染器分成{groups.Count}个组,按每组约{groupSize}个顶点");
return groups;
}
private List<Renderer> CollectRenderers()
{
List<Renderer> allRenderers = new();
foreach (var root in PostProcessInstructor.rootsToCombine)
{
if (!root)
{
continue;
}
var render = root.GetComponentsInChildren<Renderer>();
if (render is not { Length: > 0 })
{
continue;
}
allRenderers.AddRange(render);
}
return allRenderers;
}
#region 地面切图
private void SplitGroundTexture()
{
var ground = PostProcessInstructor.ground;
if (ground == null)
return;
var renderer = ground.GetComponent<MeshRenderer>();
if (renderer == null)
return;
var mat = renderer.sharedMaterial;
if (mat == null)
return;
var texture = mat.mainTexture as Texture2D;
if (texture == null)
return;
var prefix = GetScenePrefix();
var groundParent = Object.Instantiate(ground, ground.transform.parent, true);
groundParent.name = "Ground";
// 计算原始地面的大小
var originalScale = ground.transform.localScale;
var originalWidth = originalScale.x;
var originalHeight = originalScale.y;
var originPos = -new Vector3(originalWidth / 2f, originalHeight / 2f, 0);
groundParent.transform.localScale = Vector3.one;
// 确保目录存在
if (!AssetDatabase.IsValidFolder(GroundSplitDir))
{
var parentDir = Path.GetDirectoryName(GroundSplitDir);
if (!AssetDatabase.IsValidFolder(parentDir))
{
AssetDatabase.CreateFolder(Path.GetDirectoryName(parentDir), Path.GetFileName(parentDir));
}
AssetDatabase.CreateFolder(parentDir, Path.GetFileName(GroundSplitDir));
}
var textures =
SplitTextureUtil.SplitTexture(texture, GROUND_SPLIT_WIDTH, GROUND_SPLIT_HEIGHT, GroundSplitDir, prefix);
// 计算每个分割块的大小(基于原始地面大小)
var rowCount = textures.GetLength(0);
var columnCount = textures.GetLength(1);
var blockWidth = originalWidth / columnCount;
var blockHeight = originalHeight / rowCount;
for (int row = 0; row < rowCount; row++)
{
for (int column = 0; column < columnCount; column++)
{
var splitTexture = textures[row, column];
if (splitTexture == null)
{
Debug.LogError($"Failed to load split texture at row {row}, column {column}");
continue;
}
var splitGround = Object.Instantiate(ground, groundParent.transform, true);
Object.DestroyImmediate(splitGround.GetComponent<Collider>());
var splitName = $"{prefix}Ground_{row}_{column}";
splitGround.name = splitName;
// 设置固定的quad大小,不随贴图尺寸变化
splitGround.transform.localScale = new Vector3(blockWidth, blockHeight, 1);
// 创建并保存材质
var splitMat = Object.Instantiate(mat);
splitMat.mainTexture = splitTexture;
var matPath = $"{GroundSplitDir}/{splitName}.mat";
AssetDatabase.CreateAsset(splitMat, matPath);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
// 重新加载保存后的材质
var savedMat = AssetDatabase.LoadAssetAtPath<Material>(matPath);
if (savedMat == null)
{
Debug.LogError($"Failed to load saved material at {matPath}");
continue;
}
var meshRenderer = splitGround.GetComponent<MeshRenderer>();
if (meshRenderer != null)
{
meshRenderer.sharedMaterial = savedMat;
meshRenderer.shadowCastingMode = ShadowCastingMode.Off;
}
// 计算位置,使用固定的block大小
splitGround.transform.localPosition =
originPos + new Vector3(column * blockWidth + blockWidth / 2f, row * blockHeight + blockHeight / 2f,
0);
}
}
Object.DestroyImmediate(ground);
Object.DestroyImmediate(groundParent.GetComponent<Renderer>());
Object.DestroyImmediate(groundParent.GetComponent<Collider>());
var boxCollider = groundParent.gameObject.AddComponent<BoxCollider>();
boxCollider.size = new Vector3(200, 200, 1);
boxCollider.center = new Vector3(0, 0, 0.5f);
Object.DestroyImmediate(groundParent.GetComponent<MeshFilter>());
}
#endregion
private void SetFogPosition()
{
var fog = PostProcessInstructor.fog;
if (fog == null)
return;
var transform = fog.transform;
var position = transform.position;
transform.position = new Vector3(position.x, position.y + 0.01f, position.z);
}
private void RemoveRedundant()
{
Scene postScene = PostProcessInstructor.gameObject.scene;
foreach (GameObject rootObj in postScene.GetRootGameObjects())
{
EventSystem[] eventSystems = rootObj.GetComponentsInChildren<EventSystem>();
if (null == eventSystems)
continue;
for (int i = 0; i < eventSystems.Length; ++i)
{
Object.DestroyImmediate(eventSystems[i].gameObject);
}
}
}
private void DestroyUselessObject()
{
foreach (var root in PostProcessInstructor.rootsToCombine)
{
if (!root)
{
continue;
}
Object.DestroyImmediate(root.gameObject, true);
}
Object.DestroyImmediate(PostProcessInstructor.gameObject, true);
}
private bool IsStoryScene()
{
string sceneName = PostProcessInstructor.gameObject.scene.name;
return sceneName.Contains(STORY_SCENE_PREFIX);
}
private string GetScenePrefix()
{
return IsStoryScene() ? STORY_SCENE_PREFIX : "";
}
#region 保存相机参数
private void SetCameraParam()
{
var postScene = PostProcessInstructor.gameObject.scene;
var scene = _processInstructor.gameObject.scene;
foreach (var rootObj in scene.GetRootGameObjects())
{
if (rootObj.CompareTag("MainCamera"))
{
CloneCameraToScene(rootObj, postScene);
break;
}
}
}
private void CloneCameraToScene(GameObject obj, Scene scene)
{
// 只更改transform
var oldPosition = obj.transform.position;
var oldRotation = obj.transform.rotation;
GameObject oldCamera = null;
foreach (var rootObj in scene.GetRootGameObjects())
{
if (rootObj.CompareTag("MainCamera"))
{
oldCamera = rootObj;
break;
}
}
if (oldCamera == null) return;
oldCamera.transform.position = oldPosition;
oldCamera.transform.rotation = oldRotation;
}
#endregion
#region 烘焙
/// <summary>
/// 烘焙灯光贴图的方法
/// </summary>
private void BakeLightmaps()
{
// 检查是否开启了烘焙选项
if (PostProcessInstructor == null || !PostProcessInstructor.enableLightmapBaking)
return;
Debug.Log("开始灯光烘焙过程...");
// 清空旧的材质映射
originalMaterials.Clear();
renderersToRestore.Clear();
// 1. 从CombinedObjectsRoot下查找灯光,分为烘焙灯光和实时灯光
var bakeLights = new List<Light>();
var realtimeLights = new List<Light>();
var lights = CombinedObjectsRoot.GetComponentsInChildren<Light>();
foreach (var light in lights)
{
// 跳过相机上的灯光,避免重复
if (light.GetComponentInParent<Camera>() != null)
continue;
// 复制灯光对象
var realtimeLightObj = Object.Instantiate(light.gameObject, light.transform.parent);
var realtimeLight = realtimeLightObj.GetComponent<Light>();
// 设置为烘焙和实时模式
light.gameObject.name += "_Bake";
light.lightmapBakeType = LightmapBakeType.Baked;
realtimeLight.lightmapBakeType = LightmapBakeType.Realtime;
// 取消烘焙灯光的static
GameObjectUtility.SetStaticEditorFlags(light.gameObject, 0);
// 添加到列表
bakeLights.Add(light);
break;
}
// 查找并关闭所有实时灯光
var allSceneLights = Object.FindObjectsOfType<Light>();
foreach (var light in allSceneLights)
{
// 只处理实时灯光
if (light.lightmapBakeType == LightmapBakeType.Realtime)
{
if (light.gameObject.activeSelf)
{
light.gameObject.SetActive(false);
realtimeLights.Add(light);
}
}
}
// 3. 收集指定节点下需要烘焙的渲染器
var bakedRenderers = new List<Renderer>();
// 处理bakeRoot中的渲染器,从CombinedObjectsRoot下查找
foreach (var rootName in bakeRoot)
{
var rootObj = FindChildRecursive(CombinedObjectsRoot, rootName);
if (rootObj != null)
{
var renderers = rootObj.GetComponentsInChildren<Renderer>(true);
if (renderers != null && renderers.Length > 0)
{
Debug.Log($"从{rootName}收集到 {renderers.Length} 个渲染器用于烘焙");
foreach (var renderer in renderers)
{
if (renderer.receiveShadows)
{
// 保证物体阴影打开
renderer.shadowCastingMode = ShadowCastingMode.On;
bakedRenderers.Add(renderer);
// 设置静态标志
GameObjectUtility.SetStaticEditorFlags(renderer.gameObject,
StaticEditorFlags.ContributeGI | StaticEditorFlags.ReflectionProbeStatic);
}
}
}
}
}
// 处理groundRoot中的渲染器,从CombinedObjectsRoot下查找
foreach (var rootName in groundRoot)
{
var rootObj = FindChildRecursive(CombinedObjectsRoot, rootName);
if (rootObj != null)
{
var renderers = rootObj.GetComponentsInChildren<Renderer>(true);
if (renderers != null && renderers.Length > 0)
{
Debug.Log($"从{rootName}收集到 {renderers.Length} 个地面渲染器用于烘焙");
foreach (var renderer in renderers)
{
renderer.shadowCastingMode = ShadowCastingMode.Off;
GameObjectUtility.SetStaticEditorFlags(renderer.gameObject,
StaticEditorFlags.ContributeGI | StaticEditorFlags.ReflectionProbeStatic);
DebugUtil.Log("groundRoot渲染器已设置静态标志");
}
}
}
}
// 处理 PostProcessInstructor.ground 指定的渲染器
if (PostProcessInstructor.ground != null)
{
var renderer = PostProcessInstructor.ground.GetComponent<Renderer>();
if (renderer != null)
{
renderer.shadowCastingMode = ShadowCastingMode.Off;
// 设置静态标志
GameObjectUtility.SetStaticEditorFlags(renderer.gameObject,
StaticEditorFlags.ContributeGI | StaticEditorFlags.ReflectionProbeStatic);
DebugUtil.Log("PostProcessInstructor.ground渲染器已设置静态标志");
// 将PostProcessInstructor.ground的RenderQueue改为3000
var materials = renderer.sharedMaterials;
for (int i = 0; i < materials.Length; i++)
{
if (materials[i] != null)
{
renderer.shadowCastingMode = ShadowCastingMode.Off;
materials[i].renderQueue = 3000;
Debug.Log($"将PostProcessInstructor.ground的材质 {materials[i].name} 的RenderQueue从3000改回2000");
}
}
}
}
Debug.Log($"总共收集到 {bakedRenderers.Count} 个渲染器用于烘焙");
// 替换Shader为支持光照贴图的Shader
ReplaceShaderWithLightmapShader(bakedRenderers);
// 4. 设置烘焙参数并开始烘焙
var lightingSettings = new LightingSettings();
// 获取当前场景的烘焙设置
var activeScene = SceneManager.GetActiveScene();
if (Lightmapping.TryGetLightingSettings(out var existingSettings))
{
lightingSettings = existingSettings;
}
else
{
// 如果没有现有设置,创建新的并分配给场景
lightingSettings.name = $"{activeScene.name}_LightingSettings";
AssetDatabase.CreateAsset(lightingSettings, $"Assets/{lightingSettings.name}.asset");
Lightmapping.lightingSettings = lightingSettings;
}
// 设置烘焙参数
lightingSettings.lightmapper = LightingSettings.Lightmapper.ProgressiveGPU;
lightingSettings.directSampleCount = 1;
lightingSettings.indirectSampleCount = 1;
lightingSettings.environmentSampleCount = 1;
lightingSettings.lightProbeSampleCountMultiplier = 8;
lightingSettings.maxBounces = 1;
lightingSettings.lightmapResolution = 40;
lightingSettings.lightmapPadding = 2;
lightingSettings.lightmapMaxSize = 1024;
lightingSettings.lightmapCompression = LightmapCompression.HighQuality;
lightingSettings.filteringMode = LightingSettings.FilterMode.Auto;
Lightmapping.lightingSettings = lightingSettings;
// 强制场景保存以确保设置生效
EditorSceneManager.MarkSceneDirty(activeScene);
EditorSceneManager.SaveOpenScenes();
Debug.Log("开始烘焙灯光...");
// 5. 开始烘焙
Lightmapping.Bake();
Debug.Log("灯光烘焙完成");
// 6. 关闭所有烘焙物体的阴影投射
foreach (var renderer in bakedRenderers)
{
if (renderer != null)
renderer.shadowCastingMode = ShadowCastingMode.Off;
}
// 7. 删除烘焙灯光,打开实时灯光
foreach (var light in bakeLights)
{
if (light != null)
Object.DestroyImmediate(light.gameObject);
}
foreach (var light in realtimeLights)
{
if (light != null)
light.gameObject.SetActive(true);
}
// 8. 恢复非地面物体的原始材质
RestoreOriginalMaterials();
// 9. 只将PostProcessInstructor.ground的RenderQueue从3000改回2000
if (PostProcessInstructor.ground != null)
{
var renderer = PostProcessInstructor.ground.GetComponent<Renderer>();
if (renderer != null)
{
var materials = renderer.sharedMaterials;
for (int i = 0; i < materials.Length; i++)
{
if (materials[i] != null)
{
materials[i].renderQueue = 2000;
Debug.Log($"将PostProcessInstructor.ground的材质 {materials[i].name} 的RenderQueue从3000改回2000");
}
}
}
}
// 刷新资源以确保RenderQueue修改已保存
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
// 10. 复制光照烘焙文件到目标路径
CopyLightmapFiles(activeScene);
// 11. 保存场景
EditorSceneManager.MarkSceneDirty(activeScene);
EditorSceneManager.SaveOpenScenes();
Debug.Log("灯光烘焙处理完成");
}
/// <summary>
/// 移动光照烘焙相关文件到指定目录
/// </summary>
private void CopyLightmapFiles(Scene scene)
{
string sceneName = scene.name;
Debug.Log("开始移动光照烘焙文件...");
try
{
// 确保目标目录存在
string lightmapDirAssetPath = GetAssetPath(LightBakeDir);
if (!AssetDatabase.IsValidFolder(lightmapDirAssetPath))
{
// 创建父目录结构
string[] folders = lightmapDirAssetPath.Split('/');
string currentPath = folders[0]; // Assets
for (int i = 1; i < folders.Length; i++)
{
string folderName = folders[i];
string parentPath = currentPath;
currentPath = $"{currentPath}/{folderName}";
if (!AssetDatabase.IsValidFolder(currentPath))
{
AssetDatabase.CreateFolder(parentPath, folderName);
}
}
AssetDatabase.Refresh();
}
// 1. 移动LightingSettings.asset文件
string lightingSettingsPath = $"Assets/{sceneName}_LightingSettings.asset";
if (AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(lightingSettingsPath) != null)
{
string destPath = $"{lightmapDirAssetPath}/{sceneName}_LightingSettings.asset";
// 如果目标已存在,先删除
if (AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(destPath) != null)
{
AssetDatabase.DeleteAsset(destPath);
}
AssetDatabase.MoveAsset(lightingSettingsPath, destPath);
Debug.Log($"已移动: {lightingSettingsPath} -> {destPath}");
}
// 2. 移动整个光照目录
string lightmapDirName = sceneName;
string lightmapSourceDir = $"{Path.GetDirectoryName(scene.path)}/{lightmapDirName}";
string lightmapSourceDirAssetPath = GetAssetPath(lightmapSourceDir);
if (AssetDatabase.IsValidFolder(lightmapSourceDirAssetPath))
{
string destLightmapDir = $"{lightmapDirAssetPath}/{lightmapDirName}";
// 如果目标已存在,先删除
if (AssetDatabase.IsValidFolder(destLightmapDir))
{
AssetDatabase.DeleteAsset(destLightmapDir);
AssetDatabase.Refresh();
}
// 直接移动整个目录
string result = AssetDatabase.MoveAsset(lightmapSourceDirAssetPath, destLightmapDir);
if (string.IsNullOrEmpty(result))
{
Debug.Log($"已成功移动光照数据目录: {lightmapSourceDirAssetPath} -> {destLightmapDir}");
}
else
{
Debug.LogError($"移动光照数据目录失败: {result}");
}
}
else
{
Debug.LogWarning($"找不到光照数据目录: {lightmapSourceDirAssetPath}");
}
}
catch (System.Exception e)
{
Debug.LogError($"移动光照文件时出错: {e.Message}");
}
AssetDatabase.Refresh();
Debug.Log("光照烘焙文件移动完成");
}
// 获取Unity资源路径的简化方法
private string GetAssetPath(string path)
{
// 已经是Assets路径格式
if (path.StartsWith("Assets"))
return path;
// 绝对路径转相对Assets路径
string fullPath = Path.GetFullPath(path);
string assetsFullPath = Path.GetFullPath(Application.dataPath);
if (fullPath.StartsWith(assetsFullPath))
return "Assets" + fullPath.Substring(assetsFullPath.Length).Replace('\\', '/');
return path;
}
// 替换材质的Shader为光照贴图支持的Shader
private void ReplaceShaderWithLightmapShader(List<Renderer> renderers)
{
// 清空旧的材质映射
originalMaterials.Clear();
renderersToRestore.Clear();
// 追踪已处理的材质和它们的新版本
Dictionary<Material, Material> processedMaterialMap = new Dictionary<Material, Material>();
// 尝试查找光照贴图Shader
var lightmapShader = Shader.Find("NLD_URP/NLD_Scene_Lightmap");
if (lightmapShader == null)
{
Debug.LogError("找不到NLD_Scene_Lightmap着色器,请确保它存在于项目中");
return;
}
int replacedCount = 0;
HashSet<Material> processedMaterials = new HashSet<Material>(); // 避免处理重复材质
Debug.Log($"处理 {renderers.Count} 个渲染器用于Shader替换");
foreach (var renderer in renderers)
{
if (renderer == null)
continue;
// 跳过地面渲染器(它们将使用地面专用的shader)
bool isGroundRenderer = false;
Transform parent = renderer.transform.parent;
while (parent != null)
{
if (groundRoot.Contains(parent.name) ||
(PostProcessInstructor.ground != null && parent == PostProcessInstructor.ground.transform))
{
isGroundRenderer = true;
break;
}
parent = parent.parent;
}
if (isGroundRenderer)
{
Debug.Log($"跳过地面渲染器: {renderer.name}");
continue;
}
// 获取渲染器的所有材质
Material[] sharedMaterials = renderer.sharedMaterials;
if (sharedMaterials == null || sharedMaterials.Length == 0)
continue;
// 保存原始材质数组
Material[] originalMaterialsArray = new Material[sharedMaterials.Length];
Array.Copy(sharedMaterials, originalMaterialsArray, sharedMaterials.Length);
originalMaterials[renderer] = originalMaterialsArray;
// 添加到需要恢复的渲染器列表
renderersToRestore.Add(renderer);
// 创建新的材质数组,用于替换
Material[] newMaterials = new Material[sharedMaterials.Length];
for (int i = 0; i < sharedMaterials.Length; i++)
{
Material originalMaterial = sharedMaterials[i];
if (originalMaterial == null)
{
newMaterials[i] = null;
continue;
}
// 如果已经处理过这个材质,使用已经替换好的版本
if (processedMaterialMap.TryGetValue(originalMaterial, out Material replacedMaterial))
{
newMaterials[i] = replacedMaterial;
continue;
}
processedMaterials.Add(originalMaterial);
// 创建材质实例,避免修改原始共享材质
Material instanceMaterial = new Material(originalMaterial);
if (instanceMaterial.shader != lightmapShader)
{
// 记录原始shader
string originalShaderName = instanceMaterial.shader != null ? instanceMaterial.shader.name : "未知";
// 保存原始主贴图及其Scale/Offset
Texture mainTexture = null;
Vector2 texScale = Vector2.one;
Vector2 texOffset = Vector2.zero;
// 检查不同的可能贴图属性名
if (instanceMaterial.HasProperty("_MainTex"))
{
mainTexture = instanceMaterial.GetTexture("_MainTex");
texScale = instanceMaterial.GetTextureScale("_MainTex");
texOffset = instanceMaterial.GetTextureOffset("_MainTex");
Debug.Log(
$"从材质 {instanceMaterial.name} 的 _MainTex 找到主贴图, Scale:{texScale}, Offset:{texOffset}");
}
else if (instanceMaterial.HasProperty("_BaseMap"))
{
mainTexture = instanceMaterial.GetTexture("_BaseMap");
texScale = instanceMaterial.GetTextureScale("_BaseMap");
texOffset = instanceMaterial.GetTextureOffset("_BaseMap");
Debug.Log(
$"从材质 {instanceMaterial.name} 的 _BaseMap 找到主贴图, Scale:{texScale}, Offset:{texOffset}");
}
else if (instanceMaterial.HasProperty("_AlbedoMap"))
{
mainTexture = instanceMaterial.GetTexture("_AlbedoMap");
texScale = instanceMaterial.GetTextureScale("_AlbedoMap");
texOffset = instanceMaterial.GetTextureOffset("_AlbedoMap");
Debug.Log(
$"从材质 {instanceMaterial.name} 的 _AlbedoMap 找到主贴图, Scale:{texScale}, Offset:{texOffset}");
}
else if (instanceMaterial.HasProperty("_DiffuseMap"))
{
mainTexture = instanceMaterial.GetTexture("_DiffuseMap");
texScale = instanceMaterial.GetTextureScale("_DiffuseMap");
texOffset = instanceMaterial.GetTextureOffset("_DiffuseMap");
Debug.Log(
$"从材质 {instanceMaterial.name} 的 _DiffuseMap 找到主贴图, Scale:{texScale}, Offset:{texOffset}");
}
// 记录原始颜色
Color baseColor = Color.white;
if (instanceMaterial.HasProperty("_Color"))
{
baseColor = instanceMaterial.GetColor("_Color");
}
else if (instanceMaterial.HasProperty("_BaseColor"))
{
baseColor = instanceMaterial.GetColor("_BaseColor");
}
// 应用新Shader
instanceMaterial.shader = lightmapShader;
// 恢复主贴图到_BaseMap (这是Lightmap shader使用的属性名)
if (mainTexture != null)
{
if (instanceMaterial.HasProperty("_BaseMap"))
{
instanceMaterial.SetTexture("_BaseMap", mainTexture);
instanceMaterial.SetTextureScale("_BaseMap", texScale);
instanceMaterial.SetTextureOffset("_BaseMap", texOffset);
Debug.Log(
$"将主贴图设置到 {instanceMaterial.name} 的 _BaseMap, 并设置Scale:{texScale}, Offset:{texOffset}");
}
// 同时尝试设置旧属性以兼容
if (instanceMaterial.HasProperty("_MainTex"))
{
instanceMaterial.SetTexture("_MainTex", mainTexture);
instanceMaterial.SetTextureScale("_MainTex", texScale);
instanceMaterial.SetTextureOffset("_MainTex", texOffset);
}
}
else
{
Debug.LogWarning($"材质 {instanceMaterial.name} 没有找到可用的主贴图");
}
// 设置颜色
if (instanceMaterial.HasProperty("_BaseColor"))
{
instanceMaterial.SetColor("_BaseColor", baseColor);
}
if (instanceMaterial.HasProperty("_Color"))
{
instanceMaterial.SetColor("_Color", baseColor);
}
replacedCount++;
Debug.Log($"已替换材质 {instanceMaterial.name} 的Shader: {originalShaderName} -> NLD_Scene_Lightmap");
}
// 将新材质添加到映射表中
processedMaterialMap[originalMaterial] = instanceMaterial;
newMaterials[i] = instanceMaterial;
}
// 应用新材质数组
renderer.sharedMaterials = newMaterials;
}
Debug.Log($"完成Shader替换,共处理了 {replacedCount} 个材质");
}
// 恢复原始材质(地面材质除外)
private void RestoreOriginalMaterials()
{
Debug.Log("开始恢复非地面原始材质...");
int restoredCount = 0;
// 遍历所有需要恢复的渲染器
foreach (var renderer in renderersToRestore)
{
if (renderer == null || !originalMaterials.ContainsKey(renderer))
continue;
// 检查是否是地面渲染器,地面渲染器的材质不需要恢复
bool isGroundRenderer = false;
Transform parent = renderer.transform.parent;
while (parent != null)
{
if (groundRoot.Contains(parent.name) ||
(PostProcessInstructor.ground != null &&
(parent == PostProcessInstructor.ground.transform ||
renderer.gameObject == PostProcessInstructor.ground)))
{
isGroundRenderer = true;
Debug.Log($"跳过地面渲染器材质恢复: {renderer.name}");
break;
}
parent = parent.parent;
}
if (isGroundRenderer)
continue;
// 获取原始材质数组
Material[] originalMaterialsArray = originalMaterials[renderer];
// 恢复原始材质
renderer.sharedMaterials = originalMaterialsArray;
restoredCount++;
}
Debug.Log($"已恢复 {restoredCount} 个非地面渲染器的原始材质");
// 清空材质映射和渲染器列表,释放内存
originalMaterials.Clear();
renderersToRestore.Clear();
}
// 递归查找子节点的辅助方法
private Transform FindChildRecursive(Transform parent, string childName)
{
// 首先检查直接子节点
Transform child = parent.Find(childName);
if (child != null)
return child;
// 如果直接子节点中没找到,递归查找每个子节点
for (int i = 0; i < parent.childCount; i++)
{
child = FindChildRecursive(parent.GetChild(i), childName);
if (child != null)
return child;
}
return null;
}
#endregion
}