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

605 lines
18 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.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
}