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 async UniTask SetTimeline() { GameObject goOld = PostProcessInstructor.timeline; if (null == goOld) return; PlayableDirector oldDirector = goOld.GetComponent(); if (null == oldDirector) { Debug.LogError("获取PlayableDirector失败"); return; } if (null == oldDirector.playableAsset) { Debug.LogError("获取PlayableAsset失败"); return; } string oldPath = AssetDatabase.GetAssetPath(oldDirector.playableAsset); string newPath = $"{ProcessedResourceDir}/{oldDirector.playableAsset.name}.playable"; if (!AssetDatabase.CopyAsset(oldPath, newPath)) { Debug.LogError("Timeline拷贝失败"); return; } GameObject goNew = new(goOld.name); goNew.transform.parent = goOld.transform.parent; goNew.transform.position = Vector3.zero; PlayableDirector newDirector = goNew.AddComponent(); newDirector.timeUpdateMode = oldDirector.timeUpdateMode; newDirector.playOnAwake = oldDirector.playOnAwake; newDirector.extrapolationMode = oldDirector.extrapolationMode; newDirector.initialTime = oldDirector.initialTime; // 新预制体绑定新Playable资源 newDirector.playableAsset = AssetDatabase.LoadAssetAtPath(newPath); newDirector.playableAsset.name = System.IO.Path.GetFileNameWithoutExtension(newPath); SerializedObject directorSO = new(oldDirector); SerializedProperty sceneBindings = directorSO.FindProperty("m_SceneBindings"); newDirector.Play(); await UniTask.NextFrame(); // 删除PlayableDirector.Bindgins的空Key项,并把原Timeline文件的轨道Key替换到当前Timeline文件的轨道对象 Dictionary bindingsKV = new(); for (int i = 0; i < sceneBindings.arraySize; ++i) { SerializedProperty bindingElement = sceneBindings.GetArrayElementAtIndex(i); // key是源文件的轨道对象,value是当前文件的绑定value Object key = bindingElement.FindPropertyRelative("key").objectReferenceValue; Object value = bindingElement.FindPropertyRelative("value").objectReferenceValue; if (null == key) continue; bindingsKV.Add(key, value); } // 删除无用绑定 while (sceneBindings.arraySize > 0) { sceneBindings.DeleteArrayElementAtIndex(0); } directorSO.ApplyModifiedProperties(); // 设置有效绑定(新老timeline资源outputs顺序完全一致) List listOldBinding = oldDirector.playableAsset.outputs.ToList(); List listNewBinding = newDirector.playableAsset.outputs.ToList(); for (int i = 0; i < listOldBinding.Count; ++i) { TrackAsset oldTrackAsset = listOldBinding[i].sourceObject as TrackAsset; TrackAsset newTrackAsset = listNewBinding[i].sourceObject as TrackAsset; if (bindingsKV.TryGetValue(oldTrackAsset, out Object value)) newDirector.SetGenericBinding(newTrackAsset, value); } GameObject.DestroyImmediate(PostProcessInstructor.timeline); AssetDatabase.SaveAssetIfDirty(newDirector); } #endregion }