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

1391 lines
47 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.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 常量
/// <summary>
/// 后处理命名后缀
/// </summary>
public const string SCENE_POST_PROCESS_NAME = "PostProcess";
/// <summary>
/// 后处理后存放光照贴图和烘焙数据的文件夹命名
/// </summary>
private const string LIGHT_BAKE_NAME = "LightMaps";
/// <summary>
/// 后处理预制体根路径
/// </summary>
protected const string PREFAB_POSTPROCESS_DIR = "Assets/Art_Out/AutoGen/PostProcessPrefabs";
/// <summary>
/// 场景资源分组路径 暂时舍弃没有使用Addressable
/// </summary>
protected const string SCENE_ADDRESSABLE_GROUP = "PostProcessScenes";
/// <summary>
/// 预制体资源分组路径 暂时舍弃没有使用Addressable
/// </summary>
protected const string PREFAB_ADDRESSABLE_GROUP = "PostProcessPrefabs";
/// <summary>
/// 区分剧情场景的文件夹前缀
/// </summary>
private const string STORY_SCENE_PREFIX = "story_";
/// <summary>
/// 切割地面贴图宽度
/// </summary>
private const int GROUND_SPLIT_WIDTH = 1024;
/// <summary>
/// 切割地面贴图高度
/// </summary>
private const int GROUND_SPLIT_HEIGHT = 1024;
/// <summary>
/// 像素
/// </summary>
private const int UPP = 100;
#endregion
/// <summary>
/// 烘焙后需要替换shader的渲染器列表
/// </summary>
private readonly List<Renderer> needReplaceRenderers = new();
/// <summary>
/// 烘焙后需要关闭产生阴影的渲染器列表
/// </summary>
private readonly List<Renderer> allBakeRenderers = new();
/// <summary>
/// 实时灯光
/// </summary>
private readonly List<Light> realTimeLights = new();
/// <summary>
/// 烘焙灯光
/// </summary>
private readonly List<Light> bakedLights = new();
/// <summary>
/// 检查规范节点
/// </summary>
private readonly List<string> sceneRoots = new()
{
"Main Camera", "TerrainGridSystem", "Art", "SceneManager", "MapEditor", "LevelEditor", "TGSPreview",
"InputManager (Singleton)"
};
/// <summary>
/// 需要替换shader烘焙的节点
/// </summary>
private readonly List<string> bakeReplaceShaderRoot = new() { "CombinedRoot" };
/// <summary>
/// 需要烘焙的其他地面节点
/// </summary>
private readonly List<string> groundRoot = new() { "Ground_2" };
/// <summary>
/// 后处理文件夹路径
/// </summary>
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}";
/// <summary>
/// 是否完成复制场景
/// </summary>
private bool HasClone { get; set; }
/// <summary>
/// 原始场景路径
/// </summary>
private string OriginAssetPath => AssetDatabase.GetAssetPath(AssetToPostProcess);
protected PostProcessInstructor _processInstructor;
private PostProcessInstructor _clonedProcessInstructor;
private PostProcessInstructor PostProcessInstructor => HasClone ? _clonedProcessInstructor : _processInstructor;
/// <summary>
/// 用于处理后处理场景中的预制体
/// </summary>
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<PostProcessInstructor>();
return instructors.FirstOrDefault(pi => pi.gameObject.scene == scene);
}
#region 后处理多节点
/// <summary>
/// 后处理多节点场景 不烘焙 预制体不解包
/// </summary>
/// <param name="postProcessInstructors"></param>
/// <param name="destroyAfterPostProcess"></param>
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<PostProcessInstructor>();
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 普通后处理
/// <summary>
/// 后处理
/// </summary>
/// <param name="postProcessInstructor"></param>
/// <param name="destroyAfterPostProcess"></param>
/// <param name="defaultCameraParam">是否为默认相机</param>
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<string> errorMessages = new List<string>();
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}");
}
}
/// <summary>
/// 获取所有场景文件检查器
/// </summary>
private List<BaseSceneFileChecker> GetAllSceneFileCheckers()
{
var sceneCheckers = new List<BaseSceneFileChecker>();
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<MeshRenderer>();
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);
}
/// <summary>
/// 合并网格、贴图
/// </summary>
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<Transform>();
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 地面处理
/// <summary>
/// 修改地面贴图及材质的路径 用于打包
/// </summary>
private void ChangeMapTextureAndMatPath()
{
var ground = PostProcessInstructor.ground;
if (_unpackedCombinedObjectsRoot != null && HasClone)
{
ground = _unpackedCombinedObjectsRoot.Find("Ground")?.gameObject;
}
GroundUtils.ChangeMapTextureAndMatPath(ground, ProcessedResourceDir, GetScenePrefix(),
groundRoot, CombinedObjectsRoot, _unpackedCombinedObjectsRoot);
}
/// <summary>
/// 修改Terrain地形贴图及数据的路径 用于打包
/// </summary>
private void ChangeMapTextureAndMatPathForTerrain()
{
var terrain = PostProcessInstructor.terrain;
if (terrain == null)
{
Debug.LogError("找不到Terrain对象无法修改Terrain贴图和数据路径");
return;
}
var terrainComponent = terrain.GetComponent<Terrain>();
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<TerrainData>(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<TerrainLayer>(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<TerrainCollider>();
if (terrainCollider != null)
{
terrainCollider.terrainData = newTerrainData;
Debug.Log("已同步更新TerrainCollider的TerrainData引用");
}
// 标记场景为脏,确保更改被保存
EditorSceneManager.MarkSceneDirty(terrain.scene);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Debug.Log($"Terrain贴图和数据路径修改完成{newTerrainDataPath}");
}
/// <summary>
/// 处理TerrainLayer中的贴图
/// </summary>
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);
}
}
/// <summary>
/// 复制贴图到后处理目录
/// </summary>
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<Texture2D>(newTexturePath) != null)
{
return AssetDatabase.LoadAssetAtPath<Texture2D>(newTexturePath);
}
// 复制贴图文件
if (AssetDatabase.CopyAsset(originalPath, newTexturePath))
{
AssetDatabase.Refresh();
var newTexture = AssetDatabase.LoadAssetAtPath<Texture2D>(newTexturePath);
if (newTexture != null)
{
Debug.Log($"复制贴图成功:{originalPath} -> {newTexturePath}");
return newTexture;
}
}
Debug.LogWarning($"复制贴图失败:{originalPath} -> {newTexturePath}");
return originalTexture; // 返回原始贴图作为备选
}
/// <summary>
/// 给地面添加BoxCollider
/// </summary>
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);
}
/// <summary>
/// 切割地面
/// </summary>
private void SplitGroundTexture()
{
var ground = PostProcessInstructor.ground;
if (ground == null)
return;
GroundUtils.SplitGroundTexture(ground, ProcessedResourceDir, GetScenePrefix(), GROUND_SPLIT_WIDTH,
GROUND_SPLIT_HEIGHT);
}
#endregion
/// <summary>
/// 设置雾的位置
/// </summary>
private void SetFogPosition()
{
SceneUtils.SetFogPosition(PostProcessInstructor.fog);
}
/// <summary>
/// 销毁多余eventSystems
/// </summary>
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);
}
}
/// <summary>
/// 检测是否为剧情场景
/// </summary>
/// <returns></returns>
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<Camera>();
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 烘焙
/// <summary>
/// 烘焙灯光贴图的方法
/// </summary>
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("灯光烘焙处理完成");
}
/// <summary>
/// 专门为Terrain地形烘焙灯光贴图的方法
/// </summary>
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不包括TerrainTerrain使用内置材质
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地形灯光烘焙处理完成");
}
/// <summary>
/// 处理Terrain组件的烘焙设置
/// </summary>
private void ProcessTerrainForBaking()
{
var terrain = PostProcessInstructor.terrain;
if (terrain == null)
{
Debug.LogWarning("未找到Terrain对象跳过Terrain烘焙设置");
return;
}
var terrainComponent = terrain.GetComponent<Terrain>();
if (terrainComponent == null)
{
Debug.LogWarning("Terrain对象缺少Terrain组件跳过Terrain烘焙设置");
return;
}
// 设置Terrain的Static标志使其参与烘焙
UnityEditorGameObjectUtility.SetStaticEditorFlags(terrain, StaticEditorFlags.ContributeGI);
// 获取Terrain的MeshRenderer如果有的话
var terrainRenderer = terrain.GetComponent<MeshRenderer>();
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
/// <summary>
/// 设置剧情timeline
/// </summary>
private UniTask SetTimeline()
{
GameObject goOld = PostProcessInstructor.timeline;
if (null == goOld)
return UniTask.CompletedTask;
PlayableDirector oldDirector = goOld.GetComponent<PlayableDirector>();
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<TimelineAsset>(newPath);
var cmTimelineAsset = hasCinemachineTrack ? AssetDatabase.LoadAssetAtPath<TimelineAsset>(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<PlayableDirector>();
_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<PlayableDirector>();
_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<TrackAsset, bool> 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<TrackAsset>();
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<TrackAsset> _EnumerateTracks(TimelineAsset timelineAsset)
{
foreach (var rootTrack in timelineAsset.GetRootTracks())
{
if (rootTrack == null)
{
continue;
}
foreach (var track in _EnumerateTrack(rootTrack))
{
yield return track;
}
}
}
private IEnumerable<TrackAsset> _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");
}
/// <summary>
/// 同步 Timeline 的 ExposedReferences 到新场景
/// 主要解决 CinemachineShot 引用的 VirtualCamera 在场景复制后 fileID 变化的问题
/// </summary>
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})");
}
}
/// <summary>
/// 获取 GameObject 相对于场景根的完整路径
/// </summary>
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
}