using Unity.Collections;
using Unity.Mathematics;
using UnityEngine.Jobs;
namespace UnityEngine.Rendering.Universal
{
///
/// Contains cached properties needed for rendering.
///
internal class DecalCachedChunk : DecalChunk
{
public MaterialPropertyBlock propertyBlock;
public int passIndexDBuffer;
public int passIndexEmissive;
public int passIndexScreenSpace;
public int passIndexGBuffer;
public int drawOrder;
public bool isCreated;
public NativeArray decalToWorlds;
public NativeArray normalToWorlds;
public NativeArray sizeOffsets;
public NativeArray drawDistances;
public NativeArray angleFades;
public NativeArray uvScaleBias;
public NativeArray layerMasks;
public NativeArray sceneLayerMasks;
public NativeArray fadeFactors;
public NativeArray boundingSpheres;
public NativeArray scaleModes;
public NativeArray positions;
public NativeArray rotation;
public NativeArray scales;
public NativeArray dirty;
public BoundingSphere[] boundingSphereArray;
public override void RemoveAtSwapBack(int entityIndex)
{
RemoveAtSwapBack(ref decalToWorlds, entityIndex, count);
RemoveAtSwapBack(ref normalToWorlds, entityIndex, count);
RemoveAtSwapBack(ref sizeOffsets, entityIndex, count);
RemoveAtSwapBack(ref drawDistances, entityIndex, count);
RemoveAtSwapBack(ref angleFades, entityIndex, count);
RemoveAtSwapBack(ref uvScaleBias, entityIndex, count);
RemoveAtSwapBack(ref layerMasks, entityIndex, count);
RemoveAtSwapBack(ref sceneLayerMasks, entityIndex, count);
RemoveAtSwapBack(ref fadeFactors, entityIndex, count);
RemoveAtSwapBack(ref boundingSphereArray, entityIndex, count);
RemoveAtSwapBack(ref boundingSpheres, entityIndex, count);
RemoveAtSwapBack(ref scaleModes, entityIndex, count);
RemoveAtSwapBack(ref positions, entityIndex, count);
RemoveAtSwapBack(ref rotation, entityIndex, count);
RemoveAtSwapBack(ref scales, entityIndex, count);
RemoveAtSwapBack(ref dirty, entityIndex, count);
count--;
}
public override void SetCapacity(int newCapacity)
{
decalToWorlds.ResizeArray(newCapacity);
normalToWorlds.ResizeArray(newCapacity);
sizeOffsets.ResizeArray(newCapacity);
drawDistances.ResizeArray(newCapacity);
angleFades.ResizeArray(newCapacity);
uvScaleBias.ResizeArray(newCapacity);
layerMasks.ResizeArray(newCapacity);
sceneLayerMasks.ResizeArray(newCapacity);
fadeFactors.ResizeArray(newCapacity);
boundingSpheres.ResizeArray(newCapacity);
scaleModes.ResizeArray(newCapacity);
positions.ResizeArray(newCapacity);
rotation.ResizeArray(newCapacity);
scales.ResizeArray(newCapacity);
dirty.ResizeArray(newCapacity);
ArrayExtensions.ResizeArray(ref boundingSphereArray, newCapacity);
capacity = newCapacity;
}
public override void Dispose()
{
if (capacity == 0)
return;
decalToWorlds.Dispose();
normalToWorlds.Dispose();
sizeOffsets.Dispose();
drawDistances.Dispose();
angleFades.Dispose();
uvScaleBias.Dispose();
layerMasks.Dispose();
sceneLayerMasks.Dispose();
fadeFactors.Dispose();
boundingSpheres.Dispose();
scaleModes.Dispose();
positions.Dispose();
rotation.Dispose();
scales.Dispose();
dirty.Dispose();
count = 0;
capacity = 0;
}
}
///
/// Caches properties into .
/// Uses jobs with .
///
internal class DecalUpdateCachedSystem
{
private DecalEntityManager m_EntityManager;
private ProfilingSampler m_Sampler;
private ProfilingSampler m_SamplerJob;
public DecalUpdateCachedSystem(DecalEntityManager entityManager)
{
m_EntityManager = entityManager;
m_Sampler = new ProfilingSampler("DecalUpdateCachedSystem.Execute");
m_SamplerJob = new ProfilingSampler("DecalUpdateCachedSystem.ExecuteJob");
}
public void Execute()
{
using (new ProfilingScope(null, m_Sampler))
{
for (int i = 0; i < m_EntityManager.chunkCount; ++i)
Execute(m_EntityManager.entityChunks[i], m_EntityManager.cachedChunks[i], m_EntityManager.entityChunks[i].count);
}
}
private void Execute(DecalEntityChunk entityChunk, DecalCachedChunk cachedChunk, int count)
{
if (count == 0)
return;
cachedChunk.currentJobHandle.Complete();
// Make sure draw order is up to date
var material = entityChunk.material;
if (material.HasProperty("_DrawOrder"))
cachedChunk.drawOrder = material.GetInt("_DrawOrder");
// Shader can change any time in editor, so we have to update passes each time
#if !UNITY_EDITOR
if (!cachedChunk.isCreated)
#endif
{
int passIndexDBuffer = material.FindPass(DecalShaderPassNames.DBufferProjector);
cachedChunk.passIndexDBuffer = passIndexDBuffer;
int passIndexEmissive = material.FindPass(DecalShaderPassNames.DecalProjectorForwardEmissive);
cachedChunk.passIndexEmissive = passIndexEmissive;
int passIndexScreenSpace = material.FindPass(DecalShaderPassNames.DecalScreenSpaceProjector);
cachedChunk.passIndexScreenSpace = passIndexScreenSpace;
int passIndexGBuffer = material.FindPass(DecalShaderPassNames.DecalGBufferProjector);
cachedChunk.passIndexGBuffer = passIndexGBuffer;
cachedChunk.isCreated = true;
}
using (new ProfilingScope(null, m_SamplerJob))
{
UpdateTransformsJob updateTransformJob = new UpdateTransformsJob()
{
positions = cachedChunk.positions,
rotations = cachedChunk.rotation,
scales = cachedChunk.scales,
dirty = cachedChunk.dirty,
scaleModes = cachedChunk.scaleModes,
sizeOffsets = cachedChunk.sizeOffsets,
decalToWorlds = cachedChunk.decalToWorlds,
normalToWorlds = cachedChunk.normalToWorlds,
boundingSpheres = cachedChunk.boundingSpheres,
minDistance = System.Single.Epsilon,
};
var handle = updateTransformJob.Schedule(entityChunk.transformAccessArray);
cachedChunk.currentJobHandle = handle;
}
}
#if ENABLE_BURST_1_0_0_OR_NEWER
[Unity.Burst.BurstCompile]
#endif
public unsafe struct UpdateTransformsJob : IJobParallelForTransform
{
private static readonly quaternion k_MinusYtoZRotation = quaternion.EulerXYZ(-math.PI / 2.0f, 0, 0);
public NativeArray positions;
public NativeArray rotations;
public NativeArray scales;
public NativeArray dirty;
[ReadOnly] public NativeArray scaleModes;
[ReadOnly] public NativeArray sizeOffsets;
[WriteOnly] public NativeArray decalToWorlds;
[WriteOnly] public NativeArray normalToWorlds;
[WriteOnly] public NativeArray boundingSpheres;
public float minDistance;
private float DistanceBetweenQuaternions(quaternion a, quaternion b)
{
return math.distancesq(a.value, b.value);
}
public void Execute(int index, TransformAccess transform)
{
// Check if transform changed
bool positionChanged = math.distancesq(transform.position, positions[index]) > minDistance;
if (positionChanged)
positions[index] = transform.position;
bool rotationChanged = DistanceBetweenQuaternions(transform.rotation, rotations[index]) > minDistance;
if (rotationChanged)
rotations[index] = transform.rotation;
bool scaleChanged = math.distancesq(transform.localScale, scales[index]) > minDistance;
if (scaleChanged)
scales[index] = transform.localScale;
// Early out if transform did not changed
if (!positionChanged && !rotationChanged && !scaleChanged && !dirty[index])
return;
float4x4 localToWorld;
if (scaleModes[index] == DecalScaleMode.InheritFromHierarchy)
{
localToWorld = transform.localToWorldMatrix;
localToWorld = math.mul(localToWorld, new float4x4(k_MinusYtoZRotation, float3.zero));
}
else
{
quaternion rotation = math.mul(transform.rotation, k_MinusYtoZRotation);
localToWorld = float4x4.TRS(positions[index], rotation, new float3(1, 1, 1));
}
float4x4 decalRotation = localToWorld;
// z/y axis swap for normal to decal space, Unity is column major
float4 temp = decalRotation.c1;
decalRotation.c1 = decalRotation.c2;
decalRotation.c2 = temp;
normalToWorlds[index] = decalRotation;
float4x4 sizeOffset = sizeOffsets[index];
float4x4 decalToWorld = math.mul(localToWorld, sizeOffset);
decalToWorlds[index] = decalToWorld;
boundingSpheres[index] = GetDecalProjectBoundingSphere(decalToWorld);
dirty[index] = false;
}
private BoundingSphere GetDecalProjectBoundingSphere(Matrix4x4 decalToWorld)
{
float4 min = new float4(-0.5f, -0.5f, -0.5f, 1.0f);
float4 max = new float4(0.5f, 0.5f, 0.5f, 1.0f);
min = math.mul(decalToWorld, min);
max = math.mul(decalToWorld, max);
float3 position = ((max + min) / 2f).xyz;
float radius = math.length(max - min) / 2f;
BoundingSphere res = new BoundingSphere();
res.position = position;
res.radius = radius;
return res;
}
}
}
}