TG9six 03a642d635 first push
first push
2025-09-06 17:17:39 +04:00

327 lines
9.8 KiB
C#

// Perfect Culling (C) 2021 Patrick König
//
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Rendering;
namespace Koenigz.PerfectCulling
{
[System.Serializable]
public class PerfectCullingBakeGroup
{
public readonly struct RuntimeGroupContent
{
public readonly Renderer Renderer;
public readonly ShadowCastingMode ShadowCastingMode;
public RuntimeGroupContent(Renderer renderer, ShadowCastingMode shadowCastingMode)
{
Renderer = renderer;
ShadowCastingMode = shadowCastingMode;
}
}
public ArraySegment<RuntimeGroupContent> GetRuntimeGroupContent()
{
return new ArraySegment<RuntimeGroupContent>(m_runtimeGroupData, 0, m_runtimeGroupDataSize);
}
public enum GroupType
{
Other,
LOD,
User
}
// Bake data
public GroupType groupType;
public Renderer[] renderers = System.Array.Empty<Renderer>();
public UnityEngine.Behaviour[] unityBehaviours = System.Array.Empty<UnityEngine.Behaviour>();
public int vertexCount;
// Run-time
// We are not using a List because this is iterated quiet frequently
[System.NonSerialized] private int m_runtimeGroupDataSize;
[System.NonSerialized] private RuntimeGroupContent[] m_runtimeGroupData = System.Array.Empty<RuntimeGroupContent>();
[System.NonSerialized] private bool m_initialized;
public void Init()
{
if (m_initialized)
{
return;
}
m_initialized = true;
m_runtimeGroupData = new RuntimeGroupContent[renderers.Length];
foreach (Renderer r in renderers)
{
if (r == null)
{
#if UNITY_EDITOR
PerfectCullingLogger.LogWarning($"{nameof(PerfectCullingBakeGroup)} contains invalid renderer reference, it will be skipped and the renderer won't be culled.");
#endif
continue;
}
PushRuntimeRenderer(r);
}
// I think it should be fine to free the memory but it is not worth the risk at the moment
#if !UNITY_EDITOR
// Free this memory
//renderers = null;
#endif
}
/// <summary>
/// Checks whether Renderer exists in group. Please use PerfectCullingAPI instead.
/// </summary>
public bool ContainsRuntimeRenderer(Renderer r)
{
for (int i = 0; i < m_runtimeGroupDataSize; ++i)
{
if (r == m_runtimeGroupData[i].Renderer)
{
return true;
}
}
return false;
}
/// <summary>
/// Adds a new renderer as run-time data. Please use PerfectCullingAPI instead.
/// </summary>
public void PushRuntimeRenderer(Renderer renderer)
{
PushRuntimeGroupContent(new RuntimeGroupContent(renderer, renderer.shadowCastingMode));
}
/// <summary>
/// Removes run-time renderer. Please use PerfectCullingAPI instead.
/// </summary>
public bool PopRuntimeRenderer(Renderer renderer)
{
int index = -1;
for (int i = 0; i < m_runtimeGroupDataSize; ++i)
{
if (m_runtimeGroupData[i].Renderer == renderer)
{
index = i;
break;
}
}
if (index == -1)
{
return false;
}
if (m_runtimeGroupDataSize >= 2)
{
// Swap the element we want to remove with the element at the end
(m_runtimeGroupData[index], m_runtimeGroupData[m_runtimeGroupDataSize - 1]) = (m_runtimeGroupData[m_runtimeGroupDataSize - 1], m_runtimeGroupData[index]);
}
// Pop
PopRuntimeGroupContent();
return true;
}
/// <summary>
/// Adds new run-time data. Please use PerfectCullingAPI instead.
/// </summary>
private void PushRuntimeGroupContent(RuntimeGroupContent groupContent)
{
if (m_runtimeGroupDataSize >= m_runtimeGroupData.Length)
{
// We just double the array size so we don't need to do this too often...
System.Array.Resize(ref m_runtimeGroupData, Mathf.Max(1, m_runtimeGroupDataSize * 2));
}
m_runtimeGroupData[m_runtimeGroupDataSize] = groupContent;
++m_runtimeGroupDataSize;
}
/// <summary>
/// Removes run-time data and removes it. Please use PerfectCullingAPI instead.
/// </summary>
private RuntimeGroupContent PopRuntimeGroupContent()
{
if (m_runtimeGroupDataSize <= 0)
{
return default;
}
--m_runtimeGroupDataSize;
return m_runtimeGroupData[m_runtimeGroupDataSize];
}
/// <summary>
/// Clears all run-time data. Please use PerfectCullingAPI instead.
/// </summary>
public void ClearRuntimeRenderers()
{
m_runtimeGroupDataSize = 0;
}
public void Toggle(bool isVisible, bool forceNullCheck = false)
{
for (int i = 0; i < m_runtimeGroupDataSize; ++i)
{
RuntimeGroupContent groupContent = m_runtimeGroupData[i];
PerfectCullingUtil.ToggleRenderer(groupContent.Renderer, isVisible, forceNullCheck, groupContent.ShadowCastingMode);
}
int unityBehavioursCount = unityBehaviours.Length;
for (int i = 0; i < unityBehavioursCount; ++i)
{
if (forceNullCheck && unityBehaviours[i] == null)
{
continue;
}
unityBehaviours[i].enabled = isVisible;
}
}
public void ForeachRenderer(System.Action<Renderer> actionForRenderer)
{
foreach (Renderer r in renderers)
{
#if UNITY_EDITOR
if (r == null)
{
PerfectCullingLogger.LogError("Invalid renderer in bakeGroup");
continue;
}
#endif
actionForRenderer.Invoke(r);
}
}
// This only works in Edit mode due to Static Batching combining meshes.
public bool CollectMeshStats()
{
int totalVertexCount = 0;
foreach (Renderer rend in renderers)
{
if (rend == null)
{
PerfectCullingLogger.LogWarning($"Detected missing renderer");
return false;
}
MeshFilter mf = rend.GetComponent<MeshFilter>();
if (mf == null)
{
PerfectCullingLogger.LogWarning($"Detected Renderer without MeshFilter: {rend.name}", rend.gameObject);
continue;
}
if (mf.sharedMesh == null)
{
PerfectCullingLogger.LogWarning($"Detected Renderer that is missing Mesh: {rend.name}", rend.gameObject);
continue;
}
totalVertexCount += mf.sharedMesh.vertexCount;
}
vertexCount = totalVertexCount;
return true;
}
/// <summary>
/// Returns number of run-time renderers. Please use PerfectCullingAPI instead.
/// </summary>
public int GetRuntimeRendererCount()
{
return m_runtimeGroupDataSize;
}
public void RemoveInvalidRenderers()
{
renderers = renderers.Where((r) =>
(r != null && r.GetComponent<MeshFilter>() != null && r.GetComponent<MeshFilter>().sharedMesh != null)).ToArray();
}
}
public class PerfectCullingBakeGroupComparer : IEqualityComparer<PerfectCullingBakeGroup>
{
public bool Equals(PerfectCullingBakeGroup x, PerfectCullingBakeGroup y)
{
if (x == null || y == null)
{
return false;
}
if (x.renderers.Length != y.renderers.Length)
{
return false;
}
if (x.groupType != y.groupType)
{
return false;
}
for (int i = 0; i < x.renderers.Length; ++i)
{
if (x.renderers[i] != y.renderers[i])
{
return false;
}
}
return true;
}
public int GetHashCode(PerfectCullingBakeGroup obj)
{
if (obj == null)
{
return 0;
}
int hash = 17;
unchecked
{
hash = hash * 13 + (int) obj.groupType;
for (int i = 0; i < obj.renderers.Length; ++i)
{
hash = hash * 13 + obj.renderers[i].GetInstanceID();
}
for (int i = 0; i < obj.unityBehaviours.Length; ++i)
{
hash = hash * 13 + obj.unityBehaviours[i].GetInstanceID();
}
}
return hash;
}
}
}