Home

Awesome

GPUInstance

Instancing & Animation library for Unity3D.

Alt text

This library can be used to quickly and efficiently render thousands to hundreds of thousands of complex models in Unity3D. At a high level, this library uses compute shaders to implement an entity hierarchy system akin to the GameObject-Transform hierarchy Unity3D uses.

Features

A scene with many cube instances flying out in all directions. Alt text

Paths

Alt text

Billboards

Performant Instancing

Instance Transform Readbacks

Can Slow down, Speed up, & pause instance times.

Guide

This guide will be very basic- you will be expected to look at the demo scenes & demo models to learn how things work. You are expected to already know how to rig your models, create LODS (if you are using them), setup animations, etc... Additionally, this library requires that you know how to code (in c#).

Preparing a Skinned Mesh

Instancing Stuff

// Initialize character mesh list
int hierarchy_depth, skeleton_bone_count; // This function will initialize the GPUAnimationControllers and combine any that have duplicate skeletons (to save space)
var controllers = GPUSkinnedMeshComponent.PrepareControllers(characters, out hierarchy_depth, out skeleton_bone_count);

// Initialize GPU Instancer
this.m = new MeshInstancer(); // The MeshInstancer is the main object you will be using to create/modify/destroy instances
this.m.Initialize(max_parent_depth: hierarchy_depth + 2, num_skeleton_bones: skeleton_bone_count, pathCount: 2);
this.p = new PathArrayHelper(this.m); // PathArrayHelper can be used to manage & spawn paths for instances to follow

// Add all animations to GPU buffer
this.m.SetAllAnimations(controllers);

// Add all character mesh types to GPU Instancer- this must be done for each different skinned mesh prefab you have
foreach (var character in this.characters)
    this.m.AddGPUSkinnedMeshType(character);

// Everything is initialized and ready to go.. So go ahead and create instances
for (int i = 0; i < N; i++)
    for (int j = 0; j < N; j++)
    {
        var mesh = characters[Random.Range(0, characters.Count)]; // pick a random character from our character list
        var anim = mesh.anim.namedAnimations["walk"]; // pick the walk animation from the character
        instances[i, j] = new SkinnedMesh(mesh, this.m); // The SkinnedMesh struct is used to specify GPU Skinned mesh instances- It will create and manage instances for every skinend mesh & skeleton bone.
        instances[i, j].mesh.position = new Vector3(i, 0, j); // set whatever position you want for the instance
        instances[i, j].SetRadius(1.75f); // The radius is used for culling & LOD. This library uses radius aware LOD & culling. Objects with larger radius will change LOD and be culled at greater distances from the camera.
        instances[i, j].Initialize(); // Each instance must be initialized before it can be rendered. Really this just allocates some IDs for the instance.

        instances[i, j].SetAnimation(anim, speed: 1.4f, start_time: Random.Range(0.0f, 1.0f)); // set the walk animation from before

        var path = GetNewPath(); // create new path
        instances[i, j].mesh.SetPath(path, this.m); // You have to make the instance aware of any paths it should be following.
        paths[i, j] = path;

        instances[i, j].UpdateAll(); // Finnally, invoke Update(). This function will append the instance you created above to a buffer which will be sent to the GPU.
    }


// Get New Path Function. This will create a simple 2-point path.
private Path GetNewPath()
{
    // Get 2 random points which will make a path
    var p1 = RandomPointOnFloor();
    var p2 = RandomPointOnFloor();
    while ((p1 - p2).magnitude < 10) // ensure the path is atleast 10 meters long
        p2 = RandomPointOnFloor();

    // The Path Struct will specify various parameters about how you want an instance to behave whilst following a path. See Pathing.cs for more details.
    Path p = new Path(path_length: 2, this.m, loop: true, path_time: (p2 - p1).magnitude, yaw_only: true, avg_path: false, smoothing: false);
    
    // Initialize path- this allocates path arrays & reserves a path gpu id
    this.p.InitializePath(ref p);
    
    // Copy path into buffers
    var start_index = this.p.StartIndexOfPath(p); // what happening here is we're just copying the 2 random points into an array
    this.p.path[start_index] = p1;
    this.p.path[start_index + 1] = p2;
    this.p.AutoCalcPathUpAndT(p); // Auto calculate the 'up' and 'T' values for the path.
    
    // Each path you create requires that you specify an 'up direction' for each point on the path
    // This is necessary for knowing how to orient the instance whilst it follows the path
    
    // Additionally you need to specify a 'T' value. This 'T' value can be thought of as an interpolation parameter along the path.
    // Setting path[4]=0.5 will mean that the instance will be half way done traversing the path at the 4th point on the path
    // The 'T' value is used to specify how fast/slow the instance will traverse each segment in the path.
    
    // send path to GPU
    this.p.UpdatePath(ref p); // Finally, this function will append the path you created to a buffer which will send it to the GPU!

    return p;
}

Some performance considerations

Other Notes