using Unity.Burst; using Unity.Collections; using Unity.Jobs; using Unity.Mathematics; using UnityEngine; using Random = UnityEngine.Random; 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; float3 upAxis = mul(mul(parent.worldRotation, part.rotation), up()); float3 sagAxis = cross(up(), upAxis); float sagMagnitude = length(sagAxis); quaternion baseRotation; if (sagMagnitude > 0f) { sagAxis /= sagMagnitude; quaternion sagRotation = quaternion.AxisAngle(sagAxis, part.maxSagAngle * sagMagnitude); baseRotation = mul(sagRotation, parent.worldRotation); } else { baseRotation = parent.worldRotation; } part.worldRotation = mul(baseRotation, mul(part.rotation, quaternion.RotateY(part.spinAngle)) ); part.worldPosition = parent.worldPosition + mul(part.worldRotation, float3(0f, 1.5f * scale, 0f)); 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 worldPosition; public quaternion rotation, worldRotation; public float maxSagAngle; public float spinAngle; } NativeArray[] parts; NativeArray[] matrices; [SerializeField, Range(3, 8)] int depth = 4; [SerializeField] Mesh mesh = default; [SerializeField] Mesh leafMesh = default; [SerializeField] Material material = default; [SerializeField] Gradient gradientA = default; [SerializeField] Gradient gradientB = default; [SerializeField] Color leafColorA = default; [SerializeField] Color leafColorB = default; [SerializeField, Range(0f, 90f)] float maxSagAngleA = 15f; [SerializeField, Range(0f, 90f)] float maxSagAngleB = 25f; 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() { maxSagAngle = radians(Random.Range(maxSagAngleA, maxSagAngleB)), rotation = rotations[childIndex] }; } ComputeBuffer[] matricesBuffers; Vector4[] sequenceNumbers; static readonly int colorAId = Shader.PropertyToID("_ColorA"); static readonly int colorBId = Shader.PropertyToID("_ColorB"); static readonly int matricesId = Shader.PropertyToID("_Matrices"); static readonly int sequenceNumbersId = Shader.PropertyToID("_SequenceNumbers"); static MaterialPropertyBlock propertyBlock; private void OnEnable() { parts = new NativeArray[depth]; matrices = new NativeArray[depth]; matricesBuffers = new ComputeBuffer[depth]; sequenceNumbers = new Vector4[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); sequenceNumbers[i] = new Vector4(Random.value, Random.value, Random.value, Random.value); } 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; sequenceNumbers = 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)); int leafIndex = matricesBuffers.Length - 1; for (int i = 0; i < matricesBuffers.Length; i++) { Mesh instanceMesh; Color colorA, colorB; if (i == leafIndex) { colorA = leafColorA; colorB = leafColorB; instanceMesh = leafMesh; } else { colorA = gradientA.Evaluate(i / (matricesBuffers.Length - 2f)); colorB = gradientB.Evaluate(i / (matricesBuffers.Length - 2f)); instanceMesh = mesh; } propertyBlock.SetColor(colorAId, colorA); propertyBlock.SetColor(colorBId, colorB); ComputeBuffer buffer = matricesBuffers[i]; buffer.SetData(matrices[i]); float gradientInterpolator = i / (matricesBuffers.Length - 1f); propertyBlock.SetColor(colorAId, gradientA.Evaluate(gradientInterpolator)); propertyBlock.SetColor(colorBId, gradientB.Evaluate(gradientInterpolator)); propertyBlock.SetBuffer(matricesId, buffer); propertyBlock.SetVector(sequenceNumbersId, sequenceNumbers[i]); Graphics.DrawMeshInstancedProcedural(instanceMesh, 0, material, bounds, buffer.count, propertyBlock); } } }