using System; using System.Reflection; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; using BadDog.Rendering.AreaLight; namespace BadDog.Rendering.AreaLight.Samples { /// /// AreaLight Test Panel for runtime testing and debugging /// public class AreaLightTestPanel : MonoBehaviour { [Header("UI Settings")] [SerializeField] private Rect panelRect = new Rect(10, 10, 800, 500); [Header("FPS Settings")] [SerializeField] private float fpsUpdateInterval = 0.5f; // FPS tracking private float m_LastFpsUpdate; private int m_FrameCount; private float m_CurrentFps; // GUI styles private GUIStyle m_WindowStyle; private GUIStyle m_LabelStyle; private GUIStyle m_ToggleStyle; private GUIStyle m_ButtonStyle; private bool m_StylesInitialized = false; // Shadow atlas resolution options private static readonly int[] k_ShadowAtlasResolutions = { 256, 512, 1024, 2048, 4096, 8192 }; private static readonly string[] k_ShadowAtlasResolutionLabels = { "256", "512", "1024", "2048", "4096", "8192" }; private int m_ShadowAtlasResolutionIndex = 2; // Default 1024 private void Start() { InitializeFPSTracking(); SetTargetFrameRate(); InitializeShadowAtlasResolutionIndex(); } private void Update() { UpdateFPS(); } private void OnGUI() { InitializeStyles(); // Fixed window that cannot be dragged panelRect = GUILayout.Window(95279529, panelRect, DrawTestPanel, "AreaLight Test Panel", m_WindowStyle, GUILayout.Width(panelRect.width), GUILayout.Height(panelRect.height)); } private void InitializeFPSTracking() { m_LastFpsUpdate = Time.time; m_FrameCount = 0; m_CurrentFps = 0f; } private void SetTargetFrameRate() { #if UNITY_ANDROID || UNITY_IOS // Set target frame rate to 60 for mobile platforms Application.targetFrameRate = 60; #endif } private void UpdateFPS() { m_FrameCount++; float currentTime = Time.time; if (currentTime - m_LastFpsUpdate >= fpsUpdateInterval) { m_CurrentFps = m_FrameCount / (currentTime - m_LastFpsUpdate); m_FrameCount = 0; m_LastFpsUpdate = currentTime; } } private void InitializeStyles() { if (m_StylesInitialized) { return; } m_WindowStyle = new GUIStyle(GUI.skin.window) { fontSize = 38 }; m_LabelStyle = new GUIStyle(GUI.skin.label) { fontSize = 32, wordWrap = true }; m_ToggleStyle = new GUIStyle(GUI.skin.toggle) { fontSize = 32 }; m_ButtonStyle = new GUIStyle(GUI.skin.button) { fontSize = 28 }; m_StylesInitialized = true; } private void InitializeShadowAtlasResolutionIndex() { var areaLightInstance = GetActiveAreaLightInstance(); if (areaLightInstance != null) { var shadowSettings = GetShadowSettings(areaLightInstance); if (shadowSettings != null) { int currentResolution = shadowSettings.shadowAtlasResolution; for (int i = 0; i < k_ShadowAtlasResolutions.Length; i++) { if (k_ShadowAtlasResolutions[i] == currentResolution) { m_ShadowAtlasResolutionIndex = i; break; } } } } } private void DrawTestPanel(int windowID) { GUILayout.BeginVertical(); GUILayout.Space(10); DrawPerformanceSection(); DrawControlsSection(); GUILayout.EndVertical(); } private void DrawPerformanceSection() { GUILayout.Label("=== Performance ===", m_LabelStyle); Color originalColor = GUI.contentColor; SetFPSColor(); GUILayout.Label($"FPS: {m_CurrentFps:F1}", m_LabelStyle); GUI.contentColor = originalColor; GUILayout.Space(10); } private void SetFPSColor() { if (m_CurrentFps >= 60f) { GUI.contentColor = Color.green; } else if (m_CurrentFps >= 30f) { GUI.contentColor = Color.yellow; } else { GUI.contentColor = Color.red; } } private void DrawControlsSection() { GUILayout.Label("=== AreaLight Controls ===", m_LabelStyle); // Get AreaLight instance from URP asset var areaLightInstance = GetActiveAreaLightInstance(); if (areaLightInstance == null) { GUILayout.Label("AreaLight Feature not found", m_LabelStyle); return; } var lightSettings = GetLightSettings(areaLightInstance); var shadowSettings = GetShadowSettings(areaLightInstance); if (lightSettings == null || shadowSettings == null) { GUILayout.Label("Failed to access AreaLight settings", m_LabelStyle); return; } // Light Settings GUILayout.Label("--- Light Settings ---", m_LabelStyle); DrawIntSlider("Max Area Lights", ref lightSettings.maxAreaLights, 0, 8); GUILayout.Space(10); // Shadow Settings GUILayout.Label("--- Shadow Settings ---", m_LabelStyle); // Shadow Atlas Resolution (sync index first) int currentResolution = shadowSettings.shadowAtlasResolution; for (int i = 0; i < k_ShadowAtlasResolutions.Length; i++) { if (k_ShadowAtlasResolutions[i] == currentResolution) { m_ShadowAtlasResolutionIndex = i; break; } } GUILayout.Label("Shadow Atlas Resolution:", m_LabelStyle); int newIndex = GUILayout.SelectionGrid(m_ShadowAtlasResolutionIndex, k_ShadowAtlasResolutionLabels, 6, m_ButtonStyle); if (newIndex != m_ShadowAtlasResolutionIndex) { m_ShadowAtlasResolutionIndex = newIndex; shadowSettings.shadowAtlasResolution = k_ShadowAtlasResolutions[m_ShadowAtlasResolutionIndex]; } DrawIntSlider("Max Shadow Casting Lights", ref shadowSettings.maxShadowCastingLights, 0, 8); GUILayout.Space(5); // Shadow Filter (enum dropdown simulation) GUILayout.Label("Shadow Filter:", m_LabelStyle); string[] filterOptions = { "Off", "PCF2x2", "Tent5x5", "Tent7x7", "PCSS" }; int currentFilterIndex = (int)shadowSettings.shadowFilter; int newFilterIndex = GUILayout.SelectionGrid(currentFilterIndex, filterOptions, 5, m_ButtonStyle); if (newFilterIndex != currentFilterIndex) { shadowSettings.shadowFilter = (AreaLightShadowSettings.BGAreaLightShadowMode)newFilterIndex; } GUILayout.Space(5); // PCSS Settings (only show when PCSS is selected) if (shadowSettings.shadowFilter == AreaLightShadowSettings.BGAreaLightShadowMode.PCSS) { GUILayout.Label("--- PCSS Settings ---", m_LabelStyle); DrawSlider("PCSS Shadow Softness", ref shadowSettings.pcssShadowSoftness, 0f, 32f); DrawIntSlider("PCSS Blocker Sample Count", ref shadowSettings.pcssBlockerSampleCount, 4, 32); DrawIntSlider("PCSS Filter Sample Count", ref shadowSettings.pcssFilterSampleCount, 4, 32); } } private void DrawSlider(string label, ref float value, float min, float max) { GUILayout.Label($"{label}: {value:F2}", m_LabelStyle); // GUILayout.HorizontalSlider returns the current slider value // Ensure slider has enough width to be interactive value = GUILayout.HorizontalSlider(value, min, max, GUILayout.ExpandWidth(true), GUILayout.Height(20)); } private void DrawIntSlider(string label, ref int value, int min, int max) { GUILayout.Label($"{label}: {value}", m_LabelStyle); // Ensure slider has enough width to be interactive float newValue = GUILayout.HorizontalSlider(value, min, max, GUILayout.ExpandWidth(true), GUILayout.Height(20)); int intValue = Mathf.RoundToInt(newValue); // Clamp to ensure value stays within bounds value = Mathf.Clamp(intValue, min, max); } private AreaLighting GetActiveAreaLightInstance() { var urpAsset = GraphicsSettings.currentRenderPipeline as UniversalRenderPipelineAsset; if (urpAsset == null) { return null; } var scriptableRendererData = GetScriptableRendererData(urpAsset); if (scriptableRendererData == null) { return null; } if (scriptableRendererData.rendererFeatures != null) { foreach (var feature in scriptableRendererData.rendererFeatures) { if (feature is AreaLighting areaLightFeature && feature.isActive) { return areaLightFeature; } } } return null; } private ScriptableRendererData GetScriptableRendererData(UniversalRenderPipelineAsset urpAsset) { var property = typeof(UniversalRenderPipelineAsset).GetProperty("scriptableRendererData", BindingFlags.NonPublic | BindingFlags.Instance); if (property != null) { return property.GetValue(urpAsset) as ScriptableRendererData; } return null; } private AreaLightSettings GetLightSettings(AreaLighting areaLighting) { var field = typeof(AreaLighting).GetField("m_LightSettings", BindingFlags.NonPublic | BindingFlags.Instance); if (field != null) { return field.GetValue(areaLighting) as AreaLightSettings; } return null; } private AreaLightShadowSettings GetShadowSettings(AreaLighting areaLighting) { var field = typeof(AreaLighting).GetField("m_ShadowSettings", BindingFlags.NonPublic | BindingFlags.Instance); if (field != null) { return field.GetValue(areaLighting) as AreaLightShadowSettings; } return null; } } }