You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
416 lines
18 KiB
416 lines
18 KiB
using System.Collections.Generic;
|
|
using UnityEngine.SceneManagement;
|
|
|
|
#if UNITY_EDITOR
|
|
using UnityEditor;
|
|
using ProbeVolumeWithBoundsList = System.Collections.Generic.List<(UnityEngine.Rendering.ProbeVolume component, UnityEngine.Rendering.ProbeReferenceVolume.Volume volume, UnityEngine.Bounds bounds)>;
|
|
#endif
|
|
|
|
namespace UnityEngine.Rendering
|
|
{
|
|
struct GIContributors
|
|
{
|
|
#if UNITY_EDITOR
|
|
public struct TerrainContributor
|
|
{
|
|
public struct TreePrototype
|
|
{
|
|
public MeshRenderer component;
|
|
public Matrix4x4 transform;
|
|
public Bounds prefabBounds;
|
|
|
|
public List<(Matrix4x4 transform, Bounds boundsWS)> instances;
|
|
}
|
|
|
|
public Terrain component;
|
|
public Bounds boundsWithTrees;
|
|
public Bounds boundsTerrainOnly;
|
|
public TreePrototype[] treePrototypes;
|
|
}
|
|
|
|
public List<(Renderer component, Bounds bounds)> renderers;
|
|
public List<TerrainContributor> terrains;
|
|
|
|
public int Count => renderers.Count + terrains.Count;
|
|
|
|
internal enum ContributorFilter { All, Scene, Selection };
|
|
|
|
internal static bool ContributesGI(GameObject go) =>
|
|
(GameObjectUtility.GetStaticEditorFlags(go) & StaticEditorFlags.ContributeGI) != 0;
|
|
|
|
internal static Vector3[] m_Vertices = new Vector3[8];
|
|
|
|
static Bounds TransformBounds(Bounds bounds, Matrix4x4 transform)
|
|
{
|
|
Vector3 boundsMin = bounds.min, boundsMax = bounds.max;
|
|
m_Vertices[0] = new Vector3(boundsMin.x, boundsMin.y, boundsMin.z);
|
|
m_Vertices[1] = new Vector3(boundsMax.x, boundsMin.y, boundsMin.z);
|
|
m_Vertices[2] = new Vector3(boundsMax.x, boundsMax.y, boundsMin.z);
|
|
m_Vertices[3] = new Vector3(boundsMin.x, boundsMax.y, boundsMin.z);
|
|
m_Vertices[4] = new Vector3(boundsMin.x, boundsMin.y, boundsMax.z);
|
|
m_Vertices[5] = new Vector3(boundsMax.x, boundsMin.y, boundsMax.z);
|
|
m_Vertices[6] = new Vector3(boundsMax.x, boundsMax.y, boundsMax.z);
|
|
m_Vertices[7] = new Vector3(boundsMin.x, boundsMax.y, boundsMax.z);
|
|
|
|
Vector3 min = transform.MultiplyPoint(m_Vertices[0]);
|
|
Vector3 max = min;
|
|
|
|
for (int i = 1; i < 8; i++)
|
|
{
|
|
var point = transform.MultiplyPoint(m_Vertices[i]);
|
|
min = Vector3.Min(min, point);
|
|
max = Vector3.Max(max, point);
|
|
}
|
|
|
|
Bounds result = default;
|
|
result.SetMinMax(min, max);
|
|
return result;
|
|
}
|
|
|
|
static internal Matrix4x4 GetTreeInstanceTransform(Terrain terrain, TreeInstance tree)
|
|
{
|
|
var position = terrain.GetPosition() + Vector3.Scale(tree.position, terrain.terrainData.size);
|
|
var rotation = Quaternion.Euler(0, tree.rotation * Mathf.Rad2Deg, 0);
|
|
var scale = new Vector3(tree.widthScale, tree.heightScale, tree.widthScale);
|
|
|
|
return Matrix4x4.TRS(position, rotation, scale);
|
|
}
|
|
|
|
public static GIContributors Find(ContributorFilter filter, Scene? scene = null)
|
|
{
|
|
if (filter == ContributorFilter.Scene && scene == null)
|
|
return default;
|
|
|
|
Profiling.Profiler.BeginSample("GIContributors.Find");
|
|
|
|
var contributors = new GIContributors()
|
|
{
|
|
renderers = new(),
|
|
terrains = new(),
|
|
};
|
|
|
|
void PushRenderer(Renderer renderer)
|
|
{
|
|
if (!ContributesGI(renderer.gameObject) || renderer.gameObject.GetComponent<MeshFilter>() == null || !renderer.gameObject.activeInHierarchy || !renderer.enabled || !renderer.isLOD0)
|
|
return;
|
|
|
|
var bounds = renderer.bounds;
|
|
bounds.size += Vector3.one * 0.01f;
|
|
contributors.renderers.Add((renderer, bounds));
|
|
}
|
|
|
|
void PushTerrain(Terrain terrain)
|
|
{
|
|
if (!ContributesGI(terrain.gameObject) || !terrain.gameObject.activeInHierarchy || !terrain.enabled || terrain.terrainData == null)
|
|
return;
|
|
|
|
var terrainData = terrain.terrainData;
|
|
var terrainBounds = terrainData.bounds;
|
|
terrainBounds.center += terrain.GetPosition();
|
|
terrainBounds.size += Vector3.one * 0.01f;
|
|
|
|
var prototypes = terrainData.treePrototypes;
|
|
var treePrototypes = new TerrainContributor.TreePrototype[prototypes.Length];
|
|
for (int i = 0; i < prototypes.Length; i++)
|
|
{
|
|
MeshRenderer renderer = null;
|
|
|
|
var prefab = prototypes[i].prefab;
|
|
if (prefab == null)
|
|
continue;
|
|
|
|
if (prefab.TryGetComponent<LODGroup>(out var lodGroup))
|
|
{
|
|
var groups = lodGroup.GetLODs();
|
|
if (groups.Length != 0 && groups[0].renderers.Length != 0)
|
|
renderer = groups[0].renderers[0] as MeshRenderer;
|
|
}
|
|
if (renderer == null)
|
|
renderer = prefab.GetComponent<MeshRenderer>();
|
|
|
|
if (renderer != null && renderer.enabled && ContributesGI(renderer.gameObject))
|
|
{
|
|
var tr = prefab.transform;
|
|
// For some reason, tree instances are not affected by rotation and position of prefab root
|
|
// But they are affected by scale, and by any other transform in the hierarchy
|
|
var transform = Matrix4x4.TRS(tr.position, tr.rotation, Vector3.one).inverse * renderer.localToWorldMatrix;
|
|
|
|
// Compute prefab bounds. This will be used to compute highest tree to expand terrain bounds
|
|
// and to approximate the bounds of tree instances for culling during voxelization.
|
|
var prefabBounds = TransformBounds(renderer.localBounds, transform);
|
|
|
|
treePrototypes[i] = new TerrainContributor.TreePrototype()
|
|
{
|
|
component = renderer,
|
|
transform = transform,
|
|
prefabBounds = prefabBounds,
|
|
instances = new List<(Matrix4x4 transform, Bounds boundsWS)>(),
|
|
};
|
|
}
|
|
}
|
|
|
|
Vector3 totalMax = terrainBounds.max;
|
|
foreach (var tree in terrainData.treeInstances)
|
|
{
|
|
var prototype = treePrototypes[tree.prototypeIndex];
|
|
if (prototype.component == null)
|
|
continue;
|
|
|
|
// Approximate instance bounds since rotation can only be on y axis
|
|
var transform = GetTreeInstanceTransform(terrain, tree);
|
|
var boundsCenter = transform.MultiplyPoint(prototype.prefabBounds.center);
|
|
var boundsSize = prototype.prefabBounds.size;
|
|
float maxTreeWidth = Mathf.Max(boundsSize.x, boundsSize.z) * tree.widthScale * Mathf.Sqrt(2.0f);
|
|
boundsSize = new Vector3(maxTreeWidth, boundsSize.y * tree.heightScale, maxTreeWidth);
|
|
|
|
prototype.instances.Add((transform, new Bounds(boundsCenter, boundsSize)));
|
|
totalMax.y = Mathf.Max(boundsCenter.y + boundsSize.y * 0.5f, totalMax.y);
|
|
}
|
|
|
|
var totalBounds = new Bounds();
|
|
totalBounds.SetMinMax(terrainBounds.min, totalMax);
|
|
|
|
contributors.terrains.Add(new TerrainContributor()
|
|
{
|
|
component = terrain,
|
|
boundsWithTrees = totalBounds,
|
|
boundsTerrainOnly = terrainBounds,
|
|
treePrototypes = treePrototypes,
|
|
});
|
|
}
|
|
|
|
if (filter == ContributorFilter.Selection)
|
|
{
|
|
var transforms = Selection.transforms;
|
|
foreach (var transform in transforms)
|
|
{
|
|
var childrens = transform.gameObject.GetComponentsInChildren<Transform>();
|
|
foreach (var children in childrens)
|
|
{
|
|
if (children.gameObject.TryGetComponent(out Renderer renderer))
|
|
PushRenderer(renderer);
|
|
else if (children.gameObject.TryGetComponent(out Terrain terrain))
|
|
PushTerrain(terrain);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var renderers = Object.FindObjectsByType<Renderer>(FindObjectsSortMode.InstanceID);
|
|
Profiling.Profiler.BeginSample($"Find Renderers ({renderers.Length})");
|
|
foreach (var renderer in renderers)
|
|
{
|
|
if (filter != ContributorFilter.Scene || renderer.gameObject.scene == scene)
|
|
PushRenderer(renderer);
|
|
}
|
|
Profiling.Profiler.EndSample();
|
|
|
|
var terrains = Object.FindObjectsByType<Terrain>(FindObjectsSortMode.InstanceID);
|
|
Profiling.Profiler.BeginSample($"Find Terrains ({terrains.Length})");
|
|
foreach (var terrain in terrains)
|
|
{
|
|
if (filter != ContributorFilter.Scene || terrain.gameObject.scene == scene)
|
|
PushTerrain(terrain);
|
|
}
|
|
Profiling.Profiler.EndSample();
|
|
}
|
|
|
|
Profiling.Profiler.EndSample();
|
|
return contributors;
|
|
}
|
|
|
|
static bool DiscardedByProbeVolume(ProbeVolume pv, ProbeVolumeBakingSet bakingSet, float boundsVolume, int layerMask)
|
|
{
|
|
if (bakingSet == null)
|
|
return false;
|
|
|
|
float minRendererBoundingBoxSize = bakingSet.minRendererVolumeSize;
|
|
var renderersLayerMask = bakingSet.renderersLayerMask;
|
|
if (pv.overrideRendererFilters)
|
|
{
|
|
minRendererBoundingBoxSize = pv.minRendererVolumeSize;
|
|
renderersLayerMask = pv.objectLayerMask;
|
|
}
|
|
|
|
// Skip renderers that have a smaller volume than the min volume size from the profile or probe volume component
|
|
// And renderers whose layer mask is excluded
|
|
return (boundsVolume < minRendererBoundingBoxSize) || (layerMask & renderersLayerMask) == 0;
|
|
}
|
|
|
|
public GIContributors Filter(ProbeVolumeBakingSet bakingSet, Bounds cellBounds, ProbeVolumeWithBoundsList probeVolumes)
|
|
{
|
|
Profiling.Profiler.BeginSample("Filter GIContributors");
|
|
|
|
var contributors = new GIContributors()
|
|
{
|
|
renderers = new(),
|
|
terrains = new(),
|
|
};
|
|
|
|
Profiling.Profiler.BeginSample($"Filter Renderers ({renderers.Count})");
|
|
foreach (var renderer in renderers)
|
|
{
|
|
if (!cellBounds.Intersects(renderer.bounds))
|
|
continue;
|
|
|
|
var volumeSize = renderer.bounds.size;
|
|
float rendererBoundsVolume = volumeSize.x * volumeSize.y * volumeSize.z;
|
|
int rendererLayerMask = 1 << renderer.component.gameObject.layer;
|
|
|
|
foreach (var probeVolume in probeVolumes)
|
|
{
|
|
if (DiscardedByProbeVolume(probeVolume.component, bakingSet, rendererBoundsVolume, rendererLayerMask) ||
|
|
!ProbeVolumePositioning.OBBAABBIntersect(probeVolume.volume, renderer.bounds, probeVolume.bounds))
|
|
continue;
|
|
|
|
contributors.renderers.Add(renderer);
|
|
break;
|
|
}
|
|
}
|
|
Profiling.Profiler.EndSample();
|
|
|
|
Profiling.Profiler.BeginSample($"Filter Terrains ({terrains.Count})");
|
|
foreach (var terrain in terrains)
|
|
{
|
|
if (!cellBounds.Intersects(terrain.boundsWithTrees))
|
|
continue;
|
|
|
|
var volumeSize = terrain.boundsWithTrees.size;
|
|
float terrainBoundsVolume = volumeSize.x * volumeSize.y * volumeSize.z;
|
|
int terrainLayerMask = 1 << terrain.component.gameObject.layer;
|
|
|
|
// Find if terrain with trees hits at least one PV
|
|
bool contributes = false;
|
|
foreach (var probeVolume in probeVolumes)
|
|
{
|
|
if (DiscardedByProbeVolume(probeVolume.component, bakingSet, terrainBoundsVolume, terrainLayerMask) ||
|
|
!ProbeVolumePositioning.OBBAABBIntersect(probeVolume.volume, terrain.boundsWithTrees, probeVolume.bounds))
|
|
continue;
|
|
|
|
contributes = true;
|
|
break;
|
|
}
|
|
|
|
if (!contributes)
|
|
continue;
|
|
|
|
// Cull trees - iterates over all instances for each pv, may be very slow
|
|
var probeVolumesForProto = new List<Bounds>();
|
|
Vector3 totalMax = terrain.boundsTerrainOnly.max;
|
|
var treePrototypes = new TerrainContributor.TreePrototype[terrain.treePrototypes.Length];
|
|
for (int i = 0; i < treePrototypes.Length; i++)
|
|
{
|
|
var srcProto = terrain.treePrototypes[i];
|
|
// This prototype may have been previously filtered out
|
|
if (srcProto.component == null)
|
|
continue;
|
|
|
|
// Find which pv may intersect instances of this proto
|
|
probeVolumesForProto.Clear();
|
|
int prototypeLayerMask = 1 << srcProto.component.gameObject.layer;
|
|
foreach (var probeVolume in probeVolumes)
|
|
{
|
|
// Ignore bounds volume check for trees, assume they are always big enough
|
|
// Otherwise we have to do the complex math stuff to compute the actual tree bounds
|
|
if (!DiscardedByProbeVolume(probeVolume.component, bakingSet, float.MaxValue, prototypeLayerMask))
|
|
probeVolumesForProto.Add(probeVolume.bounds);
|
|
}
|
|
if (probeVolumesForProto.Count == 0)
|
|
continue;
|
|
|
|
treePrototypes[i] = new TerrainContributor.TreePrototype()
|
|
{
|
|
component = srcProto.component,
|
|
transform = srcProto.transform,
|
|
prefabBounds = srcProto.prefabBounds,
|
|
instances = new List<(Matrix4x4 transform, Bounds boundsWS)>(),
|
|
};
|
|
|
|
// Cull tree instances
|
|
for (int j = 0; j < srcProto.instances.Count; j++)
|
|
{
|
|
var treeBounds = srcProto.instances[j].boundsWS;
|
|
if (!treeBounds.Intersects(cellBounds))
|
|
continue;
|
|
|
|
foreach (var pvAABB in probeVolumesForProto)
|
|
{
|
|
if (treeBounds.Intersects(pvAABB))
|
|
{
|
|
treePrototypes[i].instances.Add(srcProto.instances[j]);
|
|
totalMax.y = Mathf.Max(treeBounds.max.y, totalMax.y);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Recompute terrain bounds by excluding trees that were filtered out
|
|
var totalBounds = new Bounds();
|
|
totalBounds.SetMinMax(terrain.boundsTerrainOnly.min, totalMax);
|
|
|
|
var terrainContrib = new TerrainContributor()
|
|
{
|
|
component = terrain.component,
|
|
boundsWithTrees = totalBounds,
|
|
boundsTerrainOnly = terrain.boundsTerrainOnly,
|
|
treePrototypes = treePrototypes,
|
|
};
|
|
contributors.terrains.Add(terrainContrib);
|
|
}
|
|
Profiling.Profiler.EndSample();
|
|
|
|
Profiling.Profiler.EndSample();
|
|
return contributors;
|
|
}
|
|
|
|
public GIContributors FilterLayerMaskOnly(LayerMask layerMask)
|
|
{
|
|
Profiling.Profiler.BeginSample("Filter GIContributors LayerMask");
|
|
|
|
var contributors = new GIContributors()
|
|
{
|
|
renderers = new(),
|
|
terrains = new(),
|
|
};
|
|
|
|
foreach (var renderer in renderers)
|
|
{
|
|
int rendererLayerMask = 1 << renderer.component.gameObject.layer;
|
|
if ((rendererLayerMask & layerMask) != 0)
|
|
contributors.renderers.Add(renderer);
|
|
}
|
|
|
|
foreach (var terrain in terrains)
|
|
{
|
|
int terrainLayerMask = 1 << terrain.component.gameObject.layer;
|
|
if ((terrainLayerMask & layerMask) != 0)
|
|
{
|
|
// Filter out trees
|
|
var filteredPrototypes = new List<TerrainContributor.TreePrototype>();
|
|
foreach (var treeProto in terrain.treePrototypes)
|
|
{
|
|
int treeProtoLayerMask = 1 << treeProto.component.gameObject.layer;
|
|
|
|
if ((treeProtoLayerMask & layerMask) != 0)
|
|
filteredPrototypes.Add(treeProto);
|
|
}
|
|
|
|
var terrainContrib = new TerrainContributor()
|
|
{
|
|
component = terrain.component,
|
|
boundsWithTrees = terrain.boundsWithTrees,
|
|
boundsTerrainOnly = terrain.boundsTerrainOnly,
|
|
treePrototypes = filteredPrototypes.ToArray(),
|
|
};
|
|
|
|
contributors.terrains.Add(terrainContrib);
|
|
}
|
|
}
|
|
|
|
Profiling.Profiler.EndSample();
|
|
return contributors;
|
|
}
|
|
#endif
|
|
}
|
|
}
|