using System.Collections.Generic; using Unity.Collections; using Unity.Mathematics; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; namespace X.Rendering.Scene { [ExecuteAlways] [DefaultExecutionOrder(100)] public class SceneEffect : MonoBehaviour { const string MultiCapsuleShadowAndAO = "_MultiCapsule_Shadow_AO_ON"; const string MultiCapsuleAO = "_MultiCapsule_AO_ON"; [SerializeField] private ShadowEdgeRemapSetupPass.ShadowEdgeRemapSettings shadowRemapSettings = new(); ShadowEdgeRemapSetupPass shadowRemapPass; [SerializeField] private CapsuleShadowPass.CapsuleShadowSettings capsuleShadowSettings = new(); CapsuleShadowPass capsuleAOPass; [SerializeField] public bool EnableShadowOffset; [SerializeField] public Vector3 CascadeShadowOffset; public int updateInterval = 1; static SceneEffect instance; public static SceneEffect Instance { get { if (!instance) { instance = GameObject.FindObjectOfType(); } return instance; } private set { instance = value; } } private bool active = false; private void Awake() { Instance = this; } private void OnEnable() { SetEnable(true); } private void OnDisable() { SetEnable(false); } private void OnDestroy() { SetEnable(false); shadowRemapPass?.Dispose(); capsuleAOPass?.Dispose(); Instance = null; } protected void SetEnable(bool enable) { active = enable; if (enable) { shadowRemapPass ??= new(shadowRemapSettings, this); capsuleAOPass ??= new(capsuleShadowSettings, this); RenderPipelineManager.beginCameraRendering += OnBeginCamera; UpdateRenderAssetsSettings(); #if UNITY_EDITOR UpdateCapsuleLights(); #endif ApplySerializeData(); } else { Shader.DisableKeyword(MultiCapsuleAO); Shader.DisableKeyword(MultiCapsuleShadowAndAO); RenderPipelineManager.beginCameraRendering -= OnBeginCamera; } } #if UNITY_EDITOR internal void AddSceneAreaEffect(CapsuleShadowAreaEffect sceneAreaEffect) { if(!sceneAreaEffects.Contains(sceneAreaEffect)) { sceneAreaEffects.Add(sceneAreaEffect); } UpdateCapsuleLights(); } internal void RmSceneAreaEffect(CapsuleShadowAreaEffect sceneAreaEffect) { sceneAreaEffects.Remove(sceneAreaEffect); UpdateCapsuleLights(); } private void OnValidate() { UpdateRenderAssetsSettings(); UpdateCapsuleLights(); } internal void AreaEffectValidate() { UpdateCapsuleLights(); } private void UpdateCapsuleLights() { List lightDirs = new List(); CapsuleLightsDir = new Vector4[32]; capsuleAOAreaSettings.Clear(); for (int i = 0; i < capsuleLights?.Length; i++) { var light = capsuleLights[i]; var v = light.lightPosition; if(!light.isPointLight) { v = -Vector3.Normalize(light.lightPosition); } lightDirs.Add(new Vector4() { x = v.x, y = v.y, z = v.z, w = light.lightIntensity }); } capsuleAOSetting.lightStartID = 0; capsuleAOSetting.lightEndID = lightDirs.Count; capsuleAOAreaSettings.Add(capsuleAOSetting); for (int i = 0; i < sceneAreaEffects.Count; i++) { var sceneAreaEffect = sceneAreaEffects[i]; ref var aoSetting = ref sceneAreaEffect.capsuleAOSetting; aoSetting.lightStartID = lightDirs.Count; for (int j = 0; j < sceneAreaEffect.capsuleLights.Length; j++) { var light = sceneAreaEffect.capsuleLights[j]; var v = light.lightPosition; if (!light.isPointLight) { v = -Vector3.Normalize(light.lightPosition); } lightDirs.Add(new Vector4() { x = v.x, y = v.y, z = v.z, w = light.lightIntensity }); } aoSetting.lightEndID = lightDirs.Count; capsuleAOAreaSettings.Add(aoSetting); } CapsuleLightsDirCount = lightDirs.Count; for (int i = 0; i < CapsuleLightsDirCount; i++) { CapsuleLightsDir[i] = lightDirs[i]; } if(CapsuleAOSettingArray.IsCreated) { CapsuleAOSettingArray.Dispose(); } CapsuleAOSettingArray = new NativeArray(capsuleAOAreaSettings.Count, Allocator.Persistent); CapsuleAOSettingArray.CopyFrom(capsuleAOAreaSettings.ToArray()); } #endif private void ApplySerializeData() { if (CapsuleAOSettingArray.IsCreated) { CapsuleAOSettingArray.Dispose(); } CapsuleAOSettingArray = new NativeArray(capsuleAOAreaSettings.Count, Allocator.Persistent); CapsuleAOSettingArray.CopyFrom(capsuleAOAreaSettings.ToArray()); Shader.EnableKeyword(MultiCapsuleAO); if (CapsuleLightsDir.Length > 0) { Shader.EnableKeyword(MultiCapsuleShadowAndAO); } else { Shader.DisableKeyword(MultiCapsuleShadowAndAO); } } private void UpdateRenderAssetsSettings() { var asset = (GraphicsSettings.currentRenderPipeline as UniversalRenderPipelineAsset); asset.EnableShadowOffset = EnableShadowOffset; asset.cascadeShadowOffset = CascadeShadowOffset; } private void OnBeginCamera(ScriptableRenderContext context, Camera cam) { if (cam.cameraType != CameraType.Game && cam.cameraType != CameraType.SceneView) { return; } var rdr = cam.GetUniversalAdditionalCameraData().scriptableRenderer; if (shadowRemapSettings.Enable && shadowRemapSettings.ShadowRemapTex) { rdr.EnqueuePass(shadowRemapPass); } if (capsuleShadowSettings.Enable) { rdr.EnqueuePass(capsuleAOPass); } } private void Update() { if (active && Time.frameCount % updateInterval == 0) { UpdateCapsuleShadow(); } } #region CapsuleAO [SerializeField , HideInInspector] private LightData[] capsuleLights; [SerializeField] private CapsuleShadowAreaSetting capsuleAOSetting = CapsuleShadowAreaSetting.GetDefault(); [HideInInspector] public NativeArray CharacterArray; [HideInInspector] public NativeArray CapsuleArray; [HideInInspector, SerializeField] public Vector4[] CapsuleLightsDir = new Vector4[32]; [HideInInspector, SerializeField] public int CapsuleLightsDirCount = 0; [HideInInspector, SerializeField] List capsuleAOAreaSettings = new(); [HideInInspector] public NativeArray CapsuleAOSettingArray; [SerializeField] private List sceneAreaEffects = new List(2); private List sceneEffectCharacters = new(); private Dictionary character2CapsuleCollider = new(); private int capsuleColliderCount = 0; public void AddSceneEffectCharacter(SceneEffectCharacter character) { sceneEffectCharacters.Add(character); AddCapsuleAOCharacter(character); } public void RmSceneEffectCharacter(SceneEffectCharacter character) { sceneEffectCharacters.Remove(character); RemoveCapsuleAOCharacter(character); } private void AddCapsuleAOCharacter(SceneEffectCharacter character) { if (character2CapsuleCollider.ContainsKey(character)) { return; } var oldCapsuleArray = CapsuleArray; var oldCharacterArray = CharacterArray; var capsules = character.GetComponentsInChildren(true); character2CapsuleCollider.Add(character, capsules); capsuleColliderCount += capsules.Length; CapsuleArray = new(capsuleColliderCount, Allocator.Persistent); CharacterArray = new(character2CapsuleCollider.Count, Allocator.Persistent); if (oldCharacterArray.IsCreated) { oldCharacterArray.Dispose(); oldCapsuleArray.Dispose(); } } private void RemoveCapsuleAOCharacter(SceneEffectCharacter character) { if(!character2CapsuleCollider.ContainsKey(character)) { return; } capsuleColliderCount -= character2CapsuleCollider[character].Length; character2CapsuleCollider.Remove(character); var oldCapsuleArray = CapsuleArray; var oldCharacterArray = CharacterArray; CapsuleArray = new(capsuleColliderCount, Allocator.Persistent); CharacterArray = new(character2CapsuleCollider.Count, Allocator.Persistent); if (oldCharacterArray.IsCreated) { oldCharacterArray.Dispose(); oldCapsuleArray.Dispose(); } } private void UpdateCapsuleShadow() { if (!CapsuleArray.IsCreated || CapsuleArray.Length == 0) { capsuleShadowSettings.Enable = false; return; } SceneEffectCharacter sceneEffectCharacter = null; for (int j = 0; j < sceneEffectCharacters.Count; j++) { sceneEffectCharacter = sceneEffectCharacters[j]; sceneEffectCharacter.capsuleAOSettingMask = 1; for (int i = 0; i < sceneAreaEffects.Count; i++) { var sceneAreaEffect = sceneAreaEffects[i]; if (sceneAreaEffect.Bounds.Contains(sceneEffectCharacter.transform.position)) { sceneEffectCharacter.capsuleAOSettingMask |= (uint)1 << (i + 1); } } } int capsuleArrayIndex = 0; int characterArrayIndex = 0; int startID = 0; sceneEffectCharacter = null; foreach (var item in character2CapsuleCollider) { for (int i = 0; i < item.Value.Length; i++) { var capsule = item.Value[i]; //capsule.direction Vector3 dir = Vector3.zero; switch (capsule.direction) { case 0: dir = new Vector3(capsule.height / 2 - capsule.radius, 0); break; case 1: dir = new Vector3(0, capsule.height / 2 - capsule.radius, 0); break; case 2: dir = new Vector3(0, 0, capsule.height / 2 - capsule.radius); break; } var a = capsule.center - dir; var b = capsule.center + dir; //var a = capsule.center - new Vector3(0, capsule.height / 2, 0); //var b = capsule.center + new Vector3(0, capsule.height / 2, 0); a = capsule.transform.TransformPoint(a); b = capsule.transform.TransformPoint(b); CapsuleArray[capsuleArrayIndex++] = new() { a = a, b = b, radius = capsule.radius, }; } if (sceneEffectCharacter != item.Key) { sceneEffectCharacter = item.Key; CharacterArray[characterArrayIndex++] = new() { position = sceneEffectCharacter.transform.position, radius = sceneEffectCharacter.transform.lossyScale.x, capsuleAOSettingMask = sceneEffectCharacter.capsuleAOSettingMask, capsuleStartID = startID, capsuleEndID = capsuleArrayIndex, }; startID = capsuleArrayIndex; } } } #endregion } }