folder structure 2

main
Thomas Woischnig 2025-02-06 20:12:56 +01:00
parent ee5b330904
commit f1f314fbd3
59 changed files with 5650 additions and 0 deletions

8
Runtime.meta Normal file
View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f5c565fe4e68ffb4ab3749497f16016a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

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);
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 462b041640bdab24f87e7de6f8a16fb3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

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();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 937c543b6caf2384898a535da09c337d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

676
Runtime/CameraRenderer.cs Normal file
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;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f2218c3942cfa8745866f4503b313216
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

179
Runtime/CellLayout.cs Normal file
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;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4cf46f5589518ec4f9ea40aaf072cfbf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

85
Runtime/CellLayoutPool.cs Normal file
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);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d431ea020bbd81c42b897bd3afc640cb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

97
Runtime/DrawCall.cs Normal file
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
Runtime/DrawCall.cs.meta Normal file
View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0345ca3ff11f4114a9cb20dfdeee77a5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

65
Runtime/DrawGroup.cs Normal file
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
Runtime/DrawGroup.cs.meta Normal file
View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 14eaba28b1cfbac43acbf8a698ee5dbe
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

90
Runtime/FrameRenderer.cs Normal file
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);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e9b14e1d6d523834db575b4973d22c0e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

196
Runtime/GrassRenderer.cs Normal file
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
};
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cdb66824e49e1344faf75691d1c8788d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

301
Runtime/GrassStreamer.cs Normal file
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);
}
}
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a9909a9557ba0b94e9de512444a0629e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

26
Runtime/InstanceBuffer.cs Normal file
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;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1d85f6533dc1bb84bb236fd27ab68b38
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

174
Runtime/InstanceMatrix.cs Normal file
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;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 80369a8a9c9a0e64daddf39789fa0f66
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

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;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ada01dd02c566634092d55b23558e707
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

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);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e591f7bae0131d548ab936dcae307d64
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

266
Runtime/ObjectData.cs Normal file
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();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 38c29b0618523cb44971f341d53349bf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

541
Runtime/RendererPool.cs Normal file
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
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 76aaa879e050ed44ba26642123a3d6fe
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

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;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8948a91a0e0980f428ba7d5418d8dc6f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

8
Runtime/Resources.meta Normal file
View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 66cad6ae64c1463448771e8e1325a51c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,438 @@
{
"m_SGVersion": 3,
"m_Type": "UnityEditor.ShaderGraph.GraphData",
"m_ObjectId": "4ea35b44b3fe4b25a7cb4e39a4b9b407",
"m_Properties": [],
"m_Keywords": [
{
"m_Id": "9ba4f485275c4fc9b2f1ce82a79369c4"
}
],
"m_Dropdowns": [],
"m_CategoryData": [
{
"m_Id": "480708220ad2491387a14ef84b7fe431"
}
],
"m_Nodes": [
{
"m_Id": "281b26d889224fc487a79fa8214bd0b1"
},
{
"m_Id": "c5119037ba0c48fbba1bb84ead9a5adb"
},
{
"m_Id": "4e57f58cc3d24bf1b53cde5e846cbd31"
},
{
"m_Id": "304803996acc4ca999953697d05a2515"
}
],
"m_GroupDatas": [],
"m_StickyNoteDatas": [],
"m_Edges": [
{
"m_OutputSlot": {
"m_Node": {
"m_Id": "304803996acc4ca999953697d05a2515"
},
"m_SlotId": 1
},
"m_InputSlot": {
"m_Node": {
"m_Id": "281b26d889224fc487a79fa8214bd0b1"
},
"m_SlotId": 1
}
},
{
"m_OutputSlot": {
"m_Node": {
"m_Id": "4e57f58cc3d24bf1b53cde5e846cbd31"
},
"m_SlotId": 1
},
"m_InputSlot": {
"m_Node": {
"m_Id": "304803996acc4ca999953697d05a2515"
},
"m_SlotId": 0
}
},
{
"m_OutputSlot": {
"m_Node": {
"m_Id": "c5119037ba0c48fbba1bb84ead9a5adb"
},
"m_SlotId": 0
},
"m_InputSlot": {
"m_Node": {
"m_Id": "4e57f58cc3d24bf1b53cde5e846cbd31"
},
"m_SlotId": 0
}
}
],
"m_VertexContext": {
"m_Position": {
"x": 0.0,
"y": 0.0
},
"m_Blocks": []
},
"m_FragmentContext": {
"m_Position": {
"x": 0.0,
"y": 0.0
},
"m_Blocks": []
},
"m_PreviewData": {
"serializedMesh": {
"m_SerializedMesh": "{\"mesh\":{\"instanceID\":0}}",
"m_Guid": ""
},
"preventRotation": false
},
"m_Path": "Sub Graphs",
"m_GraphPrecision": 1,
"m_PreviewMode": 2,
"m_OutputNode": {
"m_Id": "281b26d889224fc487a79fa8214bd0b1"
},
"m_ActiveTargets": []
}
{
"m_SGVersion": 0,
"m_Type": "UnityEditor.ShaderGraph.Vector3MaterialSlot",
"m_ObjectId": "13e38f92c90c4e709dc150f29929c84a",
"m_Id": 0,
"m_DisplayName": "Out",
"m_SlotType": 1,
"m_Hidden": false,
"m_ShaderOutputName": "Out",
"m_StageCapability": 3,
"m_Value": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"m_DefaultValue": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"m_Labels": []
}
{
"m_SGVersion": 0,
"m_Type": "UnityEditor.ShaderGraph.SubGraphOutputNode",
"m_ObjectId": "281b26d889224fc487a79fa8214bd0b1",
"m_Group": {
"m_Id": ""
},
"m_Name": "Output",
"m_DrawState": {
"m_Expanded": true,
"m_Position": {
"serializedVersion": "2",
"x": 25.00006866455078,
"y": -310.0,
"width": 96.99996185302735,
"height": 77.00001525878906
}
},
"m_Slots": [
{
"m_Id": "aca287a71573483789d5dcb380daa40c"
}
],
"synonyms": [],
"m_Precision": 0,
"m_PreviewExpanded": true,
"m_DismissedVersion": 0,
"m_PreviewMode": 0,
"m_CustomColors": {
"m_SerializableColors": []
},
"IsFirstSlotValid": true
}
{
"m_SGVersion": 1,
"m_Type": "UnityEditor.ShaderGraph.CustomFunctionNode",
"m_ObjectId": "304803996acc4ca999953697d05a2515",
"m_Group": {
"m_Id": ""
},
"m_Name": "IncludeThoMagicRenderer (Custom Function)",
"m_DrawState": {
"m_Expanded": true,
"m_Position": {
"serializedVersion": "2",
"x": -330.0,
"y": -310.0,
"width": 304.0,
"height": 94.0
}
},
"m_Slots": [
{
"m_Id": "59dd7ed4b8b14695ab4443b7b89ab9df"
},
{
"m_Id": "79b2dd99d8ea4726856c03ea82ede95b"
}
],
"synonyms": [
"code",
"HLSL"
],
"m_Precision": 0,
"m_PreviewExpanded": false,
"m_DismissedVersion": 0,
"m_PreviewMode": 0,
"m_CustomColors": {
"m_SerializableColors": []
},
"m_SourceType": 0,
"m_FunctionName": "IncludeThoMagicRenderer",
"m_FunctionSource": "678dff042fe9c004cb7522c2233af44d",
"m_FunctionBody": "Enter function body here..."
}
{
"m_SGVersion": 0,
"m_Type": "UnityEditor.ShaderGraph.Vector3MaterialSlot",
"m_ObjectId": "45a92477b6a7449c9c4d29eac6f7f4c1",
"m_Id": 0,
"m_DisplayName": "In",
"m_SlotType": 0,
"m_Hidden": false,
"m_ShaderOutputName": "In",
"m_StageCapability": 3,
"m_Value": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"m_DefaultValue": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"m_Labels": []
}
{
"m_SGVersion": 0,
"m_Type": "UnityEditor.ShaderGraph.CategoryData",
"m_ObjectId": "480708220ad2491387a14ef84b7fe431",
"m_Name": "",
"m_ChildObjectList": [
{
"m_Id": "9ba4f485275c4fc9b2f1ce82a79369c4"
}
]
}
{
"m_SGVersion": 1,
"m_Type": "UnityEditor.ShaderGraph.CustomFunctionNode",
"m_ObjectId": "4e57f58cc3d24bf1b53cde5e846cbd31",
"m_Group": {
"m_Id": ""
},
"m_Name": "SetupThoMagicRenderer (Custom Function)",
"m_DrawState": {
"m_Expanded": true,
"m_Position": {
"serializedVersion": "2",
"x": -694.0,
"y": -310.0000305175781,
"width": 296.9999694824219,
"height": 94.00001525878906
}
},
"m_Slots": [
{
"m_Id": "45a92477b6a7449c9c4d29eac6f7f4c1"
},
{
"m_Id": "ca8752373fc04409ae302adfc1808024"
}
],
"synonyms": [
"code",
"HLSL"
],
"m_Precision": 0,
"m_PreviewExpanded": false,
"m_DismissedVersion": 0,
"m_PreviewMode": 0,
"m_CustomColors": {
"m_SerializableColors": []
},
"m_SourceType": 1,
"m_FunctionName": "SetupThoMagicRenderer",
"m_FunctionSource": "06018a56d442f704f82b130cdeaf74ef",
"m_FunctionBody": "Out = In;\n#pragma instancing_options procedural:SetupThoMagicRenderer\r\r\n"
}
{
"m_SGVersion": 0,
"m_Type": "UnityEditor.ShaderGraph.Vector3MaterialSlot",
"m_ObjectId": "59dd7ed4b8b14695ab4443b7b89ab9df",
"m_Id": 0,
"m_DisplayName": "In",
"m_SlotType": 0,
"m_Hidden": false,
"m_ShaderOutputName": "In",
"m_StageCapability": 3,
"m_Value": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"m_DefaultValue": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"m_Labels": []
}
{
"m_SGVersion": 0,
"m_Type": "UnityEditor.ShaderGraph.Vector3MaterialSlot",
"m_ObjectId": "79b2dd99d8ea4726856c03ea82ede95b",
"m_Id": 1,
"m_DisplayName": "Out",
"m_SlotType": 1,
"m_Hidden": false,
"m_ShaderOutputName": "Out",
"m_StageCapability": 3,
"m_Value": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"m_DefaultValue": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"m_Labels": []
}
{
"m_SGVersion": 1,
"m_Type": "UnityEditor.ShaderGraph.ShaderKeyword",
"m_ObjectId": "9ba4f485275c4fc9b2f1ce82a79369c4",
"m_Guid": {
"m_GuidSerialized": "019818f3-0f1b-49f3-8508-39d308ae3731"
},
"m_Name": "PROCEDURAL_INSTANCING_ON",
"m_DefaultRefNameVersion": 1,
"m_RefNameGeneratedByDisplayName": "PROCEDURAL_INSTANCING_ON",
"m_DefaultReferenceName": "_PROCEDURAL_INSTANCING_ON",
"m_OverrideReferenceName": "",
"m_GeneratePropertyBlock": true,
"m_UseCustomSlotLabel": false,
"m_CustomSlotLabel": "",
"m_DismissedVersion": 0,
"m_KeywordType": 0,
"m_KeywordDefinition": 0,
"m_KeywordScope": 1,
"m_KeywordStages": 63,
"m_Entries": [],
"m_Value": 1,
"m_IsEditable": true
}
{
"m_SGVersion": 0,
"m_Type": "UnityEditor.ShaderGraph.Vector3MaterialSlot",
"m_ObjectId": "aca287a71573483789d5dcb380daa40c",
"m_Id": 1,
"m_DisplayName": "Position",
"m_SlotType": 0,
"m_Hidden": false,
"m_ShaderOutputName": "Position",
"m_StageCapability": 3,
"m_Value": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"m_DefaultValue": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"m_Labels": []
}
{
"m_SGVersion": 1,
"m_Type": "UnityEditor.ShaderGraph.PositionNode",
"m_ObjectId": "c5119037ba0c48fbba1bb84ead9a5adb",
"m_Group": {
"m_Id": ""
},
"m_Name": "Position",
"m_DrawState": {
"m_Expanded": true,
"m_Position": {
"serializedVersion": "2",
"x": -952.0,
"y": -310.0,
"width": 206.0,
"height": 131.00003051757813
}
},
"m_Slots": [
{
"m_Id": "13e38f92c90c4e709dc150f29929c84a"
}
],
"synonyms": [
"location"
],
"m_Precision": 1,
"m_PreviewExpanded": false,
"m_DismissedVersion": 0,
"m_PreviewMode": 2,
"m_CustomColors": {
"m_SerializableColors": []
},
"m_Space": 0,
"m_PositionSource": 0
}
{
"m_SGVersion": 0,
"m_Type": "UnityEditor.ShaderGraph.Vector3MaterialSlot",
"m_ObjectId": "ca8752373fc04409ae302adfc1808024",
"m_Id": 1,
"m_DisplayName": "Out",
"m_SlotType": 1,
"m_Hidden": false,
"m_ShaderOutputName": "Out",
"m_StageCapability": 3,
"m_Value": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"m_DefaultValue": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"m_Labels": []
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 9ac8f6239a1b8d94a9aa0ba4ece1f714
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 11500000, guid: 60072b568d64c40a485e0fc55012dc9f, type: 3}

View File

@ -0,0 +1,418 @@
#pragma kernel Cull_32 GROUPS=32
#pragma kernel Cull_64 GROUPS=64
#pragma kernel Cull_128 GROUPS=128
#pragma kernel Cull_256 GROUPS=256
#pragma kernel Cull_512 GROUPS=512
#pragma kernel Cull_1024 GROUPS=1024
#pragma kernel Clear_32 GROUPS=32
#pragma kernel Clear_64 GROUPS=64
#pragma kernel Clear_128 GROUPS=128
#pragma kernel Clear_256 GROUPS=256
#pragma kernel Clear_512 GROUPS=512
#pragma kernel Clear_1024 GROUPS=1024
#pragma kernel Upload_32 GROUPS=32
#pragma kernel Upload_64 GROUPS=64
#pragma kernel Upload_128 GROUPS=128
#pragma kernel Upload_256 GROUPS=256
#pragma kernel Upload_512 GROUPS=512
#pragma kernel Upload_1024 GROUPS=1024
#pragma kernel Resize_32 GROUPS=32
#pragma kernel Resize_64 GROUPS=64
#pragma kernel Resize_128 GROUPS=128
#pragma kernel Resize_256 GROUPS=256
#pragma kernel Resize_512 GROUPS=512
#pragma kernel Resize_1024 GROUPS=1024
#include "UnityCG.cginc"
struct InstanceData
{
// position.xyz : position.xyz
// position.w : rotation.x
// scale.x : scale.xz
// scale.y : scale.y
// scale.zw : rotation.yz
float4 position;
float4 scale;
uint prefabId;
//Used to stream out new instances
int uploadAction;
int uploadId;
int age;
};
struct PrefabData
{
uint batchIndex;
uint indexBufferStartOffset;
uint maxCount;
uint lodCount;
uint fadeLods;//Todo: Use Bitwise and save memory?
//x: density threshold, y: density range start, z: density range length, w: shadow distance
float4 densityInDistance;
float4 lodData[8];
uint2 indirectArgsIndexAndCountPerLod[8];
};
struct InstanceMeta
{
uint visibleLod;
uint fadeOutLod;
float fadeAnim;
// The scale of the instance can be adjusted
float Scale;
};
StructuredBuffer<InstanceData> globalUploadInstances;
RWStructuredBuffer<InstanceData> globalInstances;
RWStructuredBuffer<InstanceData> tempGlobalInstances;
StructuredBuffer<PrefabData> perCamPrefabs;
StructuredBuffer<uint> perCamCullableIndexesBuffer;
RWStructuredBuffer<InstanceMeta> perCamMeta;
RWStructuredBuffer<uint> perCamIndirectArgumentsBuffer;
RWStructuredBuffer<uint> perCamVisibleIndexesBuffer;
RWStructuredBuffer<uint> perCamShadowVisibleIndexesBuffer;
uint _CountClear;
uint _CountUpload;
uint _CountResize;
uint _CountCull;
float3 _CameraPosition;
float3 _ShadowDirection;
float4 _FrustumPlanes[6];
// Plane equation: {(a, b, c) = N, d = -dot(N, P)}.
// Returns the distance from the plane to the point 'p' along the normal.
// Positive -> in front (above), negative -> behind (below).
float DistanceFromPlane(float3 p, float4 plane)
{
return dot(float4(p, 1.0), plane);
}
// Returns 'true' if the object is outside of the frustum.
// 'size' is the (negative) size of the object's bounds.
bool CullFrustum(float3 center, float size, float4 frustumPlanes[6], int numPlanes)
{
bool outside = false;
[unroll(6)]
for (int i = 0; i < numPlanes; i++)
outside = outside || DistanceFromPlane(center, frustumPlanes[i]) < size;
return outside;
}
// Returns 'true' if the shadow of the object is outside of the frustum.
// 'size' is the (negative) size of the object's bounds.
bool CullShadowFrustum(float3 center, float size, float3 lightDirection, float4 frustumPlanes[6], int numPlanes)
{
bool outside = false;
[unroll(6)]
for (int i = 0; i < numPlanes; i++)
outside = outside || max(DistanceFromPlane(center, frustumPlanes[i]), DistanceFromPlane(center + lightDirection, frustumPlanes[i])) < size;
return outside;
}
// Calculates the adjusted scale of the instance based on the "Density in Distance"
// setting. Instances get scaled down if the density is reduced.
float ReduceDensityScale(inout InstanceData instance, float4 densityInDistance, float distance)
{
float rangeStart = densityInDistance.y;
float rangeLength = densityInDistance.z;
// Calculate a dither pattern with range [0..1]
float dither = frac(dot(float3((instance.position.xz) * 16.0f, 0), uint3(2, 7, 23) / 17.0f));
float densityThreshold = 1 - densityInDistance.x;
float distanceNormalized = (distance - rangeStart) / (rangeLength * dither + 0.001f);
if(dither > densityInDistance.x && distanceNormalized > 0)
return 1 - distanceNormalized;
else
return 1;
}
void ClearArgumentsBuffer(uint3 id : SV_DispatchThreadID)
{
if (id.x >= _CountClear)
return;
uint indirectArgsCountOffset = id.x * 5 + 1;
perCamIndirectArgumentsBuffer[indirectArgsCountOffset] = 0;
}
void Upload (uint3 id : SV_DispatchThreadID)
{
if (id.x >= _CountUpload)
return;
InstanceData instance = globalUploadInstances[id.x];
if (instance.uploadAction == 1)
{
globalInstances[instance.uploadId] = instance;
}
}
void Resize(uint3 id : SV_DispatchThreadID)
{
if (id.x >= _CountResize)
return;
tempGlobalInstances[id.x] = globalInstances[id.x];
}
void Cull (uint3 id : SV_DispatchThreadID)
{
if(id.x >= _CountCull)
return;
uint instanceIndex = perCamCullableIndexesBuffer[id.x];
InstanceData instance = globalInstances[instanceIndex];
InstanceMeta meta = perCamMeta[instanceIndex];
PrefabData prefab = perCamPrefabs[instance.prefabId];
if (instance.age == 0)
{
meta = (InstanceMeta)0;
globalInstances[instanceIndex].age = 1;
}
uint lodCount = prefab.lodCount;
uint visibleLod = 0;
uint visibleShadowLod = 0;
uint batchOffset = 0;
uint indirectArgsCount = 0;
uint indirectArgsCountOffset = 0;
uint i = 0;
uint u = 0;
// Calculate active LOD
float dist = distance(instance.position.xyz, _CameraPosition.xyz);
[unroll(8)]
for(i=0; i < lodCount; i++)
{
float maxDist =
i < lodCount - 1 ? prefab.lodData[i].y * instance.scale.y : prefab.lodData[i].y;
if(dist >= prefab.lodData[i].x * instance.scale.y && dist < maxDist)
{
visibleLod = i + 1;
if (dist < prefab.densityInDistance.w)//prefab.densityInDistance.w = max shadow distance
visibleShadowLod = i + 1;
}
}
// Reduce density
if(prefab.densityInDistance.x < 1)
{
meta.Scale = ReduceDensityScale(instance, prefab.densityInDistance, dist);
if(meta.Scale < 0.3)
{
visibleLod = 0;
visibleShadowLod = 0;
}
}
else
{
meta.Scale = 1;
}
// Frustum Culling
if(visibleLod > 0)
{
float size = -prefab.lodData[visibleLod - 1].z * length(instance.scale.xy);
const int planeCount = 5; // Do not test near/far planes
if (CullFrustum(instance.position.xyz, size, _FrustumPlanes, planeCount))
{
// Setting active LOD to 0 culls the instance. The LODs start from 1.
visibleLod = 0;
if (CullShadowFrustum(
instance.position.xyz,
size,
_ShadowDirection * 1000,
_FrustumPlanes,
planeCount))
{
visibleShadowLod = 0;
}
}
}
uint visibleCount = 0;
if (visibleLod > 0)
{
batchOffset = prefab.indexBufferStartOffset + ((visibleLod - 1) * prefab.maxCount);
indirectArgsCount = prefab.indirectArgsIndexAndCountPerLod[visibleLod - 1].y;
indirectArgsCountOffset = prefab.indirectArgsIndexAndCountPerLod[visibleLod - 1].x + 1;
InterlockedAdd(perCamIndirectArgumentsBuffer[indirectArgsCountOffset], 1, visibleCount);
for (i = 1, u = 5; i < indirectArgsCount; i++, u += 5)
{
InterlockedAdd(perCamIndirectArgumentsBuffer[indirectArgsCountOffset + u], 1);
}
perCamVisibleIndexesBuffer[batchOffset + visibleCount] = instanceIndex;
if (meta.visibleLod != visibleLod && meta.visibleLod > 0 && prefab.fadeLods > 0)
{
meta.fadeOutLod = meta.visibleLod;
meta.fadeAnim = 1;
}
}
if (visibleShadowLod > 0)
{
batchOffset = prefab.indexBufferStartOffset + ((visibleShadowLod - 1) * prefab.maxCount);
indirectArgsCount = prefab.indirectArgsIndexAndCountPerLod[visibleShadowLod - 1].y;
indirectArgsCountOffset = indirectArgsCount * 5 + prefab.indirectArgsIndexAndCountPerLod[visibleShadowLod - 1].x + 1;
InterlockedAdd(perCamIndirectArgumentsBuffer[indirectArgsCountOffset], 1, visibleCount);
for (i = 1, u = 5; i < indirectArgsCount; i++, u += 5)
{
InterlockedAdd(perCamIndirectArgumentsBuffer[indirectArgsCountOffset + u], 1);
}
perCamShadowVisibleIndexesBuffer[batchOffset + visibleCount] = instanceIndex;
if (meta.visibleLod != visibleShadowLod && meta.visibleLod > 0 && prefab.fadeLods > 0)
{
meta.fadeOutLod = meta.visibleLod;
meta.fadeAnim = 1;
}
}
if (meta.fadeOutLod == 0)
meta.fadeAnim = 0;
if (meta.fadeAnim == 0)
meta.fadeOutLod = 0;
//Add lod cross fade
if (meta.fadeAnim > 0 && meta.fadeOutLod > 0)
{
meta.fadeAnim = max(0, meta.fadeAnim - unity_DeltaTime.z);
//Normal
if (visibleLod > 0 && meta.fadeAnim > 0)
{
batchOffset = prefab.indexBufferStartOffset + ((meta.fadeOutLod - 1) * prefab.maxCount);
indirectArgsCount = prefab.indirectArgsIndexAndCountPerLod[meta.fadeOutLod - 1].y;
indirectArgsCountOffset = prefab.indirectArgsIndexAndCountPerLod[meta.fadeOutLod - 1].x + 1;
InterlockedAdd(perCamIndirectArgumentsBuffer[indirectArgsCountOffset], 1, visibleCount);
for (i = 1, u = 5; i < indirectArgsCount; i++, u += 5)
{
InterlockedAdd(perCamIndirectArgumentsBuffer[indirectArgsCountOffset + u], 1);
}
perCamVisibleIndexesBuffer[batchOffset + visibleCount] = instanceIndex;
}
//Shadow
if (visibleShadowLod > 0 && meta.fadeAnim > 0)
{
batchOffset = prefab.indexBufferStartOffset + ((meta.fadeOutLod - 1) * prefab.maxCount);
indirectArgsCount = prefab.indirectArgsIndexAndCountPerLod[meta.fadeOutLod - 1].y;
indirectArgsCountOffset = indirectArgsCount * 5 + prefab.indirectArgsIndexAndCountPerLod[meta.fadeOutLod - 1].x + 1;
InterlockedAdd(perCamIndirectArgumentsBuffer[indirectArgsCountOffset], 1, visibleCount);
for (i = 1, u = 5; i < indirectArgsCount; i++, u += 5)
{
InterlockedAdd(perCamIndirectArgumentsBuffer[indirectArgsCountOffset + u], 1);
}
perCamShadowVisibleIndexesBuffer[batchOffset + visibleCount] = instanceIndex;
}
}
meta.visibleLod = max(visibleLod, visibleShadowLod);
perCamMeta[instanceIndex] = meta;
}
[numthreads(GROUPS,1,1)]
void Cull_32(uint3 id : SV_DispatchThreadID) { Cull(id); }
[numthreads(GROUPS,1,1)]
void Cull_64(uint3 id : SV_DispatchThreadID) { Cull(id); }
[numthreads(GROUPS,1,1)]
void Cull_128(uint3 id : SV_DispatchThreadID) { Cull(id); }
[numthreads(GROUPS,1,1)]
void Cull_256(uint3 id : SV_DispatchThreadID) { Cull(id); }
[numthreads(GROUPS,1,1)]
void Cull_512(uint3 id : SV_DispatchThreadID) { Cull(id); }
[numthreads(GROUPS,1,1)]
void Cull_1024(uint3 id : SV_DispatchThreadID) { Cull(id); }
[numthreads(GROUPS, 1, 1)]
void Clear_32(uint3 id : SV_DispatchThreadID) { ClearArgumentsBuffer(id); }
[numthreads(GROUPS, 1, 1)]
void Clear_64(uint3 id : SV_DispatchThreadID) { ClearArgumentsBuffer(id); }
[numthreads(GROUPS, 1, 1)]
void Clear_128(uint3 id : SV_DispatchThreadID) { ClearArgumentsBuffer(id); }
[numthreads(GROUPS, 1, 1)]
void Clear_256(uint3 id : SV_DispatchThreadID) { ClearArgumentsBuffer(id); }
[numthreads(GROUPS, 1, 1)]
void Clear_512(uint3 id : SV_DispatchThreadID) { ClearArgumentsBuffer(id); }
[numthreads(GROUPS, 1, 1)]
void Clear_1024(uint3 id : SV_DispatchThreadID) { ClearArgumentsBuffer(id); }
[numthreads(GROUPS, 1, 1)]
void Upload_32(uint3 id : SV_DispatchThreadID) { Upload(id); }
[numthreads(GROUPS, 1, 1)]
void Upload_64(uint3 id : SV_DispatchThreadID) { Upload(id); }
[numthreads(GROUPS, 1, 1)]
void Upload_128(uint3 id : SV_DispatchThreadID) { Upload(id); }
[numthreads(GROUPS, 1, 1)]
void Upload_256(uint3 id : SV_DispatchThreadID) { Upload(id); }
[numthreads(GROUPS, 1, 1)]
void Upload_512(uint3 id : SV_DispatchThreadID) { Upload(id); }
[numthreads(GROUPS, 1, 1)]
void Upload_1024(uint3 id : SV_DispatchThreadID) { Upload(id); }
[numthreads(GROUPS, 1, 1)]
void Resize_32(uint3 id : SV_DispatchThreadID) { Resize(id); }
[numthreads(GROUPS, 1, 1)]
void Resize_64(uint3 id : SV_DispatchThreadID) { Resize(id); }
[numthreads(GROUPS, 1, 1)]
void Resize_128(uint3 id : SV_DispatchThreadID) { Resize(id); }
[numthreads(GROUPS, 1, 1)]
void Resize_256(uint3 id : SV_DispatchThreadID) { Resize(id); }
[numthreads(GROUPS, 1, 1)]
void Resize_512(uint3 id : SV_DispatchThreadID) { Resize(id); }
[numthreads(GROUPS, 1, 1)]
void Resize_1024(uint3 id : SV_DispatchThreadID) { Resize(id); }

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 4a840c1c229294d4996e80608a899881
ComputeShaderImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,136 @@
#pragma multi_compile _LOD_FADE_CROSSFADE
#ifndef NODE_THOMAGIC_RENDERER_INCLUDED
#define NODE_THOMAGIC_RENDERER_INCLUDED
#ifdef UNITY_PROCEDURAL_INSTANCING_ENABLED
#define UNITY_INDIRECT_DRAW_ARGS IndirectDrawIndexedArgs
#include "UnityIndirect.cginc"
#define Use_Macro_UNITY_MATRIX_M_instead_of_unity_ObjectToWorld unity_ObjectToWorld
#define Use_Macro_UNITY_MATRIX_I_M_instead_of_unity_WorldToObject unity_WorldToObject
#define LOD_FADE_CROSSFADE
struct InstanceData
{
// position.xyz : position.xyz
// position.w : rotation.x
// scale.x : scale.xz
// scale.y : scale.y
// scale.zw : rotation.yz
float4 position;
float4 scale;
uint prefabId;
int3 padding;
};
struct InstanceMeta
{
uint visibleLod;
uint fadeOutLod;
float fadeAnim;
// The scale of the instance can be adjusted
float Scale;
};
StructuredBuffer<InstanceData> trInstances;
StructuredBuffer<uint> trPerCamVisibleIndexesBuffer;
StructuredBuffer<InstanceMeta> trPerCamMeta;
float4x4 trInstanceMatrix;
int trLodNr;
float4x4 TRS(float3 t, float4 r, float3 s)
{
float4x4 result = (float4x4)0;
result[0][0] = (1.0f - 2.0f * (r.y * r.y + r.z * r.z)) * s.x;
result[1][0] = (r.x * r.y + r.z * r.w) * s.x * 2.0f;
result[2][0] = (r.x * r.z - r.y * r.w) * s.x * 2.0f;
result[3][0] = 0.0f;
result[0][1] = (r.x * r.y - r.z * r.w) * s.y * 2.0f;
result[1][1] = (1.0f - 2.0f * (r.x * r.x + r.z * r.z)) * s.y;
result[2][1] = (r.y * r.z + r.x * r.w) * s.y * 2.0f;
result[3][1] = 0.0f;
result[0][2] = (r.x * r.z + r.y * r.w) * s.z * 2.0f;
result[1][2] = (r.y * r.z - r.x * r.w) * s.z * 2.0f;
result[2][2] = (1.0f - 2.0f * (r.x * r.x + r.y * r.y)) * s.z;
result[3][2] = 0.0f;
result[0][3] = t.x;
result[1][3] = t.y;
result[2][3] = t.z;
result[3][3] = 1.0f;
return result;
}
void DecompressInstanceMatrix(inout float4x4 m, InstanceData instanceData, InstanceMeta meta)
{
float3 position = instanceData.position.xyz;
float4 rotation = float4(instanceData.position.w, instanceData.scale.zw, 0);
float3 scale = instanceData.scale.xyx * meta.Scale;
rotation.w = sqrt(1.0 - rotation.x * rotation.x - rotation.y * rotation.y - rotation.z * rotation.z);
m = TRS(position, rotation, scale);
}
float4x4 inverse(float4x4 input)
{
#define minor(a,b,c) determinant(float3x3(input.a, input.b, input.c))
float4x4 cofactors = float4x4(
minor(_22_23_24, _32_33_34, _42_43_44),
-minor(_21_23_24, _31_33_34, _41_43_44),
minor(_21_22_24, _31_32_34, _41_42_44),
-minor(_21_22_23, _31_32_33, _41_42_43),
-minor(_12_13_14, _32_33_34, _42_43_44),
minor(_11_13_14, _31_33_34, _41_43_44),
-minor(_11_12_14, _31_32_34, _41_42_44),
minor(_11_12_13, _31_32_33, _41_42_43),
minor(_12_13_14, _22_23_24, _42_43_44),
-minor(_11_13_14, _21_23_24, _41_43_44),
minor(_11_12_14, _21_22_24, _41_42_44),
-minor(_11_12_13, _21_22_23, _41_42_43),
-minor(_12_13_14, _22_23_24, _32_33_34),
minor(_11_13_14, _21_23_24, _31_33_34),
-minor(_11_12_14, _21_22_24, _31_32_34),
minor(_11_12_13, _21_22_23, _31_32_33)
);
#undef minor
return transpose(cofactors) / determinant(input);
}
#endif
void SetupThoMagicRenderer()
{
#ifdef UNITY_PROCEDURAL_INSTANCING_ENABLED
InitIndirectDrawArgs(0);
uint instanceID = GetIndirectInstanceID_Base(unity_InstanceID);
uint instanceIndex = trPerCamVisibleIndexesBuffer[instanceID];
InstanceMeta meta = trPerCamMeta[instanceIndex];
DecompressInstanceMatrix(unity_ObjectToWorld, trInstances[instanceIndex], meta);
unity_ObjectToWorld = mul(unity_ObjectToWorld, trInstanceMatrix);
unity_WorldToObject = inverse(unity_ObjectToWorld);
if (meta.visibleLod == trLodNr)
unity_LODFade.x = (1.0f - meta.fadeAnim);
else
unity_LODFade.x = -(1.0f - meta.fadeAnim);
// {% hd %}
// Instances are static so the previous matrix is the same as the current one.
// These matrices are required for correct motion vectors in HDRP.
#if SHADERPASS == SHADERPASS_MOTION_VECTORS && defined(SHADERPASS_CS_HLSL)
unity_MatrixPreviousM = unity_ObjectToWorld;
unity_MatrixPreviousMI = unity_WorldToObject;
#endif
// {% endhd %}
#endif
}
#endif

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 7fe5ed120e85ccf499d429b359f89173
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,9 @@
#include "ThoMagicRenderer.cginc"
#ifndef THOMAGICRENDERERSETUP_INClUDED
#define THOMAGICRENDERERSETUP_INClUDED
void IncludeThoMagicRenderer_float(float3 In, out float3 Out)
{
Out = In;
}
#endif

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 678dff042fe9c004cb7522c2233af44d
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,11 @@
using UnityEngine;
namespace Assets.ThoMagic.Renderer
{
public struct SceneRenderSettings
{
public bool HasMainLight;
public bool HasMainLightShadows;
public Vector3 MainLightDirection;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8c1bc4c6b611d9d439d0f03feecc4e71
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

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;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 533ab6e6ebf6be745ae7f02dc1922e05
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c2a37bd5b745b59448b6bc6c1ff5f09c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

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();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 365a0b5496e0c71438e4b4a0b232aca6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

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
};
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 14d63a559cc9211479181fed32a2a5c0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

272
Runtime/TreeRenderer.cs Normal file
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
};
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e0f78ad04f9bf164e81068b0dcffd13b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

36
Runtime/TreeStreamer.cs Normal file
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;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 049083d3196ea1b429c2057a188c2bf2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

7
readme.md.meta Normal file
View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 837e822b4df87d1499c0b28ad5c40ddb
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: