R&D · Procedural · UE5 · ISM · Visual Algorithms

Procedural Visual Systems

Procedural Visual Systems

A collection of real-time procedural visual algorithms implemented in UE5 C++ using Instanced Static Meshes. Circle Packing uses exponential size bias for natural distributions; Ulam Spirals plot primes on a 2D grid; Poisson Disc Sampling generates blue-noise scatter; Diffusion-Limited Aggregation grows fractal trees. All rendered as ISM instances — thousands of elements at 60 FPS with zero per-element actor cost.

Circle Packing Ulam Spiral Poisson Disc DLA ISM UE5 C++
What I Built
  • Circle Packing — 1 exponential-bias formula1 formula (-log(1-α) / ExponentBias) produces small-circle-dominated distributions; overlap check uses distance² vs (r1+r2)² — 0 sqrt calls.
  • Per-instance emissive — 4 custom floats — each circle stores 4 per-instance floats (R, G, B, EmissiveScale) in ISM custom data; shader reads all 4 per instance for coloured glow rings.
  • Radius growth animation — 0→target per tick — new circles start at 0 radius and grow toward their target via 1 FInterpTo call per tick, creating an organic "inflate" fill effect.
  • Ulam Spiral — 1 primality test per integer — spiral walk maps integers to 2D grid coords; 1 primality test at each step; prime positions spawn 1 illuminated ISM instance each.
  • DLA fractal growth — 1 ISM per aggregation — particles random-walk until they collide with the cluster; 1 ISM instance added at each aggregation point, building the fractal tree incrementally.

Circle Packing — Exponential Size Bias

-log(1 - FRand()) / ExponentBias biases the radius distribution toward small circles, matching the organic look of real circle-packing art. Overlap is tested as a squared-distance comparison — no sqrt.

CirclePackingManager.cpp — TrySpawnNewCircle()
bool ACirclePackingManager::TrySpawnNewCircle()
{
    // Exponential bias: favours small radii — matches organic packing art
    const float Alpha  = FMath::FRand();
    const float Radius = -FMath::Loge(1.f - Alpha) / ExponentBias;
    if (Radius < MinRadius || Radius > MaxRadius) return false;

    // Random position within spawn bounds
    FVector2D Pos(FMath::FRandRange(-SpawnHalfExtent, SpawnHalfExtent),
                  FMath::FRandRange(-SpawnHalfExtent, SpawnHalfExtent));

    if (IsOverlapping(Pos, Radius)) return false;

    FCircleData& C  = Circles.AddDefaulted_GetRef();
    C.Position      = Pos;
    C.TargetRadius  = Radius;
    C.CurrentRadius = 0.f;   // starts at 0, grows to target
    C.Color         = FLinearColor::MakeRandomColor();
    C.EmissiveScale = FMath::FRandRange(EmissiveMin, EmissiveMax);
    return true;
}

bool ACirclePackingManager::IsOverlapping(const FVector2D& Pos, float R) const
{
    for (const FCircleData& C : Circles)
    {
        const float DistSq  = FVector2D::DistSquared(Pos, C.Position);
        const float MinSep  = (R + C.TargetRadius) * (R + C.TargetRadius);
        if (DistSq < MinSep) return true;  // no sqrt needed
    }
    return false;
}

// Per-tick: grow radius and update ISM custom float data
void ACirclePackingManager::Tick(float DT)
{
    Super::Tick(DT);
    for (int32 i = 0; i < Circles.Num(); ++i)
    {
        FCircleData& C = Circles[i];
        C.CurrentRadius = FMath::FInterpTo(C.CurrentRadius, C.TargetRadius, DT, GrowSpeed);

        // Pack RGBA + emissive into ISM per-instance custom data
        CircleISM->SetCustomDataValue(i, 0, C.Color.R);
        CircleISM->SetCustomDataValue(i, 1, C.Color.G);
        CircleISM->SetCustomDataValue(i, 2, C.Color.B);
        CircleISM->SetCustomDataValue(i, 3, C.EmissiveScale);
    }
}
Engine Unreal Engine 5 Language C++ Algorithms Circle Packing · Ulam Spiral · Poisson Disc · DLA Size bias -log(1-α) / ExponentBias Render ISM + per-instance custom float data Category R&D · Procedural · Graphics Source github.com/khaled71612000 ↗
Connect