- Home /
Optimisation problems with altering mesh.vertices
Hey guys,
For the past week or so I've been spending my spare time trying different methods to create a "fog of war" type effect for a RTS type game.
I have found numerous posts on here with plentyful information, and through them have found the most optimised method out of the ones suggested.
Notes:
do not have Unity Pro (yet) and would much prefer to build this feature on my own (please no suggestions that require Unity Pro, or someone elses work from the Asset Store).
-The game I am working on will require a vast amount of units to be on the screen at any given time. Hence the performance issues.
So, the most optimised method that I have found out of many that I have so far tried is to place a single plane just above the walkable area and raycast onto the plane. Then iterate through each vertex of the plane and make each vertice within range have an alpha of 0 in color, ultimately creating a transparent circle(ish) around the unit.
This works really well visually, The plane that I am currently using has 1089 vertice in total. I could potentially shave a some vertices off of the plane however making it any smaller would cause unpreferrable accuracy due to the space between the vertices.
The major issue is that although I am currently working on the game entirely using the basic geometry that comes with Unity (I haven't yet built any models to use in the game), after spawning roughly 5 units, each raycasting onto the plane every 0.5 seconds, the fps drops way too much. Particularly considering that the game would ideally need to support far more units than this.
Here is the basis of my code:
void calculateVision()
{
//because the plane's normal is facing up the way, we our ray to collide from above, downwards.
ray = new Ray(transform.position+(transform.up*5f),-transform.up);
if(cnfg.fogcollider.Raycast(ray,out hitinfo, 5f))
{
//get the relative position of the hit point on the fogplane's mesh
relPoint = cnfg.fogmesh.transform.InverseTransformPoint(hitinfo.point);
Color[] colors = cnfg.fogmesh.mesh.colors;
//iterate through each vertice
for(int i = 0; i < cnfg.fogmesh.mesh.vertices.Length; i++)
{
//if the distance between the vertice and the hit point is less than vision range, grant vision
if(Vector3.Distance(cnfg.fogmesh.mesh.vertices[i], relPoint) < visionRange)
{
colors[i].a = 0f;
}
}
//apply the changes
cnfg.fogmesh.mesh.colors = colors;
}
//rerun the function
Invoke("calculateVision",cnfg.minionFogIteration);
}
cnfg.fogmesh is the meshFilter of the fog plane.
So ultimately my question is, other than further reducing the cnfg.minionFogIteration time and lowering the vertex count in the fog plane, is there a more optimised method to achieve what it is that I am trying to do, or am I just plain stupid for trying to do this in Unity Free?
I will be happy to provide a package of what I have so far if that would be any use to anyone.
Many thanks in advance for any suggestions.
Incremental optimization probably aren't what you are looking for, but for this many comparisons, it is worth doing the distance calculation locally and comparing the sqr$$anonymous$$agnitude rather than magnitude. I don't know how smart the C# compiler is, but it would be worth the test to limit the indirection as well:
Vector3[] verts = cnfg.fogmesh.mesh.vertices;
for(int i = 0; i < verts.Length; i++)
{
//if the distance between the vertice and the hit point is less than vision range, grant vision
if((verts[i] - relPoint).sqr$$anonymous$$agnitude < visionRangeSquared)
{
colors[i].a = 0f;
}
}
Hi again robertbu.
Yeah this was actually one of my initial ideas since I thought it would be a less performance heavy process than checking the distance between two points. Using the Vector3.Distance method is roughly 10% (in the scale i'm using it) more efficient though.
I have been brainstor$$anonymous$$g over this one and have come up with two potential methods to avoid having to iterate through the verts altogether. It seems to be the iterations alone which are causing the performance drop.
I'll post back here with my results.
Answer by donnysobonny · Sep 01, 2013 at 02:08 AM
Thanks for all the tips guys. It would appear that we all had a similar goal in mind, which was to lower the number of iterations as much as possible. You will be pleased to know that I have lowered the iterations to a big fat ZERO!! Haha yeah, i'm pretty happy about that.
If anyone wants to actually see code, please let me know and I will be happy to share it. However, to keep things short, here's how I have done it:
1) At startup, I am creating a plane from scratch. The one major reason for doing this is so that the distance between each vertex is a value which can be easily converted to Unity world units. Initially I was basing the method on this entirely, but later this was simply used for optimisation.
2) While I add a vertex to the mesh, I am also instantiating a "node" into the world at the exact position of the vertex in the world. This will later be used to trigger a collision.
3) IMPORTANT: To each node, I save the index value of the vertex that is in the same position as the node.
4) To each unit, I then add a capsule collider and increase the range of the capsule collider to the vision range of that unit (the range that should remove the fog of war effect). IMPORTANT: Set this collider to be a trigger and add a rigidbody to it.
5) OnTriggerEnter, I am checking for fogNodes. I then activate them and keep them activated during OnTriggerStay, and deactivate them OnTriggerEnter. Each node is running an Update loop and fading in and out the transparency based on whether it is activated or not.
Also, thanks to @Paulius Leikis for the suggestion regarding making sure that each unit accesses a publically visibly color[] array. This has increased performance roughly 16%.
After stress testing the above process, with a whopping 16236 vertice plane and well over 100 units, I am struggling to lower the fps!!
You can try it out here: http://towerdefense.pixelpug.co.uk/TD.html
Thanks again for the help guys.
Hello.
I'm interested in your code. I happen to be using a similar method but I wrote my own custom shader to speed to the performance. The only problem I am facing is in the profiler. $$anonymous$$y script sometimes jumps from 2 ms but has to stay under 1 ms at least. Your fade out effect also looks nice.
Can I get your email address for further conversation?
@otg2 It is some time since I've even looked at this project as it was unfortunately discontinued. I am more than happy to lend some advice, however, for the purpose of helping others, lets try and keep the conversation within this thread.
Firstly, my solution wasn't reliant on shader program$$anonymous$$g (other than needing to use a vertex lit shader so that I could manipulate the vertices of a mesh). I don't have a huge amount of experience in shader program$$anonymous$$g, however, from what I do know about it, I wouldn't expect great results from the way that you are trying to do it. The main reason for this is you are heavily reliant on game objects in the 3d world. I wouldn't suggest to interact too heavily between a 3d game object and a shader for particularly heavy tasks (like the one you are trying to do).
$$anonymous$$y solution was actually quite simple and should be relatively easy to set up. It ultimately was done by setting up empty game objects at the exact same position as each vertex in the "fog" mesh. I then used 3d collisions to set the opacity of the vertex in the mesh (using the vertex shader). I would suggest to use this as a starting point, as it can go many directions from this point.
Let me know if you need more help.
Dont worry about the shader. I wrote it just to speed performance on the GPU, not via the script. It is similar to a vertex color shader except it degenerates triangles that are not active. That way I can make my grid with more triangles, making my "fogofwar" with a higher resolution without making the graphic unit lag (note : Im developing on mobile so I have limited resources).
$$anonymous$$y "fogofwar" is just a plane that the 3d objects stay on. $$anonymous$$y scripts holds a note for each gameobject on the plane for the chosen vertex. In every update, I take the note of the object and calculate the distance between it and its object. I also calculate the distance between all the other 9 vertexes around the note. I take the closest one and replace the note with that chosen vertex. So the shader is not a problem at all and neither is the script. Its actually quite fast.
The problem I am facing is the scripts can jump to 2 ms ins$$anonymous$$d of staying under 1ms.
I was wondering about the fade in effect and if you had anu different method of setting the vertex colors? From what I know, doing _$$anonymous$$esh.colors32 = _Colors; where Colors is a color array is quite slow. $$anonymous$$esh.colors32[n] = Color.Black is even slower, specially when you have a grid of 132x132 verts.
But I do see what you mean about setting gameobjects at every node. I dont really think that is the best solution since it takes a lot of memory.
I might try to put node on each vertex but I dont think it will be good performance. Thanks for you answer !
And p.s. I do recommend learning to write shaders. You can optimize every graphic and performance wastly!
@otg2 apologies for not getting back to you sooner. I've recently been moving house and internet has naturally been a problem!
Off the top of my head, and without experimenting myself, I can't imagine there would be a huge performance difference in the way that you are doing it, in comparison to the way that I did it, so i'll make sure to cover a basic tip before we continue:
Check whether you actually need to check! This is a common practice in optimisation, in that you add additional conditional statements outside of the heavy lifting, to decide whether you actually need to do the heavy lifting. So for example, if you've got 17424 verts, you don't want your runtime-processes to happen on every vert, every frame. Add additional properties to allow you to decipher whether the processes need to actually run.
So, looking at some code then. I assume you've read my original solution, and understand that a "node" is placed at exactly the same position as the vert that it is indexed against (in the vert array of the mesh). This component/script is attached to each "node":
using UnityEngine;
using System.Collections;
public class fogNode : $$anonymous$$onoBehaviour {
private Config cnfg;
public int meshIndex;
public bool isNodeActive;
void Start()
{
cnfg = GameObject.Find("RoomScripts").GetComponent<Config>();
}
void Update()
{
//if active and needs fading in
if((isNodeActive)&&(cnfg.fogplanecontroller.publicColor$$anonymous$$ap[meshIndex].a > 0f))
{
cnfg.fogplanecontroller.publicColor$$anonymous$$ap[meshIndex].a -= cnfg.fogplanecontroller.nodeFadeIncrement*Time.deltaTime;
//normalise
if(cnfg.fogplanecontroller.publicColor$$anonymous$$ap[meshIndex].a < 0f)
cnfg.fogplanecontroller.publicColor$$anonymous$$ap[meshIndex].a = 0f;
}
else if((!isNodeActive)&&(cnfg.fogplanecontroller.publicColor$$anonymous$$ap[meshIndex].a < 1f))
{
cnfg.fogplanecontroller.publicColor$$anonymous$$ap[meshIndex].a += cnfg.fogplanecontroller.nodeFadeIncrement*Time.deltaTime;
//normalise
if(cnfg.fogplanecontroller.publicColor$$anonymous$$ap[meshIndex].a > 1f)
cnfg.fogplanecontroller.publicColor$$anonymous$$ap[meshIndex].a = 1f;
}
}
}
The above component ultimately controls the opacity of a vert, based on whether it is active or not. Note that I access an element of the verts array directly, and never iterate through the verts array!
This component is attached to the unit that is granted "vision":
using UnityEngine;
using System.Collections;
public class $$anonymous$$ionVisionController : $$anonymous$$onoBehaviour {
private Config cnfg;
void Start()
{
cnfg = GameObject.Find("Scripts").GetComponent<Config>();
}
void OnTriggerEnter(Collider other)
{
if(other.tag == "fogNode")
{
fogNode fn = other.gameObject.GetComponent<fogNode>();
fn.isNodeActive = true;
}
}
void OnTriggerExit(Collider other)
{
if(other.tag == "fogNode")
{
fogNode fn = other.gameObject.GetComponent<fogNode>();
fn.isNodeActive = false;
}
}
void OnTriggerStay(Collider other)
{
if(other.tag == "fogNode")
{
fogNode fn = other.gameObject.GetComponent<fogNode>();
fn.isNodeActive = true;
}
}
}
This component interacts with the fogNode component, when the unit's "vision" (represented by an empty collidable trigger attached to the unit) collides with a node.
This should point you in the right direction. If you need further help let me know.
Answer by Paulius-Liekis · Aug 31, 2013 at 06:54 PM
cnfg.fogmesh.mesh.colors = colors;
is expensive operation - modify colors for all units and then set it on the mesh.
Hmm, I hadn't really thought of this. You're suggesting that I make all units share a public Color[] array, ins$$anonymous$$d of having each unit assign their individual Color[] array to the mesh.colors?
I can sort of see where you're co$$anonymous$$g from on this, and I probably will actually do this. However would this really be more problematic than the sheer amount of iterations each unit is doing? Bearing in $$anonymous$$d that there are 1089 verts in this current test environment, so with each unit, that's another 1089 iterations ever 0.5 seconds...?
So far, all i've been focusing on is removing the need to iterate through all of the verts in order to find the ones close to me. In the past 10 $$anonymous$$utes I have managed to achieve this, however I am yet to try it to see if there is any difference in performance.
Either way, I will post my results on here, with and without your suggestion.
$$anonymous$$any thanks!
Setting colors is expensive operation, because Unity combines your colors with the rest of vertex data.
And as Eric pointed out you should never do something like this: cnfg.fogmesh.mesh.vertices[i] in a loop. This is what happens: Unity takes vertices from vertex buffers and copies it into C++ array, then that array is converted to C# array. EVERY SINGLE LOOP. Do this before the loop: Vector3[] verts = cnfg.fogmesh.mesh.vertices;
Answer by Eric5h5 · Aug 31, 2013 at 08:20 PM
You should use Mesh.colors32 rather than Mesh.colors. Also, if the plane doesn't change in size, there's no need to get a new Colors32 array from the mesh every time; just use a global Colors32 array and upload that to the mesh when needed. Getting the mesh every loop iteration isn't good (and Length can also be optimized a bit by not using it), so instead: int end = cnfg.fogmesh.mesh.vertices.Length; for(int i = 0; i < end...
.
A better idea overall would probably be to not iterate through every possible vertex, but use some kind of bucket system, so you just iterate though some of the nearer ones.
Your answer
Follow this Question
Related Questions
Set Y points of vertices on ground. 1 Answer
Add Effect to a small part on a mouse click 0 Answers
Find height above a mesh without raycasting (for bobbing a boat). 1 Answer
Fog Of War with line of sight - Tons of raycast or better solution? 0 Answers
How to find smallest length between hit point and vertices 1 Answer