Jelly Meshdeformation Part II – Implementing Jobsystem

In this tutorial, you’ll learn what Unity’s Jobsystem is and how to implement it into your existing project. To teach you the implementation aspect I decided to implement it into my Jelly Meshdeformation System.

If you haven’t read Part I of this series I’d like to advice you to do so as you’ll have it easier to understand.

 

What is Unity’s Jobsystem?

Generally speaking, before the Jobsystem happened, everything you did within Unity happend linearly – meaning one after another. The only way around that was to use multi-threading and I wrote a post on how to multi-thread safely in Unity a while ago. Click here if you care to read it. Multi-Threading, however, had the major downside of technically not being safe. Which meant that you could use it but there were some functions/ references you couldn’t use.

Now, even when using the Jobsystem you can’t access everything but a hell of a lot more than before, you just have to learn how to do it and this is what I am trying to help you with.

There are special types of “Containers” the Jobsystem uses, those differ from their regular counterpart mainly in the fact that you have to allocate/deallocate the memory yourself.

A job is basically an interface that inherits one from one of the IJob interfaces that are contained within the Job-related namespaces. What you have to do for each of the jobs you want to schedule is to write a script that handles the tasks of your job. All that is left to do is to actually populate the values you might need if there are any and then schedule it.

There are different ways of actually scheduling jobs appropriately, for example in a queue but I restrained from doing so for now as I am just trying to give you a brief example.

Preparing your Project:

If you are using the same Unity Version as I am, which is 2018.1.0b.13,  you’ll have to manually make sure that you are using .Net 4.x Equivalent. To do this you’ll have to navigate to File >> Build Settings >> Player Settings. Now you have to set the Scripting Runtime Version, which can be found within the first tab to .Net 4.x Equivalent. Unity will ask you to restart afterwards.

MeshDeformationJob:

To schedule a Job we will have to add a new Script called MeshDeformationJob.cs to our scripts folder.
This doesn’t have to be class but rather a struct and it has to be inherited from one of the IJob interfaces.

In our case this is going to be IJobParallelFor, because we want it to be run a set number of times per job. Since we are going to be using the jobsystem instead of our UpdateVerts() function, we are going to call the jobs execute function exactly the number of times that is the number of vertices of our mesh.

I’ve already mentioned the Execute() function, every job you are writing has to have it, as this is where you are going to add your custom code into your job.

However, we need to define a few variables before we are starting to work on our execute code, in the following snippet you’ll see that I am not using a regular array of Vector3 anymore but rather a NativeArray<Vector3>. NativeArrays have been added with the Jobsystem namespaces to ensure secure handling of multi-threaded code. As mentioned before they mainly differ from regular arrays by the fact that you’ll have to define an allocator. This is basically your value for the NativeArrays persistens/allocation. They also aren’t going to be affected by the garbage collection and are therefore somewhat resemblent to native code, as you have to dispose/deallocate them manually.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Collections;
using Unity.Jobs;
using UnityEngine.Jobs;

public struct MeshDeformationJob : IJobParallelFor
{
  public NativeArray<Vector3> initialVerts;
  public NativeArray<Vector3> displacedVerts;
  public NativeArray<Vector3> vertVelocities;

  public float springforce;
  public float dampening;

  public float unitformScale;
  public float time;

Let’s take a brief look at our execute function.
I basically copied the code that used to be in our UpdateVerts() functions for-loop over to execute. Execute will do the looping for us from now on.

You might also notice that I am no longer using Time.deltaTime but I rather have to pass in a time, this is solely because we can’t access Time.deltaTime from a Job. This is another great example of the limitations that the JobSystem brings, but again these are safety precautions that will make sure that your code will run smoothly and you can work around it pretty easily by just passing the time once you initialized the job.

public void Execute(int i)
{
  Vector3 velocity = vertVelocities[i];
  Vector3 displacement = displacedVerts[i] - 
  initialVerts[i];


  displacement *= unitformScale;
  velocity -= displacement * springforce * time;
  velocity *= unitformScale - dampening * time;

  vertVelocities[i] = velocity;
  displacedVerts[i] += velocity * (time/
  unitformScale);
}

 

JellyManagement:

Let’s get straight into the interesting part and modify our JellyManagement.cs to be able to handle our newly created job.
The first step you have to do is to update your using statements to the following.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Collections;
using Unity.Jobs;
using UnityEngine.Jobs;

I’ve already mentioned above that we need to initialize our job, in order to do so we are going to write a new function for it called ExecuteMeshDeformationJob().
I am going to transform my existing arrays of the initial vertices, the deformed vertices and the vertex velocities into NativeArrays. I have to do that so I can then safely pass them into my job.

I am also going to create a JobHandle. The JobHandle is what will tell us when our Job is complete so that we are able to copy the native arrays back to their corresponding regular arrays and to then dispose them. Again, you need to do that because you are in charge of the memory allocation and deallocation when you are working with the Jobsystem.

Once all of this is done we are just setting our mesh vertices to the displaced vertices again and that is all we have to do.

void ExecuteMeshDeformationJob(JellyBody _jellyBody)
{
  NativeArray<Vector3> initialVertsAccess = new NativeArray<Vector3>
  (_jellyBody.InitialVerts, Allocator.TempJob);
  NativeArray<Vector3> displacedVertsAccess = new NativeArray<Vector3>
  (_jellyBody.DisplacedVerts, Allocator.TempJob);
  NativeArray<Vector3> vertVelocitiesAccess = new NativeArray<Vector3>
  (_jellyBody.VertVelocities, Allocator.TempJob);

  MeshDeformationJob meshDeformationJob = new MeshDeformationJob
  {
    initialVerts = initialVertsAccess,
    displacedVerts = displacedVertsAccess,
    vertVelocities = vertVelocitiesAccess,
    springforce = (useManualSettings) ? _jellyBody.Stiffness : stiffness,
    dampening = (useManualSettings) ? _jellyBody.Attenuation : attenuation,
    unitformScale = _jellyBody.UnitformScale,
    time = Time.deltaTime
  };
  JobHandle meshDeformationJobHandle = meshDeformationJob.Schedule
  (_jellyBody.DisplacedVerts.Length,_jellyBody.DisplacedVerts.Length);
  meshDeformationJobHandle.Complete();

  initialVertsAccess.CopyTo(_jellyBody.InitialVerts);
  initialVertsAccess.Dispose();

  displacedVertsAccess.CopyTo(_jellyBody.DisplacedVerts);
  displacedVertsAccess.Dispose();

  vertVelocitiesAccess.CopyTo(_jellyBody.VertVelocities);
  vertVelocitiesAccess.Dispose();

  _jellyBody.JellyMesh.vertices = _jellyBody.DisplacedVerts;
}

 

Get Part II of the Repository on GitHub!

Liked it? Take a second to support Kristin on Patreon!

3 comments

  1. Great articles! The new JobSystem is a beast and it’s always nice to see good tutorial on it. Just a quick question: would it be possible to implement collisions between jellies and regular colliders? So that the jellies actually deform when on the floor?

    1. First of all: thank you!

      This is actually an awesome question pointing towards something I had planned to address in the post but then forgot. So the jelly can collide with everything but collisions between Jellies and Stiff objects won’t have the perfect outcome like f.e the bottom flattening perfectly this is due to the fact that we have a collider. You could update the colliders size when the mesh is deforming but that will cost you performance, significantly!
      You can not utilize the Jobsystem for actions like updating the collider yet and if our object would be a more complex mesh and we were to use a mesh collider we’d than have to recalculate within update this would be sheer performance mayhem.

      With all of that being said: What one could do is writing your own physics system using raycast commands and a personalized gravity system. I did consider doing that but I felt it would be to much of a deep dive.
      I’d consider adding it if a lot of people are interested though.

      Another thing that can “simulate” the flattening is to set the stiffness really low. But you’ll have to adjust our pressure force and probably the volume values quite a bit (which is fiddely) in order to achieve a nice general outcome.

      I hope this helps you out and answers your Question.

Leave a Reply

Your email address will not be published. Required fields are marked *