using Unity.Burst; using Unity.Collections; using Unity.Jobs; using Unity.Mathematics; using UnityEngine; using static Unity.Mathematics.math; using quaternion = Unity.Mathematics.quaternion; public class Fractal : MonoBehaviour { [BurstCompile(FloatPrecision.Standard, FloatMode.Fast, CompileSynchronously = true)] private struct UpdateFractalLevelJob : IJobFor { public float spinAngleDelta; public float scale; [ReadOnly] public NativeArray parents; public NativeArray parts; [WriteOnly] public NativeArray matrices; public void Execute(int i) { var parent = parents[i / 5]; var part = parts[i]; part.spinAngle += spinAngleDelta; part.worldRotation = mul(parent.worldRotation, mul(part.rotation, quaternion.RotateY(part.spinAngle)) ); part.worldPosition = parent.worldPosition + mul(parent.worldRotation, 1.5f * scale * part.direction); parts[i] = part; float3x3 r = float3x3(part.worldRotation) * scale; matrices[i] = float3x4(r.c0, r.c1, r.c2, part.worldPosition); } } private struct FractalPart { public float3 direction, worldPosition; public quaternion rotation, worldRotation; public float spinAngle; } NativeArray[] parts; NativeArray[] matrices; [SerializeField, Range(1, 8)] int depth = 4; [SerializeField] Mesh mesh = default; [SerializeField] Material material = default; static float3[] directions = { up(), right(), left(), forward(), back() }; static quaternion[] rotations = { quaternion.identity, quaternion.RotateZ(-0.5f * PI), quaternion.RotateZ(0.5f * PI), quaternion.RotateX(0.5f * PI), quaternion.RotateX(-0.5f * PI) }; private FractalPart CreatePart(int childIndex) { return new FractalPart() { direction = directions[childIndex], rotation = rotations[childIndex] }; } ComputeBuffer[] matricesBuffers; static readonly int matricesId = Shader.PropertyToID("_Matrices"); static MaterialPropertyBlock propertyBlock; private void OnEnable() { parts = new NativeArray[depth]; matrices = new NativeArray[depth]; matricesBuffers = new ComputeBuffer[depth]; int stride = 12 * 4; for (int i = 0, length = 1; i < parts.Length; i++, length *= 5) { parts[i] = new NativeArray(length, Allocator.Persistent); matrices[i] = new NativeArray(length, Allocator.Persistent); matricesBuffers[i] = new ComputeBuffer(length, stride); } parts[0][0] = CreatePart(0); for (int li = 1; li < parts.Length; li++) { NativeArray levelParts = parts[li]; for (int fpi = 0; fpi < levelParts.Length; fpi += 5) { for (int ci = 0; ci < 5; ci++) { levelParts[fpi + ci] = CreatePart(ci); } } } if (propertyBlock == null) { propertyBlock = new MaterialPropertyBlock(); } } private void OnDisable() { for (int i = 0; i < matricesBuffers.Length; i++) { matricesBuffers[i].Release(); parts[i].Dispose(); matrices[i].Dispose(); } parts = null; matrices = null; matricesBuffers = null; } void OnValidate() { if (parts != null && enabled) { OnDisable(); OnEnable(); } } private void Update() { float spinAngleDelta = 0.125f * PI * Time.deltaTime; FractalPart rootPart = parts[0][0]; rootPart.spinAngle += spinAngleDelta; rootPart.worldRotation = mul(transform.rotation, mul(rootPart.rotation, quaternion.RotateY(rootPart.spinAngle)) ); rootPart.worldPosition = transform.position; parts[0][0] = rootPart; float objectScale = transform.lossyScale.x; float3x3 r = float3x3(rootPart.worldRotation) * objectScale; matrices[0][0] = float3x4(r.c0, r.c1, r.c2, rootPart.worldPosition); float scale = objectScale; JobHandle jobHandle = default; for (int li = 1; li < parts.Length; li++) { scale *= 0.5f; jobHandle = new UpdateFractalLevelJob { spinAngleDelta = spinAngleDelta, scale = scale, parents = parts[li - 1], parts = parts[li], matrices = matrices[li] }.ScheduleParallel(parts[li].Length, 5, jobHandle); } jobHandle.Complete(); var bounds = new Bounds(rootPart.worldPosition, float3(3f * objectScale)); for (int i = 0; i < matricesBuffers.Length; i++) { ComputeBuffer buffer = matricesBuffers[i]; buffer.SetData(matrices[i]); propertyBlock.SetBuffer(matricesId, buffer); Graphics.DrawMeshInstancedProcedural(mesh, 0, material, bounds, buffer.count, propertyBlock); } } }