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