605 lines
18 KiB
C#
605 lines
18 KiB
C#
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using UnityEditor;
|
||
using UnityEditor.SceneManagement;
|
||
using UnityEngine;
|
||
using UnityEngine.Rendering;
|
||
using UnityEngine.SceneManagement;
|
||
using Object = UnityEngine.Object;
|
||
|
||
public abstract class PostProcessor
|
||
{
|
||
/// <summary>
|
||
/// 后处理命名后缀
|
||
/// </summary>
|
||
public const string SCENE_POST_PROCESS_NAME = "PostProcess";
|
||
|
||
/// <summary>
|
||
/// 合并网格后的节点
|
||
/// </summary>
|
||
private const string SCENE_COMBINED_NAME = "Combined";
|
||
|
||
/// <summary>
|
||
/// 后处理后存放地面贴图材质的文件夹命名
|
||
/// </summary>
|
||
private const string GROUND_TEXTURE_MAT_PATH = "Ground";
|
||
|
||
/// <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;
|
||
|
||
/// <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}/{LIGHT_BAKE_NAME}";
|
||
|
||
private string CombinedDir => $"{ProcessedResourceDir}/{SCENE_COMBINED_NAME}";
|
||
|
||
private string GroundSplitDir => $"{ProcessedResourceDir}/{GROUND_TEXTURE_MAT_PATH}";
|
||
|
||
/// <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;
|
||
}
|
||
|
||
public static PostProcessInstructor FindInstructorInScene(Scene scene)
|
||
{
|
||
var instructors = Object.FindObjectsOfType<PostProcessInstructor>();
|
||
return instructors.FirstOrDefault(pi => pi.gameObject.scene == scene);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 后处理
|
||
/// </summary>
|
||
/// <param name="postProcessInstructor"></param>
|
||
/// <param name="destroyAfterPostProcess"></param>
|
||
/// <param name="defaultCameraParam">是否为默认相机</param>
|
||
public 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;
|
||
}
|
||
|
||
var combineRootObj = BakeCombinedGameObject();
|
||
|
||
_PrepareForBake(combineRootObj.transform);
|
||
|
||
// 如果Art为预制体先进行解包
|
||
UnpackPostSceneArt();
|
||
|
||
if (!PostProcessInstructor.enableSplitGround)
|
||
{
|
||
// 关闭切割地面 给地面添加BoxCollider
|
||
GroundAddBoxCollider();
|
||
ChangeMapTextureAndMatPath();
|
||
}
|
||
else
|
||
{
|
||
SplitGroundTexture();
|
||
}
|
||
|
||
BakeLightmaps();
|
||
|
||
SetFogPosition();
|
||
if (!defaultCameraParam)
|
||
{
|
||
SetCameraParam();
|
||
}
|
||
|
||
UnpackPostSceneArt();
|
||
RemoveRedundant();
|
||
HideCamera();
|
||
|
||
DestroyUselessObject();
|
||
AfterAll(destroyAfterPostProcess);
|
||
|
||
|
||
PostProcessData.EditorInstance.AddPostProcessData(AssetToPostProcess, OutputAssetPath);
|
||
Debug.Log($"执行后处理成功,后处理资源源路径:{OriginAssetPath} 后处理后路径:{OutputAssetPath}");
|
||
AssetDatabase.SaveAssets();
|
||
AssetDatabase.Refresh();
|
||
}
|
||
|
||
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;
|
||
meshRenderer.material.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);
|
||
AssetDatabase.CreateFolder(ProcessedResourceDir, SCENE_COMBINED_NAME);
|
||
AssetDatabase.CreateFolder(ProcessedResourceDir, GROUND_TEXTURE_MAT_PATH);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 合并网格、贴图
|
||
/// </summary>
|
||
private GameObject BakeCombinedGameObject()
|
||
{
|
||
var art = CombinedObjectsRoot;
|
||
|
||
// 收集渲染器
|
||
var renderers = MeshCombineUtils.CollectRenderers(PostProcessInstructor.rootsToCombine);
|
||
|
||
Debug.Log($"合批前收集到 {renderers.Count} 个渲染器");
|
||
|
||
var prefix = GetScenePrefix();
|
||
var combinedRoot = new GameObject("CombinedRoot");
|
||
combinedRoot.transform.parent = art.transform;
|
||
|
||
try
|
||
{
|
||
if (PostProcessInstructor.enableBatchGrouping)
|
||
{
|
||
// 按照距离对渲染器进行分组
|
||
Dictionary<string, List<Renderer>> rendererGroups =
|
||
MeshCombineUtils.GroupRenderersByProximity(renderers, PostProcessInstructor.batchGroupDistance);
|
||
SceneCombineUtil.Instance.CombineGameObjectsByGroups(rendererGroups, combinedRoot.transform,
|
||
CombinedDir,
|
||
(int)PostProcessInstructor.postProcessTextureUnitSize,
|
||
PostProcessInstructor.sampleMaterials, prefix);
|
||
}
|
||
else
|
||
{
|
||
// 使用原有的合批方式
|
||
SceneCombineUtil.Instance.CombineGameObjects(renderers, combinedRoot.transform, CombinedDir,
|
||
(int)PostProcessInstructor.postProcessTextureUnitSize,
|
||
PostProcessInstructor.sampleMaterials, prefix);
|
||
}
|
||
|
||
// 如果是剧情场景,重命名生成的所有物体添加前缀
|
||
if (!string.IsNullOrEmpty(prefix))
|
||
{
|
||
var combined = combinedRoot.GetComponentsInChildren<Transform>();
|
||
foreach (var trans in combined)
|
||
{
|
||
if (trans != combinedRoot.transform && !trans.name.StartsWith(prefix))
|
||
{
|
||
trans.name = prefix + trans.name;
|
||
}
|
||
}
|
||
}
|
||
|
||
foreach (var 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, GroundSplitDir, GetScenePrefix(),
|
||
groundRoot, CombinedObjectsRoot, _unpackedCombinedObjectsRoot);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 给地面添加BoxCollider
|
||
/// </summary>
|
||
private void GroundAddBoxCollider()
|
||
{
|
||
var ground = PostProcessInstructor.ground;
|
||
if (_unpackedCombinedObjectsRoot != null && HasClone)
|
||
{
|
||
ground = _unpackedCombinedObjectsRoot.Find("Ground")?.gameObject;
|
||
}
|
||
|
||
if (ground != null)
|
||
{
|
||
SceneUtils.AddBoxColliderToGround(ground);
|
||
}
|
||
else
|
||
{
|
||
Debug.LogError($"场景地面节点不存在,无法添加BoxCollider");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 切割地面
|
||
/// </summary>
|
||
private void SplitGroundTexture()
|
||
{
|
||
var ground = PostProcessInstructor.ground;
|
||
if (ground == null)
|
||
return;
|
||
|
||
GroundUtils.SplitGroundTexture(ground, GroundSplitDir, 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);
|
||
}
|
||
|
||
/// <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("灯光烘焙处理完成");
|
||
}
|
||
|
||
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;
|
||
SceneUtils.UnpackPrefab(CombinedObjectsRoot.gameObject);
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
} |