upm update7

This commit is contained in:
twoischnig committed 2025-02-06 20:53:29 +01:00
1 parent 6be10231ce
commit 30a591bafe
51 files changed
+9 -1

No files matched your search

+128
View File
@@ -0,0 +1,128 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Assets.ThoMagic.Renderer
{
public class BillboardDrawCall : DrawCall
{
private static Mesh _billboardMesh;
private static Mesh GetBillboardMesh()
{
if (_billboardMesh == null)
{
_billboardMesh = new Mesh();
_billboardMesh.name = "Billboard Mesh";
_billboardMesh.vertices = new Vector3[6];
_billboardMesh.triangles = new int[6] {
0,
1,
2,
3,
4,
5
};
_billboardMesh.SetUVs(0,new Vector2[6]
{
new Vector2(0.0f, 0.0f),
new Vector2(0.0f, 1f),
new Vector2(1f, 1f),
new Vector2(0.0f, 0.0f),
new Vector2(1f, 1f),
new Vector2(1f, 0.0f)
});
_billboardMesh.SetUVs(1, new Vector4[6]
{
new Vector4(1f, 1f, 0.0f, 0.0f),
new Vector4(1f, 1f, 0.0f, 0.0f),
new Vector4(1f, 1f, 0.0f, 0.0f),
new Vector4(1f, 1f, 0.0f, 0.0f),
new Vector4(1f, 1f, 0.0f, 0.0f),
new Vector4(1f, 1f, 0.0f, 0.0f)
});
_billboardMesh.UploadMeshData(true);
}
return _billboardMesh;
}
public BillboardDrawCall(
int lodNr,
BillboardAsset billboardAsset,
Material material,
Matrix4x4 localToWorldMatrix,
uint baseIndirectArgsIndex,
List<GraphicsBuffer.IndirectDrawIndexedArgs> indirectDrawIndexedArgs,
in RenderParams renderParams)
: base(lodNr,
GetBillboardMesh(),
new Material[1]{ material },
localToWorldMatrix,
baseIndirectArgsIndex,
indirectDrawIndexedArgs,
in renderParams)
{
if (billboardAsset == null)
throw new ArgumentNullException(nameof(billboardAsset));
SetBillboardPerBatch(billboardAsset);
}
internal override void Dispose() {
UnityEngine.Object.Destroy(_billboardMesh);
base.Dispose();
}
public override void Draw(Camera camera, ObjectData obj, GraphicsBuffer indirectDrawIndexedArgs)
{
SetBillboardPerCamera(camera);
base.Draw(camera, obj, indirectDrawIndexedArgs);
}
private void SetBillboardPerCamera(Camera camera)
{
Vector3 position = camera.transform.position;
Vector3 vector3_1 = camera.transform.forward * -1f;
vector3_1.y = 0.0f;
vector3_1.Normalize();
Vector3 vector3_2 = camera.transform.right * -1f;
vector3_2.y = 0.0f;
vector3_2.Normalize();
float num1 = Mathf.Atan2(vector3_1.z, vector3_1.x);
float num2 = num1 + (num1 < 0.0f ? 6.283185f : 0.0f);
Vector4 vector4;
vector4.x = position.x;
vector4.y = position.y;
vector4.z = position.z;
vector4.w = num2;
for (int index = 0; index < RenderParams.Length; ++index)
{
RenderParams[index].matProps.SetVector("unity_BillboardNormal", vector3_1);
RenderParams[index].matProps.SetVector("unity_BillboardTangent", vector3_2);
RenderParams[index].matProps.SetVector("unity_BillboardCameraParams", vector4);
}
}
private void SetBillboardPerBatch(BillboardAsset asset)
{
Vector4 vector4_1 = Vector4.zero;
vector4_1.x = asset.imageCount;
vector4_1.y = 1.0f / (6.28318548202515f / asset.imageCount);
Vector4 vector4_2 = Vector4.zero;
vector4_2.x = asset.width;
vector4_2.y = asset.height;
vector4_2.z = asset.bottom;
Vector4[] imageTexCoords = asset.GetImageTexCoords();
for (int index = 0; index < RenderParams.Length; ++index)
{
RenderParams[index].matProps.SetVector("unity_BillboardInfo", vector4_1);
RenderParams[index].matProps.SetVector("unity_BillboardSize", vector4_2);
RenderParams[index].matProps.SetVectorArray("unity_BillboardImageTexCoords", imageTexCoords);
}
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 462b041640bdab24f87e7de6f8a16fb3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+161
View File
@@ -0,0 +1,161 @@
using System;
using UnityEngine;
namespace Assets.ThoMagic.Renderer
{
[DisallowMultipleComponent]
[ExecuteAlways]
public class ThoMagicRendererCameraSettings : MonoBehaviour, IInstanceRenderSettings
{
[SerializeField]
private bool _supported;
[SerializeField]
private bool _overrideSupported;
[SerializeField]
private bool _render;
[SerializeField]
private bool _overrideRendering;
[Min(0.0f)]
[SerializeField]
private float _renderDistance;
[SerializeField]
private bool _overrideRenderDistance;
[SerializeField]
private bool _renderShadows;
[SerializeField]
private bool _overrideRenderShadows;
[Min(0.0f)]
[SerializeField]
private float _shadowDistance;
[SerializeField]
private bool _overrideShadowDistance;
[Range(0.01f, 1f)]
[SerializeField]
private float _densityInDistance;
[SerializeField]
private bool _overrideDensityInDistance;
[SerializeField]
private Vector2 _densityInDistanceFalloff;
[SerializeField]
private bool _overrideDensityInDistanceFalloff;
private ReflectionProbe _reflectionProbe;
public InstanceRenderSettings Settings => new InstanceRenderSettings()
{
Supported = !_overrideSupported || _supported,
Render = !_overrideRendering || _render,
RenderDistance = _overrideRenderDistance ? _renderDistance : -1f,
ShadowDistance = _overrideShadowDistance ? _shadowDistance : -1f,
Shadows = !_overrideRenderShadows || _renderShadows,
DensityInDistance = _overrideDensityInDistance ? _densityInDistance : 1f,
DensityInDistanceFalloff = _overrideDensityInDistanceFalloff ? _densityInDistanceFalloff : Vector2.zero
};
public bool? Supported
{
get => !_overrideSupported ? new bool?() : new bool?(_supported);
set
{
_overrideSupported = value.HasValue;
if (!value.HasValue)
return;
_supported = value.Value;
}
}
public bool? Render
{
get => !_overrideRendering ? new bool?() : new bool?(_render);
set
{
_overrideRendering = value.HasValue;
if (!value.HasValue)
return;
_render = value.Value;
}
}
public float? RenderDistance
{
get => !_overrideRenderDistance ? new float?() : new float?(_renderDistance);
set
{
_overrideRenderDistance = value.HasValue;
if (!value.HasValue)
return;
_renderDistance = value.Value;
}
}
public bool? RenderShadows
{
get => !_overrideRenderShadows ? new bool?() : new bool?(_renderShadows);
set
{
_overrideRenderShadows = value.HasValue;
if (!value.HasValue)
return;
_renderShadows = value.Value;
}
}
public float? ShadowDistance
{
get => !_overrideShadowDistance ? new float?() : new float?(_shadowDistance);
set
{
_overrideShadowDistance = value.HasValue;
if (!value.HasValue)
return;
_shadowDistance = value.Value;
}
}
public float? DensityInDistance
{
get => !_overrideDensityInDistance ? new float?() : new float?(_densityInDistance);
set
{
_overrideDensityInDistance = value.HasValue;
if (!value.HasValue)
return;
_densityInDistance = value.Value;
}
}
public Vector2? DensityInDistanceFalloff
{
get => !_overrideDensityInDistanceFalloff ? new Vector2?() : new Vector2?(_densityInDistanceFalloff);
set
{
_overrideDensityInDistanceFalloff = value.HasValue;
if (!value.HasValue)
return;
_densityInDistanceFalloff = value.Value;
}
}
private void OnEnable()
{
_reflectionProbe = GetComponent<ReflectionProbe>();
if (_reflectionProbe == null)
return;
CameraRenderer.ReflectionProbeSettings = this;
}
private void OnDisable()
{
if (_reflectionProbe == null || CameraRenderer.ReflectionProbeSettings as ThoMagicRendererCameraSettings == this)
return;
CameraRenderer.ReflectionProbeSettings = null;
}
private void OnValidate()
{
if (_reflectionProbe == null || !Application.isEditor)
return;
_reflectionProbe.RenderProbe();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 937c543b6caf2384898a535da09c337d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+676
View File
@@ -0,0 +1,676 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.XR;
namespace Assets.ThoMagic.Renderer
{
public class CameraRenderer : IDisposable
{
internal static int nextCameraRendererId = 0;
int cameraRendererId;
public static IInstanceRenderSettings ReflectionProbeSettings;
public readonly Camera Camera;
public IInstanceRenderSettings cameraSettings;
private readonly bool _singlePassInstanced = false;
private int cachedBufferHash;
private Vector3 _origin;
private Light _mainLight;
List<InstanceStreamer> visibleStreams = new List<InstanceStreamer>();
List<PrefabData> prefabData = new List<PrefabData>();
List<GraphicsBuffer.IndirectDrawIndexedArgs> indirectDrawIndexedArgs = new List<GraphicsBuffer.IndirectDrawIndexedArgs>();
Dictionary<int, float> objRenderDistanceCache = new Dictionary<int, float>();
Dictionary<uint, InstanceRenderSettings> appliedSettings = new Dictionary<uint, InstanceRenderSettings>();
List<uint> cullableIndexes = new List<uint>();
public Camera ReferenceCamera { get; private set; }
private CommandBuffer commandBuffer;
private ComputeShader instancingShader;
private ComputeBuffer prefabBuffer;
private GraphicsBuffer indirectDrawIndexedArgsBuffer;
private ComputeBuffer metaBuffer;
private ComputeBuffer cullableIndexesBuffer;
private ComputeBuffer visibleIndexesBuffer;
private ComputeBuffer visibleShadowIndexesBuffer;
private float cachedFov;
private float cachedBias;
private bool lodInvalid;
SceneRenderSettings sceneSettings = new SceneRenderSettings();
private Plane[] _cachedFrustumPlanes;
private Vector4[] _cachedShaderFrustumPlanes;
internal bool instanceCountChanged = true;
internal bool rebuildPrefabs = true;
private int cullShaderId = 0, clearShaderId = 0;
private uint maxInstancesCount = 0;
private uint indexBufferOffset = 0;
private int lastCamSettingsHash = 0;
public CameraRenderer(Camera camera)
{
commandBuffer = new CommandBuffer();
cameraRendererId = Interlocked.Increment(ref nextCameraRendererId);
instancingShader = UnityEngine.Object.Instantiate(Resources.Load<ComputeShader>("ThoMagic Renderer Instancing"));
instancingShader.hideFlags = HideFlags.HideAndDontSave;
cullShaderId = instancingShader.FindKernel("Cull_64");
clearShaderId = instancingShader.FindKernel("Clear_64");
instancingShader.name = $"ThoMagic Renderer Instancing - {camera.name}";
Camera = camera != null ? camera : throw new ArgumentNullException(nameof(camera));
cameraSettings = camera.GetComponent<IInstanceRenderSettings>();
ReferenceCamera = camera;
if (camera.cameraType == CameraType.Game || camera.cameraType == CameraType.VR)
{
_singlePassInstanced = XRSettings.enabled && (XRSettings.stereoRenderingMode == XRSettings.StereoRenderingMode.SinglePassInstanced || XRSettings.stereoRenderingMode == XRSettings.StereoRenderingMode.SinglePassMultiview);
if (Application.isEditor && XRSettings.enabled && !_singlePassInstanced && XRSettings.loadedDeviceName == "MockHMD Display" && this.LoadMockHmdSetting() == "SinglePassInstanced")
_singlePassInstanced = true;
}
}
public void SetFloatingOrigin(in Vector3 origin)
{
_origin = origin;
}
public void SetReferenceCamera(Camera camera)
{
ReferenceCamera = camera != null ? camera : Camera;
cameraSettings = ReferenceCamera.GetComponent<IInstanceRenderSettings>();
}
public void SetMainLight(Light light) => _mainLight = light;
private void CalculateFrustumPlanes()
{
if (_cachedFrustumPlanes == null)
_cachedFrustumPlanes = new Plane[6];
GeometryUtility.CalculateFrustumPlanes(ReferenceCamera, _cachedFrustumPlanes);
if (_cachedShaderFrustumPlanes == null)
_cachedShaderFrustumPlanes = new Vector4[6];
for (int index = 0; index < 6; ++index)
_cachedShaderFrustumPlanes[index] = new Vector4(_cachedFrustumPlanes[index].normal.x, _cachedFrustumPlanes[index].normal.y, _cachedFrustumPlanes[index].normal.z, _cachedFrustumPlanes[index].distance);
}
public void Render()
{
//Used in editor if selected camera is not editor scene camera. Then only the culled instances of the selected camera is rendered.
if (ReferenceCamera == null)
SetReferenceCamera(Camera);
if (_mainLight == null)
_mainLight = FindMainLight();
if (_mainLight != null)
{
sceneSettings.HasMainLight = true;
sceneSettings.HasMainLightShadows = _mainLight.shadows > 0;
sceneSettings.MainLightDirection = _mainLight.transform.forward;
}
else
{
sceneSettings.HasMainLight = false;
sceneSettings.HasMainLightShadows = false;
}
var finalSettings = InstanceRenderSettings.Default(ReferenceCamera);
if (ReferenceCamera.cameraType == CameraType.Reflection && ReflectionProbeSettings != null)
finalSettings.Merge(ReflectionProbeSettings.Settings);
if (cameraSettings == null && Application.isEditor && !Application.isPlaying)
cameraSettings = ReferenceCamera.GetComponent<IInstanceRenderSettings>();
if (cameraSettings != null)
finalSettings.Merge(cameraSettings.Settings);
if(finalSettings.GetHashCode() != lastCamSettingsHash)
{
lastCamSettingsHash = finalSettings.GetHashCode();
rebuildPrefabs = true;
}
if (!rebuildPrefabs && Application.isEditor && !Application.isPlaying)
{
foreach (var renderObject in RendererPool.objectsList)
{
var settingsEditor = finalSettings;
var renderObjectSettingsNew = renderObject.GameObject.GetComponent<IInstanceRenderSettings>();
if (renderObjectSettingsNew != null)
settingsEditor.Merge(renderObjectSettingsNew.Settings);
if(!appliedSettings.ContainsKey(renderObject.prefabId) || settingsEditor.GetHashCode() != appliedSettings[renderObject.prefabId].GetHashCode())
{
renderObject.Settings = renderObjectSettingsNew;
rebuildPrefabs = true;
}
}
}
CalculateFrustumPlanes();
//Find visible streams for this frame
visibleStreams.Clear();
foreach (var streamer in RendererPool.streamers.Values)
if (streamer.IsInRange(ReferenceCamera, _cachedFrustumPlanes))
{
streamer.UpdateForCamera(ReferenceCamera, _cachedFrustumPlanes);
visibleStreams.Add(streamer);
}
bool prefabDataChanged = false;
bool noNeedToUpdateIndirectIndexOffsets = false;
//A prefab (object) has been added, removed or changed
if (rebuildPrefabs)
{
noNeedToUpdateIndirectIndexOffsets = true;
rebuildPrefabs = false;
lodInvalid = false;
cachedFov = ReferenceCamera.fieldOfView;
cachedBias = QualitySettings.lodBias;
appliedSettings.Clear();
indirectDrawIndexedArgs.Clear();
prefabData.Clear();
prefabData.AddRange(RendererPool.prefabData);
maxInstancesCount = 0;
indexBufferOffset = 0;
uint batchIndex = 0;
uint prefabCount = 0;
uint indexArgsSize = 0;
int totalIndirectArgsIndex = 0;
foreach (var renderObject in RendererPool.objectsList)
{
var localPrefabData = prefabData[(int)renderObject.prefabId];
var finalObjSettings = finalSettings;
if (renderObject.Settings != null)
{
finalObjSettings.Merge(renderObject.Settings.Settings);
ValidateSettings(ref finalObjSettings);
}
CalculateCullingDistances(renderObject, finalObjSettings, cachedFov, cachedBias, renderObject.LodTransitions, renderObject.LodSizes, renderObject.lods);
float distance1 = RelativeHeightToDistance(finalObjSettings.DensityInDistanceFalloff.x, renderObject.LodSizes[0], cachedFov);
float distance2 = RelativeHeightToDistance(finalObjSettings.DensityInDistanceFalloff.y, renderObject.LodSizes[0], cachedFov);
var densityInDistance = new Vector4(finalObjSettings.DensityInDistance,
distance1,
distance2 - distance1,
finalObjSettings.ShadowDistance);
localPrefabData.lodCount = renderObject.lodCount;
localPrefabData.fadeLods = renderObject.fadeLods;
localPrefabData.batchIndex = batchIndex;
localPrefabData.indexBufferStartOffset = indexBufferOffset;
localPrefabData.maxCount = renderObject.count;
localPrefabData.densityInDistance = densityInDistance;
renderObject.indirectArgsPerSubmeshOffsets.Clear();
indirectDrawIndexedArgs.AddRange(renderObject.indirectDrawIndexedArgs);
for (int i = 0; i < renderObject.lodCount; i++)
{
renderObject.indirectArgsPerLodOffsets[i] = indexArgsSize;
//All indirect arguments for this lod including shadows
for (int z = 0; z < renderObject.indirectArgsPerLodWithShadowsCounts[i]; z++)
{
//Set batchoffset
var idia = indirectDrawIndexedArgs[totalIndirectArgsIndex];
idia.startInstance = indexBufferOffset + (uint)(i * renderObject.count);
indirectDrawIndexedArgs[totalIndirectArgsIndex] = idia;
//**
renderObject.indirectArgsPerSubmeshOffsets.Add(totalIndirectArgsIndex);
totalIndirectArgsIndex++;
indexArgsSize += 5;
}
}
localPrefabData.SetLods(renderObject.lods, renderObject.indirectArgsPerLodOffsets, renderObject.indirectArgsPerLodCounts);
prefabData[(int)renderObject.prefabId] = localPrefabData;
appliedSettings[renderObject.prefabId] = finalObjSettings;
batchIndex += renderObject.lodCount;
indexBufferOffset += renderObject.count * renderObject.lodCount;
maxInstancesCount += renderObject.count;
prefabCount++;
}
//Nothing to draw yet
if (maxInstancesCount == 0)
{
rebuildPrefabs = true;
return;
}
if (metaBuffer == null || metaBuffer.count < maxInstancesCount)
{
metaBuffer?.Dispose();
int nextCount = RendererPool.RESIZE_COUNT * (((int)maxInstancesCount - 1) / RendererPool.RESIZE_COUNT + 1);
metaBuffer = new ComputeBuffer(nextCount, sizeof(uint) * 4, ComputeBufferType.Structured);
metaBuffer.name = $"ThoMagic metaBuffer ({Camera.name})";
cullableIndexesBuffer?.Dispose();
cullableIndexesBuffer = new ComputeBuffer(nextCount, sizeof(uint), ComputeBufferType.Structured);
cullableIndexesBuffer.name = $"ThoMagic cullingIndexesBuffer ({Camera.name})";
}
if (indirectDrawIndexedArgsBuffer == null || indirectDrawIndexedArgsBuffer.count < indirectDrawIndexedArgs.Count)
{
indirectDrawIndexedArgsBuffer?.Dispose();
int nextCount = RendererPool.RESIZE_COUNT * (((int)indirectDrawIndexedArgs.Count - 1) / RendererPool.RESIZE_COUNT + 1);
indirectDrawIndexedArgsBuffer = new GraphicsBuffer(GraphicsBuffer.Target.IndirectArguments, nextCount, GraphicsBuffer.IndirectDrawIndexedArgs.size);
indirectDrawIndexedArgsBuffer.name = $"ThoMagic indirectDrawIndexedArgsBuffer ({Camera.name})";
}
indirectDrawIndexedArgsBuffer.SetData(indirectDrawIndexedArgs);
if (prefabBuffer == null || prefabBuffer.count < prefabCount)
{
prefabBuffer?.Dispose();
int nextCount = RendererPool.RESIZE_COUNT * (((int)prefabCount - 1) / RendererPool.RESIZE_COUNT + 1);
prefabBuffer = new ComputeBuffer(nextCount, PrefabData.Stride, ComputeBufferType.Structured);
prefabBuffer.name = $"ThoMagic prefabBuffer ({Camera.name})";
}
if (visibleIndexesBuffer == null || visibleIndexesBuffer.count < indexBufferOffset)
{
visibleIndexesBuffer?.Dispose();
int nextCount = RendererPool.RESIZE_COUNT * (((int)indexBufferOffset - 1) / RendererPool.RESIZE_COUNT + 1);
visibleIndexesBuffer = new ComputeBuffer(nextCount, sizeof(uint), ComputeBufferType.Structured);
visibleIndexesBuffer.name = $"ThoMagic visibleIndexesBuffer ({Camera.name})";
visibleShadowIndexesBuffer?.Dispose();
visibleShadowIndexesBuffer = new ComputeBuffer(nextCount, sizeof(uint), ComputeBufferType.Structured);
visibleShadowIndexesBuffer.name = $"ThoMagic visibleShadowIndexesBuffer ({Camera.name})";
};
prefabDataChanged = true;
instanceCountChanged = true;
}
//Nothing to draw yet
if (maxInstancesCount == 0)
return;
//Either instances have been added/removed or the visible streams have changed.
//We need to rebuild the visible instances index buffer
if (instanceCountChanged || BuffersChanged(visibleStreams, ref cachedBufferHash))
{
if (instanceCountChanged)
{
if (!noNeedToUpdateIndirectIndexOffsets)//If already rebuild by prefab update, this is not needed
{
maxInstancesCount = 0;
indexBufferOffset = 0;
uint indexArgsSize = 0;
int totalIndirectArgsIndex = 0;
foreach (var renderObject in RendererPool.objectsList)
{
for (int i = 0; i < renderObject.lodCount; i++)
{
//All indirect arguments for this lod including shadows
for (int z = 0; z < renderObject.indirectArgsPerLodWithShadowsCounts[i]; z++)
{
//Set batchoffset
var idia = indirectDrawIndexedArgs[totalIndirectArgsIndex];
idia.startInstance = indexBufferOffset + (uint)(i * renderObject.count);
indirectDrawIndexedArgs[totalIndirectArgsIndex] = idia;
//**
totalIndirectArgsIndex++;
indexArgsSize += 5;
}
}
var pd = prefabData[(int)renderObject.prefabId];
if (pd.maxCount != renderObject.count)
prefabDataChanged = true;
pd.indexBufferStartOffset = indexBufferOffset;
pd.maxCount = renderObject.count;
prefabData[(int)renderObject.prefabId] = pd;
indexBufferOffset += renderObject.count * renderObject.lodCount;
maxInstancesCount += renderObject.count;
}
if (metaBuffer == null || metaBuffer.count < maxInstancesCount)
{
metaBuffer?.Dispose();
int nextCount = RendererPool.RESIZE_COUNT * (((int)maxInstancesCount - 1) / RendererPool.RESIZE_COUNT + 1);
metaBuffer = new ComputeBuffer(nextCount, sizeof(uint) * 4, ComputeBufferType.Structured);
metaBuffer.name = $"ThoMagic metaBuffer ({Camera.name})";
cullableIndexesBuffer?.Dispose();
cullableIndexesBuffer = new ComputeBuffer(nextCount, sizeof(uint), ComputeBufferType.Structured);
cullableIndexesBuffer.name = $"ThoMagic cullingIndexesBuffer ({Camera.name})";
}
if (visibleIndexesBuffer == null || visibleIndexesBuffer.count < indexBufferOffset)
{
visibleIndexesBuffer?.Dispose();
int nextCount = RendererPool.RESIZE_COUNT * (((int)indexBufferOffset - 1) / RendererPool.RESIZE_COUNT + 1);
visibleIndexesBuffer = new ComputeBuffer(nextCount, sizeof(uint), ComputeBufferType.Structured);
visibleIndexesBuffer.name = $"ThoMagic visibleIndexesBuffer ({Camera.name})";
visibleShadowIndexesBuffer?.Dispose();
visibleShadowIndexesBuffer = new ComputeBuffer(nextCount, sizeof(uint), ComputeBufferType.Structured);
visibleShadowIndexesBuffer.name = $"ThoMagic visibleShadowIndexesBuffer ({Camera.name})";
};
indirectDrawIndexedArgsBuffer.SetData(indirectDrawIndexedArgs);
}
}
instanceCountChanged = false;
cullableIndexes.Clear();
foreach (var stream in visibleStreams)
{
foreach (var objectInstanceIds in stream.objectInstanceIds.Values)
{
cullableIndexes.AddRange(objectInstanceIds);
}
}
cullableIndexesBuffer.SetData(cullableIndexes);
}
//Nothing to render
if (cullableIndexes.Count == 0 || RendererPool.globalInstanceBuffer == null)
return;
//Lod settings changed, update prefab lod data
if (cachedFov != ReferenceCamera.fieldOfView || cachedBias != QualitySettings.lodBias || lodInvalid)
{
lodInvalid = false;
cachedFov = ReferenceCamera.fieldOfView;
cachedBias = QualitySettings.lodBias;
foreach (var renderObject in RendererPool.objectsList)
{
CalculateCullingDistances(renderObject, appliedSettings[renderObject.prefabId], cachedFov, cachedBias, renderObject.LodTransitions, renderObject.LodSizes, renderObject.lods);
float distance1 = RelativeHeightToDistance(appliedSettings[renderObject.prefabId].DensityInDistanceFalloff.x, renderObject.LodSizes[0], cachedFov);
float distance2 = RelativeHeightToDistance(appliedSettings[renderObject.prefabId].DensityInDistanceFalloff.y, renderObject.LodSizes[0], cachedFov);
var densityInDistance = new Vector4(appliedSettings[renderObject.prefabId].DensityInDistance,
distance1,
distance2 - distance1,
appliedSettings[renderObject.prefabId].ShadowDistance);
var pd = prefabData[(int)renderObject.prefabId];
pd.SetLods(renderObject.lods, renderObject.indirectArgsPerLodOffsets, renderObject.indirectArgsPerLodCounts);
pd.densityInDistance = densityInDistance;
prefabData[(int)renderObject.prefabId] = pd;
}
prefabDataChanged = true;
}
if(prefabDataChanged)
prefabBuffer.SetData(prefabData);
//Compute culling
//Reset visible count of instances to zero
/*instancingShader.SetInt("_Count", indirectDrawIndexedArgs.Count);
instancingShader.SetBuffer(clearShaderId, "perCamIndirectArgumentsBuffer", indirectDrawIndexedArgsBuffer);
instancingShader.Dispatch(clearShaderId, Mathf.CeilToInt(indirectDrawIndexedArgs.Count / 64.0f), 1, 1);
//Cull
instancingShader.SetBuffer(cullShaderId, "globalInstances", RendererPool.globalInstanceBuffer);
instancingShader.SetBuffer(cullShaderId, "perCamPrefabs", prefabBuffer);
instancingShader.SetBuffer(cullShaderId, "perCamMeta", metaBuffer);
instancingShader.SetBuffer(cullShaderId, "perCamIndirectArgumentsBuffer", indirectDrawIndexedArgsBuffer);
instancingShader.SetBuffer(cullShaderId, "perCamCullableIndexesBuffer", cullableIndexesBuffer);
instancingShader.SetBuffer(cullShaderId, "perCamVisibleIndexesBuffer", visibleIndexesBuffer);
instancingShader.SetBuffer(cullShaderId, "perCamShadowVisibleIndexesBuffer", visibleShadowIndexesBuffer);
instancingShader.SetVector("_CameraPosition", ReferenceCamera.transform.position);
instancingShader.SetVectorArray("_FrustumPlanes", _cachedShaderFrustumPlanes);
instancingShader.SetVector("_ShadowDirection", sceneSettings.MainLightDirection);
instancingShader.SetInt("_Count", cullableIndexes.Count);
instancingShader.Dispatch(cullShaderId, Mathf.CeilToInt(cullableIndexes.Count / 64.0f), 1, 1);*/
commandBuffer.Clear();
commandBuffer.SetComputeIntParam(instancingShader, "_CountClear", indirectDrawIndexedArgs.Count);
commandBuffer.SetComputeBufferParam(instancingShader, clearShaderId, "perCamIndirectArgumentsBuffer", indirectDrawIndexedArgsBuffer);
commandBuffer.DispatchCompute(instancingShader, clearShaderId, Mathf.CeilToInt(indirectDrawIndexedArgs.Count / 64.0f), 1, 1);
commandBuffer.SetComputeBufferParam(instancingShader, cullShaderId, "globalInstances", RendererPool.globalInstanceBuffer);
commandBuffer.SetComputeBufferParam(instancingShader, cullShaderId, "perCamPrefabs", prefabBuffer);
commandBuffer.SetComputeBufferParam(instancingShader, cullShaderId, "perCamMeta", metaBuffer);
commandBuffer.SetComputeBufferParam(instancingShader, cullShaderId, "perCamIndirectArgumentsBuffer", indirectDrawIndexedArgsBuffer);
commandBuffer.SetComputeBufferParam(instancingShader, cullShaderId, "perCamCullableIndexesBuffer", cullableIndexesBuffer);
commandBuffer.SetComputeBufferParam(instancingShader, cullShaderId, "perCamVisibleIndexesBuffer", visibleIndexesBuffer);
commandBuffer.SetComputeBufferParam(instancingShader, cullShaderId, "perCamShadowVisibleIndexesBuffer", visibleShadowIndexesBuffer);
commandBuffer.SetComputeVectorParam(instancingShader, "_CameraPosition", ReferenceCamera.transform.position);
commandBuffer.SetComputeVectorArrayParam(instancingShader, "_FrustumPlanes", _cachedShaderFrustumPlanes);
commandBuffer.SetComputeVectorParam(instancingShader, "_ShadowDirection", sceneSettings.MainLightDirection);
commandBuffer.SetComputeIntParam(instancingShader, "_CountCull", cullableIndexes.Count);
commandBuffer.DispatchCompute(instancingShader, cullShaderId, Mathf.CeilToInt(cullableIndexes.Count / 64.0f), 1, 1);
Graphics.ExecuteCommandBuffer(commandBuffer);
var fence = Graphics.CreateGraphicsFence(GraphicsFenceType.AsyncQueueSynchronisation, SynchronisationStageFlags.ComputeProcessing);
Graphics.WaitOnAsyncGraphicsFence(fence);
//Render all prefabs (objects)
foreach (var renderObject in RendererPool.objectsList)
if (renderObject.count > 0)//Ignore if no instance is registered
RenderObject(renderObject, appliedSettings[renderObject.prefabId]);
}
private void CalculateCullingDistances(
ObjectData renderObject,
InstanceRenderSettings finalSettings,
float fieldOfView,
float bias,
float[] relativeTransitionHeight,
float[] detailSize,
Vector4[] lodData)
{
for (int index = 0; index < relativeTransitionHeight.Length; ++index)
lodData[index] = new Vector4(index > 0 ? lodData[index - 1].y : 0.0f, RelativeHeightToDistance(relativeTransitionHeight[index], detailSize[index], fieldOfView) * bias, detailSize[index], 0.0f);
var renderDistance = Mathf.Min(finalSettings.RenderDistance, RelativeHeightToDistance(renderObject.LodTransitions[renderObject.LodTransitions.Length - 1], renderObject.LodSizes[renderObject.LodTransitions.Length - 1], this.ReferenceCamera.fieldOfView, QualitySettings.lodBias));
objRenderDistanceCache[renderObject.GetHashCode()] = renderDistance;
for (int index = 0; index < renderObject.LodSizes.Length; ++index)
{
renderObject.lods[index].x = Mathf.Min(renderObject.lods[index].x, renderDistance);
renderObject.lods[index].y = Mathf.Min(renderObject.lods[index].y, renderDistance);
}
}
public static float RelativeHeightToDistance(
float relativeHeight,
float size,
float fieldOfView)
{
if (relativeHeight <= 0.0f || relativeHeight == float.MaxValue)
return float.MaxValue;
float num = Mathf.Tan((float)(Math.PI / 180.0 * (double)fieldOfView * 0.5));
return size * 0.5f / relativeHeight / num;
}
private void RenderObject(
ObjectData obj,
InstanceRenderSettings renderSettings)
{
if (LayerIsCulled(ReferenceCamera, obj.GameObject.layer) || (/*(!Application.isEditor || Application.isPlaying) &&*/ SceneIsCulled(ReferenceCamera, obj.GameObject)))
return;
if (!renderSettings.Render)
return;
foreach(var drawGroup in obj.drawGroups)
{
if (drawGroup == null) continue;
drawGroup.SetInstances(RendererPool.globalInstanceBuffer, visibleIndexesBuffer, metaBuffer);
drawGroup.Draw(Camera, obj, indirectDrawIndexedArgsBuffer);
}
if (!renderSettings.Shadows)
return;
foreach (var drawGroup in obj.drawShadowGroups)
{
if (drawGroup == null) continue;
drawGroup.SetInstances(RendererPool.globalInstanceBuffer, visibleShadowIndexesBuffer, metaBuffer);
drawGroup.Draw(Camera, obj, indirectDrawIndexedArgsBuffer);
}
}
private void ValidateSettings(ref InstanceRenderSettings settings)
{
if (settings.RenderDistance <= 0.0f)
settings.RenderDistance = this.ReferenceCamera.farClipPlane;
if (settings.ShadowDistance > 0.0f)
return;
settings.ShadowDistance = settings.RenderDistance;
}
private bool LayerIsCulled(Camera camera, int layer)
{
if (camera.cullingMask != -1)
{
int num = layer;
if ((camera.cullingMask & 1 << num) == 0)
return true;
}
return false;
}
private bool SceneIsCulled(Camera camera, GameObject gameObject)
{
if (camera.scene != null && camera.scene.handle != 0 && camera.scene != gameObject.scene)
return true;
else
return false;
}
private string LoadMockHmdSetting()
{
try
{
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
{
Type type = assembly.GetType("Unity.XR.MockHMD.MockHMDBuildSettings", false);
if (!(type == (Type)null))
{
object obj = type.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).GetValue((object)null);
return type.GetField("renderMode").GetValue(obj).ToString();
}
}
}
catch
{
}
return null;
}
private Light FindMainLight()
{
Light currentLight = null;
foreach (Light otherLight in UnityEngine.Object.FindObjectsOfType<Light>())
{
if (otherLight.type == LightType.Directional)
{
if (otherLight.shadows > 0)
{
currentLight = otherLight;
break;
}
if (currentLight == null)
currentLight = otherLight;
else if (otherLight.intensity > currentLight.intensity)
currentLight = otherLight;
}
}
return currentLight;
}
private float RelativeHeightToDistance(
float relativeHeight,
float size,
float fieldOfView,
float bias)
{
if ((double)relativeHeight <= 0.0 || (double)relativeHeight == 3.40282346638529E+38)
return float.MaxValue;
float num = Mathf.Tan((float)(Math.PI / 180.0 * (double)fieldOfView * 0.5));
return size * 0.5f / relativeHeight / num * bias;
}
private bool BuffersChanged(List<InstanceStreamer> buffers, ref int cachedHash)
{
int hash = ComputeHash(buffers);
if (hash == cachedHash)
return false;
cachedHash = hash;
return true;
}
private int ComputeHash(List<InstanceStreamer> buffers)
{
if (buffers == null || buffers.Count == 0)
return 0;
int num = buffers[0].GetHashCode();
for (int index = 1; index < buffers.Count; ++index)
num = HashCode.Combine(num, buffers[index].GetHashCode());
return num;
}
public void Dispose()
{
UnityEngine.Object.DestroyImmediate(instancingShader);
prefabBuffer?.Dispose();
indirectDrawIndexedArgsBuffer?.Dispose();
metaBuffer?.Dispose();
cullableIndexesBuffer?.Dispose();
visibleIndexesBuffer?.Dispose();
visibleShadowIndexesBuffer?.Dispose();
commandBuffer?.Dispose();
prefabBuffer = null;
indirectDrawIndexedArgsBuffer = null;
metaBuffer = null;
visibleIndexesBuffer = null;
visibleShadowIndexesBuffer = null;
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f2218c3942cfa8745866f4503b313216
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+179
View File
@@ -0,0 +1,179 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Assets.ThoMagic.Renderer
{
public class CellLayout
{
public Vector3 Origin;
private readonly int _cellsX;
private readonly int _cellsZ;
private readonly float _cellSizeX;
private readonly float _cellSizeZ;
private readonly Cell[] _cells;
private readonly Dictionary<int, Plane[]> _cameraPlanes = new Dictionary<int, Plane[]>();
private const int _planeCount = 6;
private int _cachedFrameId;
private Camera _cachedCamera;
private Plane[] _planes = new Plane[6];
private Double3[] _absNormals = new Double3[6];
private Double3[] _planeNormal = new Double3[6];
private double[] _planeDistance = new double[6];
public CellLayout(float cellSizeX, float cellSizeZ, Bounds worldBounds)
{
Origin = worldBounds.min;
_cellSizeX = cellSizeX;
_cellSizeZ = cellSizeZ;
_cellsX = Mathf.CeilToInt(worldBounds.size.x / _cellSizeX);
_cellsZ = Mathf.CeilToInt(worldBounds.size.z / _cellSizeZ);
_cells = new Cell[_cellsX * _cellsZ];
float num1 = _cellSizeX * 0.5f;
float num2 = _cellSizeZ * 0.5f;
for (int index1 = 0; index1 < _cellsZ; ++index1)
{
for (int index2 = 0; index2 < _cellsX; ++index2)
{
int index3 = index1 * _cellsX + index2;
float num3 = (float)index2 * _cellSizeX + num1;
float num4 = (float)index1 * _cellSizeZ + num2;
_cells[index3].HeightMin = double.MaxValue;
_cells[index3].HeightMax = double.MinValue;
_cells[index3].Center = new Double3((double)num3, 0.0, (double)num4);
_cells[index3].Extends = new Double3((double)num1, double.NaN, (double)num2);
}
}
}
public Cell[] GetCells() => _cells;
public int GetCellCount() => _cells.Length;
public void OverwriteCellHeightBounds()
{
int length = _cells.Length;
for (int index = 0; index < length; ++index)
_cells[index].HeightOverwrite = true;
}
public void SetCellHeightBounds(int cellIndex, double min, double max)
{
ref Cell local = ref _cells[cellIndex];
if (local.HeightOverwrite)
{
local.HeightOverwrite = false;
local.HeightMax = max;
local.HeightMin = min;
}
else
{
local.HeightMax = Math.Max(local.HeightMax, max);
local.HeightMin = Math.Min(local.HeightMin, min);
}
local.Center = new Double3(local.Center.x, (local.HeightMax + local.HeightMin) * 0.5, local.Center.z);
local.Extends = new Double3(local.Extends.x, (local.HeightMax + local.HeightMin) * 0.5, local.Extends.z);
}
public void Update(Camera camera, int frameId, bool frustumCulling)
{
if (camera == _cachedCamera && frameId == _cachedFrameId)
return;
_cachedCamera = camera;
_cachedFrameId = frameId;
Plane[] planes;
if (!_cameraPlanes.TryGetValue(((object)camera).GetHashCode(), out planes))
_cameraPlanes[((object)camera).GetHashCode()] = planes = new Plane[6];
GeometryUtility.CalculateFrustumPlanes(camera, planes);
SetPlanes(planes);
double x1 = camera.transform.position.x;
double z1 = camera.transform.position.z;
Double3 absNormal1 = _absNormals[0];
Double3 absNormal2 = _absNormals[1];
Double3 absNormal3 = _absNormals[2];
Double3 absNormal4 = _absNormals[3];
Double3 absNormal5 = _absNormals[4];
Double3 absNormal6 = _absNormals[5];
Double3 double3_1 = _planeNormal[0];
Double3 double3_2 = _planeNormal[1];
Double3 double3_3 = _planeNormal[2];
Double3 double3_4 = _planeNormal[3];
Double3 double3_5 = _planeNormal[4];
Double3 double3_6 = _planeNormal[5];
double num1 = _planeDistance[0];
double num2 = _planeDistance[1];
double num3 = _planeDistance[2];
double num4 = _planeDistance[3];
double num5 = _planeDistance[4];
double num6 = _planeDistance[5];
double x2 = (double)Origin.x;
double y = (double)Origin.y;
double z2 = (double)Origin.z;
int length = _cells.Length;
for (int index = 0; index < length; ++index)
{
ref Cell local = ref _cells[index];
double num7 = local.Center.x + x2;
double num8 = local.Center.y + y;
double num9 = local.Center.z + z2;
Double3 extends = local.Extends;
local.DistanceX = Math.Abs(x1 - num7);
local.DistanceZ = Math.Abs(z1 - num9);
if (frustumCulling && !double.IsNaN(extends.y))
{
bool flag = extends.x * absNormal1.x + extends.y * absNormal1.y + extends.z * absNormal1.z + (double3_1.x * num7 + double3_1.y * num8 + double3_1.z * num9) < -num1 || extends.x * absNormal2.x + extends.y * absNormal2.y + extends.z * absNormal2.z + (double3_2.x * num7 + double3_2.y * num8 + double3_2.z * num9) < -num2 || extends.x * absNormal3.x + extends.y * absNormal3.y + extends.z * absNormal3.z + (double3_3.x * num7 + double3_3.y * num8 + double3_3.z * num9) < -num3 || extends.x * absNormal4.x + extends.y * absNormal4.y + extends.z * absNormal4.z + (double3_4.x * num7 + double3_4.y * num8 + double3_4.z * num9) < -num4 || extends.x * absNormal5.x + extends.y * absNormal5.y + extends.z * absNormal5.z + (double3_5.x * num7 + double3_5.y * num8 + double3_5.z * num9) < -num5 || extends.x * absNormal6.x + extends.y * absNormal6.y + extends.z * absNormal6.z + (double3_6.x * num7 + double3_6.y * num8 + double3_6.z * num9) < -num6;
local.InFrustum = !flag;
}
else
local.InFrustum = true;
}
}
private void SetPlanes(Plane[] planes)
{
_planes = planes;
for (int index = 0; index < 6; ++index)
{
Plane plane = _planes[index];
Vector3 vector = plane.normal;
Double3 double3 = new Double3(in vector);
_absNormals[index] = new Double3(Math.Abs(double3.x), Math.Abs(double3.y), Math.Abs(double3.z));
_planeNormal[index] = double3;
_planeDistance[index] = plane.distance;
}
}
public struct Cell
{
public double DistanceX;
public double DistanceZ;
public bool InFrustum;
public Double3 Center;
public Double3 Extends;
public double HeightMin;
public double HeightMax;
public bool HeightOverwrite;
}
public readonly struct Double3
{
public readonly double x;
public readonly double y;
public readonly double z;
public Double3(in Vector3 vector)
{
x = (double)vector.x;
y = (double)vector.y;
z = (double)vector.z;
}
public Double3(double x, double y, double z)
{
this.x = x;
this.y = y;
this.z = z;
}
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4cf46f5589518ec4f9ea40aaf072cfbf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+85
View File
@@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Assets.ThoMagic.Renderer
{
internal class CellLayoutPool
{
private static readonly Dictionary<CellLayout, int> _hashLookup = new Dictionary<CellLayout, int>();
private static readonly Dictionary<int, CellLayout> _sharedCells = new Dictionary<int, CellLayout>();
private static readonly Dictionary<int, HashSet<int>> _usageTracker = new Dictionary<int, HashSet<int>>();
public static int Count => CellLayoutPool._sharedCells.Count;
internal static bool Validate() => CellLayoutPool._hashLookup.Count == CellLayoutPool._sharedCells.Count && CellLayoutPool._hashLookup.Count == CellLayoutPool._usageTracker.Count;
public static CellLayout Get(
object owner,
float cellSizeX,
float cellSizeZ,
int cellsX,
int cellsZ,
Bounds bounds)
{
if (owner == null)
throw new ArgumentNullException(nameof(owner));
int hashCode = CellLayoutPool.GetHashCode(cellSizeX, cellSizeZ, cellsX, cellsZ, bounds.min);
CellLayout key;
if (!CellLayoutPool._sharedCells.TryGetValue(hashCode, out key))
{
CellLayoutPool._sharedCells[hashCode] = key = new CellLayout(cellSizeX, cellSizeZ, bounds);
CellLayoutPool._hashLookup[key] = hashCode;
}
CellLayoutPool.IncreaseUsage(hashCode, owner);
return key;
}
public static void Return(object owner, CellLayout layout)
{
if (owner == null)
throw new ArgumentNullException(nameof(owner));
int hash = layout != null ? CellLayoutPool.GetHashCode(layout) : throw new ArgumentNullException(nameof(layout));
if (CellLayoutPool.DecreaseUsage(hash, owner) != 0)
return;
CellLayoutPool.Dispose(hash, layout);
}
private static void Dispose(int hash, CellLayout layout)
{
CellLayoutPool._usageTracker.Remove(hash);
CellLayoutPool._sharedCells.Remove(hash);
CellLayoutPool._hashLookup.Remove(layout);
}
private static int GetHashCode(CellLayout layout) => CellLayoutPool._hashLookup[layout];
private static void IncreaseUsage(int hash, object owner)
{
HashSet<int> intSet;
if (!CellLayoutPool._usageTracker.TryGetValue(hash, out intSet) || intSet == null)
CellLayoutPool._usageTracker[hash] = intSet = new HashSet<int>();
int hashCode = owner.GetHashCode();
if (intSet.Contains(hashCode))
return;
intSet.Add(hashCode);
}
private static int DecreaseUsage(int hash, object owner)
{
HashSet<int> intSet = CellLayoutPool._usageTracker[hash];
intSet.Remove(owner.GetHashCode());
return intSet.Count;
}
private static int GetHashCode(
float cellSizeX,
float cellSizeZ,
int cellsX,
int cellsZ,
Vector3 initialOrigin)
{
return HashCode.Combine<float, float, int, int, Vector3>(cellSizeX, cellSizeZ, cellsX, cellsZ, initialOrigin);
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d431ea020bbd81c42b897bd3afc640cb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+97
View File
@@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Assets.ThoMagic.Renderer
{
public class DrawCall
{
public readonly Mesh Mesh;
public readonly Material[] Materials;
public readonly RenderParams[] RenderParams;
private readonly Matrix4x4 _localToWorldMatrix;
private readonly uint baseIndirectArgsIndex;
private readonly int lodNr;
public DrawCall(
int lodNr,
Mesh mesh,
Material[] materials,
Matrix4x4 localToWorldMatrix,
uint baseIndirectArgsIndex,
List<GraphicsBuffer.IndirectDrawIndexedArgs> indirectDrawIndexedArgs,
in RenderParams renderParams)
{
if (mesh == null)
throw new ArgumentNullException(nameof(mesh));
if (materials == null)
throw new ArgumentNullException(nameof(materials));
if (materials.Length != mesh.subMeshCount)
throw new IndexOutOfRangeException(nameof(materials));
this.baseIndirectArgsIndex = baseIndirectArgsIndex;
_localToWorldMatrix = localToWorldMatrix;
Mesh = mesh;
Materials = materials;
RenderParams = new RenderParams[mesh.subMeshCount];
this.lodNr = lodNr;
for (int index = 0; index < mesh.subMeshCount; ++index)
{
RenderParams[index] = renderParams;
RenderParams[index].material = materials[index];
RenderParams[index].matProps = new MaterialPropertyBlock();
RenderParams[index].matProps.SetMatrix("trInstanceMatrix", _localToWorldMatrix);
RenderParams[index].matProps.SetInteger("trLodNr", lodNr + 1);
RenderParams[index].worldBounds = new Bounds(Vector3.zero, 1000f * Vector3.one);
indirectDrawIndexedArgs.Add(new GraphicsBuffer.IndirectDrawIndexedArgs
{
indexCountPerInstance = mesh.GetIndexCount(index),
baseVertexIndex = mesh.GetBaseVertex(index),
startIndex = mesh.GetIndexStart(index),
startInstance = 0,
instanceCount = 0
});
}
}
public virtual void SetInstances(ComputeBuffer instances, ComputeBuffer visibleIndexBuffer, ComputeBuffer metaBuffer)
{
for (int index = 0; index < this.RenderParams.Length; ++index)
{
RenderParams[index].matProps.SetBuffer("trInstances", instances);
RenderParams[index].matProps.SetBuffer("trPerCamVisibleIndexesBuffer", visibleIndexBuffer);
RenderParams[index].matProps.SetBuffer("trPerCamMeta", metaBuffer);
}
}
public virtual void Draw(Camera camera, ObjectData obj, GraphicsBuffer indirectDrawIndexedArgs)
{
if (Materials == null)
throw new ObjectDisposedException("Materials");
if (Mesh == null)
throw new ObjectDisposedException("Mesh");
var bounds = new Bounds(camera.transform.position, Vector3.one * 1000f);
for (int index = 0; index < RenderParams.Length; ++index)
{
var renderParam = RenderParams[index];
if (renderParam.material != null)
{
renderParam.camera = camera;
renderParam.worldBounds = bounds;
Graphics.RenderMeshIndirect(in renderParam, Mesh, indirectDrawIndexedArgs, 1, obj.indirectArgsPerSubmeshOffsets[(int)baseIndirectArgsIndex + index]);
}
}
}
internal virtual void Dispose()
{
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0345ca3ff11f4114a9cb20dfdeee77a5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+65
View File
@@ -0,0 +1,65 @@
using System.Collections.Generic;
using UnityEngine;
namespace Assets.ThoMagic.Renderer
{
public class DrawGroup
{
private List<DrawCall> _drawCalls = new List<DrawCall>();
public void Add(DrawCall drawCall) => _drawCalls.Add(drawCall);
private int lodNr;
public DrawGroup(int lodNr)
{
this.lodNr = lodNr;
}
public uint Add(
Mesh mesh,
Material[] materials,
Matrix4x4 matrix,
uint indirectArgsCount,
List<GraphicsBuffer.IndirectDrawIndexedArgs> indirectDrawIndexedArgs,
in RenderParams renderParams)
{
var drawCall = new DrawCall(lodNr, mesh, materials, matrix, indirectArgsCount, indirectDrawIndexedArgs, in renderParams);
_drawCalls.Add(drawCall);
return (uint)drawCall.Mesh.subMeshCount;
}
public uint Add(
BillboardAsset mesh,
Material material,
Matrix4x4 matrix,
uint indirectArgsCount,
List<GraphicsBuffer.IndirectDrawIndexedArgs> indirectDrawIndexedArgs,
in RenderParams renderParams)
{
var drawCall = new BillboardDrawCall(lodNr, mesh, material, matrix, indirectArgsCount, indirectDrawIndexedArgs, in renderParams);
_drawCalls.Add(drawCall);
return (uint)drawCall.Mesh.subMeshCount;
}
public void Draw(Camera camera, ObjectData obj, GraphicsBuffer indirectDrawIndexedArgs)
{
foreach (var drawCall in _drawCalls)
drawCall?.Draw(camera, obj, indirectDrawIndexedArgs);
}
public virtual void SetInstances(ComputeBuffer instances, ComputeBuffer visibleIndexBuffer, ComputeBuffer metaBuffer)
{
foreach (var drawCall in _drawCalls)
drawCall?.SetInstances(instances, visibleIndexBuffer, metaBuffer);
}
internal void Dispose()
{
foreach (var drawCall in _drawCalls)
drawCall?.Dispose();
_drawCalls.Clear();
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 14eaba28b1cfbac43acbf8a698ee5dbe
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+90
View File
@@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine;
using UnityEngine.Rendering;
namespace Assets.ThoMagic.Renderer
{
public class FrameRenderer
{
[RuntimeInitializeOnLoadMethod]
public static void Initialize()
{
#if UNITY_EDITOR
AssemblyReloadEvents.beforeAssemblyReload += AssemblyReloadEvents_beforeAssemblyReload;
#endif
RendererPool.RemoveDestroyedCameras();
RendererPool.Initialize();
Camera.onPreCull -= RenderCamera;
Camera.onPreCull += RenderCamera;
RenderPipelineManager.beginContextRendering -= OnBeginContextRendering;
RenderPipelineManager.beginContextRendering += OnBeginContextRendering;
}
private static void AssemblyReloadEvents_beforeAssemblyReload()
{
RendererPool.Destroy();
}
private static void OnBeginContextRendering(
ScriptableRenderContext context,
List<Camera> cameras)
{
RendererPool.BuildBuffers();
RendererPool.RemoveDestroyedCameras();
foreach(var camera in cameras)
{
if (!CameraIsSupported(camera))
{
RendererPool.RemoveCamera(camera.GetHashCode());
}
else
{
CameraRenderer cameraRenderer = RendererPool.GetCamera(RendererPool.RegisterCamera(camera));
try
{
cameraRenderer.Render();
}
catch (Exception ex)
{
Debug.LogException(ex);
}
}
}
}
private static void RenderCamera(Camera camera)
{
if (camera == null)
throw new NullReferenceException(nameof(camera));
if (!CameraIsSupported(camera))
{
RendererPool.RemoveCamera(camera.GetHashCode());
}
else
{
RendererPool.BuildBuffers();
RendererPool.RemoveDestroyedCameras();
int cameraId = RendererPool.RegisterCamera(camera);
try
{
RendererPool.GetCamera(cameraId)?.Render();
}
catch (Exception ex)
{
Debug.LogException(ex);
}
}
}
private static bool CameraIsSupported(Camera camera)
{
IInstanceRenderSettings instanceRenderSettings;
return (Application.isPlaying || camera.cameraType != CameraType.Preview) && (!camera.TryGetComponent(out instanceRenderSettings) || instanceRenderSettings.Settings.Supported);
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e9b14e1d6d523834db575b4973d22c0e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+196
View File
@@ -0,0 +1,196 @@
using System.Collections.Generic;
using UnityEngine;
namespace Assets.ThoMagic.Renderer
{
public class GrassRenderer : GrassStreamer
{
private readonly Terrain terrain;
private readonly TerrainData terrainData;
private bool isDirty;
public GrassRenderer(Terrain terrain)
: base(terrain)
{
this.terrain = terrain;
terrainData = terrain.terrainData;
}
public void OnTerrainChanged(TerrainChangedFlags flags)
{
if (flags.HasFlag(TerrainChangedFlags.DelayedHeightmapUpdate))
isDirty = true;
else if (flags.HasFlag(TerrainChangedFlags.Heightmap))
isDirty = true;
else if (flags.HasFlag(TerrainChangedFlags.HeightmapResolution))
isDirty = true;
else if (flags.HasFlag(TerrainChangedFlags.Holes))
isDirty = true;
else if (flags.HasFlag(TerrainChangedFlags.DelayedHolesUpdate))
isDirty = true;
else if (flags.HasFlag(TerrainChangedFlags.FlushEverythingImmediately))
isDirty = true;
}
public void Destroy()
{
Recycle();
}
public void LateUpdate()
{
if (isDirty)
{
isDirty = false;
Load();
}
if (Application.isEditor && !Application.isPlaying)
RebuildChangedPrototypes();
Update();
}
public void Load()
{
DetailPrototype[] detailPrototypes = terrainData.detailPrototypes;
Recycle();
int ownerHash = GetHashCode();
if (grassPrefabs == null)
{
grassPrefabs = new List<int>();
grassPrefabSet = new List<bool>();
}
float maxDetailDistance = float.MinValue;
for (int index = 0; index < detailPrototypes.Length; ++index)
{
if (!detailPrototypes[index].usePrototypeMesh || detailPrototypes[index].prototype == null)
{
AddDummy(index);
}
else
{
GameObject prototypeToRender = RendererUtility.GetPrototypeToRender(detailPrototypes[index]);
if (!RendererUtility.SupportsProceduralInstancing(prototypeToRender))
{
AddDummy(index);
}
else
{
var settings = GetSettingsOrDefault(prototypeToRender);
maxDetailDistance = Mathf.Max(maxDetailDistance, settings.Settings.RenderDistance);
grassPrefabs.Add(RendererPool.RegisterObject(prototypeToRender, settings, this, ownerHash));
grassPrefabSet.Add(true);
}
}
}
Build(maxDetailDistance, detailPrototypes.Length);
}
private void AddDummy(int i)
{
grassPrefabs.Add(-1);
grassPrefabSet.Add(false);
}
public override void Recycle()
{
if (grassPrefabs == null)
return;
base.Recycle();
int ownerHash = GetHashCode();
for (int index = 0; index < grassPrefabs.Count; ++index)
{
if (grassPrefabSet[index])
{
RendererPool.RemoveObject(grassPrefabs[index], ownerHash);
}
}
grassPrefabs.Clear();
grassPrefabSet.Clear();
}
private void RebuildChangedPrototypes()
{
DetailPrototype[] detailPrototypes = terrainData.detailPrototypes;
if (detailPrototypes.Length != grassPrefabs.Count)
{
Load();
}
else
{
int ownerHash = GetHashCode();
for (int index = 0; index < grassPrefabs.Count; ++index)
{
if (grassPrefabSet[index])
{
GameObject prototypeToRender = RendererUtility.GetPrototypeToRender(detailPrototypes[index]);
GameObject gameObject = RendererPool.GetObject(grassPrefabs[index]);
if (prototypeToRender != gameObject)
{
RendererPool.RemoveObject(grassPrefabs[index], ownerHash);
if (prototypeToRender != null)
{
grassPrefabs[index] = RendererPool.RegisterObject(prototypeToRender, GetSettingsOrDefault(prototypeToRender), this, ownerHash);
grassPrefabSet[index] = true;
}
else
{
grassPrefabs[index] = -1;
grassPrefabSet[index] = false;
}
}
else if (RendererPool.ContentHashChanged(grassPrefabs[index]))
{
RendererPool.RemoveObject(grassPrefabs[index], GetHashCode());
if (prototypeToRender != null)
{
grassPrefabs[index] = RendererPool.RegisterObject(prototypeToRender, GetSettingsOrDefault(prototypeToRender), this, ownerHash);
grassPrefabSet[index] = true;
}
else
{
grassPrefabs[index] = -1;
grassPrefabSet[index] = false;
}
}
else if (prototypeToRender != null)
RendererPool.SetObjectSettings(prototypeToRender, GetSettingsOrDefault(prototypeToRender));
}
}
}
}
private IInstanceRenderSettings GetSettingsOrDefault(GameObject gameObject)
{
IInstanceRenderSettings instanceRenderSettings;
return gameObject.TryGetComponent(out instanceRenderSettings) ? instanceRenderSettings : new DefaultRenderSettings();
}
private class DefaultRenderSettings : IInstanceRenderSettings
{
public InstanceRenderSettings Settings => new InstanceRenderSettings()
{
Render = true,
DensityInDistance = 0.125f,
DensityInDistanceFalloff = new Vector2(0.08f, 0.0075f),
RenderDistance = 150f,
ShadowDistance = 50f,
Shadows = true,
Supported = true
};
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cdb66824e49e1344faf75691d1c8788d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+301
View File
@@ -0,0 +1,301 @@
using System.Collections.Generic;
using UnityEngine;
namespace Assets.ThoMagic.Renderer
{
/// <summary>
/// Subdivides a terrain into smaller 2d-cells in the xz-plane based on terrain settings. Only cells in range 'maxDetailDistance' of the camera
/// are alive.
/// </summary>
public class GrassStreamer : InstanceStreamer
{
/// <summary>
/// The limit of how many instances are created per frame.
/// TODO: While the setting is global, the budget is per streamer and each
/// terrain has it's own streamer. This should be a real global budget.
/// </summary>
static int globalStreamingBudget = 10000;
protected List<int> grassPrefabs;
protected List<bool> grassPrefabSet;
private readonly Terrain terrain;
private readonly TerrainData terrainData;
/// <summary>
/// Used to find cells in range of the camera.
/// </summary>
private float maxDetailDistance;
private Bounds worldBounds;
private int layerCount;
private Cell[] Cells;
private float cellSizeX, cellSizeZ;
private int cellsX, cellsZ;
/// <summary>
/// A queue with a list of cells to load. Cells are loaded per frame
/// til the streaming budget is spent.
/// </summary>
private Queue<int> cellsToLoad = new Queue<int>();
/// <summary>
/// List of cells unloaded in a frame, only temporary list used to remove from loadedCells.
/// </summary>
private List<int> cellsToUnloaded = new List<int>();
//A map of loaded cells. The cell index is z * cellCountX + x.
private Dictionary<int, Cell> loadedCells = new Dictionary<int, Cell>();
public static void SetStreamingBudget(int budget) => globalStreamingBudget = budget;
private static float CalculatePatchSizeX(TerrainData terrainData) => (float)((double)terrainData.detailResolutionPerPatch / (double)terrainData.detailResolution * terrainData.size.x);
private static float CalculatePatchSizeZ(TerrainData terrainData) => (float)((double)terrainData.detailResolutionPerPatch / (double)terrainData.detailResolution * terrainData.size.z);
public GrassStreamer(
Terrain terrain)
: base()
{
this.terrain = terrain;
terrainData = terrain.terrainData;
}
/// <summary>
/// Update loaded cells. Called by renderer. The renderer calls this once each frame.
/// </summary>
public void Update()
{
//Load cells until either all cells are loaded or budget is spent.
//Returns the budget left.
int streamingBudget = UpdateLoadCellsAsync();
//Go through all loaded cells and reduce 'inRangeOfAnyCamera'.
//If 'inRangeOfAnyCamera' is equal or below zero unload the cell.
//'inRangeOfAnyCamera' is reset to x if a camera renders and this
//cell is in range.
foreach (var cellIndex in loadedCells.Keys)
{
var cell = loadedCells[cellIndex];
//If cell is out of range and budget exist, unload.
if (cell.inRangeOfAnyCamera <= 0 && streamingBudget > 0)
{
streamingBudget -= UnloadCell(cellIndex);
cellsToUnloaded.Add(cellIndex);
}
--cell.inRangeOfAnyCamera;
}
//Remove all cells unloaded from 'loadedCells'.
if (cellsToUnloaded.Count > 0)
{
foreach (var id in cellsToUnloaded)
loadedCells.Remove(id);
cellsToUnloaded.Clear();
}
}
/// <summary>
/// Rebuild the cell layout. Called on initialize and on certain terrain changes.
/// </summary>
/// <param name="maxDistance">Max distance where a cell is in range</param>
/// <param name="layerCount">Count of detail layers</param>
public void Build(float maxDistance, int layerCount)
{
maxDetailDistance = maxDistance;
this.layerCount = layerCount;
cellSizeX = CalculatePatchSizeX(terrainData);
cellSizeZ = CalculatePatchSizeZ(terrainData);
Vector3 position = terrain.GetPosition();
worldBounds = new Bounds(terrainData.bounds.center + position, terrainData.bounds.size);
cellsX = Mathf.CeilToInt(worldBounds.size.x / cellSizeX);
cellsZ = Mathf.CeilToInt(worldBounds.size.z / cellSizeZ);
Cells = new Cell[cellsX * cellsZ];
float cellSizeX_2 = cellSizeX * 0.5f;
float cellSizeZ_2 = cellSizeZ * 0.5f;
for (int indexZ = 0; indexZ < cellsZ; ++indexZ)
{
for (int indexX = 0; indexX < cellsX; ++indexX)
{
int cellIndex = indexZ * cellsX + indexX;
Cells[cellIndex] = new Cell();
float cellCenterX = indexX * cellSizeX + cellSizeX_2;
float cellCenterZ = indexZ * cellSizeZ + cellSizeZ_2;
Cells[cellIndex].CenterX = cellCenterX;
Cells[cellIndex].CenterZ = cellCenterZ;
}
}
}
/// <summary>
/// Is this streamer within range?
/// </summary>
/// <param name="camera">The camera to check for</param>
/// <param name="planes">Planes of the camera frustum</param>
/// <returns></returns>
public override bool IsInRange(Camera camera, Plane[] planes)
{
if (layerCount == 0)
return false;
if (!terrain.editorRenderFlags.HasFlag(TerrainRenderFlags.Details))
return false;
var eyePos = camera.transform.position;
if ((eyePos - worldBounds.ClosestPoint(eyePos)).magnitude <= Mathf.Min(camera.farClipPlane, maxDetailDistance))
return true;
return false;
}
/// <summary>
/// Load cells until either all cells are loaded from the cellsToLoad list
/// or the budget is spent.
/// </summary>
/// <returns>Unspent budget</returns>
private int UpdateLoadCellsAsync()
{
int streamingBudget = globalStreamingBudget;
if (globalStreamingBudget <= 0 || Cells == null || Cells.Length == 0)
return streamingBudget;
while(streamingBudget > 0 && cellsToLoad.TryDequeue(out var cellIndex))
{
streamingBudget -= LoadCell(cellIndex);
}
return streamingBudget;
}
public virtual void Recycle()
{
UnloadAll();
}
private void UnloadAll()
{
if (Cells == null || Cells.Length == 0)
return;
for (int cellIndex = 0; cellIndex < Cells.Length; ++cellIndex)
{
UnloadCell(cellIndex);
}
loadedCells.Clear();
}
/// <summary>
/// Remove all instances of a cell.
/// </summary>
/// <param name="cellIndex"></param>
/// <returns>The amount of deleted instances</returns>
private int UnloadCell(int cellIndex)
{
if (cellIndex < 0 || cellIndex >= Cells.Length || !Cells[cellIndex].IsLoaded)
return 0;
var cell = Cells[cellIndex];
cell.IsLoaded = false;
cell.IsLoading = false;
int deletedInstances = cell.loadedInstances.Count;
foreach (var instanceInfo in cell.loadedInstances)
RemoveInstance(instanceInfo.objectId, instanceInfo.instanceId);
cell.loadedInstances.Clear();
return deletedInstances;
}
/// <summary>
/// Load all instances for a cell.
/// </summary>
/// <param name="cellIndex"></param>
/// <returns>The amount of loaded instances</returns>
private int LoadCell(int cellIndex)
{
if (cellIndex < 0 || cellIndex >= Cells.Length || Cells[cellIndex].IsLoaded)
return 0;
var cell = Cells[cellIndex];
cell.IsLoaded = true;
cell.IsLoading = false;
loadedCells.Add(cellIndex, cell);
int x = Mathf.FloorToInt(cell.CenterX / cellSizeX);
int z = Mathf.FloorToInt(cell.CenterZ / cellSizeZ);
Vector3 size = terrainData.size;
Vector3 terrainOffset = terrain.transform.position;
Quaternion terrainRotation = terrain.transform.rotation;
float detailObjectDensity = terrain.detailObjectDensity;
for (int layer = 0; layer < layerCount; layer++)
{
var instanceTransforms = terrainData.ComputeDetailInstanceTransforms(x, z, layer, detailObjectDensity, out var _);
for (int index = 0; index < instanceTransforms.Length; ++index)
{
var local = instanceTransforms[index];
Vector3 interpolatedNormal = terrainData.GetInterpolatedNormal((local.posX / size.x), (local.posZ / size.z));
Quaternion rotation = terrainRotation * Quaternion.FromToRotation(Vector3.up, interpolatedNormal) * Quaternion.AngleAxis((local.rotationY * 57.2957801818848f), Vector3.up);
var instanceId = AddInstance(grassPrefabs[layer], new Vector3(local.posX, local.posY, local.posZ) + terrainOffset, rotation, local.scaleXZ, local.scaleY);
cell.loadedInstances.Add(new InstanceInfo { instanceId = instanceId, objectId = grassPrefabs[layer] });
}
}
return cell.loadedInstances.Count;
}
/// <summary>
/// Called by each camera at the start of a frame.
/// Finds all cells visible in range of a camera. Either
/// resets the 'inRangeOfAnyCamera' counter or adds the cell
/// to the load list.
/// </summary>
/// <param name="camera"></param>
/// <param name="planes"></param>
public override void UpdateForCamera(Camera camera, Plane[] planes)
{
if (Cells == null || Cells.Length == 0)
return;
var range = Mathf.Min(camera.farClipPlane, maxDetailDistance);
var rangeX = Mathf.CeilToInt((range + cellSizeX * 0.5f) / cellSizeX);
var rangeZ = Mathf.CeilToInt((range + cellSizeZ * 0.5f) / cellSizeZ);
var eyePos = camera.transform.position - worldBounds.min;
var eyeCellX = Mathf.FloorToInt(eyePos.x / cellSizeX);
var eyeCellZ = Mathf.FloorToInt(eyePos.z / cellSizeZ);
for(int indexZ = eyeCellZ - rangeZ; indexZ < eyeCellZ + rangeZ; indexZ++)
for (int indexX = eyeCellX - rangeX; indexX < eyeCellX + rangeX; indexX++)
{
if(indexZ >= 0 && indexZ < cellsZ &&
indexX >= 0 && indexX < cellsX)
{
int cellIndex = indexZ * cellsX + indexX;
if (Cells[cellIndex].IsLoaded || Cells[cellIndex].IsLoading)
{
Cells[cellIndex].inRangeOfAnyCamera = 2;
}
else
{
Cells[cellIndex].IsLoading = true;
Cells[cellIndex].inRangeOfAnyCamera = 2;
cellsToLoad.Enqueue(cellIndex);
}
}
}
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a9909a9557ba0b94e9de512444a0629e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+26
View File
@@ -0,0 +1,26 @@
using System.Collections.Generic;
using System.Threading;
namespace Assets.ThoMagic.Renderer
{
public class InstanceBuffer
{
private static int nextInstanceBufferId = 1;
private readonly int instanceBufferId;
public readonly ObjectData objectData;
public readonly Dictionary<int, InstanceData> instanceData;
public InstanceBuffer(ObjectData objectData)
{
this.objectData = objectData;
instanceData = new Dictionary<int, InstanceData>();
instanceBufferId = Interlocked.Increment(ref InstanceBuffer.nextInstanceBufferId);
}
public override int GetHashCode()
{
return instanceBufferId;
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1d85f6533dc1bb84bb236fd27ab68b38
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+174
View File
@@ -0,0 +1,174 @@
using UnityEngine;
namespace Assets.ThoMagic.Renderer
{
public struct InstanceData
{
public const int Add = 1;
public const int Delete = 2;
public static readonly int Stride = 48;
public readonly Vector4 Packed1;
public readonly Vector4 Packed2;
public readonly uint prefabId;
//Used to stream out new instances
public readonly int uploadAction;
public readonly int uploadId;
public readonly int padding;
public InstanceData(
int uploadAction,
int uploadId,
uint prefabId,
float x,
float y,
float z,
in Quaternion rotation,
float scaleXZ,
float scaleY)
{
float num = rotation.w > 0.0 ? 1f : -1f;
Packed1.x = x;
Packed1.y = y;
Packed1.z = z;
Packed1.w = (rotation.x * num);
Packed2.x = scaleXZ;
Packed2.y = scaleY;
Packed2.z = (rotation.y * num);
Packed2.w = (rotation.z * num);
this.prefabId = prefabId;
this.uploadAction = uploadAction;
this.uploadId = uploadId;
padding = 0;
}
public InstanceData(
int uploadAction,
int uploadId)
{
Packed1.x = 0;
Packed1.y = 0;
Packed1.z = 0;
Packed1.w = 0;
Packed2.x = 0;
Packed2.y = 0;
Packed2.z = 0;
Packed2.w = 0;
this.prefabId = 0;
this.uploadAction = uploadAction;
this.uploadId = uploadId;
padding = 0;
}
}
public struct PrefabData
{
public static readonly int Stride = 228;
public uint batchIndex;
public uint indexBufferStartOffset;
public uint maxCount;
public uint lodCount;
public uint fadeLods;
public Vector4 densityInDistance;
public Vector4 lodData0;
public Vector4 lodData1;
public Vector4 lodData2;
public Vector4 lodData3;
public Vector4 lodData4;
public Vector4 lodData5;
public Vector4 lodData6;
public Vector4 lodData7;
public uint indirectArgsOffset0;
public uint indirectArgsCount0;
public uint indirectArgsOffset1;
public uint indirectArgsCount1;
public uint indirectArgsOffset2;
public uint indirectArgsCount2;
public uint indirectArgsOffset3;
public uint indirectArgsCount3;
public uint indirectArgsOffset4;
public uint indirectArgsCount4;
public uint indirectArgsOffset5;
public uint indirectArgsCount5;
public uint indirectArgsOffset6;
public uint indirectArgsCount6;
public uint indirectArgsOffset7;
public uint indirectArgsCount7;
public void SetLods(Vector4[] lodData, uint[] indirectArgsPerLodOffset, uint[] indirectArgsPerLodCount)
{
this.lodData0 = lodCount > 0 ? lodData[0] : Vector4.zero;
this.lodData1 = lodCount > 1 ? lodData[1] : Vector4.zero;
this.lodData2 = lodCount > 2 ? lodData[2] : Vector4.zero;
this.lodData3 = lodCount > 3 ? lodData[3] : Vector4.zero;
this.lodData4 = lodCount > 4 ? lodData[4] : Vector4.zero;
this.lodData5 = lodCount > 5 ? lodData[5] : Vector4.zero;
this.lodData6 = lodCount > 6 ? lodData[6] : Vector4.zero;
this.lodData7 = lodCount > 7 ? lodData[7] : Vector4.zero;
this.indirectArgsOffset0 = lodCount > 0 ? indirectArgsPerLodOffset[0] : 0;
this.indirectArgsOffset1 = lodCount > 1 ? indirectArgsPerLodOffset[1] : 0;
this.indirectArgsOffset2 = lodCount > 2 ? indirectArgsPerLodOffset[2] : 0;
this.indirectArgsOffset3 = lodCount > 3 ? indirectArgsPerLodOffset[3] : 0;
this.indirectArgsOffset4 = lodCount > 4 ? indirectArgsPerLodOffset[4] : 0;
this.indirectArgsOffset5 = lodCount > 5 ? indirectArgsPerLodOffset[5] : 0;
this.indirectArgsOffset6 = lodCount > 6 ? indirectArgsPerLodOffset[6] : 0;
this.indirectArgsOffset7 = lodCount > 7 ? indirectArgsPerLodOffset[7] : 0;
this.indirectArgsCount0 = lodCount > 0 ? indirectArgsPerLodCount[0] : 0;
this.indirectArgsCount1 = lodCount > 1 ? indirectArgsPerLodCount[1] : 0;
this.indirectArgsCount2 = lodCount > 2 ? indirectArgsPerLodCount[2] : 0;
this.indirectArgsCount3 = lodCount > 3 ? indirectArgsPerLodCount[3] : 0;
this.indirectArgsCount4 = lodCount > 4 ? indirectArgsPerLodCount[4] : 0;
this.indirectArgsCount5 = lodCount > 5 ? indirectArgsPerLodCount[5] : 0;
this.indirectArgsCount6 = lodCount > 6 ? indirectArgsPerLodCount[6] : 0;
this.indirectArgsCount7 = lodCount > 7 ? indirectArgsPerLodCount[7] : 0;
}
public PrefabData(
uint batchIndex,
uint indexBufferStartOffset,
uint maxCount,
uint lodCount,
uint fadeLods,
Vector4 densityInDistance,
Vector4[] lodData,
uint[] indirectArgsPerLodOffset,
uint[] indirectArgsPerLodCount
)
{
this.batchIndex = batchIndex;
this.indexBufferStartOffset = indexBufferStartOffset;
this.maxCount = maxCount;
this.lodCount = lodCount;
this.fadeLods = fadeLods;
this.densityInDistance = densityInDistance;
this.lodData0 = lodCount > 0 ? lodData[0] : Vector4.zero;
this.lodData1 = lodCount > 1 ? lodData[1] : Vector4.zero;
this.lodData2 = lodCount > 2 ? lodData[2] : Vector4.zero;
this.lodData3 = lodCount > 3 ? lodData[3] : Vector4.zero;
this.lodData4 = lodCount > 4 ? lodData[4] : Vector4.zero;
this.lodData5 = lodCount > 5 ? lodData[5] : Vector4.zero;
this.lodData6 = lodCount > 6 ? lodData[6] : Vector4.zero;
this.lodData7 = lodCount > 7 ? lodData[7] : Vector4.zero;
this.indirectArgsOffset0 = lodCount > 0 ? indirectArgsPerLodOffset[0] : 0;
this.indirectArgsOffset1 = lodCount > 1 ? indirectArgsPerLodOffset[1] : 0;
this.indirectArgsOffset2 = lodCount > 2 ? indirectArgsPerLodOffset[2] : 0;
this.indirectArgsOffset3 = lodCount > 3 ? indirectArgsPerLodOffset[3] : 0;
this.indirectArgsOffset4 = lodCount > 4 ? indirectArgsPerLodOffset[4] : 0;
this.indirectArgsOffset5 = lodCount > 5 ? indirectArgsPerLodOffset[5] : 0;
this.indirectArgsOffset6 = lodCount > 6 ? indirectArgsPerLodOffset[6] : 0;
this.indirectArgsOffset7 = lodCount > 7 ? indirectArgsPerLodOffset[7] : 0;
this.indirectArgsCount0 = lodCount > 0 ? indirectArgsPerLodCount[0] : 0;
this.indirectArgsCount1 = lodCount > 1 ? indirectArgsPerLodCount[1] : 0;
this.indirectArgsCount2 = lodCount > 2 ? indirectArgsPerLodCount[2] : 0;
this.indirectArgsCount3 = lodCount > 3 ? indirectArgsPerLodCount[3] : 0;
this.indirectArgsCount4 = lodCount > 4 ? indirectArgsPerLodCount[4] : 0;
this.indirectArgsCount5 = lodCount > 5 ? indirectArgsPerLodCount[5] : 0;
this.indirectArgsCount6 = lodCount > 6 ? indirectArgsPerLodCount[6] : 0;
this.indirectArgsCount7 = lodCount > 7 ? indirectArgsPerLodCount[7] : 0;
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 80369a8a9c9a0e64daddf39789fa0f66
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+60
View File
@@ -0,0 +1,60 @@
using System;
using UnityEngine;
namespace Assets.ThoMagic.Renderer
{
public interface IInstanceRenderSettings
{
InstanceRenderSettings Settings { get; }
}
public struct InstanceRenderSettings
{
int hashCodeCache;
public bool Supported;
public bool Render;
public float RenderDistance;
public float ShadowDistance;
public bool Shadows;
public float DensityInDistance;
public Vector2 DensityInDistanceFalloff;
public static InstanceRenderSettings Default(Camera camera) => new InstanceRenderSettings()
{
Supported = true,
Render = true,
Shadows = true,
RenderDistance = camera.farClipPlane,
ShadowDistance = camera.farClipPlane,
DensityInDistance = 1f,
DensityInDistanceFalloff = Vector2.zero,
};
public void Merge(InstanceRenderSettings other)
{
Supported = Supported && other.Supported;
Render = Render && other.Render;
Shadows = Shadows && other.Shadows;
if (RenderDistance > 0.0f && other.RenderDistance > 0.0f)
RenderDistance = Mathf.Min(RenderDistance, other.RenderDistance);
else if (other.RenderDistance > 0.0f)
RenderDistance = other.RenderDistance;
if (ShadowDistance > 0.0f && other.ShadowDistance > 0.0f)
ShadowDistance = Mathf.Min(ShadowDistance, other.ShadowDistance);
else if (other.ShadowDistance > 0.0f)
ShadowDistance = other.ShadowDistance;
DensityInDistance = Mathf.Min(DensityInDistance, other.DensityInDistance);
DensityInDistanceFalloff.x = Mathf.Max(DensityInDistanceFalloff.x, other.DensityInDistanceFalloff.x);
DensityInDistanceFalloff.y = Mathf.Max(DensityInDistanceFalloff.y, other.DensityInDistanceFalloff.y);
hashCodeCache = HashCode.Combine(Render, Shadows, RenderDistance, ShadowDistance, DensityInDistance, DensityInDistanceFalloff.x, DensityInDistanceFalloff.y);
}
public override int GetHashCode()
{
if(hashCodeCache == 0 || (Application.isEditor && !Application.isPlaying))
hashCodeCache = HashCode.Combine(Render, Shadows, RenderDistance, ShadowDistance, DensityInDistance, DensityInDistanceFalloff.x, DensityInDistanceFalloff.y);
return hashCodeCache;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ada01dd02c566634092d55b23558e707
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+89
View File
@@ -0,0 +1,89 @@
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
namespace Assets.ThoMagic.Renderer
{
public class InstanceStreamer
{
public struct InstanceInfo
{
public int instanceId;
public int objectId;
}
public class Cell
{
public float CenterX;
public float CenterZ;
public bool IsLoaded;
public bool IsLoading;
public int inRangeOfAnyCamera;
public List<InstanceInfo> loadedInstances = new List<InstanceInfo>();
}
internal static int nextStreamerId = 1;
protected readonly int streamerInstanceId = Interlocked.Increment(ref nextStreamerId);
public readonly HashSet<int> owners = new HashSet<int>();
public Dictionary<int, HashSet<uint>> objectInstanceIds = new Dictionary<int, HashSet<uint>>();
public virtual int AddInstance(int objectId, Vector3 pos, Quaternion orientation, float scaleXZ, float scaleY)
{
var instanceId = RendererPool.AddInstance(objectId, pos, orientation, scaleXZ, scaleY);
if (!objectInstanceIds.ContainsKey(objectId))
objectInstanceIds.Add(objectId, new HashSet<uint>());
objectInstanceIds[objectId].Add((uint)instanceId);
return instanceId;
}
public virtual void RemoveInstance(int objectId, int instanceId, bool noRemove = false)
{
if (noRemove)
{
RendererPool.RemoveInstance(objectId, instanceId);
}
else
{
if (!objectInstanceIds.ContainsKey(objectId))
return;
if (objectInstanceIds[objectId].Remove((uint)instanceId))
RendererPool.RemoveInstance(objectId, instanceId);
}
}
public void Clear()
{
foreach (var obj in objectInstanceIds)
{
if (!objectInstanceIds.ContainsKey(obj.Key))
continue;
foreach (var id in obj.Value)
{
RemoveInstance(obj.Key, (int)id, true);
}
}
objectInstanceIds.Clear();
}
public virtual void UpdateForCamera(Camera camera, Plane[] planes)
{
}
public virtual bool IsInRange(Camera camera, Plane[] planes)
{
return true;
}
public override int GetHashCode()
{
return streamerInstanceId.GetHashCode();//HashCode.Combine(streamerInstanceId, objectInstanceIds.Count);
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e591f7bae0131d548ab936dcae307d64
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+266
View File
@@ -0,0 +1,266 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
namespace Assets.ThoMagic.Renderer
{
public class ObjectData
{
public readonly GameObject GameObject;
public readonly HashSet<int> Owners = new HashSet<int>();
public readonly uint prefabId;
public float[] LodSizes;
public float[] LodTransitions;
public LOD[] LODs;
public Vector4[] lods = new Vector4[8];
public uint[] indirectArgsPerLodOffsets = new uint[8];
public uint[] indirectArgsPerLodCounts = new uint[8];
public uint[] indirectArgsPerLodWithShadowsCounts = new uint[8];
public List<int> indirectArgsPerSubmeshOffsets = new List<int>();
public uint lodCount = 1;
public uint fadeLods = 0;
public uint indirectArgsCount = 1;
public uint count = 0;
public IInstanceRenderSettings Settings;
public List<GraphicsBuffer.IndirectDrawIndexedArgs> indirectDrawIndexedArgs = new List<GraphicsBuffer.IndirectDrawIndexedArgs>();
public DrawGroup[] drawGroups;
public DrawGroup[] drawShadowGroups;
public int contentHashCache;
private bool _isDisposed;
public ObjectData(GameObject gameObject, IInstanceRenderSettings settings)
{
GameObject = gameObject;
Settings = settings;
var lodGroup = GameObject.GetComponent<LODGroup>();
LOD[] lodArray;
if (lodGroup == null)
lodArray = new LOD[1]
{
new LOD(0.0001f, GameObject.GetComponentsInChildren<MeshRenderer>())
};
else
lodArray = lodGroup.GetLODs();
LODs = lodArray;
LodSizes = new float[lodArray.Length];
LodTransitions = new float[lodArray.Length];
lodCount = (uint)lodArray.Length;
if (lodGroup != null && lodGroup.fadeMode == LODFadeMode.CrossFade)
fadeLods = 1;
for (int index = 0; index < lodArray.Length; ++index)
{
LodSizes[index] = CalculateBoundsForRenderers(lodArray[index].renderers);
LodTransitions[index] = lodArray[index].screenRelativeTransitionHeight;
}
if (RendererPool.prefabDataFreeSlot.Count > 0)
{
prefabId = RendererPool.prefabDataFreeSlot.Dequeue();
RendererPool.prefabData[(int)prefabId] = new PrefabData();
}
else
{
prefabId = (uint)RendererPool.prefabData.Count;
RendererPool.prefabData.Add(new PrefabData());
}
RendererPool.rebuildPrefabs = true;
indirectArgsCount = BuildRenderer(indirectDrawIndexedArgs);
}
public void Dispose()
{
if (this._isDisposed)
return;
foreach (var drawGroup in drawGroups)
{
drawGroup?.Dispose();
}
foreach (var drawGroup in drawShadowGroups)
{
drawGroup?.Dispose();
}
this._isDisposed = true;
}
private uint BuildRenderer(List<GraphicsBuffer.IndirectDrawIndexedArgs> indirectDrawIndexedArgs)
{
uint indirectArgsCount = 0;
drawGroups = new DrawGroup[lodCount];
drawShadowGroups = new DrawGroup[lodCount];
for (int index = 0; index < lodCount; ++index)
{
uint indirectArgsPerLodCount = 0;
foreach (var renderer in LODs[index].renderers)
{
if (renderer != null)
{
if (renderer is MeshRenderer)
{
var meshFilter = renderer.GetComponent<MeshFilter>();
if (meshFilter != null)
{
var sharedMesh = meshFilter.sharedMesh;
var sharedMaterials = renderer.sharedMaterials;
if (sharedMesh != null && sharedMaterials != null && sharedMaterials.Length == sharedMesh.subMeshCount)
{
if (drawGroups[index] == null)
drawGroups[index] = new DrawGroup(index);
RenderParams renderParams1 = new RenderParams();
renderParams1.layer = renderer.gameObject.layer;
renderParams1.receiveShadows = renderer.receiveShadows;
renderParams1.rendererPriority = renderer.rendererPriority;
renderParams1.renderingLayerMask = renderer.renderingLayerMask;
renderParams1.shadowCastingMode = ShadowCastingMode.Off;
renderParams1.reflectionProbeUsage = renderer.reflectionProbeUsage;
renderParams1.motionVectorMode = renderer.motionVectorGenerationMode;
var count = drawGroups[index].Add(sharedMesh, sharedMaterials, Matrix4x4.identity, //renderer.transform.localToWorldMatrix,
indirectArgsCount, indirectDrawIndexedArgs, in renderParams1);
indirectArgsPerLodCount += count;
}
}
}
else if (renderer is BillboardRenderer billboardRenderer)
{
if (billboardRenderer.billboard != null && billboardRenderer.billboard.material != null)
{
if (drawGroups[index] == null)
drawGroups[index] = new DrawGroup(index);
RenderParams renderParams1 = new RenderParams();
renderParams1.layer = renderer.gameObject.layer;
renderParams1.receiveShadows = renderer.receiveShadows;
renderParams1.rendererPriority = renderer.rendererPriority;
renderParams1.renderingLayerMask = renderer.renderingLayerMask;
renderParams1.shadowCastingMode = ShadowCastingMode.Off;
renderParams1.reflectionProbeUsage = renderer.reflectionProbeUsage;
renderParams1.motionVectorMode = renderer.motionVectorGenerationMode;
var count = drawGroups[index].Add(billboardRenderer.billboard, billboardRenderer.material, renderer.transform.localToWorldMatrix,
indirectArgsCount, indirectDrawIndexedArgs, in renderParams1);
indirectArgsPerLodCount += count;
}
}
}
}
indirectArgsPerLodCounts[index] = indirectArgsPerLodCount;
uint indirectArgsPerLodCountShadowOffset = indirectArgsPerLodCount;
foreach (var renderer in LODs[index].renderers)
{
if (renderer != null)
{
if (renderer is MeshRenderer)
{
var meshFilter = renderer.GetComponent<MeshFilter>();
if (meshFilter != null)
{
var sharedMesh = meshFilter.sharedMesh;
var sharedMaterials = renderer.sharedMaterials;
if (sharedMesh != null && sharedMaterials != null && sharedMaterials.Length == sharedMesh.subMeshCount)
{
if (renderer.shadowCastingMode > 0)
{
if (drawShadowGroups[index] == null)
drawShadowGroups[index] = new DrawGroup(index);
var renderParams1 = new RenderParams();
renderParams1.layer = renderer.gameObject.layer;
renderParams1.shadowCastingMode = ShadowCastingMode.ShadowsOnly;
renderParams1.renderingLayerMask = renderer.renderingLayerMask;
renderParams1.rendererPriority = renderer.rendererPriority;
indirectArgsPerLodCount += drawShadowGroups[index].Add(sharedMesh, sharedMaterials, Matrix4x4.identity, //renderer.transform.localToWorldMatrix,
indirectArgsPerLodCountShadowOffset + indirectArgsCount, indirectDrawIndexedArgs, in renderParams1);
}
else
{
//indirectArgsPerLodCount += (uint)sharedMesh.subMeshCount;
}
}
}
}
else if (renderer is BillboardRenderer billboardRenderer)
{
if (renderer.shadowCastingMode > 0)
{
if (billboardRenderer.billboard != null && billboardRenderer.billboard.material != null)
{
if (drawShadowGroups[index] == null)
drawShadowGroups[index] = new DrawGroup(index);
var renderParams1 = new RenderParams();
renderParams1.layer = renderer.gameObject.layer;
renderParams1.shadowCastingMode = ShadowCastingMode.ShadowsOnly;
renderParams1.renderingLayerMask = renderer.renderingLayerMask;
renderParams1.rendererPriority = renderer.rendererPriority;
indirectArgsPerLodCount += drawShadowGroups[index].Add(billboardRenderer.billboard, billboardRenderer.material, renderer.transform.localToWorldMatrix,
indirectArgsPerLodCountShadowOffset + indirectArgsCount, indirectDrawIndexedArgs, in renderParams1);
}
else
{
//indirectArgsPerLodCount += 1;
}
}
}
}
}
indirectArgsPerLodWithShadowsCounts[index] = indirectArgsPerLodCount;
indirectArgsCount += indirectArgsPerLodCount;
}
return indirectArgsCount;
}
private static float CalculateBoundsForRenderers(UnityEngine.Renderer[] renderers)
{
Bounds bounds = new Bounds();
bool first = true;
foreach (var renderer in renderers)
{
if (renderer != null)
{
if (first)
{
bounds = renderer.bounds;
first = false;
}
else
bounds.Encapsulate(renderer.bounds);
}
}
return Mathf.Max(Mathf.Max(bounds.size.x, bounds.size.y), bounds.size.z);
}
public override int GetHashCode()
{
return GameObject.GetHashCode();
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 38c29b0618523cb44971f341d53349bf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+541
View File
@@ -0,0 +1,541 @@
using System;
using System.Collections.Generic;
using System.Threading;
using Unity.Collections;
using UnityEngine;
using UnityEngine.Rendering;
namespace Assets.ThoMagic.Renderer
{
public static class RendererPool
{
public const int RESIZE_COUNT = 100000;
public const int STREAMOUT_SIZE = 10000;
public static bool isDisposed = true;
private static ComputeShader instancingShader = null;
private static CommandBuffer commandBuffer = null;
private static int uploadInstancesShaderId = 0, resizeInstanceBufferShaderId = 0;
private static object threadSecurity = new object();
private static int maxInstanceCount = 0;
private static int instanceUploadCount = 0;
private static readonly Dictionary<int, CameraRenderer> cameras = new Dictionary<int, CameraRenderer>();
public static readonly Dictionary<int, ObjectData> objects = new Dictionary<int, ObjectData>();
public static readonly Dictionary<int, InstanceStreamer> streamers = new Dictionary<int, InstanceStreamer>();
public static readonly List<ObjectData> objectsList = new List<ObjectData>();
public static readonly List<PrefabData> prefabData = new List<PrefabData>();
public static NativeArray<InstanceData> uploadInstancesArray;
public static readonly Queue<InstanceData> uploadInstancesArrayQueue = new Queue<InstanceData>();
public static readonly Queue<uint> prefabDataFreeSlot = new Queue<uint>();
public static readonly Queue<int> instanceBufferFreeSlot = new Queue<int>();
public static ComputeBuffer globalUpdateInstanceBuffer = null;
public static ComputeBuffer globalInstanceBuffer = null;
public static ComputeBuffer tempGlobalInstanceBuffer = null;
private static Vector3 _origin;
private static List<int> _keysToRemove = new List<int>();
private static bool _instanceCountChanged;
public static bool externInstanceCountChanged
{
set
{
if (value == true)
{
foreach (var camera in cameras.Values)
{
camera.instanceCountChanged = value;
}
}
}
}
public static bool instanceCountChanged
{
set
{
if (value == true && !_instanceCountChanged)
{
_instanceCountChanged = true;
foreach (var camera in cameras.Values)
{
camera.instanceCountChanged = value;
}
}
}
}
public static bool rebuildPrefabs
{
set
{
if (value == true)
{
foreach (var camera in cameras.Values)
{
camera.rebuildPrefabs = value;
}
}
}
}
public static void Destroy()
{
if (isDisposed)
return;
isDisposed = true;
_keysToRemove.Clear();
foreach (KeyValuePair<int, CameraRenderer> camera in cameras)
{
_keysToRemove.Add(camera.Key);
}
foreach (int id in _keysToRemove)
RemoveCamera(id);
maxInstanceCount = 0;
instanceUploadCount = 0;
uploadInstancesArrayQueue.Clear();
globalInstanceBuffer?.Dispose();
globalInstanceBuffer = null;
globalUpdateInstanceBuffer?.Dispose();
globalUpdateInstanceBuffer = null;
tempGlobalInstanceBuffer?.Dispose();
tempGlobalInstanceBuffer = null;
commandBuffer?.Dispose();
commandBuffer = null;
prefabDataFreeSlot.Clear();
instanceBufferFreeSlot.Clear();
_keysToRemove.Clear();
InstanceStreamer.nextStreamerId = 1;
CameraRenderer.nextCameraRendererId = 1;
uploadInstancesArray.Dispose();
}
internal static void BuildBuffers()
{
Monitor.Enter(threadSecurity);
bool deleteOldBuffer = false;
//Load shader TODO: (Move this to init?)
if(instancingShader == null)
{
uploadInstancesArray = new NativeArray<InstanceData>(STREAMOUT_SIZE, Allocator.Persistent);
instancingShader = UnityEngine.Object.Instantiate(Resources.Load<ComputeShader>("ThoMagic Renderer Instancing"));
instancingShader.hideFlags = HideFlags.HideAndDontSave;
uploadInstancesShaderId = instancingShader.FindKernel("Upload_64");
resizeInstanceBufferShaderId = instancingShader.FindKernel("Resize_64");
instancingShader.name = $"ThoMagic Renderer Instancing - RendererPool";
commandBuffer = new CommandBuffer();
}
commandBuffer.Clear();
//Are there queued instances ready for upload no current upload is planned?
if (instanceUploadCount == 0 && uploadInstancesArrayQueue.Count > 0)
{
int take = Math.Min(STREAMOUT_SIZE, uploadInstancesArrayQueue.Count);
for (int i = 0; i < take; i++)
{
uploadInstancesArray[instanceUploadCount++] = uploadInstancesArrayQueue.Dequeue();
}
instanceCountChanged = true;
}
//Upload instances
if (_instanceCountChanged && maxInstanceCount > 0)
{
//Create update buffer if null. TODO: Move to init?
if(globalUpdateInstanceBuffer == null)
{
globalUpdateInstanceBuffer = new ComputeBuffer(STREAMOUT_SIZE, InstanceData.Stride, ComputeBufferType.Structured);//, ComputeBufferMode.SubUpdates);
}
//Resize and copy global instance buffer in gpu
if (globalInstanceBuffer == null || globalInstanceBuffer.count < maxInstanceCount)
{
int nextCount = RESIZE_COUNT * ((maxInstanceCount - 1) / RESIZE_COUNT + 1);
tempGlobalInstanceBuffer = new ComputeBuffer(nextCount, InstanceData.Stride, ComputeBufferType.Structured);
tempGlobalInstanceBuffer.name = $"ThoMagic global instance buffer";
Debug.Log($"Upsized globalInstanceBuffer {nextCount.ToString("N")} / {(nextCount * InstanceData.Stride / 1024 / 1024)}mb");
if (globalInstanceBuffer != null)
{
deleteOldBuffer = true;
commandBuffer.SetComputeBufferParam(instancingShader, resizeInstanceBufferShaderId, "tempGlobalInstances", tempGlobalInstanceBuffer);
commandBuffer.SetComputeBufferParam(instancingShader, resizeInstanceBufferShaderId, "globalInstances", globalInstanceBuffer);
commandBuffer.SetComputeIntParam(instancingShader, "_CountResize", globalInstanceBuffer.count);
commandBuffer.DispatchCompute(instancingShader, resizeInstanceBufferShaderId, Mathf.CeilToInt(globalInstanceBuffer.count / 64.0f), 1, 1);
/*instancingShader.SetBuffer(resizeInstanceBufferShaderId, "tempGlobalInstances", tempGlobalInstanceBuffer);
instancingShader.SetBuffer(resizeInstanceBufferShaderId, "globalInstances", globalInstanceBuffer);
instancingShader.SetInt("_Count", globalInstanceBuffer.count);
instancingShader.Dispatch(resizeInstanceBufferShaderId, Mathf.CeilToInt(globalInstanceBuffer.count / 64.0f), 1, 1);
globalInstanceBuffer.Dispose();
globalInstanceBuffer = tempGlobalInstanceBuffer;
tempGlobalInstanceBuffer = null;*/
}
else
{
globalInstanceBuffer = tempGlobalInstanceBuffer;
//tempGlobalInstanceBuffer = null;
}
}
else
{
tempGlobalInstanceBuffer = globalInstanceBuffer;
}
//Upload
globalUpdateInstanceBuffer.SetData(uploadInstancesArray);
/*var updateTarget = globalUpdateInstanceBuffer.BeginWrite<InstanceData>(0, instanceUploadCount);
NativeArray<InstanceData>.Copy(uploadInstancesArray, updateTarget, instanceUploadCount);
globalUpdateInstanceBuffer.EndWrite<InstanceData>(instanceUploadCount);*/
/*instancingShader.SetBuffer(uploadInstancesShaderId, "globalUploadInstances", globalUpdateInstanceBuffer);
instancingShader.SetBuffer(uploadInstancesShaderId, "globalInstances", globalInstanceBuffer);
instancingShader.SetInt("_Count", instanceUploadCount);
instancingShader.Dispatch(uploadInstancesShaderId, Mathf.CeilToInt(instanceUploadCount / 64.0f), 1, 1);*/
commandBuffer.SetComputeBufferParam(instancingShader, uploadInstancesShaderId, "globalUploadInstances", globalUpdateInstanceBuffer);
commandBuffer.SetComputeBufferParam(instancingShader, uploadInstancesShaderId, "globalInstances", tempGlobalInstanceBuffer);
commandBuffer.SetComputeIntParam(instancingShader, "_CountUpload", instanceUploadCount);
commandBuffer.DispatchCompute(instancingShader, uploadInstancesShaderId, Mathf.CeilToInt(instanceUploadCount / 64.0f), 1, 1);
Graphics.ExecuteCommandBuffer(commandBuffer);
var fence = Graphics.CreateGraphicsFence(GraphicsFenceType.AsyncQueueSynchronisation, SynchronisationStageFlags.ComputeProcessing);
Graphics.WaitOnAsyncGraphicsFence(fence);
if(deleteOldBuffer)
{
globalInstanceBuffer.Dispose();
globalInstanceBuffer = tempGlobalInstanceBuffer;
tempGlobalInstanceBuffer = null;
}
instanceUploadCount = 0;
_instanceCountChanged = false;
}
Monitor.Exit(threadSecurity);
}
#region Instance handling
internal static int AddInstance(int objectId, Vector3 pos, Quaternion orientation, float scaleXZ, float scaleY)
{
if (isDisposed)
return -1;
Monitor.Enter(threadSecurity);
var myId = maxInstanceCount;
var objectData = GetObjectData(objectId);
objectData.count++;
//Take free slot in instance buffer
if (instanceBufferFreeSlot.Count > 0)
{
myId = instanceBufferFreeSlot.Dequeue();
}
else
{
maxInstanceCount++;
}
//Directly upload if STREAMOUT_SIZE not reached, otherwise queue for upload
if (instanceUploadCount < STREAMOUT_SIZE && uploadInstancesArrayQueue.Count == 0)
uploadInstancesArray[instanceUploadCount++] = new InstanceData(InstanceData.Add, myId, objectData.prefabId, pos.x, pos.y, pos.z, orientation, scaleXZ, scaleY);
else
uploadInstancesArrayQueue.Enqueue(new InstanceData(InstanceData.Add, myId, objectData.prefabId, pos.x, pos.y, pos.z, orientation, scaleXZ, scaleY));
instanceCountChanged = true;
Monitor.Exit(threadSecurity);
return myId;
}
internal static void RemoveInstance(int objectId, int instanceId)
{
if (isDisposed)
return;
Monitor.Enter(threadSecurity);
var objectData = GetObjectData(objectId);
objectData.count--;
//No need to reset instance data because the cullable index is removed, instance data will be reset on reuse.
/*if (instanceUploadCount < STREAMOUT_SIZE && uploadInstancesArrayQueue.Count == 0)
uploadInstancesArray[instanceUploadCount++] = new InstanceData(InstanceData.Delete, instanceId);
else
uploadInstancesArrayQueue.Enqueue(new InstanceData(InstanceData.Delete, instanceId));*/
externInstanceCountChanged = true;
//Remember free slot in instance buffer
instanceBufferFreeSlot.Enqueue(instanceId);
Monitor.Exit(threadSecurity);
}
#endregion
#region Camera handling
public static IEnumerable<CameraRenderer> GetCameras()
{
return cameras.Values;
}
public static int RegisterCamera(Camera camera)
{
int num = camera != null ? camera.GetHashCode() : throw new ArgumentNullException(nameof(camera));
if (!cameras.ContainsKey(num))
{
CameraRenderer cameraRenderer = new CameraRenderer(camera);
cameraRenderer.SetFloatingOrigin(in _origin);
cameras.Add(num, cameraRenderer);
}
return num;
}
public static CameraRenderer GetCamera(int cameraId)
{
CameraRenderer cameraRenderer;
return cameras.TryGetValue(cameraId, out cameraRenderer) ? cameraRenderer : null;
}
public static bool RemoveCamera(int cameraId)
{
CameraRenderer cameraRenderer;
if (!cameras.TryGetValue(cameraId, out cameraRenderer))
return false;
cameraRenderer?.Dispose();
return cameras.Remove(cameraId);
}
public static void RemoveDestroyedCameras()
{
_keysToRemove.Clear();
foreach (KeyValuePair<int, CameraRenderer> camera in cameras)
{
if (camera.Value.Camera == null)
_keysToRemove.Add(camera.Key);
}
foreach (int id in _keysToRemove)
RemoveCamera(id);
}
#endregion
#region Object handling
public static int RegisterObject(GameObject gameObject, IInstanceRenderSettings settings, InstanceStreamer source, int ownerHash)
{
int num = gameObject != null ? /*gameObject.GetHashCode()*/ CalculateContentHash(gameObject) : throw new ArgumentNullException(nameof(gameObject));
if(!streamers.ContainsKey(ownerHash))
streamers.Add(ownerHash, source);
if (!objects.ContainsKey(num))
{
var obj = new ObjectData(gameObject, settings);
objects.Add(num, obj);
objectsList.Add(obj);
obj.contentHashCache = CalculateContentHash(num, false);
}
else
SetObjectSettings(gameObject, settings);
if (!objects[num].Owners.Contains(ownerHash))
objects[num].Owners.Add(ownerHash);
streamers[ownerHash].owners.Add(num);
return num;
}
public static void RemoveObject(int objectId, int ownerHash)
{
if (objects.TryGetValue(objectId, out var objectData))
{
objectData.Owners.Remove(ownerHash);
if (objectData.Owners.Count == 0)
{
objects.Remove(objectId);
objectsList.Remove(objectData);
prefabDataFreeSlot.Enqueue(objectData.prefabId);
}
}
if (streamers.TryGetValue(ownerHash, out var streamer))
{
streamer.owners.Remove(objectId);
if (streamer.owners.Count == 0)
streamers.Remove(ownerHash);
}
rebuildPrefabs = true;
}
public static GameObject GetObject(int objectId)
{
return objects.TryGetValue(objectId, out var objectData) ? objectData.GameObject : null;
}
public static ObjectData GetObjectData(int objectId)
{
return objects[objectId];
}
public static void SetObjectSettings(GameObject prefab, IInstanceRenderSettings instanceRenderSettings)
{
int hashCode = prefab.GetHashCode();
ObjectData objectData;
if (!objects.TryGetValue(hashCode, out objectData))
return;
if ((objectData.Settings == null && instanceRenderSettings != null)
|| (objectData.Settings != null && instanceRenderSettings == null)
|| instanceRenderSettings.Settings.GetHashCode() != objectData.Settings.Settings.GetHashCode())
{
objectData.Settings = instanceRenderSettings;
rebuildPrefabs = true;
}
}
private static int CalculateContentHash(GameObject gameObject)
{
int num = 13;
if (gameObject == null)
return num;
var lodGroup = gameObject.GetComponent<LODGroup>();
LOD[] lodArray;
if (lodGroup == null)
lodArray = new LOD[1]
{
new LOD(0.0001f, gameObject.GetComponentsInChildren<MeshRenderer>())
};
else
lodArray = lodGroup.GetLODs();
foreach (LOD loD in lodArray)
num = HashCode.Combine<int, int>(num, CalculateContentHash(loD));
return num;
}
public static int CalculateContentHash(int objectId, bool reload = false)
{
var obj = objects[objectId];
int num = 13;
if (obj.GameObject == null)
return num;
if (reload)
{
var lodGroup = obj.GameObject.GetComponent<LODGroup>();
LOD[] lodArray;
if (lodGroup == null)
lodArray = new LOD[1]
{
new LOD(0.0001f, obj.GameObject.GetComponentsInChildren<MeshRenderer>())
};
else
lodArray = lodGroup.GetLODs();
obj.LODs = lodArray;
}
foreach (LOD loD in obj.LODs)
num = HashCode.Combine<int, int>(num, CalculateContentHash(loD));
return num;
}
private static int CalculateContentHash(LOD lod)
{
int num = 13;
if (lod.renderers != null)
{
foreach (var renderer in lod.renderers)
{
if (renderer == null)
{
num = HashCode.Combine<int, int>(num, 13);
}
else
{
MeshFilter component = renderer.GetComponent<MeshFilter>();
num = HashCode.Combine<int, int, int, int>(HashCode.Combine<int, int, int, int, int, int, int, int>(
num,
component == null || component.sharedMesh == null ? 13 : component.sharedMesh.GetHashCode(),
renderer.shadowCastingMode.GetHashCode(),
CalculateContentHash(renderer.sharedMaterials),
renderer.motionVectorGenerationMode.GetHashCode(),
renderer.receiveShadows.GetHashCode(),
renderer.rendererPriority.GetHashCode(),
renderer.renderingLayerMask.GetHashCode()),
renderer.gameObject.layer.GetHashCode(),
renderer.gameObject.transform.localPosition.GetHashCode(),
lod.screenRelativeTransitionHeight.GetHashCode());
}
}
}
return num;
}
private static int CalculateContentHash(Material[] materials)
{
int num = 13;
if (materials != null)
{
foreach (Material material in materials)
num = HashCode.Combine<int, int>(num,
material != null ? material.GetHashCode() : 13);
}
return num;
}
public static bool ContentHashChanged(int objectId)
{
if (!objects.ContainsKey(objectId))
return false;
return objects[objectId].contentHashCache != CalculateContentHash(objectId, true);
}
internal static void Initialize()
{
isDisposed = false;
BuildBuffers();
}
#endregion
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 76aaa879e050ed44ba26642123a3d6fe
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+55
View File
@@ -0,0 +1,55 @@
using System;
using UnityEngine;
namespace Assets.ThoMagic.Renderer
{
public class RendererUtility
{
public static GameObject GetPrototypeToRender(DetailPrototype prototype) => prototype.prototype == null ? null :
prototype.prototype.transform.root.gameObject;
public static bool SupportsProceduralInstancing(GameObject gameObject)
{
foreach (var componentsInChild in gameObject.GetComponentsInChildren<MeshRenderer>())
{
foreach (var sharedMaterial in componentsInChild.sharedMaterials)
{
if (sharedMaterial != null && sharedMaterial.shader != null && !RendererUtility.SupportsProceduralInstancing(sharedMaterial))
return false;
}
}
return true;
}
public static bool SupportsProceduralInstancing(Material material)
{
if (material == null)
throw new ArgumentNullException(nameof(material));
if (material.shader == null)
throw new ArgumentNullException("material.shader");
return true;
}
public static bool IsSupportedByUnity(DetailPrototype prototype) => !prototype.usePrototypeMesh || prototype.prototype == null || prototype.prototype.GetComponent<LODGroup>() == null;
public static GameObject GetSupportedPlaceholder(DetailPrototype prototype) => IsSupportedByUnity(prototype) ? prototype.prototype : GetSupportedPlaceholder(prototype.prototype);
public static GameObject GetSupportedPlaceholder(GameObject prototype)
{
if (prototype == null)
return prototype;
LODGroup component = prototype.GetComponent<LODGroup>();
if (component == null)
return prototype;
foreach (LOD loD in component.GetLODs())
{
foreach (UnityEngine.Renderer renderer in loD.renderers)
{
if (renderer != null)
return renderer.gameObject;
}
}
return null;
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8948a91a0e0980f428ba7d5418d8dc6f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+11
View File
@@ -0,0 +1,11 @@
using UnityEngine;
namespace Assets.ThoMagic.Renderer
{
public struct SceneRenderSettings
{
public bool HasMainLight;
public bool HasMainLightShadows;
public Vector3 MainLightDirection;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8c1bc4c6b611d9d439d0f03feecc4e71
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+299
View File
@@ -0,0 +1,299 @@
using System;
using System.Collections.Generic;
#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine;
using UnityEngine.Rendering;
namespace Assets.ThoMagic.Renderer
{
/// <summary>
/// Adds a tree renderer and grass renderer to a terrain object.
/// </summary>
[AddComponentMenu("ThoMagic/Detail Renderer")]
[ExecuteAlways]
[DisallowMultipleComponent]
public class TerrainDetailRenderer : MonoBehaviour
{
/// <summary>
/// This event is called after this detail renderer is initialized.
/// </summary>
public event EventHandler<TerrainDetailRenderer> Initialized;
/// <summary>
/// The terrain object.
/// </summary>
public Terrain terrain;
/// <summary>
/// Cached terrain data.
/// </summary>
public TerrainData terrainData;
[NonSerialized]
private bool isInitialized = false;
[NonSerialized]
private TreeRenderer treeRenderer;
[NonSerialized]
private GrassRenderer grassRenderer;
[NonSerialized]
private Vector3 lastPosition;
[NonSerialized]
private Quaternion lastOrientation;
[NonSerialized]
private Vector3 lastScale;
[Tooltip("Should ThoMagic Renderer automatically refresh the terrain data if the terrain was modified at runtime? If disabled, use Refresh() from a custom script to refresh the terrain data.")]
[SerializeField]
private bool _autoRefreshTerrainAtRuntime = true;
[Tooltip("Should the default grass and tree rendering of Unity be enabled when ThoMagic Renderer is disabled?")]
[SerializeField]
private bool _enableUnityRendererWhenDisabled = true;
[Tooltip("Delay the initialization of ThoMagic Renderer until the first LateUpdate event")]
[SerializeField]
private bool _delayInitialize = true;
/// <summary>
/// If true and the terrain is changed at runtime the renderers are updated automaticly.
/// If needed you can also refresh them manually by calling Refresh().
/// </summary>
public bool AutoRefreshTerrainAtRuntime
{
get => _autoRefreshTerrainAtRuntime;
set => _autoRefreshTerrainAtRuntime = value;
}
/// <summary>
/// Enable the default unity renderer for trees and grass if this component gets disabled?
/// </summary>
public bool EnableUnityRendererWhenDisabled
{
get => _enableUnityRendererWhenDisabled;
set => _enableUnityRendererWhenDisabled = value;
}
private bool HasValidTerrainData()
{
TerrainData terrainData = GetTerrainData();
return terrainData != null && terrainData.detailResolution > 0 && terrainData.alphamapResolution > 0 && terrainData.heightmapResolution > 0 && terrainData.size != Vector3.zero;
}
private bool CanInitialize() => isActiveAndEnabled && HasValidTerrainData();
private void Awake()
{
//Load resources
}
private void OnEnable()
{
if (_delayInitialize && Application.isPlaying || (isInitialized || !CanInitialize()))
return;
Initialize();
}
private void Start()
{
if (_delayInitialize && Application.isPlaying || (isInitialized || !CanInitialize()))
return;
Initialize();
}
private void OnDisable()
{
if (!isInitialized)
return;
Destroy();
}
private void OnDestroy()
{
if (!isInitialized)
return;
Destroy();
}
//Called by unity on terrain changes
public void OnTerrainChanged(TerrainChangedFlags flags)
{
if (!isInitialized || !AutoRefreshTerrainAtRuntime)
return;
Refresh(flags);
}
/// <summary>
/// Used to refresh the renderers.
/// </summary>
/// <param name="flags">The terrain changes</param>
public void Refresh(TerrainChangedFlags flags)
{
//if a detail object has to be replaced by a placeholder return immediatly, as a second OnTerrainChanged will be called
//cause the detail protoypes where changed on the terrain.
if(flags.HasFlag(TerrainChangedFlags.FlushEverythingImmediately) && ReplaceUnsupportedDetails() && AutoRefreshTerrainAtRuntime)
return;
treeRenderer?.OnTerrainChanged(flags);
grassRenderer?.OnTerrainChanged(flags);
}
/// <summary>
/// Replace unsupported grass details with placeholders.
/// </summary>
/// <returns>True if a placeholder was created, otherwise false.</returns>
private bool ReplaceUnsupportedDetails()
{
bool flag = false;
DetailPrototype[] detailPrototypes = terrainData.detailPrototypes;
for (int index = 0; index < detailPrototypes.Length; ++index)
{
if (!RendererUtility.IsSupportedByUnity(detailPrototypes[index]))
{
GameObject supportedPlaceholder = RendererUtility.GetSupportedPlaceholder(detailPrototypes[index]);
if (supportedPlaceholder != detailPrototypes[index].prototype)
{
detailPrototypes[index].prototype = supportedPlaceholder;
flag = true;
}
}
}
if (flag)
terrainData.detailPrototypes = detailPrototypes;
return flag;
}
private void DisableUnityRenderer()
{
if (terrain == null)
return;
terrain.drawTreesAndFoliage = false;
}
private void RestoreUnityRenderer()
{
if (terrain == null || !EnableUnityRendererWhenDisabled)
return;
terrain.drawTreesAndFoliage = true;
}
private void Initialize()
{
if (isInitialized)
return;
DisableUnityRenderer();
GetTerrainData();
ReplaceUnsupportedDetails();
if (treeRenderer == null)
treeRenderer = new TreeRenderer(terrain);
treeRenderer?.Load();
if (grassRenderer == null)
grassRenderer = new GrassRenderer(terrain);
grassRenderer?.Load();
if (Initialized != null)
Initialized(this, this);
isInitialized = true;
lastPosition = terrain.transform.position;
lastOrientation = terrain.transform.rotation;
lastScale = terrain.transform.localScale;
#if UNITY_EDITOR
SceneView.lastActiveSceneView?.Repaint();
RenderPipelineManager.endContextRendering -= OnBeginContextRendering;
RenderPipelineManager.endContextRendering += OnBeginContextRendering;
#endif
}
#if UNITY_EDITOR
private void OnBeginContextRendering(
ScriptableRenderContext context,
List<Camera> cameras)
{
LateUpdate();
}
#endif
private void LateUpdate()
{
if (!isInitialized && CanInitialize())
Initialize();
if (!isInitialized)
return;
//TODO: We are currently not supporting a floating offset,
//so if the terrain moves we refresh/rebuild everything,
//which is expenisve. If possible we should switch to a
//floating offset.
if (terrain.transform.position != lastPosition ||
terrain.transform.localScale != lastScale ||
terrain.transform.rotation != lastOrientation)
{
lastPosition = terrain.transform.position;
lastOrientation = terrain.transform.rotation;
lastScale = terrain.transform.localScale;
Refresh(TerrainChangedFlags.FlushEverythingImmediately);
}
treeRenderer?.LateUpdate();
grassRenderer?.LateUpdate();
}
private void Destroy()
{
if (!isInitialized)
return;
treeRenderer?.Destroy();
treeRenderer = null;
grassRenderer?.Destroy();
grassRenderer = null;
RestoreUnityRenderer();
isInitialized = false;
#if UNITY_EDITOR
RenderPipelineManager.endContextRendering -= OnBeginContextRendering;
#endif
}
private TerrainData GetTerrainData()
{
if (terrain == null)
terrain = GetComponent<Terrain>();
terrainData = terrain != null ? terrain.terrainData : null;
return terrainData;
}
private Bounds CalculateBounds(GameObject obj)
{
var meshRenderer = obj.GetComponentsInChildren<MeshRenderer>();
Bounds b = new Bounds();
if (meshRenderer.Length > 0)
{
b = new Bounds(meshRenderer[0].bounds.center, meshRenderer[0].bounds.size);
for (int r = 1; r < meshRenderer.Length; r++)
{
b.Encapsulate(meshRenderer[r].bounds);
}
}
return b;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 533ab6e6ebf6be745ae7f02dc1922e05
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+36
View File
@@ -0,0 +1,36 @@
{
"name": "ThoMagicRenderer",
"rootNamespace": "Assets.ThoMagic.Renderer",
"references": [],
"includePlatforms": [],
"excludePlatforms": [
"Android",
"Editor",
"EmbeddedLinux",
"GameCoreScarlett",
"GameCoreXboxOne",
"iOS",
"LinuxStandalone64",
"CloudRendering",
"macOSStandalone",
"PS4",
"PS5",
"QNX",
"Stadia",
"Switch",
"tvOS",
"WSA",
"VisionOS",
"WebGL",
"WindowsStandalone32",
"WindowsStandalone64",
"XboxOne"
],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": false,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 6ec33a4469ad6b8408b91f0b07c9c971
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,145 @@
using System;
using UnityEngine;
namespace Assets.ThoMagic.Renderer
{
[AddComponentMenu("ThoMagic/Renderer Object Settings")]
[DisallowMultipleComponent]
[ExecuteAlways]
public class ThoMagicRendererObjectSettings : MonoBehaviour, IInstanceRenderSettings
{
[SerializeField]
private bool _render = true;
[SerializeField]
private bool _overrideRendering;
[Min(0.0f)]
[SerializeField]
private float _renderDistance = 150;
[SerializeField]
private bool _overrideRenderDistance;
[SerializeField]
private bool _renderShadows = true;
[SerializeField]
private bool _overrideRenderShadows;
[Min(0.0f)]
[SerializeField]
private float _shadowDistance = 80;
[SerializeField]
private bool _overrideShadowDistance;
[Range(0.01f, 1f)]
[SerializeField]
private float _densityInDistance = 0.125f;
[SerializeField]
private bool _overrideDensityInDistance;
[SerializeField]
private Vector2 _densityInDistanceFalloff = new Vector2(0.08f, 0.0075f);
[SerializeField]
private bool _overrideDensityInDistanceFalloff;
private ReflectionProbe _reflectionProbe;
public InstanceRenderSettings Settings => new InstanceRenderSettings()
{
Supported = true,
Render = !this._overrideRendering || this._render,
RenderDistance = this._overrideRenderDistance ? this._renderDistance : -1f,
ShadowDistance = this._overrideShadowDistance ? this._shadowDistance : -1f,
Shadows = !this._overrideRenderShadows || this._renderShadows,
DensityInDistance = this._overrideDensityInDistance ? this._densityInDistance : 1f,
DensityInDistanceFalloff = this._overrideDensityInDistanceFalloff ? this._densityInDistanceFalloff : Vector2.zero
};
public bool? Render
{
get => !this._overrideRendering ? new bool?() : new bool?(this._render);
set
{
this._overrideRendering = value.HasValue;
if (!value.HasValue)
return;
this._render = value.Value;
}
}
public float? RenderDistance
{
get => !this._overrideRenderDistance ? new float?() : new float?(this._renderDistance);
set
{
this._overrideRenderDistance = value.HasValue;
if (!value.HasValue)
return;
this._renderDistance = value.Value;
}
}
public bool? RenderShadows
{
get => !this._overrideRenderShadows ? new bool?() : new bool?(this._renderShadows);
set
{
this._overrideRenderShadows = value.HasValue;
if (!value.HasValue)
return;
this._renderShadows = value.Value;
}
}
public float? ShadowDistance
{
get => !this._overrideShadowDistance ? new float?() : new float?(this._shadowDistance);
set
{
this._overrideShadowDistance = value.HasValue;
if (!value.HasValue)
return;
this._shadowDistance = value.Value;
}
}
public float? DensityInDistance
{
get => !this._overrideDensityInDistance ? new float?() : new float?(this._densityInDistance);
set
{
this._overrideDensityInDistance = value.HasValue;
if (!value.HasValue)
return;
this._densityInDistance = value.Value;
}
}
public Vector2? DensityInDistanceFalloff
{
get => !this._overrideDensityInDistanceFalloff ? new Vector2?() : new Vector2?(this._densityInDistanceFalloff);
set
{
this._overrideDensityInDistanceFalloff = value.HasValue;
if (!value.HasValue)
return;
this._densityInDistanceFalloff = value.Value;
}
}
private void OnEnable()
{
_reflectionProbe = GetComponent<ReflectionProbe>();
if (_reflectionProbe == null)
return;
CameraRenderer.ReflectionProbeSettings = this;
}
private void OnDisable()
{
if (_reflectionProbe == null || CameraRenderer.ReflectionProbeSettings as ThoMagicRendererObjectSettings != this)
return;
CameraRenderer.ReflectionProbeSettings = null;
}
private void OnValidate()
{
if (_reflectionProbe == null || !Application.isEditor)
return;
this._reflectionProbe.RenderProbe();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c2a37bd5b745b59448b6bc6c1ff5f09c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+133
View File
@@ -0,0 +1,133 @@
using System;
using System.Collections.Generic;
#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine;
using UnityEngine.Rendering;
namespace Assets.ThoMagic.Renderer
{
[ExecuteAlways]
[DisallowMultipleComponent]
public class TileObjectRenderer : MonoBehaviour
{
/// <summary>
/// This event is called after this detail renderer is initialized.
/// </summary>
public event EventHandler<TileObjectRenderer> Initialized;
[NonSerialized]
private bool isInitialized = false;
[Tooltip("Delay the initialization of ThoMagic Renderer until the first LateUpdate event")]
[SerializeField]
private bool _delayInitialize = true;
private bool CanInitialize() => isActiveAndEnabled;
[NonSerialized]
private TileObjectStreamer tileObjectStreamer;
private void Awake()
{
//Load resources
}
private void OnEnable()
{
if (_delayInitialize && Application.isPlaying || (isInitialized || !CanInitialize()))
return;
Initialize();
}
private void Start()
{
if (_delayInitialize && Application.isPlaying || (isInitialized || !CanInitialize()))
return;
Initialize();
}
private void OnDisable()
{
if (!isInitialized)
return;
Destroy();
}
private void OnDestroy()
{
if (!isInitialized)
return;
Destroy();
}
private void Initialize()
{
if (isInitialized)
return;
if (tileObjectStreamer == null)
tileObjectStreamer = new TileObjectStreamer(gameObject);
tileObjectStreamer?.Load();
if (Initialized != null)
Initialized(this, this);
isInitialized = true;
#if UNITY_EDITOR
SceneView.lastActiveSceneView?.Repaint();
RenderPipelineManager.endContextRendering -= OnBeginContextRendering;
RenderPipelineManager.endContextRendering += OnBeginContextRendering;
#endif
}
#if UNITY_EDITOR
private void OnBeginContextRendering(
ScriptableRenderContext context,
List<Camera> cameras)
{
//LateUpdate();
}
#endif
private void LateUpdate()
{
if (!isInitialized && CanInitialize())
Initialize();
if (!isInitialized)
return;
tileObjectStreamer?.LateUpdate();
}
private void Destroy()
{
if (!isInitialized)
return;
try
{
tileObjectStreamer?.Destroy();
}
catch { }
tileObjectStreamer = null;
isInitialized = false;
#if UNITY_EDITOR
RenderPipelineManager.endContextRendering -= OnBeginContextRendering;
#endif
}
private void OnTransformChildrenChanged()
{
tileObjectStreamer?.MarkDirty();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 365a0b5496e0c71438e4b4a0b232aca6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+246
View File
@@ -0,0 +1,246 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Assets.ThoMagic.Renderer
{
public class TileObjectStreamer : InstanceStreamer
{
private readonly GameObject objectContainer;
private Bounds worldBounds;
private float maxDistance;
private Dictionary<int, int> registeredPrefabs = new Dictionary<int, int>();
private Dictionary<int, GameObject> instanceToGameObject = new Dictionary<int, GameObject>();
private bool isDirty = true;
public TileObjectStreamer(GameObject objectContainer)
: base()
{
this.objectContainer = objectContainer;
}
public void Recycle()
{
Clear();
int ownerHash = GetHashCode();
foreach (var item in registeredPrefabs)
{
RendererPool.RemoveObject(item.Value, ownerHash);
}
foreach (var item in instanceToGameObject)
{
var mrs = item.Value.gameObject.GetComponentsInChildren<MeshRenderer>();
foreach(var mr in mrs)
mr.enabled = true;
var lod = item.Value.gameObject.GetComponent<LODGroup>();
if (lod != null)
lod.enabled = true;
}
registeredPrefabs.Clear();
instanceToGameObject.Clear();
}
public void Load()
{
isDirty = false;
Recycle();
worldBounds.SetMinMax(Vector3.zero, Vector3.zero);
registeredPrefabs.Clear();
instanceToGameObject.Clear();
int ownerHash = GetHashCode();
foreach (Transform child in objectContainer.transform)
{
var hash = CalculateHash(child.gameObject);
if(hash != 0)
{
int objectId;
if (registeredPrefabs.ContainsKey(hash))
{
objectId = registeredPrefabs[hash];
}
else
{
var settings = GetSettingsOrDefault(child.gameObject);
objectId = RendererPool.RegisterObject(child.gameObject, settings, this, ownerHash);
registeredPrefabs.Add(hash, objectId);
if (settings.Settings.RenderDistance == 0)
maxDistance = float.MaxValue;
maxDistance = Mathf.Max(maxDistance, settings.Settings.RenderDistance);
}
var mrs = child.gameObject.GetComponentsInChildren<MeshRenderer>();
Bounds bounds = new Bounds();
bool first = true;
foreach (var mr in mrs)
{
if (first)
{
bounds = mr.bounds;
}
else
{
bounds.Encapsulate(mr.bounds);
}
mr.enabled = false;
}
var lod = child.gameObject.GetComponent<LODGroup>();
if(lod != null)
lod.enabled = false;
var instanceId = AddInstance(objectId, new Vector3(child.position.x, child.position.y, child.position.z), child.rotation, child.lossyScale.x, child.lossyScale.z);
instanceToGameObject.Add(instanceId, child.gameObject);
if (instanceToGameObject.Count == 1)
worldBounds = bounds;
else
worldBounds.Encapsulate(bounds);
}
}
}
private IInstanceRenderSettings GetSettingsOrDefault(GameObject gameObject)
{
IInstanceRenderSettings instanceRenderSettings;
return gameObject.TryGetComponent(out instanceRenderSettings) ? instanceRenderSettings : new DefaultRenderSettings();
}
private int CalculateHash(GameObject gameObject)
{
int num = 13;
var lodGroup = gameObject.GetComponent<LODGroup>();
LOD[] lodArray;
if (lodGroup == null)
{
var mr = gameObject.GetComponentsInChildren<MeshRenderer>();
if (mr == null)
return 0;
lodArray = new LOD[1]
{
new LOD(0.0001f, mr)
};
}
else
lodArray = lodGroup.GetLODs();
foreach (var loD in lodArray)
num = HashCode.Combine<int, int>(num, CalcualteContentHash(loD));
return num;
}
private int CalcualteContentHash(LOD lod)
{
int num = 13;
if (lod.renderers != null)
{
foreach (var renderer in lod.renderers)
{
if (renderer == null)
{
num = HashCode.Combine<int, int>(num, 13);
}
else
{
MeshFilter component = renderer.GetComponent<MeshFilter>();
num = HashCode.Combine<int, int, int>(HashCode.Combine<int, int, int, int, int, int, int, int>(
num,
component == null || component.sharedMesh == null ? 13 : component.sharedMesh.GetHashCode(),
renderer.shadowCastingMode.GetHashCode(),
CalculateContentHash(renderer.sharedMaterials),
renderer.motionVectorGenerationMode.GetHashCode(),
renderer.receiveShadows.GetHashCode(),
renderer.rendererPriority.GetHashCode(),
renderer.renderingLayerMask.GetHashCode()),
renderer.gameObject.layer.GetHashCode(),
lod.screenRelativeTransitionHeight.GetHashCode());
}
}
}
return num;
}
private int CalculateContentHash(Material[] materials)
{
int num = 13;
if (materials != null)
{
foreach (Material material in materials)
num = HashCode.Combine<int, int>(num,
material != null ? material.GetHashCode() : 13);
}
return num;
}
public override bool IsInRange(Camera referenceCamera, Plane[] planes)
{
var eyePos = referenceCamera.transform.position;
if ((eyePos - worldBounds.ClosestPoint(eyePos)).magnitude <= Mathf.Min(referenceCamera.farClipPlane, maxDistance))
return true;
return false;
}
public override int GetHashCode()
{
return streamerInstanceId.GetHashCode();//HashCode.Combine(streamerInstanceId, objectInstanceIds.Count);
}
public void Destroy()
{
Recycle();
}
public void LateUpdate()
{
if (isDirty)
{
Load();
}
}
public void MarkDirty()
{
isDirty = true;
}
private class DefaultRenderSettings : IInstanceRenderSettings
{
public InstanceRenderSettings Settings => new InstanceRenderSettings()
{
Render = true,
DensityInDistance = 1f,
DensityInDistanceFalloff = Vector2.zero,
RenderDistance = 0.0f,
ShadowDistance = 0.0f,
Shadows = true,
Supported = true
};
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 14d63a559cc9211479181fed32a2a5c0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+272
View File
@@ -0,0 +1,272 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace Assets.ThoMagic.Renderer
{
public class TreeRenderer : TreeStreamer
{
private Terrain terrain;
private TerrainData terrainData;
private List<int> treePrototypesRegistered;
private Dictionary<int, TreeInfo> treeToInstances = new Dictionary<int, TreeInfo>();
private HashSet<int> touchedTrees = new HashSet<int>();
private bool isDirty;
private float maxTreeDistance = 0;
class TreeInfo
{
internal int instanceId;
internal int objectId;
}
public TreeRenderer(Terrain terrain)
: base(terrain)
{
this.terrain = terrain;
terrainData = terrain.terrainData;
}
public void LateUpdate()
{
if (isDirty)
{
isDirty = false;
ApplyChanges();
}
if (Application.isEditor && !Application.isPlaying)
RebuildChangedPrototypes();
}
int GetTreeTerrainInstanceId(TreeInstance tree)
{
return HashCode.Combine(tree.position.x, tree.position.y, tree.position.z, tree.rotation, tree.widthScale, tree.heightScale, tree.prototypeIndex);
}
public void ApplyChanges()
{
Vector3 size = terrainData.size;
Vector3 offset = terrain.transform.position;
var treeInstances = terrainData.treeInstances;
touchedTrees.Clear();
foreach (var treeInstance in treeInstances)
{
if (treeInstance.widthScale == 0)
continue;
var treeTerrainInstanceId = GetTreeTerrainInstanceId(treeInstance);
if (!treeToInstances.ContainsKey(treeTerrainInstanceId))
{
var treeId = treePrototypesRegistered[treeInstance.prototypeIndex];
var prefab = RendererPool.GetObject(treeId);
var quaternion = Quaternion.Euler(0.0f, 57.2957801818848f * treeInstance.rotation, 0.0f);
var instanceId = AddInstance(treeId, new Vector3(treeInstance.position.x * size.x,
treeInstance.position.y * size.y,
treeInstance.position.z * size.z) + offset, quaternion,
treeInstance.widthScale * prefab.transform.localScale.x,
treeInstance.heightScale * prefab.transform.localScale.y
);
treeToInstances[treeTerrainInstanceId] = new TreeInfo { instanceId = instanceId, objectId = treeId };
touchedTrees.Add(treeTerrainInstanceId);
}
else
{
touchedTrees.Add(treeTerrainInstanceId);
}
}
foreach(var treeTerrainInstanceId in treeToInstances.Keys.ToList())
{
if(!touchedTrees.Contains(treeTerrainInstanceId))
{
RemoveInstance(treeToInstances[treeTerrainInstanceId].objectId, treeToInstances[treeTerrainInstanceId].instanceId);
treeToInstances.Remove(treeTerrainInstanceId);
}
}
Build(maxTreeDistance);
}
public void Load()
{
maxTreeDistance = 0;
Vector3 size = terrainData.size;
Vector3 offset = terrain.transform.position;
Recycle();
int ownerHash = GetHashCode();
if (treePrototypesRegistered == null)
treePrototypesRegistered = new List<int>();
var treePrototypes = terrainData.treePrototypes;
if(treePrototypes.Length > 0)
{
foreach(var treePrototype in treePrototypes)
{
if(treePrototype.prefab != null)
{
var settings = GetSettingsOrDefault(treePrototype.prefab);
treePrototypesRegistered.Add(RendererPool.RegisterObject(treePrototype.prefab, settings, this, ownerHash));
if (settings.Settings.RenderDistance == 0)
maxTreeDistance = float.MaxValue;
if(settings.Settings.RenderDistance > maxTreeDistance)
maxTreeDistance = settings.Settings.RenderDistance;
}
}
var treeInstances = terrainData.treeInstances;
foreach (var treeInstance in treeInstances)
{
var treeTerrainInstanceId = GetTreeTerrainInstanceId(treeInstance);
var treeId = treePrototypesRegistered[treeInstance.prototypeIndex];
var prefab = RendererPool.GetObject(treeId);
var quaternion = Quaternion.Euler(0.0f, 57.2957801818848f * treeInstance.rotation, 0.0f);
var instanceId = AddInstance(treeId, new Vector3(treeInstance.position.x * size.x,
treeInstance.position.y * size.y,
treeInstance.position.z * size.z) + offset, quaternion,
treeInstance.widthScale * prefab.transform.localScale.x,
treeInstance.heightScale * prefab.transform.localScale.y);
treeToInstances[treeTerrainInstanceId] = new TreeInfo { instanceId = instanceId, objectId = treeId };
}
}
Build(maxTreeDistance);
}
private void Recycle()
{
if (treePrototypesRegistered == null)
return;
treeToInstances.Clear();
Clear();
var ownerHash = GetHashCode();
for (int index = 0; index < treePrototypesRegistered.Count; ++index)
{
var objectId = treePrototypesRegistered[index];
RendererPool.RemoveObject(objectId, ownerHash);
}
treePrototypesRegistered.Clear();
}
private void RebuildChangedPrototypes()
{
TreePrototype[] treePrototypes = terrainData.treePrototypes;
if (treePrototypes.Length != treePrototypesRegistered.Count)
{
Load();
}
else
{
int ownerHash = GetHashCode();
maxTreeDistance = 0;
for (int index = 0; index < treePrototypesRegistered.Count; ++index)
{
int treeId = treePrototypesRegistered[index];
GameObject prefab = treePrototypes[index].prefab;
GameObject gameObject = RendererPool.GetObject(treeId);
if (prefab != gameObject)
{
RendererPool.RemoveObject(treeId, ownerHash);
if (prefab != null)
{
var settings = GetSettingsOrDefault(prefab);
if (settings.Settings.RenderDistance == 0)
maxTreeDistance = float.MaxValue;
if (settings.Settings.RenderDistance > maxTreeDistance)
maxTreeDistance = settings.Settings.RenderDistance;
treePrototypesRegistered[index] = RendererPool.RegisterObject(prefab, settings, this, ownerHash);
}
}
else if (RendererPool.ContentHashChanged(treeId))
{
RendererPool.RemoveObject(treeId, ownerHash);
if (prefab != null)
{
var settings = GetSettingsOrDefault(prefab);
if (settings.Settings.RenderDistance == 0)
maxTreeDistance = float.MaxValue;
if (settings.Settings.RenderDistance > maxTreeDistance)
maxTreeDistance = settings.Settings.RenderDistance;
treePrototypesRegistered[index] = RendererPool.RegisterObject(prefab, settings, this, ownerHash);
}
}
else if (prefab != null)
{
var settings = GetSettingsOrDefault(prefab);
if (settings.Settings.RenderDistance == 0)
maxTreeDistance = float.MaxValue;
if (settings.Settings.RenderDistance > maxTreeDistance)
maxTreeDistance = settings.Settings.RenderDistance;
RendererPool.SetObjectSettings(prefab, settings);
}
}
}
}
public void Destroy()
{
Recycle();
}
public void OnTerrainChanged(TerrainChangedFlags flags)
{
if(flags.HasFlag(TerrainChangedFlags.TreeInstances))
isDirty = true;
else if (flags.HasFlag(TerrainChangedFlags.DelayedHeightmapUpdate))
isDirty = true;
else if (flags.HasFlag(TerrainChangedFlags.Heightmap))
isDirty = true;
else if (flags.HasFlag(TerrainChangedFlags.HeightmapResolution))
isDirty = true;
else if (flags.HasFlag(TerrainChangedFlags.Holes))
isDirty = true;
else if (flags.HasFlag(TerrainChangedFlags.DelayedHolesUpdate))
isDirty = true;
else if (flags.HasFlag(TerrainChangedFlags.FlushEverythingImmediately))
isDirty = true;
}
private IInstanceRenderSettings GetSettingsOrDefault(GameObject gameObject)
{
IInstanceRenderSettings instanceRenderSettings;
return gameObject.TryGetComponent(out instanceRenderSettings) ? instanceRenderSettings : new DefaultRenderSettings();
}
private class DefaultRenderSettings : IInstanceRenderSettings
{
public InstanceRenderSettings Settings => new InstanceRenderSettings()
{
Render = true,
DensityInDistance = 1f,
DensityInDistanceFalloff = Vector2.zero,
RenderDistance = 0.0f,
ShadowDistance = 0.0f,
Shadows = true,
Supported = true
};
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e0f78ad04f9bf164e81068b0dcffd13b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+36
View File
@@ -0,0 +1,36 @@
using UnityEngine;
namespace Assets.ThoMagic.Renderer
{
public class TreeStreamer : InstanceStreamer
{
private readonly Terrain _terrain;
private Bounds worldBounds;
private float maxTreeDistance;
public TreeStreamer(Terrain terrain)
: base()
=> _terrain = terrain;
public void Build(float maxDistance)
{
maxTreeDistance = maxDistance;
Vector3 position = _terrain.GetPosition();
worldBounds = new Bounds(_terrain.terrainData.bounds.center + position, _terrain.terrainData.bounds.size);
}
public override bool IsInRange(Camera referenceCamera, Plane[] planes)
{
if (!_terrain.editorRenderFlags.HasFlag(TerrainRenderFlags.Trees))
return false;
var eyePos = referenceCamera.transform.position;
if ((eyePos - worldBounds.ClosestPoint(eyePos)).magnitude <= Mathf.Min(referenceCamera.farClipPlane, maxTreeDistance))
return true;
return false;
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 049083d3196ea1b429c2057a188c2bf2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: