using System.Collections.Generic;
using UnityEngine.Rendering;
using UnityEngine.SceneManagement;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace UnityEngine.Experimental.Rendering
{
///
/// A marker to determine what area of the scene is considered by the Probe Volumes system
///
[ExecuteAlways]
[AddComponentMenu("Light/Probe Volume (Experimental)")]
public class ProbeVolume : MonoBehaviour
{
///
/// If is a global bolume
///
public bool globalVolume = false;
///
/// The size
///
public Vector3 size = new Vector3(10, 10, 10);
///
/// Geometry distance offset
///
[HideInInspector, Range(0f, 2f)]
public float geometryDistanceOffset = 0.2f;
///
/// The
///
public LayerMask objectLayerMask = -1;
///
/// The lowest subdivision level override
///
[HideInInspector]
public int lowestSubdivLevelOverride = 0;
///
/// The highest subdivision level override
///
[HideInInspector]
public int highestSubdivLevelOverride = -1;
///
/// If the subdivision levels need to be overriden
///
[HideInInspector]
public bool overridesSubdivLevels = false;
[SerializeField] internal bool mightNeedRebaking = false;
[SerializeField] internal Matrix4x4 cachedTransform;
[SerializeField] internal int cachedHashCode;
#if UNITY_EDITOR
///
/// Returns the extents of the volume.
///
/// The extents of the ProbeVolume.
public Vector3 GetExtents()
{
return size;
}
internal void UpdateGlobalVolume(Scene scene)
{
if (gameObject.scene != scene) return;
Bounds bounds = new Bounds();
bool foundABound = false;
bool ContributesToGI(Renderer renderer)
{
var flags = GameObjectUtility.GetStaticEditorFlags(renderer.gameObject) & StaticEditorFlags.ContributeGI;
return (flags & StaticEditorFlags.ContributeGI) != 0;
}
void ExpandBounds(Bounds currBound)
{
if (!foundABound)
{
bounds = currBound;
foundABound = true;
}
else
{
bounds.Encapsulate(currBound);
}
}
var renderers = UnityEngine.GameObject.FindObjectsOfType();
foreach (Renderer renderer in renderers)
{
bool contributeGI = ContributesToGI(renderer) && renderer.gameObject.activeInHierarchy && renderer.enabled;
if (contributeGI && renderer.gameObject.scene == scene)
{
ExpandBounds(renderer.bounds);
}
}
transform.position = bounds.center;
float minBrickSize = ProbeReferenceVolume.instance.MinBrickSize();
Vector3 tmpClamp = (bounds.size + new Vector3(minBrickSize, minBrickSize, minBrickSize));
tmpClamp.x = Mathf.Max(0f, tmpClamp.x);
tmpClamp.y = Mathf.Max(0f, tmpClamp.y);
tmpClamp.z = Mathf.Max(0f, tmpClamp.z);
size = tmpClamp;
}
internal void OnLightingDataAssetCleared()
{
mightNeedRebaking = true;
}
internal void OnBakeCompleted()
{
// We cache the data of last bake completed.
cachedTransform = gameObject.transform.worldToLocalMatrix;
cachedHashCode = GetHashCode();
mightNeedRebaking = false;
}
public override int GetHashCode()
{
int hash = 17;
unchecked
{
hash = hash * 23 + size.GetHashCode();
hash = hash * 23 + overridesSubdivLevels.GetHashCode();
hash = hash * 23 + highestSubdivLevelOverride.GetHashCode();
hash = hash * 23 + lowestSubdivLevelOverride.GetHashCode();
hash = hash * 23 + geometryDistanceOffset.GetHashCode();
hash = hash * 23 + objectLayerMask.GetHashCode();
}
return hash;
}
internal float GetMinSubdivMultiplier()
{
float maxSubdiv = ProbeReferenceVolume.instance.GetMaxSubdivision() - 1;
return overridesSubdivLevels ? Mathf.Clamp(lowestSubdivLevelOverride / maxSubdiv, 0.0f, 1.0f) : 0.0f;
}
internal float GetMaxSubdivMultiplier()
{
float maxSubdiv = ProbeReferenceVolume.instance.GetMaxSubdivision() - 1;
return overridesSubdivLevels ? Mathf.Clamp(highestSubdivLevelOverride / maxSubdiv, 0.0f, 1.0f) : 1.0f;
}
// Momentarily moving the gizmo rendering for bricks and cells to Probe Volume itself,
// only the first probe volume in the scene will render them. The reason is that we dont have any
// other non-hidden component related to APV.
#region APVGizmo
static List sProbeVolumeInstances = new();
MeshGizmo brickGizmos;
MeshGizmo cellGizmo;
void DisposeGizmos()
{
brickGizmos?.Dispose();
brickGizmos = null;
cellGizmo?.Dispose();
cellGizmo = null;
}
void OnEnable()
{
sProbeVolumeInstances.Add(this);
}
void OnDisable()
{
sProbeVolumeInstances.Remove(this);
DisposeGizmos();
}
// Only the first PV of the available ones will draw gizmos.
bool IsResponsibleToDrawGizmo() => sProbeVolumeInstances.Count > 0 && sProbeVolumeInstances[0] == this;
internal bool ShouldCullCell(Vector3 cellPosition, Vector3 originWS = default(Vector3))
{
var cellSizeInMeters = ProbeReferenceVolume.instance.MaxBrickSize();
var debugDisplay = ProbeReferenceVolume.instance.debugDisplay;
if (debugDisplay.realtimeSubdivision)
{
var profile = ProbeReferenceVolume.instance.sceneData.GetProfileForScene(gameObject.scene);
if (profile == null)
return true;
cellSizeInMeters = profile.cellSizeInMeters;
}
var cameraTransform = SceneView.lastActiveSceneView.camera.transform;
Vector3 cellCenterWS = cellPosition * cellSizeInMeters + originWS + Vector3.one * (cellSizeInMeters / 2.0f);
// Round down to cell size distance
float roundedDownDist = Mathf.Floor(Vector3.Distance(cameraTransform.position, cellCenterWS) / cellSizeInMeters) * cellSizeInMeters;
if (roundedDownDist > ProbeReferenceVolume.instance.debugDisplay.subdivisionViewCullingDistance)
return true;
var frustumPlanes = GeometryUtility.CalculateFrustumPlanes(SceneView.lastActiveSceneView.camera);
var volumeAABB = new Bounds(cellCenterWS, cellSizeInMeters * Vector3.one);
return !GeometryUtility.TestPlanesAABB(frustumPlanes, volumeAABB);
}
// TODO: We need to get rid of Handles.DrawWireCube to be able to have those at runtime as well.
void OnDrawGizmos()
{
if (!ProbeReferenceVolume.instance.isInitialized || !IsResponsibleToDrawGizmo() || ProbeReferenceVolume.instance.sceneData == null)
return;
var debugDisplay = ProbeReferenceVolume.instance.debugDisplay;
var cellSizeInMeters = ProbeReferenceVolume.instance.MaxBrickSize();
if (debugDisplay.realtimeSubdivision)
{
var profile = ProbeReferenceVolume.instance.sceneData.GetProfileForScene(gameObject.scene);
if (profile == null)
return;
cellSizeInMeters = profile.cellSizeInMeters;
}
if (debugDisplay.drawBricks)
{
var subdivColors = ProbeReferenceVolume.instance.subdivisionDebugColors;
IEnumerable GetVisibleBricks()
{
if (debugDisplay.realtimeSubdivision)
{
// realtime subdiv cells are already culled
foreach (var kp in ProbeReferenceVolume.instance.realtimeSubdivisionInfo)
{
var cellVolume = kp.Key;
foreach (var brick in kp.Value)
{
yield return brick;
}
}
}
else
{
foreach (var cell in ProbeReferenceVolume.instance.cells.Values)
{
if (ShouldCullCell(cell.position, ProbeReferenceVolume.instance.GetTransform().posWS))
continue;
if (cell.bricks == null)
continue;
foreach (var brick in cell.bricks)
yield return brick;
}
}
}
if (brickGizmos == null)
brickGizmos = new MeshGizmo((int)(Mathf.Pow(3, ProbeBrickIndex.kMaxSubdivisionLevels) * MeshGizmo.vertexCountPerCube));
brickGizmos.Clear();
foreach (var brick in GetVisibleBricks())
{
if (brick.subdivisionLevel < 0)
continue;
Vector3 scaledSize = Vector3.one * Mathf.Pow(3, brick.subdivisionLevel);
Vector3 scaledPos = brick.position + scaledSize / 2;
brickGizmos.AddWireCube(scaledPos, scaledSize, subdivColors[brick.subdivisionLevel]);
}
brickGizmos.RenderWireframe(ProbeReferenceVolume.instance.GetRefSpaceToWS(), gizmoName: "Brick Gizmo Rendering");
}
if (debugDisplay.drawCells)
{
IEnumerable GetVisibleCellCenters()
{
if (debugDisplay.realtimeSubdivision)
{
foreach (var kp in ProbeReferenceVolume.instance.realtimeSubdivisionInfo)
{
kp.Key.CalculateCenterAndSize(out var center, out var _);
yield return center;
}
}
else
{
foreach (var cell in ProbeReferenceVolume.instance.cells.Values)
{
if (ShouldCullCell(cell.position, ProbeReferenceVolume.instance.GetTransform().posWS))
continue;
var positionF = new Vector3(cell.position.x, cell.position.y, cell.position.z);
var center = positionF * cellSizeInMeters + cellSizeInMeters * 0.5f * Vector3.one;
yield return center;
}
}
}
Matrix4x4 trs = Matrix4x4.TRS(ProbeReferenceVolume.instance.GetTransform().posWS, ProbeReferenceVolume.instance.GetTransform().rot, Vector3.one);
// For realtime subdivision, the matrix from ProbeReferenceVolume.instance can be wrong if the profile changed since the last bake
if (debugDisplay.realtimeSubdivision)
trs = Matrix4x4.TRS(transform.position, Quaternion.identity, Vector3.one);
// Fetching this from components instead of from the reference volume allows the user to
// preview how cells will look before they commit to a bake.
Gizmos.color = new Color(0, 1, 0.5f, 0.2f);
Gizmos.matrix = trs;
if (cellGizmo == null)
cellGizmo = new MeshGizmo();
cellGizmo.Clear();
foreach (var center in GetVisibleCellCenters())
{
Gizmos.DrawCube(center, Vector3.one * cellSizeInMeters);
cellGizmo.AddWireCube(center, Vector3.one * cellSizeInMeters, new Color(0, 1, 0.5f, 1));
}
cellGizmo.RenderWireframe(Gizmos.matrix, gizmoName: "Brick Gizmo Rendering");
}
}
#endregion
#endif // UNITY_EDITOR
}
} // UnityEngine.Experimental.Rendering.HDPipeline