- Home /
Large Numbers of GameObjects/Items
I've been tasked with developing a front end for a situational awareness system that tracks objects globally and I've two solutions, both with serious issues, and despite a few weeks of research failing to find a solution.
The scenario: Currently I'm tracking around 45,000 objects, this is due to increase to around 150,000. Each object has a position, direction and speed that vary over time and need to be displayed on screen. Hardware isn't so much an issue as my workstation has a Quadro K5000 card and the system that will be running this in production has three Quadro K6000, 24 cores and 256GB RAM.
The approaches I've taken are as follows;
A particle system based on this tutorial. This approach is brilliant for the global view and can easily handle a million data points on screen at a time. This includes rendering on a rotating globe. The big issue here is that due to a limitation, of the particle system or of my knowledge, zooming in causes the whole system to be occluded. As I understand it, this occurs when the centre of the particle system is off camera for performance purposes.
An array of GameObject with a simple mesh, instantiated in Start() and moved into position as needed. This allows me to render 45,000 or so objects and zoom in at will as they each have a rendered of their own, the massive drawback to this approach is that transformations need to be done on the main thread and as such performance drops to around 13fps making the whole thing jerky.
Personally I love the particle system approach as performance is brilliant and can handle up to a million particles without breaking a sweat. If I could solve the occlusion issue it would be ideal. From the past few weeks of forum trawling it seems to have been an issue since the system was introduced and not an easy one to fix. It certainly doesn't help that I'm not using the particle system for it's intended use I guess.
My question is, is there a best practice for handling large numbers of objects such as my requirement? I'm not sure if my approaches are off or my expectations but if someone could point me in the right direction I'd greatly appreciate it.
I went ahead and published your question since someone might know how to solve your ParticleSystem occlusion issue, but in general, design questions should be posted to Unity Forums. If you cannot solve the occlusion problem, then look to doing a custom mesh solution. Someone who knows Unity and meshes could knock it out in a day or two. Google 'voxels'.
Untested idea: Based on a quick glance at your 'this tutorial' link, you are setting the particle properties directly in the particle array. If so, make sure you are using 'World' simulation space, make sure the particle system is in front of the camera, then parent the particle system to the camera.
Edit: See the answer by @djarcas in this question:
http://answers.unity3d.com/questions/218369/shuriken-particle-system-not-rendering-particles-w.html
Though if you are always setting the position of your particles manually, the my untested idea should work.
Thanks for publishing and apologies for posting in the wrong place, still new to the community and finding my feet.
Alas I'd tried the parenting option already, it still requires the camera to have the centre of the particle system in view and setting to world space doesn't seem to make a difference either.
Voxels do look quite cool as a concept, it'll give a really nice volumetric look to the UI too. Thanks for the tip, I'll take a look.
For completeness, I had a long think overnight and I'm probably going to have to use both my approaches and switch modes depending on zoom level.
Alas I'd tried the parenting option already, it still requires the camera to have the centre of the particle system in view
If you make the particle system a child of the camera while the particle system is in view, then no matter how you move or rotate the camera, the particle system will still be in view.
In that case I may have misunderstood how to parent an object. I've created an offset to render points at different points around 0,0 which seems to work. Is parenting done in code or in the hierarchy window?
Answer by robertbu · Aug 16, 2014 at 12:16 AM
I think your current particle system issue can be figured out. But I was giving some though to how this would be done in a mesh (vs individual game object), and decided to write some test code. What I came up with is a class called ParticleDisplay. It has most of the standard particle properties:
color;
lifetime;
position;
rotation;
size;
velocity;
angularVelocity
There's no emitter...just access to the array so the properties can be modified dynamically. Performance is around 10x faster than using game objects and roughly 1/2 the performance of a particle system with all particles displayed. I'm sure there are general performance improvements to be had in this code, but given your hardware, the best thing would be to rework it to use threads.
using UnityEngine;
using System.Collections;
public class ParticleDisplay : MonoBehaviour {
public Camera camera; // Camera to use for rotation
public int maxParticles = 1000;
public bool dynamicColor = false; // Use color defined in particle
public Material material; // Material for particles
public float defaultParticleSize = 0.1f;
public Color defaultParticleColor = Color.white;
public struct Particle {
public float angularVelocity;
public Color color;
public float lifetime;
public Vector3 position;
public float rotation;
public float size;
public Vector3 velocity;
}
public Particle[] particles;
private const int quadsPerMesh = 16380;
private GameObject[] goMeshes;
private Mesh[] meshes;
private bool isRunning = false;
private Transform camTrans;
void Awake() {
if (camera == null)
camera = Camera.main;
if (camera != null)
camTrans = camera.transform;
CreateMeshes();
}
void CreateMeshes() {
if (maxParticles <= 0) return;
particles = new Particle[maxParticles];
for (int i = 0; i < particles.Length; i++) {
particles[i].size = defaultParticleSize;
particles[i].color = defaultParticleColor;
}
int meshCount = maxParticles / quadsPerMesh + 1;
int quadsLastMesh = maxParticles % quadsPerMesh;
goMeshes = new GameObject[meshCount];
meshes = new Mesh[meshCount];
for (int i = 0; i < meshCount; i++) {
GameObject go = new GameObject();
go.transform.parent = transform;
MeshFilter mf = go.AddComponent<MeshFilter>();
Mesh mesh = new Mesh();
mesh.MarkDynamic ();
mf.mesh = mesh;
Renderer rend = go.AddComponent<MeshRenderer>();
rend.material = material;
Vector3[] vertices;
if (i != meshCount - 1) {
vertices = new Vector3[4 * quadsPerMesh];
}
else {
vertices = new Vector3[4 * quadsLastMesh];
}
mesh.vertices = vertices;
int[] triangles = new int[mesh.vertices.Length / 2 * 3];
for (int j = 0; j < vertices.Length / 4; j++) {
triangles[j * 6 + 0] = j * 4 + 0; // 0_ 3 0 ___ 3
triangles[j * 6 + 1] = j * 4 + 3; // | / | /|
triangles[j * 6 + 2] = j * 4 + 1; // 1|/ 1|/__|2
triangles[j * 6 + 3] = j * 4 + 3; // 3
triangles[j * 6 + 4] = j * 4 + 2; // /|
triangles[j * 6 + 5] = j * 4 + 1; // 1/_|2
}
mesh.triangles = triangles;
Color[] colors = new Color[mesh.vertices.Length];
for (int j = 0; j < colors.Length; j++) {
colors[j] = defaultParticleColor;
}
mesh.colors = colors;
Vector2[] uvs = new Vector2[mesh.vertices.Length];
for (int j = 0; j < vertices.Length / 4; j++) {
uvs[j * 4 + 0] = new Vector2(0,1);
uvs[j * 4 + 1] = new Vector2(0,0);
uvs[j * 4 + 2] = new Vector2(1,0);
uvs[j * 4 + 3] = new Vector2(1,1);
}
mesh.uv = uvs;
goMeshes[i] = go;
meshes[i] = mesh;
}
}
public void Clear () {
for (int i = 0; i < particles.Length; i++)
particles[i].lifetime = -1.0f;
}
public void Play() {
isRunning = true;
foreach (GameObject go in goMeshes) go.renderer.enabled = true;
}
public void Stop () {
isRunning = false;
foreach (GameObject go in goMeshes) go.renderer.enabled = false;
}
void LateUpdate() {
if (!isRunning) return;
Vector3 fwd = camTrans.forward;
Quaternion qCam = camTrans.rotation;
for (int i = 0; i < meshes.Length; i++) {
Mesh mesh = meshes[i];
Vector3[] vertices = mesh.vertices;
Color[] colors = null;
if (dynamicColor) {
colors = mesh.colors;
}
for (int j = 0; j < vertices.Length / 4; j++) {
Particle particle = particles[i * quadsPerMesh + j];
if (particle.lifetime < 0) {
vertices[j * 4] = Vector3.zero;
vertices[j * 4 + 1] = Vector3.zero;
vertices[j * 4 + 2] = Vector3.zero;
vertices[j * 4 + 3] = Vector3.zero;
}
else {
float l = particle.size / 2.0f;
Vector3 v0 = new Vector3(-l, l, 0);
Vector3 v1 = new Vector3(-l,-l, 0);
Vector3 v2 = new Vector3( l,-l, 0);
Vector3 v3 = new Vector3( l, l, 0);
Quaternion q = Quaternion.AngleAxis(particle.rotation, fwd) * qCam;
vertices[j * 4] = particle.position + q * v0;
vertices[j * 4 + 1] = particle.position + q * v1;
vertices[j * 4 + 2] = particle.position + q * v2;
vertices[j * 4 + 3] = particle.position + q * v3;
if (dynamicColor) {
colors[j * 4] = particle.color;
colors[j * 4 + 1] = particle.color;
colors[j * 4 + 2] = particle.color;
colors[j * 4 + 3] = particle.color;
}
particle.lifetime -= Time.deltaTime;
particle.rotation += particle.angularVelocity * Time.deltaTime;
particle.position += particle.velocity * Time.deltaTime;
particles[i * quadsPerMesh + j] = particle;
}
}
mesh.vertices = vertices;
if (dynamicColor) {
mesh.colors = colors;
}
}
}
}
And here is simple class as an example of how to drive the ParticleDisplay class:
using UnityEngine;
using System.Collections;
public class ParticleDisplayTest : MonoBehaviour {
private ParticleDisplay pd;
private ParticleDisplay.Particle[] pdp;
void Start () {
pd = GetComponent<ParticleDisplay>();
pdp = pd.particles;
for (int i = 0; i < pdp.Length; i++) {
Particle p;
pdp[i].velocity = Random.insideUnitSphere;
pdp[i].angularVelocity = Random.Range (0.0f, 180.0f);
pdp[i].lifetime = Mathf.Infinity;
pdp[i].color = new Color(Random.Range (0.0f, 1.0f), Random.Range (0.0f, 1.0f), Random.Range (0.0f, 1.0f));
}
pd.Play();
}
void Update () {
for (int i = 0; i < pdp.Length; i++) {
if (pdp[i].position.magnitude > 8.0f) {
pdp[i].velocity = -pdp[i].velocity;
pdp[i].position = pdp[i].position.normalized * 7.999f;
pdp[i].color.r = (pdp[i].color.r + Time.deltaTime * 0.5f) % 1.0f;
}
}
}
}
There's no default material, so you'll need to create one. I used Particles/Additive as the shader, and a snowflake with transparency for the test image.
Apologies for not responding, I'll have a look through this tomorrow as it looks very promising! Thanks for your hard work. :)