using System; using System.Collections.Generic; using System.Linq; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.Playables; using UnityEngine.Rendering; using UnityEngine.SceneManagement; using PhxhSDK.Editor.FileChecker; using Object = UnityEngine.Object; using UnityEngine.Timeline; using Cysharp.Threading.Tasks; using PhxhSDK; using UnityEditorGameObjectUtility = UnityEditor.GameObjectUtility; public abstract class PostProcessor { #region 常量 /// /// 后处理命名后缀 /// public const string SCENE_POST_PROCESS_NAME = "PostProcess"; /// /// 后处理后存放光照贴图和烘焙数据的文件夹命名 /// private const string LIGHT_BAKE_NAME = "LightMaps"; /// /// 后处理预制体根路径 /// protected const string PREFAB_POSTPROCESS_DIR = "Assets/Art_Out/AutoGen/PostProcessPrefabs"; /// /// 场景资源分组路径 暂时舍弃没有使用Addressable /// protected const string SCENE_ADDRESSABLE_GROUP = "PostProcessScenes"; /// /// 预制体资源分组路径 暂时舍弃没有使用Addressable /// protected const string PREFAB_ADDRESSABLE_GROUP = "PostProcessPrefabs"; /// /// 区分剧情场景的文件夹前缀 /// private 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; #endregion /// /// 烘焙后需要替换shader的渲染器列表 /// private readonly List needReplaceRenderers = new(); /// /// 烘焙后需要关闭产生阴影的渲染器列表 /// private readonly List allBakeRenderers = new(); /// /// 实时灯光 /// private readonly List realTimeLights = new(); /// /// 烘焙灯光 /// private readonly List bakedLights = new(); /// /// 检查规范节点 /// private readonly List sceneRoots = new() { "Main Camera", "TerrainGridSystem", "Art", "SceneManager", "MapEditor", "LevelEditor", "TGSPreview", "InputManager (Singleton)" }; /// /// 需要替换shader烘焙的节点 /// private readonly List bakeReplaceShaderRoot = new() { "CombinedRoot" }; /// /// 需要烘焙的其他地面节点 /// private readonly List groundRoot = new() { "Ground_2" }; /// /// 后处理文件夹路径 /// protected string ProcessedResourceDir => $"{ResourcesBaseDir}/{PostProcessFolderName}"; protected abstract string PostProcessFolderName { get; } protected abstract string ResourcesBaseDir { get; } protected abstract Object AssetToPostProcess { get; } protected abstract string OutputAssetPath { get; } private string LightBakeDir => $"{ProcessedResourceDir}"; /// /// 是否完成复制场景 /// private bool HasClone { get; set; } /// /// 原始场景路径 /// private string OriginAssetPath => AssetDatabase.GetAssetPath(AssetToPostProcess); protected PostProcessInstructor _processInstructor; private PostProcessInstructor _clonedProcessInstructor; private PostProcessInstructor PostProcessInstructor => HasClone ? _clonedProcessInstructor : _processInstructor; /// /// 用于处理后处理场景中的预制体 /// private Transform _unpackedCombinedObjectsRoot; private Transform CombinedObjectsRoot { get { if (_unpackedCombinedObjectsRoot != null && HasClone) return _unpackedCombinedObjectsRoot; if (PostProcessInstructor == null || PostProcessInstructor.combinedObjectsRoot == null) { Debug.LogError("找不到Art节点!"); return PostProcessInstructor.transform.parent; } return PostProcessInstructor.combinedObjectsRoot; } } protected abstract PostProcessInstructor Clone(); protected abstract void AfterAll(bool destroyAfterPostProcess); protected virtual void Init(PostProcessInstructor postProcessInstructor) { _processInstructor = postProcessInstructor; PostProcessInstructor.DicNumber.Clear(); } public static PostProcessInstructor FindInstructorInScene(Scene scene) { var instructors = Object.FindObjectsOfType(); return instructors.FirstOrDefault(pi => pi.gameObject.scene == scene); } #region 后处理多节点 /// /// 后处理多节点场景 不烘焙 预制体不解包 /// /// /// public void PostProcessMore(PostProcessInstructor[] postProcessInstructors, bool destroyAfterPostProcess = false) { if (postProcessInstructors.Length <= 0) return; var firstPostProcessInstructor = postProcessInstructors[0]; if (!CheckPostProcessInstructor(firstPostProcessInstructor)) return; Init(firstPostProcessInstructor); HasClone = false; EditorSceneManager.SaveOpenScenes(); // 创建文件夹 AssetDatabase.DeleteAsset(ProcessedResourceDir); AssetDatabase.CreateFolder(ResourcesBaseDir, PostProcessFolderName); // 复制场景为后处理场景 Clone(); // 在后处理场景中重新获取 PostProcessInstructor 组 PostProcessInstructor[] newPostProcessInstructors = Object.FindObjectsOfType(); int index = 0; foreach (PostProcessInstructor postProcessInstructor in newPostProcessInstructors) { // DebugUtil.LogError($"重新获取的postProcessInstructor属于场景:{postProcessInstructor.gameObject.scene.name}"); _processInstructor = postProcessInstructor; if (PostProcessInstructor == null) { // DebugUtil.LogError($"PostProcessInstructor为空,克隆参数:{HasClone}"); continue; } BakeCombinedGameObject(index); ++index; if (!PostProcessInstructor.enableSplitGround) { // 关闭切割地面 给地面添加BoxCollider GroundAddBoxCollider(); ChangeMapTextureAndMatPath(); } else { SplitGroundTexture(); } SetFogPosition(); RemoveRedundant(); HideCamera(); DestroyUselessObject(); } AfterAll(destroyAfterPostProcess); PostProcessData.EditorInstance.AddPostProcessData(AssetToPostProcess, OutputAssetPath); Debug.Log($"执行后处理成功,后处理资源源路径:{OriginAssetPath} 后处理后路径:{OutputAssetPath}"); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); } #endregion #region 普通后处理 /// /// 后处理 /// /// /// /// 是否为默认相机 public async void PostProcess(PostProcessInstructor postProcessInstructor, bool destroyAfterPostProcess = false, bool defaultCameraParam = true) { if (!CheckPostProcessInstructor(postProcessInstructor)) return; Init(postProcessInstructor); HasClone = false; EditorSceneManager.SaveOpenScenes(); PrepareFolders(); // 复制 ProcessInstructor 组件 _clonedProcessInstructor = Clone(); HasClone = true; if (_clonedProcessInstructor == null) { Debug.LogError("_clonedProcessInstructor is null!"); return; } GameObject combineRootObj = BakeCombinedGameObject(0); _PrepareForBake(combineRootObj.transform); await SetTimeline(); // 如果Art为预制体先进行解包 UnpackPostSceneArt(); if(!postProcessInstructor.isTerrain) { if (!PostProcessInstructor.enableSplitGround) { // 关闭切割地面 给地面添加BoxCollider GroundAddBoxCollider(); ChangeMapTextureAndMatPath(); } else { SplitGroundTexture(); } } else { // Terrain 地形处理 DebugUtil.Log("开始处理 Terrain 地形"); ChangeMapTextureAndMatPathForTerrain(); } // 根据地面类型选择不同的烘焙方法 if (PostProcessInstructor.isTerrain) { BakeLightmapsForTerrain(); } else { BakeLightmaps(); } SetFogPosition(); if (!defaultCameraParam) { SetCameraParam(); } UnpackPostSceneArt(); RemoveRedundant(); HideCamera(); DestroyUselessObject(); AfterAll(destroyAfterPostProcess); PostProcessData.EditorInstance.AddPostProcessData(AssetToPostProcess, OutputAssetPath); Debug.Log($"执行后处理成功,后处理资源源路径:{OriginAssetPath} 后处理后路径:{OutputAssetPath}"); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); CheckScene(); } #endregion private void CheckScene() { try { // 获取当前活动的场景 Scene activeScene = EditorSceneManager.GetActiveScene(); if (!activeScene.IsValid()) { Debug.LogWarning("[PostProcessor] 当前没有有效的活动场景,跳过场景检查"); return; } Debug.Log($"[PostProcessor] 开始检查场景: {activeScene.name}"); // 获取场景根对象 GameObject[] rootObjects = activeScene.GetRootGameObjects(); if (rootObjects == null || rootObjects.Length == 0) { Debug.LogWarning($"[PostProcessor] 场景 {activeScene.name} 没有根对象,跳过检查"); return; } // 获取所有场景文件检查器 var sceneCheckers = GetAllSceneFileCheckers(); if (sceneCheckers.Count == 0) { Debug.LogWarning("[PostProcessor] 没有找到任何场景文件检查器"); return; } Debug.Log($"[PostProcessor] 找到 {sceneCheckers.Count} 个场景检查器,开始执行检查..."); // 执行所有检查器 bool hasErrors = false; List errorMessages = new List(); foreach (var checker in sceneCheckers) { try { Debug.Log($"[PostProcessor] 执行检查器: {checker.CheckerName}"); // 执行检查 object checkResult = checker.CheckScene(activeScene, rootObjects); if (checkResult != null) { hasErrors = true; string errorMsg = $"[{checker.CheckerName}] {checkResult}"; errorMessages.Add(errorMsg); Debug.LogWarning($"[PostProcessor] 检查失败: {errorMsg}"); } else { Debug.Log($"[PostProcessor] 检查器 {checker.CheckerName} 检查通过"); } } catch (System.Exception ex) { hasErrors = true; string errorMsg = $"[{checker.CheckerName}] 检查器执行异常: {ex.Message}"; errorMessages.Add(errorMsg); Debug.LogError($"[PostProcessor] {errorMsg}"); } } // 输出检查结果汇总 if (hasErrors) { Debug.LogError($"[PostProcessor] 场景 {activeScene.name} 检查完成,发现 {errorMessages.Count} 个问题:"); foreach (var errorMsg in errorMessages) { Debug.LogError($"[PostProcessor] • {errorMsg}"); } } else { Debug.Log($"[PostProcessor] 场景 {activeScene.name} 检查完成,所有检查器均通过"); } } catch (System.Exception ex) { Debug.LogError($"[PostProcessor] 场景检查过程中发生异常: {ex.Message}\n{ex.StackTrace}"); } } /// /// 获取所有场景文件检查器 /// private List GetAllSceneFileCheckers() { var sceneCheckers = new List(); try { // 获取所有检查器 var allCheckers = FileCheckerRegistry.Instance.GetAllCheckers(); // 筛选出场景文件检查器 foreach (var checker in allCheckers) { if (checker is BaseSceneFileChecker sceneChecker) { sceneCheckers.Add(sceneChecker); } } } catch (System.Exception ex) { Debug.LogError($"[PostProcessor] 获取场景文件检查器时发生异常: {ex.Message}"); } return sceneCheckers; } private void _PrepareForBake(Transform combineRootParent) { if (PostProcessInstructor == null || !PostProcessInstructor.enableLightmapBaking) return; for (var i = 0; i < combineRootParent.childCount; ++i) { var child = combineRootParent.GetChild(i); if (child == null) continue; var meshRenderer = child.GetComponent(); if (meshRenderer == null) { continue; } if (meshRenderer.shadowCastingMode == ShadowCastingMode.On) { meshRenderer.shadowCastingMode = ShadowCastingMode.On; meshRenderer.staticShadowCaster = true; if (meshRenderer.sharedMaterial != null) meshRenderer.sharedMaterial.doubleSidedGI = true; } } } 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)) { Debug.LogWarning($"{currentScene.name} 场景有其他节点:{obj.name},请检查节点规范"); } } return true; } private void PrepareFolders() { AssetDatabase.DeleteAsset(ProcessedResourceDir); AssetDatabase.CreateFolder(ResourcesBaseDir, PostProcessFolderName); } /// /// 合并网格、贴图 /// private GameObject BakeCombinedGameObject(int index) { Transform art = CombinedObjectsRoot; string outputPath = $"{ProcessedResourceDir}"; // 收集渲染器 var renderers = MeshCombineUtils.CollectRenderers(PostProcessInstructor.rootsToCombine); Debug.Log($"合批前收集到 {renderers.Count} 个渲染器"); var prefix = GetScenePrefix(); var combinedRoot = new GameObject("CombinedRoot"); combinedRoot.transform.parent = art.transform; DebugUtil.Log($"合并节点是:{combinedRoot.GetFullPath()},输出路径:{outputPath}," + $"单位贴图尺寸:{(int)PostProcessInstructor.postProcessTextureUnitSize}"); try { if (PostProcessInstructor.enableBatchGrouping) { var limitRange = MeshCombineUtils.CalculateCurrentHexagonMapBounds(); // 可以根据实际地图调整 DebugUtil.Log($"边界宽:{limitRange.x},高:{limitRange.y}"); // 按照距离对渲染器进行分组 var rendererGroups = MeshCombineUtils.GroupRenderersByProximity( renderers, PostProcessInstructor.batchGroupDistance, limitRange.x, limitRange.y); SceneCombineUtil.Instance.CombineGameObjectsByGroups(rendererGroups, combinedRoot.transform, outputPath, (int)PostProcessInstructor.postProcessTextureUnitSize, PostProcessInstructor.sampleMaterials, prefix); } else { // 使用原有的合批方式 SceneCombineUtil.Instance.CombineGameObjects(renderers, combinedRoot.transform, outputPath, (int)PostProcessInstructor.postProcessTextureUnitSize, PostProcessInstructor.sampleMaterials, prefix); } // 如果是剧情场景,重命名生成的所有物体添加前缀 if (!string.IsNullOrEmpty(prefix)) { var combined = combinedRoot.GetComponentsInChildren(); foreach (var trans in combined) { if (trans != combinedRoot.transform && !trans.name.StartsWith(prefix)) { trans.name = prefix + trans.name; } } } foreach (var root in PostProcessInstructor.rootsToCombine) { if (!root) { continue; } Object.DestroyImmediate(root.gameObject); } } catch (System.Exception ex) { Debug.LogError($"合批过程中发生错误: {ex.Message}\n{ex.StackTrace}"); // 如果合批失败,保留原始物体 if (combinedRoot != null) { Object.DestroyImmediate(combinedRoot); } } return combinedRoot; } #region 地面处理 /// /// 修改地面贴图及材质的路径 用于打包 /// private void ChangeMapTextureAndMatPath() { var ground = PostProcessInstructor.ground; if (_unpackedCombinedObjectsRoot != null && HasClone) { ground = _unpackedCombinedObjectsRoot.Find("Ground")?.gameObject; } GroundUtils.ChangeMapTextureAndMatPath(ground, ProcessedResourceDir, GetScenePrefix(), groundRoot, CombinedObjectsRoot, _unpackedCombinedObjectsRoot); } /// /// 修改Terrain地形贴图及数据的路径 用于打包 /// private void ChangeMapTextureAndMatPathForTerrain() { var terrain = PostProcessInstructor.terrain; if (terrain == null) { Debug.LogError("找不到Terrain对象,无法修改Terrain贴图和数据路径"); return; } var terrainComponent = terrain.GetComponent(); if (terrainComponent == null || terrainComponent.terrainData == null) { Debug.LogError("Terrain对象缺少Terrain组件或TerrainData"); return; } var originalTerrainData = terrainComponent.terrainData; var originalPath = AssetDatabase.GetAssetPath(originalTerrainData); if (string.IsNullOrEmpty(originalPath)) { Debug.LogError("无法获取TerrainData的原始路径"); return; } // 确保目标目录存在 if (!AssetDatabase.IsValidFolder(ProcessedResourceDir)) { Debug.LogError($"后处理目录不存在:{ProcessedResourceDir}"); return; } // 复制TerrainData到后处理文件夹 var newTerrainDataPath = $"{ProcessedResourceDir}/{originalTerrainData.name}.asset"; if (!AssetDatabase.CopyAsset(originalPath, newTerrainDataPath)) { Debug.LogError($"复制TerrainData失败:{originalPath} -> {newTerrainDataPath}"); return; } // 加载新的TerrainData var newTerrainData = AssetDatabase.LoadAssetAtPath(newTerrainDataPath); if (newTerrainData == null) { Debug.LogError($"加载新TerrainData失败:{newTerrainDataPath}"); return; } // 处理TerrainLayers及其贴图 var terrainLayers = newTerrainData.terrainLayers; var newTerrainLayers = new TerrainLayer[terrainLayers.Length]; for (int i = 0; i < terrainLayers.Length; i++) { var originalLayer = terrainLayers[i]; if (originalLayer == null) continue; // 为每个 TerrainLayer 创建新的实例并保存到后处理文件夹 var newLayer = Object.Instantiate(originalLayer); // 处理贴图并更新新layer的贴图引用为复制后的贴图 ProcessTerrainLayerTextures(newLayer); // 创建新的TerrainLayer资产文件路径 var newLayerPath = $"{ProcessedResourceDir}/{originalLayer.name}_{i}.terrainlayer"; // 保存新的 TerrainLayer 为独立的资产文件 AssetDatabase.CreateAsset(newLayer, newLayerPath); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); // 重新加载已保存的 TerrainLayer 确保引用正确 newTerrainLayers[i] = AssetDatabase.LoadAssetAtPath(newLayerPath); if (newTerrainLayers[i] == null) { Debug.LogError($"保存TerrainLayer失败:{newLayerPath}"); newTerrainLayers[i] = originalLayer; // 使用原始引用作为备选 } else { Debug.Log($"成功创建新的TerrainLayer:{newLayerPath},使用复制后的贴图"); } } // 更新TerrainData的TerrainLayers newTerrainData.terrainLayers = newTerrainLayers; // 更新Terrain组件的TerrainData引用 terrainComponent.terrainData = newTerrainData; // 同时更新TerrainCollider的TerrainData引用,避免不一致警告 var terrainCollider = terrain.GetComponent(); if (terrainCollider != null) { terrainCollider.terrainData = newTerrainData; Debug.Log("已同步更新TerrainCollider的TerrainData引用"); } // 标记场景为脏,确保更改被保存 EditorSceneManager.MarkSceneDirty(terrain.scene); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); Debug.Log($"Terrain贴图和数据路径修改完成:{newTerrainDataPath}"); } /// /// 处理TerrainLayer中的贴图 /// private void ProcessTerrainLayerTextures(TerrainLayer terrainLayer) { if (terrainLayer == null) return; // 处理漫反射贴图 if (terrainLayer.diffuseTexture != null) { terrainLayer.diffuseTexture = CopyTextureToProcessedDir(terrainLayer.diffuseTexture); } // 处理法线贴图 if (terrainLayer.normalMapTexture != null) { terrainLayer.normalMapTexture = CopyTextureToProcessedDir(terrainLayer.normalMapTexture); } // 处理遮罩贴图 if (terrainLayer.maskMapTexture != null) { terrainLayer.maskMapTexture = CopyTextureToProcessedDir(terrainLayer.maskMapTexture); } } /// /// 复制贴图到后处理目录 /// private Texture2D CopyTextureToProcessedDir(Texture2D originalTexture) { if (originalTexture == null) return null; var originalPath = AssetDatabase.GetAssetPath(originalTexture); if (string.IsNullOrEmpty(originalPath)) return originalTexture; var extension = System.IO.Path.GetExtension(originalPath); var newTexturePath = $"{ProcessedResourceDir}/{originalTexture.name}{extension}"; // 如果文件已存在,直接加载返回 if (AssetDatabase.LoadAssetAtPath(newTexturePath) != null) { return AssetDatabase.LoadAssetAtPath(newTexturePath); } // 复制贴图文件 if (AssetDatabase.CopyAsset(originalPath, newTexturePath)) { AssetDatabase.Refresh(); var newTexture = AssetDatabase.LoadAssetAtPath(newTexturePath); if (newTexture != null) { Debug.Log($"复制贴图成功:{originalPath} -> {newTexturePath}"); return newTexture; } } Debug.LogWarning($"复制贴图失败:{originalPath} -> {newTexturePath}"); return originalTexture; // 返回原始贴图作为备选 } /// /// 给地面添加BoxCollider /// private void GroundAddBoxCollider() { var ground = PostProcessInstructor.ground; if (_unpackedCombinedObjectsRoot != null && HasClone) { ground = _unpackedCombinedObjectsRoot.Find("Ground")?.gameObject; } SceneUtils.RemoveCollider(ground); var boxColliderObj = new GameObject("BoxCollider"); boxColliderObj.layer = LayerMask.NameToLayer(Framework.Constants.Layer.GROUND_NAME); SceneUtils.AddBoxCollider(boxColliderObj); boxColliderObj.transform.localScale = new Vector3(300, 300, 1f); boxColliderObj.transform.localRotation = Quaternion.Euler(90f, 0f, 0f); boxColliderObj.transform.SetParent(PostProcessInstructor.combinedObjectsRoot.transform, false); // 移除下层地面的碰撞体 var ground_2 = GameObject.Find("Ground_2"); SceneUtils.RemoveCollider(ground_2); } /// /// 切割地面 /// private void SplitGroundTexture() { var ground = PostProcessInstructor.ground; if (ground == null) return; GroundUtils.SplitGroundTexture(ground, ProcessedResourceDir, GetScenePrefix(), GROUND_SPLIT_WIDTH, GROUND_SPLIT_HEIGHT); } #endregion /// /// 设置雾的位置 /// private void SetFogPosition() { SceneUtils.SetFogPosition(PostProcessInstructor.fog); } /// /// 销毁多余eventSystems /// private void RemoveRedundant() { SceneUtils.RemoveRedundantEventSystems(PostProcessInstructor.gameObject.scene); } private void DestroyUselessObject() { foreach (var root in PostProcessInstructor.rootsToCombine) { if (!root) { continue; } Object.DestroyImmediate(root.gameObject, true); } Object.DestroyImmediate(PostProcessInstructor.gameObject, true); var designerObj = GameObject.Find("Designer"); if (designerObj != null) { Object.DestroyImmediate(designerObj, true); } } /// /// 检测是否为剧情场景 /// /// private bool IsStoryScene() { var 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; } private void HideCamera() { var scene = PostProcessInstructor.gameObject.scene; var cameras = Object.FindObjectsOfType(); foreach (var camera in cameras) { if (camera.gameObject.scene == scene) { camera.gameObject.SetActive(false); // Debug.Log($"已隐藏相机: {camera.gameObject.name}"); } } EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene()); EditorSceneManager.SaveOpenScenes(); } #endregion #region 烘焙 /// /// 烘焙灯光贴图的方法 /// private void BakeLightmaps() { // 检查是否开启了烘焙选项 if (PostProcessInstructor == null || !PostProcessInstructor.enableLightmapBaking) return; Debug.Log("开始灯光烘焙过程..."); // 1. 从CombinedObjectsRoot下查找灯光 改为烘焙灯光 LightmapUtils.PrepareLight(CombinedObjectsRoot, realTimeLights, bakedLights); // 2. 处理参与烘焙的节点 LightmapUtils.PreProcessBake(bakeReplaceShaderRoot, CombinedObjectsRoot, true, true, allBakeRenderers, needReplaceRenderers); LightmapUtils.PreProcessBake(groundRoot, CombinedObjectsRoot, false, false, allBakeRenderers, needReplaceRenderers); ProcessGround(3000); Debug.Log($"总共收集到 {needReplaceRenderers.Count} 个渲染器需要替换shader"); Debug.Log($"总共收集到 {allBakeRenderers.Count} 个渲染器参与烘焙"); // 3.替换Shader为支持光照贴图的Shader MaterialUtils.ReplaceShaderWithLightmapShader(needReplaceRenderers); // 4. 开始烘焙 LightmapUtils.Bake(SceneManager.GetActiveScene()); // 5. 关闭所有烘焙物体的阴影投射 foreach (var renderer in allBakeRenderers) { if (renderer != null) renderer.shadowCastingMode = ShadowCastingMode.Off; } // 6. 打开实时灯光 LightmapUtils.RevertLight(realTimeLights, bakedLights); // 7.还原地面RenderQueue ProcessGround(2000); // 8. 复制光照烘焙文件到目标路径 LightmapUtils.CopyLightmapFiles(SceneManager.GetActiveScene(), LightBakeDir); // 9. 保存场景 EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene()); EditorSceneManager.SaveOpenScenes(); Debug.Log("灯光烘焙处理完成"); } /// /// 专门为Terrain地形烘焙灯光贴图的方法 /// private void BakeLightmapsForTerrain() { // 检查是否开启了烘焙选项 if (PostProcessInstructor == null || !PostProcessInstructor.enableLightmapBaking) return; Debug.Log("开始Terrain地形灯光烘焙过程..."); // 1. 从CombinedObjectsRoot下查找灯光 改为烘焙灯光 LightmapUtils.PrepareLight(CombinedObjectsRoot, realTimeLights, bakedLights); // 2. 处理参与烘焙的合批节点(不包括地面) LightmapUtils.PreProcessBake(bakeReplaceShaderRoot, CombinedObjectsRoot, true, true, allBakeRenderers, needReplaceRenderers); // 3. 处理Terrain组件的烘焙设置 ProcessTerrainForBaking(); Debug.Log($"总共收集到 {needReplaceRenderers.Count} 个渲染器需要替换shader"); Debug.Log($"总共收集到 {allBakeRenderers.Count} 个渲染器参与烘焙"); // 4. 替换Shader为支持光照贴图的Shader(不包括Terrain,Terrain使用内置材质) MaterialUtils.ReplaceShaderWithLightmapShader(needReplaceRenderers); // 5. 开始烘焙 LightmapUtils.Bake(SceneManager.GetActiveScene()); // 6. 关闭所有烘焙物体的阴影投射 foreach (var renderer in allBakeRenderers) { if (renderer != null) renderer.shadowCastingMode = ShadowCastingMode.Off; } // 7. 打开实时灯光 LightmapUtils.RevertLight(realTimeLights, bakedLights); // 8. 复制光照烘焙文件到目标路径 LightmapUtils.CopyLightmapFiles(SceneManager.GetActiveScene(), LightBakeDir); // 9. 保存场景 EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene()); EditorSceneManager.SaveOpenScenes(); Debug.Log("Terrain地形灯光烘焙处理完成"); } /// /// 处理Terrain组件的烘焙设置 /// private void ProcessTerrainForBaking() { var terrain = PostProcessInstructor.terrain; if (terrain == null) { Debug.LogWarning("未找到Terrain对象,跳过Terrain烘焙设置"); return; } var terrainComponent = terrain.GetComponent(); if (terrainComponent == null) { Debug.LogWarning("Terrain对象缺少Terrain组件,跳过Terrain烘焙设置"); return; } // 设置Terrain的Static标志,使其参与烘焙 UnityEditorGameObjectUtility.SetStaticEditorFlags(terrain, StaticEditorFlags.ContributeGI); // 获取Terrain的MeshRenderer(如果有的话) var terrainRenderer = terrain.GetComponent(); if (terrainRenderer != null) { // Terrain通常不使用MeshRenderer,但如果有的话也要处理 terrainRenderer.shadowCastingMode = ShadowCastingMode.On; terrainRenderer.receiveShadows = true; allBakeRenderers.Add(terrainRenderer); Debug.Log("已将Terrain的MeshRenderer添加到烘焙列表"); } // 设置Terrain的烘焙相关属性 terrainComponent.shadowCastingMode = ShadowCastingMode.On; // 确保TerrainData支持光照贴图 if (terrainComponent.terrainData != null) { // Terrain的光照贴图设置通过TerrainData处理 Debug.Log($"Terrain {terrain.name} 已设置为参与光照烘焙"); } Debug.Log("Terrain烘焙设置完成"); } private void ProcessGround(int renderQueue) { if (PostProcessInstructor.ground != null) { SceneUtils.SetGroundRenderQueue(PostProcessInstructor.ground, renderQueue); } } private void UnpackPostSceneArt() { if (CombinedObjectsRoot != null && PrefabUtility.IsPartOfPrefabInstance(CombinedObjectsRoot.gameObject)) { // 保存对象的引用 _unpackedCombinedObjectsRoot = CombinedObjectsRoot; PhxhSDK.EditorUtils.UnpackPrefab(CombinedObjectsRoot.gameObject); } } #endregion #region 剧情Timeline /// /// 设置剧情timeline /// private UniTask SetTimeline() { GameObject goOld = PostProcessInstructor.timeline; if (null == goOld) return UniTask.CompletedTask; PlayableDirector oldDirector = goOld.GetComponent(); if (null == oldDirector) { Debug.LogError("获取PlayableDirector失败"); return UniTask.CompletedTask; } if (null == oldDirector.playableAsset) { Debug.LogError("获取PlayableAsset失败"); return UniTask.CompletedTask; } string oldPath = AssetDatabase.GetAssetPath(oldDirector.playableAsset); string newPath = $"{ProcessedResourceDir}/{oldDirector.playableAsset.name}.playable"; string cmPath = $"{ProcessedResourceDir}/{oldDirector.playableAsset.name}_cinemachine.playable"; var oldTimelineAsset = oldDirector.playableAsset as TimelineAsset; var hasCinemachineTrack = oldTimelineAsset != null && _EnumerateTracks(oldTimelineAsset).Any(_IsCinemachineTrack); if (!AssetDatabase.CopyAsset(oldPath, newPath)) { Debug.LogError("Timeline拷贝失败"); return UniTask.CompletedTask; } if (hasCinemachineTrack && !AssetDatabase.CopyAsset(oldPath, cmPath)) { Debug.LogError("Cinemachine Timeline拷贝失败"); return UniTask.CompletedTask; } var newTimelineAsset = AssetDatabase.LoadAssetAtPath(newPath); var cmTimelineAsset = hasCinemachineTrack ? AssetDatabase.LoadAssetAtPath(cmPath) : null; if (newTimelineAsset == null || (hasCinemachineTrack && cmTimelineAsset == null)) { Debug.LogError("加载后处理Timeline资源失败"); return UniTask.CompletedTask; } _FilterTimelineTracks(newTimelineAsset, keepCinemachine: false); if (hasCinemachineTrack) { _FilterTimelineTracks(cmTimelineAsset, keepCinemachine: true); } EditorUtility.SetDirty(newTimelineAsset); AssetDatabase.SaveAssetIfDirty(newTimelineAsset); if (hasCinemachineTrack) { EditorUtility.SetDirty(cmTimelineAsset); AssetDatabase.SaveAssetIfDirty(cmTimelineAsset); } GameObject goNew = new(goOld.name); goNew.transform.parent = goOld.transform.parent; goNew.transform.position = Vector3.zero; goNew.transform.rotation = Quaternion.identity; PlayableDirector newDirector = goNew.AddComponent(); _CopyDirectorSettings(oldDirector, newDirector); newDirector.playableAsset = newTimelineAsset; PlayableDirector cmDirector = null; if (hasCinemachineTrack) { GameObject goCm = new("timeline_cinemachine"); goCm.transform.parent = goOld.transform.parent; goCm.transform.position = Vector3.zero; goCm.transform.rotation = Quaternion.identity; cmDirector = goCm.AddComponent(); _CopyDirectorSettings(oldDirector, cmDirector); cmDirector.playableAsset = cmTimelineAsset; } _CopyDirectorBindings(oldDirector, newDirector, track => !_IsCinemachineTrack(track)); if (cmDirector != null) { _CopyDirectorBindings(oldDirector, cmDirector, _IsCinemachineTrack); _SyncExposedReferences(oldDirector, cmDirector); } GameObject.DestroyImmediate(goOld); PostProcessInstructor.timeline = goNew; EditorUtility.SetDirty(PostProcessInstructor); EditorUtility.SetDirty(newDirector); if (cmDirector != null) { EditorUtility.SetDirty(cmDirector); } EditorSceneManager.MarkSceneDirty(goNew.scene); AssetDatabase.SaveAssetIfDirty(newDirector); if (cmDirector != null) { AssetDatabase.SaveAssetIfDirty(cmDirector); } return UniTask.CompletedTask; } private void _CopyDirectorSettings(PlayableDirector oldDirector, PlayableDirector newDirector) { newDirector.timeUpdateMode = oldDirector.timeUpdateMode; newDirector.playOnAwake = oldDirector.playOnAwake; newDirector.extrapolationMode = oldDirector.extrapolationMode; newDirector.initialTime = oldDirector.initialTime; } private void _CopyDirectorBindings(PlayableDirector oldDirector, PlayableDirector newDirector, Func trackFilter) { var oldTimelineAsset = oldDirector.playableAsset as TimelineAsset; var newTimelineAsset = newDirector.playableAsset as TimelineAsset; if (oldTimelineAsset == null || newTimelineAsset == null) { return; } var oldTracks = oldTimelineAsset.GetOutputTracks() .Where(track => track != null && trackFilter(track)) .ToList(); var newTracks = newTimelineAsset.GetOutputTracks() .Where(track => track != null) .ToList(); for (int i = 0; i < oldTracks.Count && i < newTracks.Count; ++i) { var value = oldDirector.GetGenericBinding(oldTracks[i]); if (value == null) { continue; } newDirector.SetGenericBinding(newTracks[i], value); } } private void _FilterTimelineTracks(TimelineAsset timelineAsset, bool keepCinemachine) { var allTracks = _EnumerateTracks(timelineAsset).ToList(); var cmTrackAncestors = new HashSet(); foreach (var track in allTracks.Where(_IsCinemachineTrack)) { for (var current = track; current != null; current = current.parent as TrackAsset) { cmTrackAncestors.Add(current); } } var removeTracks = allTracks .Where(track => keepCinemachine ? !cmTrackAncestors.Contains(track) : _IsCinemachineTrack(track)) .OrderByDescending(_GetTrackDepth) .ToList(); foreach (var track in removeTracks) { timelineAsset.DeleteTrack(track); } } private IEnumerable _EnumerateTracks(TimelineAsset timelineAsset) { foreach (var rootTrack in timelineAsset.GetRootTracks()) { if (rootTrack == null) { continue; } foreach (var track in _EnumerateTrack(rootTrack)) { yield return track; } } } private IEnumerable _EnumerateTrack(TrackAsset track) { yield return track; foreach (var childTrack in track.GetChildTracks()) { if (childTrack == null) { continue; } foreach (var nestedTrack in _EnumerateTrack(childTrack)) { yield return nestedTrack; } } } private int _GetTrackDepth(TrackAsset track) { var depth = 0; for (var parent = track.parent as TrackAsset; parent != null; parent = parent.parent as TrackAsset) { depth++; } return depth; } private bool _IsCinemachineTrack(TrackAsset track) { return track != null && track.GetType().FullName.Contains("Cinemachine"); } /// /// 同步 Timeline 的 ExposedReferences 到新场景 /// 主要解决 CinemachineShot 引用的 VirtualCamera 在场景复制后 fileID 变化的问题 /// private void _SyncExposedReferences(PlayableDirector oldDirector, PlayableDirector newDirector) { SerializedObject oldDirectorSO = new(oldDirector); SerializedProperty oldExposedRefs = oldDirectorSO.FindProperty("m_ExposedReferences.m_References"); if (oldExposedRefs == null || oldExposedRefs.arraySize == 0) { Debug.LogWarning("旧 PlayableDirector 没有 ExposedReferences"); return; } // 遍历旧的暴露引用,在新场景中找到对应的对象 for (int i = 0; i < oldExposedRefs.arraySize; ++i) { SerializedProperty refElement = oldExposedRefs.GetArrayElementAtIndex(i); SerializedProperty guidProp = refElement.FindPropertyRelative("first"); SerializedProperty objProp = refElement.FindPropertyRelative("second"); if (guidProp == null || objProp == null) continue; string exposedName = guidProp.stringValue; Object oldReferencedObj = objProp.objectReferenceValue; if (string.IsNullOrEmpty(exposedName) || oldReferencedObj == null) continue; if (oldReferencedObj is not Component oldComponent) { continue; } // 先只处理 Cinemachine 相关轨道,避免误改其他 ExposedReference。 if (!oldComponent.GetType().FullName.Contains("Cinemachine")) { continue; } // 在新场景中找到对应的 GameObject(通过完整路径匹配) string oldObjPath = GetGameObjectPath(oldComponent.gameObject); GameObject newReferencedObjGo = GameObject.Find(oldObjPath); if (newReferencedObjGo == null) { Debug.LogWarning($"在新场景中找不到对应的 GameObject: {oldObjPath} (exposedName: {exposedName})"); continue; } var newReferencedObj = newReferencedObjGo.GetComponent(oldComponent.GetType()); if (newReferencedObj == null) { Debug.LogWarning($"在新场景中找不到对应组件: {oldComponent.GetType().FullName}, path: {oldObjPath}, exposedName: {exposedName}"); continue; } // 设置新的引用 newDirector.SetReferenceValue(new PropertyName(exposedName), newReferencedObj); Debug.Log($"同步 Cinemachine ExposedReference: {exposedName} -> {oldObjPath} ({oldComponent.GetType().Name})"); } } /// /// 获取 GameObject 相对于场景根的完整路径 /// private string GetGameObjectPath(GameObject go) { if (go == null) return ""; var path = go.name; Transform parent = go.transform.parent; while (parent != null) { path = parent.name + "/" + path; parent = parent.parent; } return path; } #endregion }