Files
ZHGD_Web/Assets/Art/Art Plugins/GPUInstancer/Scripts/GPUInstancerDetailManager.cs
2025-07-13 23:16:20 +08:00

1184 lines
58 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Threading;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace GPUInstancer
{
/// <summary>
/// Add this to a Unity terrain for GPU Instancing details at runtime.
/// </summary>
[ExecuteInEditMode]
public class GPUInstancerDetailManager : GPUInstancerTerrainManager
{
public int detailLayer;
public bool runInThreads = false;
private static ComputeShader _grassInstantiationComputeShader;
private ComputeBuffer _generatingVisibilityBuffer;
#if !UNITY_2017_1_OR_NEWER || UNITY_ANDROID
private ComputeBuffer _managedBuffer;
private Matrix4x4[] _managedData;
#endif
private bool _triggerEvent;
private ComputeBuffer counterBuffer;
private int[] counterData = new int[1];
#region MonoBehaviour Methods
public override void Awake()
{
base.Awake();
if (_grassInstantiationComputeShader == null)
_grassInstantiationComputeShader = (ComputeShader)Resources.Load(GPUInstancerConstants.GRASS_INSTANTIATION_RESOURCE_PATH);
}
public override void OnDisable()
{
if (runInThreads && activeThreads.Count > 0)
{
foreach (Thread thread in activeThreads)
{
thread.Abort();
}
}
ClearCompletedThreads();
threadHeightMapData = null;
threadDetailMapData = null;
base.OnDisable();
if (_generatingVisibilityBuffer != null)
{
_generatingVisibilityBuffer.Release();
}
}
public override void Reset()
{
base.Reset();
#if UNITY_EDITOR
keepSimulationLive = true;
#endif
}
#endregion MonoBehaviour Methods
#region Override Methods
public override void ClearInstancingData()
{
base.ClearInstancingData();
if (terrain != null && terrain.detailObjectDistance == 0)
{
terrain.detailObjectDistance = terrainSettings.maxDetailDistance > 250 ? 250 : terrainSettings.maxDetailDistance;
}
#if !UNITY_2017_1_OR_NEWER || UNITY_ANDROID
if (_managedBuffer != null)
_managedBuffer.Release();
_managedBuffer = null;
#endif
if (counterBuffer != null)
counterBuffer.Release();
counterBuffer = null;
}
public override void GeneratePrototypes(bool forceNew = false)
{
base.GeneratePrototypes(forceNew);
if (terrainSettings != null && terrain != null && terrain.terrainData != null)
{
GPUInstancerUtility.SetDetailInstancePrototypes(gameObject, prototypeList, terrain.terrainData.detailPrototypes, 2, terrainSettings, forceNew);
}
}
#if UNITY_EDITOR
public override void CheckPrototypeChanges()
{
base.CheckPrototypeChanges();
if (!Application.isPlaying && terrainSettings != null && terrain != null && terrain.terrainData != null)
{
if (prototypeList.Count != terrain.terrainData.detailPrototypes.Length)
{
GeneratePrototypes();
}
int index = 0;
foreach (GPUInstancerDetailPrototype prototype in prototypeList)
{
prototype.prototypeIndex = index;
index++;
}
if (prototypeList.Count != terrain.terrainData.detailPrototypes.Length)
terrainSettings.warningText = "Detail Prototypes on the Unity Terrain do not match the Detail Prototypes in this Detail Manager. Adding and removing Detail Prototypes should be done from the Detail Manager and not from the Unity Terrain. To fix this error, you can press the Generate Prototypes button below. This will reset the prototypes in this manager with the default settings.";
#if UNITY_2022_2_OR_NEWER
else if (terrain.terrainData.detailScatterMode == DetailScatterMode.CoverageMode)
terrainSettings.warningText = "\"Detail Scatter Mode\" terrain setting is set to \"Coverage Mode\" which is not supported. Go to terrain settings and change the \"Detail Scatter Mode\" setting to \"Instance Count Mode\" to be able to use the Detail Manager.";
#endif
else
terrainSettings.warningText = null;
AddProxyToTerrain();
}
}
#endif
public override void InitializeRuntimeDataAndBuffers(bool forceNew = true)
{
base.InitializeRuntimeDataAndBuffers(forceNew);
if (!forceNew && isInitialized)
return;
if (terrainSettings == null)
return;
if (!string.IsNullOrEmpty(terrainSettings.warningText))
{
Debug.LogError("A GPU Instancer Detail Manager currently has errors. Please refer to the error description in the Detail manager.", this);
#if UNITY_EDITOR
if (gpuiSimulator != null)
gpuiSimulator.StopSimulation();
keepSimulationLive = false;
#endif
return;
}
replacingInstances = false;
initalizingInstances = true;
if (prototypeList != null && prototypeList.Count > 0)
{
GPUInstancerUtility.AddDetailInstanceRuntimeDataToList(runtimeDataList, prototypeList, terrainSettings, detailLayer);
}
terrain.detailObjectDistance = 0;
InitializeSpatialPartitioning();
isInitialized = true;
}
public override void UpdateSpatialPartitioningCells(GPUInstancerCameraData renderingCameraData)
{
base.UpdateSpatialPartitioningCells(renderingCameraData);
if (terrainSettings == null || spData == null)
return;
if (!initalizingInstances && !replacingInstances && spData.IsActiveCellUpdateRequired(renderingCameraData.mainCamera.transform.position))
{
replacingInstances = true;
if (GPUInstancerConstants.DETAIL_STORE_INSTANCE_DATA)
StartCoroutineAlt(GenerateVisibilityBufferFromActiveCellsCoroutine());
else
StartCoroutineAlt(MergeVisibilityBufferFromActiveCellsCoroutine());
}
}
public override void DeletePrototype(GPUInstancerPrototype prototype, bool removeSO = true)
{
if (terrainSettings != null && terrain != null && terrain.terrainData != null)
{
int detailPrototypeIndex = prototypeList.IndexOf(prototype);
DetailPrototype[] detailPrototypes = terrain.terrainData.detailPrototypes;
List<DetailPrototype> newDetailPrototypes = new List<DetailPrototype>();
List<int[,]> newDetailLayers = new List<int[,]>();
for (int i = 0; i < detailPrototypes.Length; i++)
{
if (i != detailPrototypeIndex)
{
newDetailPrototypes.Add(detailPrototypes[i]);
newDetailLayers.Add(terrain.terrainData.GetDetailLayer(0, 0, terrain.terrainData.detailResolution, terrain.terrainData.detailResolution, i));
}
terrain.terrainData.SetDetailLayer(0, 0, i, new int[terrain.terrainData.detailResolution, terrain.terrainData.detailResolution]);
}
terrain.terrainData.detailPrototypes = newDetailPrototypes.ToArray();
for (int i = 0; i < newDetailLayers.Count; i++)
{
terrain.terrainData.SetDetailLayer(0, 0, i, newDetailLayers[i]);
}
terrain.terrainData.RefreshPrototypes();
if (removeSO)
base.DeletePrototype(prototype, removeSO);
GeneratePrototypes(false);
if (!removeSO)
base.DeletePrototype(prototype, removeSO);
}
else
base.DeletePrototype(prototype, removeSO);
}
public override void RemoveInstancesInsideBounds(Bounds bounds, float offset, List<GPUInstancerPrototype> prototypeFilter = null)
{
//#if UNITY_EDITOR
// UnityEngine.Profiling.Profiler.BeginSample("GPUInstancerDetailManager.RemoveInstancesInsideBounds");
//#endif
base.RemoveInstancesInsideBounds(bounds, offset, prototypeFilter);
if (spData != null && !initalizingInstances)
{
int detailMapSize = terrain.terrainData.detailResolution / spData.cellRowAndCollumnCountPerTerrain;
float unitSizeX = terrain.terrainData.size.x / spData.cellRowAndCollumnCountPerTerrain / detailMapSize;
float unitSizeZ = terrain.terrainData.size.z / spData.cellRowAndCollumnCountPerTerrain / detailMapSize;
int sizeX = Mathf.CeilToInt((bounds.extents.x * 2 + offset) / unitSizeX);
int sizeZ = Mathf.CeilToInt((bounds.extents.z * 2 + offset) / unitSizeZ);
foreach (GPUInstancerDetailCell detailCell in spData.GetCellList())
{
if (detailCell.cellInnerBounds.Intersects(bounds))
{
if (detailCell.isActive && detailCell.detailInstanceBuffers != null)
{
foreach (int i in detailCell.detailInstanceBuffers.Keys)
{
if (prototypeFilter != null && !prototypeFilter.Contains(prototypeList[i]))
continue;
GPUInstancerUtility.RemoveInstancesInsideBounds(detailCell.detailInstanceBuffers[i], bounds.center, bounds.extents, offset);
}
}
int startX = Mathf.FloorToInt((bounds.center.x - bounds.extents.x - detailCell.instanceStartPosition.x - offset) / unitSizeX);
int startZ = Mathf.FloorToInt((bounds.center.z - bounds.extents.z - detailCell.instanceStartPosition.z - offset) / unitSizeZ);
for (int i = 0; i < detailCell.detailMapData.Count; i++)
{
if (prototypeFilter != null && !prototypeFilter.Contains(prototypeList[i]))
continue;
for (int y = startZ; y < sizeZ + startZ; y++)
{
if (y < 0 || y >= detailMapSize)
continue;
for (int x = startX; x < sizeX + startX; x++)
{
if (x < 0 || x >= detailMapSize)
continue;
detailCell.detailMapData[i][x + y * detailMapSize] = 0;
}
}
}
}
}
}
//#if UNITY_EDITOR
// UnityEngine.Profiling.Profiler.EndSample();
//#endif
}
public override void RemoveInstancesInsideCollider(Collider collider, float offset, List<GPUInstancerPrototype> prototypeFilter = null)
{
//#if UNITY_EDITOR
// UnityEngine.Profiling.Profiler.BeginSample("GPUInstancerDetailManager.RemoveInstancesInsideCollider");
//#endif
base.RemoveInstancesInsideCollider(collider, offset, prototypeFilter);
if (spData != null && !initalizingInstances)
{
Bounds bounds = collider.bounds;
int detailMapSize = terrain.terrainData.detailResolution / spData.cellRowAndCollumnCountPerTerrain;
float unitSizeX = terrain.terrainData.size.x / spData.cellRowAndCollumnCountPerTerrain / detailMapSize;
float unitSizeZ = terrain.terrainData.size.z / spData.cellRowAndCollumnCountPerTerrain / detailMapSize;
int sizeX = Mathf.CeilToInt((bounds.extents.x * 2 + offset) / unitSizeX);
int sizeZ = Mathf.CeilToInt((bounds.extents.z * 2 + offset) / unitSizeZ);
Vector3 testCenter = Vector3.zero;
Vector3 closestPoint = Vector3.zero;
foreach (GPUInstancerDetailCell detailCell in spData.GetCellList())
{
if (detailCell.cellInnerBounds.Intersects(bounds))
{
if (detailCell.isActive && detailCell.detailInstanceBuffers != null)
{
foreach (int i in detailCell.detailInstanceBuffers.Keys)
{
if (prototypeFilter != null && !prototypeFilter.Contains(prototypeList[i]))
continue;
if (collider is BoxCollider)
GPUInstancerUtility.RemoveInstancesInsideBoxCollider(detailCell.detailInstanceBuffers[i], (BoxCollider)collider, offset);
else if (collider is SphereCollider)
GPUInstancerUtility.RemoveInstancesInsideSphereCollider(detailCell.detailInstanceBuffers[i], (SphereCollider)collider, offset);
else if (collider is CapsuleCollider)
GPUInstancerUtility.RemoveInstancesInsideCapsuleCollider(detailCell.detailInstanceBuffers[i], (CapsuleCollider)collider, offset);
else
GPUInstancerUtility.RemoveInstancesInsideBounds(detailCell.detailInstanceBuffers[i], collider.bounds.center, collider.bounds.extents, offset);
}
}
int startX = Mathf.FloorToInt((bounds.center.x - bounds.extents.x - detailCell.instanceStartPosition.x - offset) / unitSizeX);
int startZ = Mathf.FloorToInt((bounds.center.z - bounds.extents.z - detailCell.instanceStartPosition.z - offset) / unitSizeZ);
for (int y = startZ; y < sizeZ + startZ; y++)
{
if (y < 0 || y >= detailMapSize)
continue;
for (int x = startX; x < sizeX + startX; x++)
{
if (x < 0 || x >= detailMapSize)
continue;
testCenter.x = detailCell.instanceStartPosition.x + x * unitSizeX;
testCenter.z = detailCell.instanceStartPosition.z + y * unitSizeZ;
testCenter.y = terrain.SampleHeight(testCenter);
closestPoint = collider.ClosestPoint(testCenter);
if (Vector3.Distance(closestPoint, testCenter) > unitSizeX + offset)
continue;
for (int i = 0; i < detailCell.detailMapData.Count; i++)
{
if (prototypeFilter != null && !prototypeFilter.Contains(prototypeList[i]))
continue;
detailCell.detailMapData[i][x + y * detailMapSize] = 0;
}
}
}
}
}
}
//#if UNITY_EDITOR
// UnityEngine.Profiling.Profiler.EndSample();
//#endif
}
public override void SetGlobalPositionOffset(Vector3 offsetPosition)
{
base.SetGlobalPositionOffset(offsetPosition);
if (spData != null)
{
foreach (GPUInstancerDetailCell cell in spData.GetCellList())
{
if (cell == null)
continue;
cell.instanceStartPosition += offsetPosition;
cell.cellBounds.center += offsetPosition;
if (cell.detailInstanceBuffers != null)
{
foreach (ComputeBuffer detailBuffer in cell.detailInstanceBuffers.Values)
{
if (detailBuffer != null)
{
GPUInstancerConstants.computeRuntimeModification.SetBuffer(GPUInstancerConstants.computeBufferTransformOffsetId,
GPUInstancerConstants.VisibilityKernelPoperties.INSTANCE_DATA_BUFFER, detailBuffer);
GPUInstancerConstants.computeRuntimeModification.SetInt(
GPUInstancerConstants.VisibilityKernelPoperties.BUFFER_PARAMETER_BUFFER_SIZE, detailBuffer.count);
GPUInstancerConstants.computeRuntimeModification.SetVector(
GPUInstancerConstants.RuntimeModificationKernelProperties.BUFFER_PARAMETER_POSITION_OFFSET, offsetPosition);
GPUInstancerConstants.computeRuntimeModification.Dispatch(GPUInstancerConstants.computeBufferTransformOffsetId,
Mathf.CeilToInt(detailBuffer.count / GPUInstancerConstants.COMPUTE_SHADER_THREAD_COUNT), 1, 1);
}
}
}
if (GPUInstancerConstants.DETAIL_STORE_INSTANCE_DATA && cell.detailInstanceList != null)
{
foreach (Matrix4x4[] instanceDataArray in cell.detailInstanceList.Values)
{
for (int i = 0; i < instanceDataArray.Length; i++)
{
instanceDataArray[i].SetColumn(3, instanceDataArray[i].GetColumn(3) + new Vector4(offsetPosition.x, offsetPosition.y, offsetPosition.z, 0));
}
}
}
}
}
}
#endregion Override Methods
#region Helper Methods
private static int FixBounds(int value, int max, int failValue)
{
if (value >= max)
return failValue;
return value;
}
public static Matrix4x4[] GetInstanceDataForDetailPrototype(GPUInstancerDetailPrototype detailPrototype, int[] detailMap, float[] heightMapData,
int detailMapSize, int heightMapSize,
int detailResolution, int heightResolution,
Vector3 startPosition, Vector3 terrainSize,
int instanceCount)
{
Matrix4x4[] result = new Matrix4x4[instanceCount];
if (instanceCount == 0)
return result;
System.Random randomNumberGenerator = new System.Random();
float detailHeightMapScale = (heightResolution - 1.0f) / detailResolution;
int heightDataSize = heightMapSize * heightMapSize;
float sizeDetailXScale = terrainSize.x / detailResolution;
float sizeDetailZScale = terrainSize.z / detailResolution;
float normalScale = heightResolution / (terrainSize.x / terrainSize.y);
float px, py, leftBottomH, leftTopH, rightBottomH, rightTopH;
int heightIndex;
Vector3 position;
Vector3 scale = Vector3.zero;
Quaternion rotation = Quaternion.identity;
Vector3 P = Vector3.zero;
Vector3 Q = new Vector3(0, 0, 1);
Vector3 R = new Vector3(1, 0, 0);
Vector3 terrainPointNormal = Vector3.zero;
int counter = 0;
for (int y = 0; y < detailMapSize; y++)
{
for (int x = 0; x < detailMapSize; x++)
{
for (int j = 0; j < detailMap[y * detailMapSize + x]; j++) // for the amount of detail at this point and for this prototype
{
position.x = x + randomNumberGenerator.Range(0f, 0.99f);
position.y = 0;
position.z = y + randomNumberGenerator.Range(0f, 0.99f);
// set height
px = position.x * detailHeightMapScale;
py = position.z * detailHeightMapScale;
heightIndex = Mathf.FloorToInt(px) + Mathf.FloorToInt(py) * heightMapSize;
leftBottomH = heightMapData[heightIndex];
leftTopH = heightMapData[FixBounds(heightIndex + heightMapSize, heightDataSize, heightIndex)];
rightBottomH = heightMapData[heightIndex + 1];
rightTopH = heightMapData[FixBounds(heightIndex + heightMapSize + 1, heightDataSize, heightIndex)];
position.x *= sizeDetailXScale;
position.y = GPUInstancerUtility.SampleTerrainHeight(px - Mathf.Floor(px), py - Mathf.Floor(py), leftBottomH, leftTopH, rightBottomH, rightTopH) * terrainSize.y;
position.z *= sizeDetailZScale;
position += startPosition;
// get normal
//Vector3 terrainPointNormal = GPUInstancerUtility.ComputeTerrainNormal(leftBottomH, leftTopH, rightBottomH, normalScale);
P.y = leftBottomH * normalScale;
Q.y = leftTopH * normalScale;
P.y = rightBottomH * normalScale;
terrainPointNormal = Vector3.Cross(Q - R, R - P).normalized;
rotation.SetFromToRotation(Vector3.up, terrainPointNormal);
rotation *= Quaternion.AngleAxis(randomNumberGenerator.Range(0.0f, 360.0f), Vector3.up);
float randomScale = randomNumberGenerator.Range(0.0f, 1.0f);
float xzScale = detailPrototype.detailScale.x + (detailPrototype.detailScale.y - detailPrototype.detailScale.x) * randomScale;
float yScale = detailPrototype.detailScale.z + (detailPrototype.detailScale.w - detailPrototype.detailScale.z) * randomScale;
scale.x = xzScale;
scale.y = yScale;
scale.z = xzScale;
result[counter].SetTRS(position, rotation, scale);
counter++;
}
}
}
return result;
}
private static Matrix4x4[] GetInstanceDataForDetailPrototypeWithComputeShader(GPUInstancerDetailPrototype detailPrototype, int[] detailMap, float[] heightMapData,
int detailMapSize, int heightMapSize,
int detailResolution, int heightResolution,
Vector3 startPosition, Vector3 terrainSize,
int instanceCount,
ComputeShader grassInstantiationComputeShader, GPUInstancerTerrainSettings terrainSettings,
ComputeBuffer counterBuffer, int[] counterData)
{
Matrix4x4[] result = new Matrix4x4[instanceCount];
if (instanceCount == 0)
return result;
ComputeBuffer visibilityBuffer;
// set compute shader
int grassInstantiationComputeKernelId = grassInstantiationComputeShader.FindKernel(GPUInstancerConstants.GRASS_INSTANTIATION_KERNEL);
ComputeBuffer heightMapBuffer = new ComputeBuffer(heightMapData.Length, GPUInstancerConstants.STRIDE_SIZE_INT);
heightMapBuffer.SetData(heightMapData);
visibilityBuffer = new ComputeBuffer(instanceCount, GPUInstancerConstants.STRIDE_SIZE_MATRIX4X4);
counterBuffer.SetData(counterData);
ComputeBuffer detailMapBuffer = new ComputeBuffer(Mathf.CeilToInt(detailMapSize * detailMapSize), GPUInstancerConstants.STRIDE_SIZE_INT);
detailMapBuffer.SetData(detailMap);
// dispatch compute shader
DispatchDetailComputeShader(grassInstantiationComputeShader, grassInstantiationComputeKernelId,
visibilityBuffer, detailMapBuffer, heightMapBuffer,
new Vector4(detailMapSize, detailMapSize, heightMapSize, heightMapSize), startPosition, terrainSize, detailResolution, heightResolution, detailPrototype.detailScale,
terrainSettings.GetHealthyDryNoiseTexture(detailPrototype), detailPrototype.noiseSpread, detailPrototype.GetInstanceID(), detailPrototype.detailDensity, counterBuffer, detailPrototype.terrainNormalEffect);
detailMapBuffer.Release();
visibilityBuffer.GetData(result);
visibilityBuffer.Release();
heightMapBuffer.Release();
return result;
}
private static ComputeBuffer GetComputeBufferForDetailPrototypeWithComputeShader(GPUInstancerDetailPrototype detailPrototype,
int detailMapSize, int heightMapSize,
int detailResolution, int heightResolution,
Vector3 startPosition, Vector3 terrainSize,
int instanceCount,
ComputeShader grassInstantiationComputeShader, GPUInstancerTerrainSettings terrainSettings,
ComputeBuffer heightMapBuffer, ComputeBuffer detailMapBuffer, ComputeBuffer counterBuffer, int[] counterData)
{
if (instanceCount == 0)
return null;
ComputeBuffer visibilityBuffer;
// set compute shader
int grassInstantiationComputeKernelId = grassInstantiationComputeShader.FindKernel(GPUInstancerConstants.GRASS_INSTANTIATION_KERNEL);
visibilityBuffer = new ComputeBuffer(instanceCount, GPUInstancerConstants.STRIDE_SIZE_MATRIX4X4);
counterBuffer.SetData(counterData);
// dispatch compute shader
DispatchDetailComputeShader(grassInstantiationComputeShader, grassInstantiationComputeKernelId,
visibilityBuffer, detailMapBuffer, heightMapBuffer,
new Vector4(detailMapSize, detailMapSize, heightMapSize, heightMapSize), startPosition, terrainSize, detailResolution, heightResolution, detailPrototype.detailScale,
terrainSettings.GetHealthyDryNoiseTexture(detailPrototype), detailPrototype.noiseSpread, detailPrototype.GetInstanceID(), detailPrototype.detailDensity, counterBuffer, detailPrototype.terrainNormalEffect);
return visibilityBuffer;
}
private static void DispatchDetailComputeShader(ComputeShader grassComputeShader, int grassInstantiationComputeKernelId, ComputeBuffer visibilityBuffer, ComputeBuffer detailMapBuffer, ComputeBuffer heightMapBuffer, Vector4 detailAndHeightMapSize, Vector3 startPosition, Vector3 terrainSize, int detailResolution, int heightResolution, Vector4 detailScale, Texture healthyDryNoiseTexture, float noiseSpread, int instanceID, float detailDensity, ComputeBuffer counterBuffer, float terrainNormalEffect)
{
// setup compute shader
grassComputeShader.SetBuffer(grassInstantiationComputeKernelId, GPUInstancerConstants.VisibilityKernelPoperties.INSTANCE_DATA_BUFFER, visibilityBuffer);
grassComputeShader.SetBuffer(grassInstantiationComputeKernelId, GPUInstancerConstants.GrassKernelProperties.DETAIL_MAP_DATA_BUFFER, detailMapBuffer);
grassComputeShader.SetBuffer(grassInstantiationComputeKernelId, GPUInstancerConstants.GrassKernelProperties.HEIGHT_MAP_DATA_BUFFER, heightMapBuffer);
grassComputeShader.SetBuffer(grassInstantiationComputeKernelId, GPUInstancerConstants.GrassKernelProperties.COUNTER_BUFFER, counterBuffer);
grassComputeShader.SetInt(GPUInstancerConstants.GrassKernelProperties.TERRAIN_DETAIL_RESOLUTION_DATA, detailResolution);
grassComputeShader.SetInt(GPUInstancerConstants.GrassKernelProperties.TERRAIN_HEIGHT_RESOLUTION_DATA, heightResolution);
grassComputeShader.SetVector(GPUInstancerConstants.GrassKernelProperties.GRASS_START_POSITION_DATA, startPosition);
grassComputeShader.SetVector(GPUInstancerConstants.GrassKernelProperties.TERRAIN_SIZE_DATA, terrainSize);
grassComputeShader.SetVector(GPUInstancerConstants.GrassKernelProperties.DETAIL_SCALE_DATA, detailScale);
grassComputeShader.SetVector(GPUInstancerConstants.GrassKernelProperties.DETAIL_AND_HEIGHT_MAP_SIZE_DATA, detailAndHeightMapSize);
if (healthyDryNoiseTexture != null)
{
grassComputeShader.SetTexture(grassInstantiationComputeKernelId, GPUInstancerConstants.GrassKernelProperties.HEALTHY_DRY_NOISE_TEXTURE, healthyDryNoiseTexture);
grassComputeShader.SetFloat(GPUInstancerConstants.GrassKernelProperties.NOISE_SPREAD, noiseSpread);
}
grassComputeShader.SetFloat(GPUInstancerConstants.GrassKernelProperties.DETAIL_UNIQUE_VALUE, instanceID / 1000f);
grassComputeShader.SetFloat(GPUInstancerConstants.GrassKernelProperties.DETAIL_DENSITY, detailDensity);
grassComputeShader.SetFloat(GPUInstancerConstants.GrassKernelProperties.TERRAIN_NORMAL_EFFECT, terrainNormalEffect);
// dispatch
grassComputeShader.Dispatch(grassInstantiationComputeKernelId,
Mathf.CeilToInt(detailAndHeightMapSize.x / GPUInstancerConstants.COMPUTE_SHADER_THREAD_COUNT_2D),
1,
Mathf.CeilToInt(detailAndHeightMapSize.y / GPUInstancerConstants.COMPUTE_SHADER_THREAD_COUNT_2D));
}
private void StartCoroutineAlt(IEnumerator routine)
{
if (Application.isPlaying)
StartCoroutine(routine);
else
while (routine.MoveNext()) ;
}
#endregion Helper Methods
#region Spatial Partitioning Cell Management
public override void InitializeSpatialPartitioning()
{
base.InitializeSpatialPartitioning();
GPUInstancerUtility.ReleaseSPBuffers(spData);
spData = new GPUInstancerSpatialPartitioningData<GPUInstancerCell>();
GPUInstancerUtility.CalculateSpatialPartitioningValuesFromTerrain(spData, terrain, terrainSettings.maxDetailDistance, terrainSettings.autoSPCellSize ? 0 : terrainSettings.preferedSPCellSize);
// initialize cells
GenerateCellsInstanceDataFromTerrain();
}
private IEnumerator GenerateVisibilityBufferFromActiveCellsCoroutine()
{
if (!initalizingInstances)
{
#if !UNITY_2017_1_OR_NEWER || UNITY_ANDROID
if (_managedBuffer == null)
_managedBuffer = new ComputeBuffer(GPUInstancerConstants.BUFFER_COROUTINE_STEP_NUMBER, GPUInstancerConstants.STRIDE_SIZE_MATRIX4X4);
if (_managedData == null)
_managedData = new Matrix4x4[GPUInstancerConstants.BUFFER_COROUTINE_STEP_NUMBER];
#endif
List<GPUInstancerRuntimeData> runtimeDatas = new List<GPUInstancerRuntimeData>(runtimeDataList);
int totalCount = 0;
int lastbreak = 0;
for (int r = 0; r < runtimeDatas.Count; r++)
{
GPUInstancerRuntimeData rd = runtimeDatas[r];
if (spData.activeCellList != null && spData.activeCellList.Count > 0)
{
int totalInstanceCount = 0;
foreach (GPUInstancerDetailCell cell in spData.activeCellList)
{
if (cell != null && cell.detailInstanceList != null)
{
totalInstanceCount += cell.detailInstanceList[r].Length;
}
}
rd.bufferSize = totalInstanceCount;
rd.instanceCount = totalInstanceCount;
if (totalInstanceCount == 0)
{
if (rd.transformationMatrixVisibilityBuffer != null)
rd.transformationMatrixVisibilityBuffer.Release();
rd.transformationMatrixVisibilityBuffer = null;
continue;
}
_generatingVisibilityBuffer = new ComputeBuffer(totalInstanceCount, GPUInstancerConstants.STRIDE_SIZE_MATRIX4X4);
int startIndex = 0;
int cellStartIndex = 0;
int count = 0;
for (int c = 0; c < spData.activeCellList.Count; c++)
{
GPUInstancerDetailCell detailCell = (GPUInstancerDetailCell)spData.activeCellList[c];
cellStartIndex = 0;
for (int i = 0; i < Mathf.Ceil((float)detailCell.detailInstanceList[r].Length / (float)GPUInstancerConstants.BUFFER_COROUTINE_STEP_NUMBER); i++)
{
cellStartIndex = i * GPUInstancerConstants.BUFFER_COROUTINE_STEP_NUMBER;
count = GPUInstancerConstants.BUFFER_COROUTINE_STEP_NUMBER;
if (cellStartIndex + count > detailCell.detailInstanceList[r].Length)
count = detailCell.detailInstanceList[r].Length - cellStartIndex;
#if UNITY_2017_1_OR_NEWER && !UNITY_ANDROID
_generatingVisibilityBuffer.SetDataPartial(detailCell.detailInstanceList[r], cellStartIndex, startIndex, count);
#else
_generatingVisibilityBuffer.SetDataPartial(detailCell.detailInstanceList[r], cellStartIndex, startIndex, count, _managedBuffer, _managedData);
#endif
startIndex += count;
totalCount += count;
if (count + cellStartIndex < detailCell.detailInstanceList[r].Length - 1 && totalCount - lastbreak > GPUInstancerConstants.BUFFER_COROUTINE_STEP_NUMBER)
{
lastbreak = totalCount;
yield return null;
}
}
if (initalizingInstances)
break;
}
if (initalizingInstances)
break;
if (initalizingInstances)
break;
if (rd.transformationMatrixVisibilityBuffer != null)
rd.transformationMatrixVisibilityBuffer.Release();
rd.transformationMatrixVisibilityBuffer = _generatingVisibilityBuffer;
}
if (initalizingInstances)
break;
GPUInstancerUtility.InitializeGPUBuffer(rd);
lastbreak = totalCount;
yield return null;
}
if (initalizingInstances)
{
if (_generatingVisibilityBuffer != null)
_generatingVisibilityBuffer.Release();
GPUInstancerUtility.ReleaseInstanceBuffers(runtimeDatas);
}
_generatingVisibilityBuffer = null;
replacingInstances = false;
if (!initalizingInstances)
{
if (_triggerEvent)
GPUInstancerUtility.TriggerEvent(GPUInstancerEventType.DetailInitializationFinished);
_triggerEvent = false;
isInitial = true;
}
}
}
private IEnumerator MergeVisibilityBufferFromActiveCellsCoroutine()
{
if (!initalizingInstances)
{
List<GPUInstancerRuntimeData> runtimeDatas = new List<GPUInstancerRuntimeData>(runtimeDataList);
int detailMapSize = terrain.terrainData.detailResolution / spData.cellRowAndCollumnCountPerTerrain;
int heightMapSize = (terrain.terrainData.heightmapResolution - 1) / spData.cellRowAndCollumnCountPerTerrain + 1;
int detailResolution = terrain.terrainData.detailResolution;
int heightmapResolution = terrain.terrainData.heightmapResolution;
Vector3 terrainSize = terrain.terrainData.size;
ComputeBuffer heightMapBuffer = null;
ComputeBuffer detailMapBuffer = null;
GPUInstancerDetailCell heightMapCell = null;
int generatedBuffers = 0;
if (spData.activeCellList != null && spData.activeCellList.Count > 0)
{
foreach (GPUInstancerDetailCell cell in spData.activeCellList)
{
generatedBuffers = 0;
if (cell != null && cell.totalDetailCounts != null)
{
if (cell.detailInstanceBuffers == null)
cell.detailInstanceBuffers = new Dictionary<int, ComputeBuffer>();
for (int r = 0; r < runtimeDatas.Count; r++)
{
if (cell.totalDetailCounts[r] > 0)
{
if (!cell.detailInstanceBuffers.ContainsKey(r) || cell.detailInstanceBuffers[r] == null)
{
#if UNITY_EDITOR
UnityEngine.Profiling.Profiler.BeginSample("GPUInstancerDetailManager.MergeVisibilityBufferFromActiveCellsCoroutine");
#endif
if (heightMapCell != cell)
{
if (heightMapBuffer == null)
heightMapBuffer = new ComputeBuffer(cell.heightMapData.Length, GPUInstancerConstants.STRIDE_SIZE_INT);
heightMapBuffer.SetData(cell.heightMapData);
heightMapCell = cell;
}
if (detailMapBuffer == null)
detailMapBuffer = new ComputeBuffer(detailMapSize * detailMapSize, GPUInstancerConstants.STRIDE_SIZE_INT);
detailMapBuffer.SetData(cell.detailMapData[r]);
cell.detailInstanceBuffers[r] =
GetComputeBufferForDetailPrototypeWithComputeShader((GPUInstancerDetailPrototype)prototypeList[r],
detailMapSize, heightMapSize,
detailResolution, heightmapResolution,
cell.instanceStartPosition,
terrainSize, cell.totalDetailCounts[r], _grassInstantiationComputeShader, terrainSettings,
heightMapBuffer, detailMapBuffer, counterBuffer, counterData);
generatedBuffers += detailMapSize;
#if UNITY_EDITOR
UnityEngine.Profiling.Profiler.EndSample();
#endif
if (generatedBuffers >= GPUInstancerConstants.DETAIL_BUFFER_MERGE_FRAME_LIMIT)
{
generatedBuffers = 0;
yield return null;
}
}
}
}
}
}
}
for (int r = 0; r < runtimeDatas.Count; r++)
{
GPUInstancerRuntimeData rd = runtimeDatas[r];
if (spData.activeCellList != null && spData.activeCellList.Count > 0)
{
int totalInstanceCount = 0;
foreach (GPUInstancerDetailCell cell in spData.activeCellList)
{
if (cell != null && cell.totalDetailCounts != null)
{
totalInstanceCount += cell.totalDetailCounts[r];
}
}
rd.bufferSize = totalInstanceCount;
rd.instanceCount = totalInstanceCount;
if (totalInstanceCount == 0)
{
if (rd.transformationMatrixVisibilityBuffer != null)
rd.transformationMatrixVisibilityBuffer.Release();
rd.transformationMatrixVisibilityBuffer = null;
continue;
}
_generatingVisibilityBuffer = new ComputeBuffer(totalInstanceCount, GPUInstancerConstants.STRIDE_SIZE_MATRIX4X4);
int startIndex = 0;
for (int c = 0; c < spData.activeCellList.Count; c++)
{
GPUInstancerDetailCell detailCell = (GPUInstancerDetailCell)spData.activeCellList[c];
if (!detailCell.detailInstanceBuffers.ContainsKey(r) || detailCell.detailInstanceBuffers[r] == null)
continue;
_generatingVisibilityBuffer.CopyComputeBuffer(startIndex, detailCell.detailInstanceBuffers[r].count, detailCell.detailInstanceBuffers[r]);
startIndex += detailCell.detailInstanceBuffers[r].count;
//yield return null;
}
if (rd.transformationMatrixVisibilityBuffer != null)
rd.transformationMatrixVisibilityBuffer.Release();
rd.transformationMatrixVisibilityBuffer = _generatingVisibilityBuffer;
}
GPUInstancerUtility.InitializeGPUBuffer(rd);
}
if (heightMapBuffer != null)
heightMapBuffer.Release();
if (detailMapBuffer != null)
detailMapBuffer.Release();
_generatingVisibilityBuffer = null;
replacingInstances = false;
if (_triggerEvent)
GPUInstancerUtility.TriggerEvent(GPUInstancerEventType.DetailInitializationFinished);
_triggerEvent = false;
}
}
private void GenerateCellsInstanceDataFromTerrain()
{
if (counterBuffer == null)
counterBuffer = new ComputeBuffer(1, GPUInstancerConstants.STRIDE_SIZE_INT);
StartCoroutineAlt(FillCellsDetailData(terrain));
}
public void FillCellsDetailDataCallBack()
{
ClearCompletedThreads();
if (threadHeightMapData == null || (runInThreads && activeThreads.Count > 0))
return;
threadHeightMapData = null;
threadDetailMapData = null;
if (GPUInstancerConstants.DETAIL_STORE_INSTANCE_DATA)
{
int detailMapSize = terrain.terrainData.detailResolution / spData.cellRowAndCollumnCountPerTerrain;
int heightMapSize = (terrain.terrainData.heightmapResolution - 1) / spData.cellRowAndCollumnCountPerTerrain + 1;
int detailResolution = terrain.terrainData.detailResolution;
int heightmapResolution = terrain.terrainData.heightmapResolution;
Vector3 terrainSize = terrain.terrainData.size;
StartCoroutineAlt(SetInstanceDataForDetailCells(spData, prototypeList, detailMapSize, heightMapSize, detailResolution, heightmapResolution, terrainSize,
counterBuffer, counterData, terrainSettings, SetInstanceDataForDetailCellsCallback));
}
else
SetInstanceDataForDetailCellsCallback();
}
private void SetInstanceDataForDetailCellsCallback()
{
initalizingInstances = false;
if (spData == null)
return;
foreach (GPUInstancerCell cell in spData.activeCellList)
cell.isActive = false;
spData.activeCellList.Clear();
_triggerEvent = true;
}
private static IEnumerator SetInstanceDataForDetailCells(GPUInstancerSpatialPartitioningData<GPUInstancerCell> spData, List<GPUInstancerPrototype> prototypeList,
int detailMapSize, int heightMapSize, int detailResolution, int heightmapResolution, Vector3 terrainSize, ComputeBuffer counterBuffer, int[] counterData,
GPUInstancerTerrainSettings terrainSettings, Action callback)
{
int totalCreated = 0;
foreach (GPUInstancerDetailCell cell in spData.GetCellList())
{
if (cell.detailMapData == null)
continue;
cell.detailInstanceList = new Dictionary<int, Matrix4x4[]>();
for (int i = 0; i < prototypeList.Count; i++)
{
totalCreated += cell.totalDetailCounts[i];
#if !UNITY_EDITOR && UNITY_ANDROID
if (SystemInfo.graphicsDeviceType == UnityEngine.Rendering.GraphicsDeviceType.Vulkan)
{
cell.detailInstanceList[i] = GetInstanceDataForDetailPrototype((GPUInstancerDetailPrototype)prototypeList[i], cell.detailMapData[i], cell.heightMapData,
detailMapSize, heightMapSize,
detailResolution, heightmapResolution,
cell.instanceStartPosition,
terrainSize, cell.totalDetailCounts[i]);
}
else
{
#endif
cell.detailInstanceList[i] = GetInstanceDataForDetailPrototypeWithComputeShader((GPUInstancerDetailPrototype)prototypeList[i], cell.detailMapData[i], cell.heightMapData,
detailMapSize, heightMapSize,
detailResolution, heightmapResolution,
cell.instanceStartPosition,
terrainSize, cell.totalDetailCounts[i], _grassInstantiationComputeShader, terrainSettings, counterBuffer, counterData);
#if !UNITY_EDITOR && UNITY_ANDROID
}
#endif
if (totalCreated >= GPUInstancerConstants.BUFFER_COROUTINE_STEP_NUMBER)
{
totalCreated = 0;
yield return null;
}
}
}
callback();
}
public float[,] threadHeightMapData;
public List<int[,]> threadDetailMapData;
public int threadHeightResolution;
public int threadDetailResolution;
public IEnumerator FillCellsDetailData(Terrain terrain)
{
threadHeightResolution = terrain.terrainData.heightmapResolution;
threadDetailResolution = terrain.terrainData.detailResolution;
if (threadHeightMapData == null)
{
#if UNITY_EDITOR
UnityEngine.Profiling.Profiler.BeginSample("GPUInstancerDetailManager.FillCellsDetailData.GetHeights");
#endif
threadHeightMapData = terrain.terrainData.GetHeights(0, 0, threadHeightResolution, threadHeightResolution);
#if UNITY_EDITOR
UnityEngine.Profiling.Profiler.EndSample();
#endif
}
if (threadDetailMapData == null)
{
threadDetailMapData = new List<int[,]>();
for (int i = 0; i < terrain.terrainData.detailPrototypes.Length; i++)
{
#if UNITY_EDITOR
UnityEngine.Profiling.Profiler.BeginSample("GPUInstancerDetailManager.FillCellsDetailData.GetDetailLayer");
#endif
threadDetailMapData.Add(terrain.terrainData.GetDetailLayer(0, 0, threadDetailResolution, threadDetailResolution, i));
#if UNITY_EDITOR
UnityEngine.Profiling.Profiler.EndSample();
#endif
if (runInThreads && i % 3 == 0)
yield return null;
}
}
if (runInThreads)
{
#if UNITY_EDITOR
UnityEngine.Profiling.Profiler.BeginSample("GPUInstancerDetailManager.FillCellsDetailData.Threading");
#endif
ParameterizedThreadStart fillCellsDetailData = new ParameterizedThreadStart(FillCellsDetailDataThread);
Thread fillCellsDetailDataThread = new Thread(fillCellsDetailData);
fillCellsDetailDataThread.IsBackground = true;
Vector4 coord = Vector4.zero;
coord.z = Mathf.CeilToInt(spData.cellRowAndCollumnCountPerTerrain / 2.0f);
coord.w = spData.cellRowAndCollumnCountPerTerrain;
threadStartQueue.Enqueue(new GPUIThreadData() { thread = fillCellsDetailDataThread, parameter = coord });
if (spData.cellRowAndCollumnCountPerTerrain > 1)
{
fillCellsDetailDataThread = new Thread(fillCellsDetailData);
fillCellsDetailDataThread.IsBackground = true;
coord.x = coord.z;
coord.z = spData.cellRowAndCollumnCountPerTerrain;
threadStartQueue.Enqueue(new GPUIThreadData() { thread = fillCellsDetailDataThread, parameter = coord });
}
#if UNITY_EDITOR
UnityEngine.Profiling.Profiler.EndSample();
#endif
}
else
{
#if UNITY_EDITOR
UnityEngine.Profiling.Profiler.BeginSample("GPUInstancerDetailManager.FillCellsDetailData.Main");
#endif
Vector4 coord = new Vector4(0, 0, spData.cellRowAndCollumnCountPerTerrain, spData.cellRowAndCollumnCountPerTerrain);
StartCoroutineAlt(FillCellsDetailDataCoroutine(coord));
#if UNITY_EDITOR
UnityEngine.Profiling.Profiler.EndSample();
#endif
}
}
public void FillCellsDetailDataThread(object parameter)
{
try
{
Vector4 coord = (Vector4)parameter;
int startX = (int)coord.x;
int startZ = (int)coord.y;
int endX = (int)coord.z;
int endZ = (int)coord.w;
int detailMapSize = threadDetailResolution / spData.cellRowAndCollumnCountPerTerrain;
int heightMapSize = ((threadHeightResolution - 1) / spData.cellRowAndCollumnCountPerTerrain) + 1;
//Debug.Log("Cell DetailMapSize: " + detailMapSize + " Cell HeightMapSize: " + heightMapSize);
GPUInstancerCell cell = null;
for (int z = startZ; z < endZ; z++)
{
for (int x = startX; x < endX; x++)
{
if (spData == null)
return;
int hash = GPUInstancerCell.CalculateHash(x, 0, z);
spData.GetCell(hash, out cell);
if (cell != null)
{
GPUInstancerDetailCell detailCell = (GPUInstancerDetailCell)cell;
detailCell.heightMapData = threadHeightMapData.MirrorAndFlatten(detailCell.coordX * (heightMapSize - 1), detailCell.coordZ * (heightMapSize - 1),
heightMapSize, heightMapSize);
detailCell.detailMapData = new List<int[]>();
detailCell.totalDetailCounts = new List<int>();
for (int i = 0; i < threadDetailMapData.Count; i++)
{
int[] detailMapData = threadDetailMapData[i].MirrorAndFlatten(detailCell.coordX * detailMapSize, detailCell.coordZ * detailMapSize,
detailMapSize, detailMapSize);
detailCell.detailMapData.Add(detailMapData);
int total = 0;
foreach (int num in detailMapData)
total += num;
detailCell.totalDetailCounts.Add(total);
}
}
else
throw new Exception("Can not find cell!");
}
}
threadQueue.Enqueue(FillCellsDetailDataCallBack);
}
catch (Exception e)
{
threadException = e;
threadQueue.Enqueue(LogThreadException);
}
}
public IEnumerator FillCellsDetailDataCoroutine(Vector4 coord)
{
int startX = (int)coord.x;
int startZ = (int)coord.y;
int endX = (int)coord.z;
int endZ = (int)coord.w;
int detailMapSize = threadDetailResolution / spData.cellRowAndCollumnCountPerTerrain;
int heightMapSize = ((threadHeightResolution - 1) / spData.cellRowAndCollumnCountPerTerrain) + 1;
//Debug.Log("Cell DetailMapSize: " + detailMapSize + " Cell HeightMapSize: " + heightMapSize);
GPUInstancerCell cell = null;
for (int z = startZ; z < endZ; z++)
{
for (int x = startX; x < endX; x++)
{
int hash = GPUInstancerCell.CalculateHash(x, 0, z);
spData.GetCell(hash, out cell);
if (cell != null)
{
GPUInstancerDetailCell detailCell = (GPUInstancerDetailCell)cell;
detailCell.heightMapData = threadHeightMapData.MirrorAndFlatten(detailCell.coordX * (heightMapSize - 1), detailCell.coordZ * (heightMapSize - 1),
heightMapSize, heightMapSize);
detailCell.detailMapData = new List<int[]>();
detailCell.totalDetailCounts = new List<int>();
for (int i = 0; i < threadDetailMapData.Count; i++)
{
int[] detailMapData = threadDetailMapData[i].MirrorAndFlatten(detailCell.coordX * detailMapSize, detailCell.coordZ * detailMapSize,
detailMapSize, detailMapSize);
detailCell.detailMapData.Add(detailMapData);
int total = 0;
foreach (int num in detailMapData)
total += num;
detailCell.totalDetailCounts.Add(total);
}
yield return null;
}
else
Debug.LogError("Can not find cell!");
}
}
FillCellsDetailDataCallBack();
}
#endregion Spatial Partitioning Cell Management
#region Public Methods
public void SetDetailMapData(List<int[,]> detailMapData)
{
threadDetailMapData = detailMapData;
}
public int[,] GetDetailLayer(int layer)
{
if (!isInitialized || terrain == null || spData == null)
return null;
int detailResolution = terrain.terrainData.detailResolution;
int detailMapSize = detailResolution / spData.cellRowAndCollumnCountPerTerrain;
int[,] result = new int[detailResolution, detailResolution];
GPUInstancerCell cell;
GPUInstancerDetailCell detailCell;
for (int cx = 0; cx < spData.cellRowAndCollumnCountPerTerrain; cx++)
{
for (int cz = 0; cz < spData.cellRowAndCollumnCountPerTerrain; cz++)
{
if (spData.GetCell(GPUInstancerCell.CalculateHash(cx, 0, cz), out cell))
{
detailCell = (GPUInstancerDetailCell)cell;
if (detailCell.detailMapData != null)
{
for (int x = 0; x < detailMapSize; x++)
{
for (int y = 0; y < detailMapSize; y++)
{
result[y + cz * detailMapSize, x + cx * detailMapSize] = detailCell.detailMapData[layer][x + y * detailMapSize];
}
}
}
}
}
}
return result;
}
public List<int[,]> GetDetailMapData()
{
if (!isInitialized || terrain == null || spData == null)
return null;
List<int[,]> result = new List<int[,]>();
for (int i = 0; i < prototypeList.Count; i++)
{
result.Add(GetDetailLayer(i));
}
return result;
}
#endregion
}
}