using System.Collections.Generic; using UnityEngine.Rendering; namespace UnityEngine.Experimental.Rendering { /// /// Modes for Debugging Probes /// [GenerateHLSL] public enum DebugProbeShadingMode { /// /// Based on Spherical Harmonics /// SH, /// /// Based on validity /// Validity, /// /// Based on validity over a dilation threshold /// ValidityOverDilationThreshold, /// /// Based on size /// Size } class ProbeVolumeDebug { public bool drawProbes; public bool drawBricks; public bool drawCells; public bool realtimeSubdivision; public int subdivisionCellUpdatePerFrame = 4; public float subdivisionDelayInSeconds = 1; public DebugProbeShadingMode probeShading; public float probeSize = 1.0f; public float subdivisionViewCullingDistance = 500.0f; public float probeCullingDistance = 200.0f; public int maxSubdivToVisualize = ProbeBrickIndex.kMaxSubdivisionLevels; public float exposureCompensation; } public partial class ProbeReferenceVolume { class CellInstancedDebugProbes { public List probeBuffers; public List props; public Hash128 cellHash; public Vector3 cellPosition; } const int kProbesPerBatch = 1023; internal ProbeVolumeDebug debugDisplay { get; } = new ProbeVolumeDebug(); /// Colors that can be used for debug visualization of the brick structure subdivision. public Color[] subdivisionDebugColors { get; } = new Color[ProbeBrickIndex.kMaxSubdivisionLevels]; DebugUI.Widget[] m_DebugItems; Mesh m_DebugMesh; Material m_DebugMaterial; List m_CellDebugData = new List(); Plane[] m_DebugFrustumPlanes = new Plane[6]; internal float dilationValidtyThreshold = 0.25f; // We ned to store this here to access it // Field used for the realtime subdivision preview internal Dictionary> realtimeSubdivisionInfo = new Dictionary>(); /// /// Render Probe Volume related debug /// /// The camera public void RenderDebug(Camera camera) { if (camera.cameraType != CameraType.Reflection && camera.cameraType != CameraType.Preview) { if (debugDisplay.drawProbes) { DrawProbeDebug(camera); } } } void InitializeDebug(Mesh debugProbeMesh, Shader debugProbeShader) { m_DebugMesh = debugProbeMesh; m_DebugMaterial = CoreUtils.CreateEngineMaterial(debugProbeShader); m_DebugMaterial.enableInstancing = true; // Hard-coded colors for now. Debug.Assert(ProbeBrickIndex.kMaxSubdivisionLevels == 7); // Update list if this changes. subdivisionDebugColors[0] = new Color(1.0f, 0.0f, 0.0f); subdivisionDebugColors[1] = new Color(0.0f, 1.0f, 0.0f); subdivisionDebugColors[2] = new Color(0.0f, 0.0f, 1.0f); subdivisionDebugColors[3] = new Color(1.0f, 1.0f, 0.0f); subdivisionDebugColors[4] = new Color(1.0f, 0.0f, 1.0f); subdivisionDebugColors[5] = new Color(0.0f, 1.0f, 1.0f); subdivisionDebugColors[6] = new Color(0.5f, 0.5f, 0.5f); RegisterDebug(); #if UNITY_EDITOR UnityEditor.Lightmapping.lightingDataCleared += OnClearLightingdata; #endif } void CleanupDebug() { UnregisterDebug(true); CoreUtils.Destroy(m_DebugMaterial); #if UNITY_EDITOR UnityEditor.Lightmapping.lightingDataCleared -= OnClearLightingdata; #endif } void RefreshDebug(DebugUI.Field field, T value) { UnregisterDebug(false); RegisterDebug(); } void DebugCellIndexChanged(DebugUI.Field field, T value) { ClearDebugData(); } void RegisterDebug() { var widgetList = new List(); var subdivContainer = new DebugUI.Container() { displayName = "Subdivision Visualization" }; subdivContainer.children.Add(new DebugUI.BoolField { displayName = "Display Cells", getter = () => debugDisplay.drawCells, setter = value => debugDisplay.drawCells = value, onValueChanged = RefreshDebug }); subdivContainer.children.Add(new DebugUI.BoolField { displayName = "Display Bricks", getter = () => debugDisplay.drawBricks, setter = value => debugDisplay.drawBricks = value, onValueChanged = RefreshDebug }); #if UNITY_EDITOR subdivContainer.children.Add(new DebugUI.BoolField { displayName = "Realtime Update", getter = () => debugDisplay.realtimeSubdivision, setter = value => debugDisplay.realtimeSubdivision = value, onValueChanged = RefreshDebug }); if (debugDisplay.realtimeSubdivision) { var cellUpdatePerFrame = new DebugUI.IntField { displayName = "Number Of Cell Update Per Frame", getter = () => debugDisplay.subdivisionCellUpdatePerFrame, setter = value => debugDisplay.subdivisionCellUpdatePerFrame = value, min = () => 1, max = () => 100 }; var delayBetweenUpdates = new DebugUI.FloatField { displayName = "Delay Between Two Updates In Seconds", getter = () => debugDisplay.subdivisionDelayInSeconds, setter = value => debugDisplay.subdivisionDelayInSeconds = value, min = () => 0.1f, max = () => 10 }; subdivContainer.children.Add(new DebugUI.Container { children = { cellUpdatePerFrame, delayBetweenUpdates } }); } #endif if (debugDisplay.drawCells || debugDisplay.drawBricks) { subdivContainer.children.Add(new DebugUI.FloatField { displayName = "Culling Distance", getter = () => debugDisplay.subdivisionViewCullingDistance, setter = value => debugDisplay.subdivisionViewCullingDistance = value, min = () => 0.0f }); } var probeContainer = new DebugUI.Container() { displayName = "Probe Visualization" }; probeContainer.children.Add(new DebugUI.BoolField { displayName = "Display Probes", getter = () => debugDisplay.drawProbes, setter = value => debugDisplay.drawProbes = value, onValueChanged = RefreshDebug }); if (debugDisplay.drawProbes) { probeContainer.children.Add(new DebugUI.EnumField { displayName = "Probe Shading Mode", getter = () => (int)debugDisplay.probeShading, setter = value => debugDisplay.probeShading = (DebugProbeShadingMode)value, autoEnum = typeof(DebugProbeShadingMode), getIndex = () => (int)debugDisplay.probeShading, setIndex = value => debugDisplay.probeShading = (DebugProbeShadingMode)value, onValueChanged = RefreshDebug }); probeContainer.children.Add(new DebugUI.FloatField { displayName = "Probe Size", getter = () => debugDisplay.probeSize, setter = value => debugDisplay.probeSize = value, min = () => 0.1f, max = () => 10.0f }); if (debugDisplay.probeShading == DebugProbeShadingMode.SH) probeContainer.children.Add(new DebugUI.FloatField { displayName = "Probe Exposure Compensation", getter = () => debugDisplay.exposureCompensation, setter = value => debugDisplay.exposureCompensation = value }); probeContainer.children.Add(new DebugUI.FloatField { displayName = "Culling Distance", getter = () => debugDisplay.probeCullingDistance, setter = value => debugDisplay.probeCullingDistance = value, min = () => 0.0f }); probeContainer.children.Add(new DebugUI.IntField { displayName = "Max subdivision displayed", getter = () => debugDisplay.maxSubdivToVisualize, setter = (v) => debugDisplay.maxSubdivToVisualize = Mathf.Min(v, ProbeReferenceVolume.instance.GetMaxSubdivision()), min = () => 0, max = () => ProbeReferenceVolume.instance.GetMaxSubdivision(), }); } widgetList.Add(subdivContainer); widgetList.Add(probeContainer); m_DebugItems = widgetList.ToArray(); var panel = DebugManager.instance.GetPanel("Probe Volume", true); panel.children.Add(m_DebugItems); } void UnregisterDebug(bool destroyPanel) { if (destroyPanel) DebugManager.instance.RemovePanel("Probe Volume"); else DebugManager.instance.GetPanel("Probe Volume", false).children.Remove(m_DebugItems); } bool ShouldCullCell(Vector3 cellPosition, Transform cameraTransform, Plane[] frustumPlanes) { var cellSize = MaxBrickSize(); var originWS = GetTransform().posWS; Vector3 cellCenterWS = cellPosition * cellSize + originWS + Vector3.one * (cellSize / 2.0f); // We do coarse culling with cell, finer culling later. float distanceRoundedUpWithCellSize = Mathf.CeilToInt(debugDisplay.probeCullingDistance / cellSize) * cellSize; if (Vector3.Distance(cameraTransform.position, cellCenterWS) > distanceRoundedUpWithCellSize) return true; var volumeAABB = new Bounds(cellCenterWS, cellSize * Vector3.one); return !GeometryUtility.TestPlanesAABB(frustumPlanes, volumeAABB); } void DrawProbeDebug(Camera camera) { if (debugDisplay.drawProbes) { // TODO: Update data on ref vol changes if (m_CellDebugData.Count == 0) CreateInstancedProbes(); GeometryUtility.CalculateFrustumPlanes(camera, m_DebugFrustumPlanes); m_DebugMaterial.shaderKeywords = null; if (m_SHBands == ProbeVolumeSHBands.SphericalHarmonicsL1) m_DebugMaterial.EnableKeyword("PROBE_VOLUMES_L1"); else if (m_SHBands == ProbeVolumeSHBands.SphericalHarmonicsL2) m_DebugMaterial.EnableKeyword("PROBE_VOLUMES_L2"); foreach (var debug in m_CellDebugData) { if (ShouldCullCell(debug.cellPosition, camera.transform, m_DebugFrustumPlanes)) continue; for (int i = 0; i < debug.probeBuffers.Count; ++i) { var probeBuffer = debug.probeBuffers[i]; var props = debug.props[i]; props.SetInt("_ShadingMode", (int)debugDisplay.probeShading); props.SetFloat("_ExposureCompensation", debugDisplay.exposureCompensation); props.SetFloat("_ProbeSize", debugDisplay.probeSize); props.SetFloat("_CullDistance", debugDisplay.probeCullingDistance); props.SetInt("_MaxAllowedSubdiv", debugDisplay.maxSubdivToVisualize); props.SetFloat("_ValidityThreshold", dilationValidtyThreshold); Graphics.DrawMeshInstanced(m_DebugMesh, 0, m_DebugMaterial, probeBuffer, probeBuffer.Length, props, ShadowCastingMode.Off, false, 0, camera, LightProbeUsage.Off, null); } } } } void ClearDebugData() { m_CellDebugData.Clear(); realtimeSubdivisionInfo.Clear(); } void CreateInstancedProbes() { int maxSubdiv = ProbeReferenceVolume.instance.GetMaxSubdivision() - 1; foreach (var cell in ProbeReferenceVolume.instance.cells.Values) { if (cell.sh == null || cell.sh.Length == 0) continue; float largestBrickSize = cell.bricks.Count == 0 ? 0 : cell.bricks[0].subdivisionLevel; List probeBuffers = new List(); List props = new List(); CellChunkInfo chunks; if (!m_ChunkInfo.TryGetValue(cell.index, out chunks)) continue; Vector4[] texels = new Vector4[kProbesPerBatch]; float[] validity = new float[kProbesPerBatch]; float[] relativeSize = new float[kProbesPerBatch]; List probeBuffer = new List(); var debugData = new CellInstancedDebugProbes(); debugData.probeBuffers = probeBuffers; debugData.props = props; debugData.cellPosition = cell.position; int idxInBatch = 0; for (int i = 0; i < cell.probePositions.Length; i++) { var brickSize = cell.bricks[i / 64].subdivisionLevel; int chunkIndex = i / m_Pool.GetChunkSizeInProbeCount(); var chunk = chunks.chunks[chunkIndex]; int indexInChunk = i % m_Pool.GetChunkSizeInProbeCount(); int brickIdx = indexInChunk / 64; int indexInBrick = indexInChunk % 64; Vector2Int brickStart = new Vector2Int(chunk.x + brickIdx * 4, chunk.y); int indexInSlice = indexInBrick % 16; Vector3Int texelLoc = new Vector3Int(brickStart.x + (indexInSlice % 4), brickStart.y + (indexInSlice / 4), indexInBrick / 16); probeBuffer.Add(Matrix4x4.TRS(cell.probePositions[i], Quaternion.identity, Vector3.one * (0.3f * (brickSize + 1)))); validity[idxInBatch] = cell.validity[i]; texels[idxInBatch] = new Vector4(texelLoc.x, texelLoc.y, texelLoc.z, brickSize); relativeSize[idxInBatch] = (float)brickSize / (float)maxSubdiv; idxInBatch++; if (probeBuffer.Count >= kProbesPerBatch || i == cell.probePositions.Length - 1) { idxInBatch = 0; MaterialPropertyBlock prop = new MaterialPropertyBlock(); prop.SetFloatArray("_Validity", validity); prop.SetFloatArray("_RelativeSize", relativeSize); prop.SetVectorArray("_IndexInAtlas", texels); props.Add(prop); probeBuffers.Add(probeBuffer.ToArray()); probeBuffer = new List(); } } m_CellDebugData.Add(debugData); } } void OnClearLightingdata() { ClearDebugData(); } } }