⬆️ ⬇️

Optimization Unity3d UI by GPU (for example minimap) or create a minimap without additional cameras and sprites

Hello!



"If you can count something on the GPU, do it."

// Of course, within reason



image

')

VS



image

Pay attention to the difference in FPS



I will begin, perhaps, with prehistory. One of our programmers decided to check the UI for the FPS drop. And we found an interesting relationship, when the minimap was disconnected, the FPS went up as a percentage. Interesting. Need to solve a problem. Immediately I will write about the atlases and various pools, we tried. And then I decided to address this issue in more detail. And here the first thought that visited me, the UI uses the material, so you can transfer everything to the GPU, let's begin.



I started to google, found a lot of, let's say, bad solutions, such as

Do not do this even if you want to do it quickly .



Why the above example is bad can be found in the comments. But for the lazy, I will list.

streeter12 July 4, 2016 at 2:51 pm

This method, despite its simplicity, has obvious disadvantages.



1. Poor performance.

2. To add new tags, you need to create new spheres (extra rubbish in prefabs).

3. Adding new types of tags and their filters for different players is very difficult.

4. To change the appearance of the label, you must create a mesh!

5. Unnecessary objects in each scene => extra complexity => more difficult to develop and support.

6. It is difficult to test => more possible bugs (including 3).

7. Realtime type replacement.

8. It is necessary to clutter the scene with a fake substrate.

9. What about those who should not take into account the rotation? reset to Update or drag through tranform.position.

etc.



Having found nothing, I finally began to write a shader.



What I started with.



1. It is necessary to store the icons themselves. Atlas will do. Now you need to find a suitable test. I took from the game WOW. there he already had the right content.



2. It is necessary to forward this information to the shader. Plus as much as possible to abandon the various UI transformations, such as coordinates relative to something, and so on.



In general, we will begin implementation.



static const int MaxCount = 256; float4 _Targets[MaxCount]; float _WorldSizeX; float _WorldSizeY; 


Such fields I announced in the shader. Put a maximum of 256 on the map points. The rest will be discarded. The float _WorldSizeX field solved the transformation problem, I decided to feed Vector3 into a shader as it is. Not tricky fraud, I got the indices and normalized coordinates, and thus got almost what I wanted.



Nuance: GC counts y otherwise from bottom to top. Of course, I made the inverse of the values ​​so that the upper left index was 0. But then I decided to abandon this idea. It is better to solve this by the top layer. For example code.



And of that, I had 3 fields from Vector4 (XZW) occupied with which I took Y later.



Tests



Having made a shader, I decided to check the difference. Now FPS was at the level of adding delete only one ui element. The speed has increased significantly, in the first two screenshots you can see the difference.



For example, what the shader script looks like.



 protected virtual void Update () { for (int i = 0; i < list.Length; i++) { list[i] = new Vector4(0, 0, 0, -1); } for (int i = 0; i < list.Length && i < targets.Count ; i++) { list[i] = new Vector4(targets[i].x *kx, targets[i].y, targets[i].z*ky, targets[i].w); } image.GetModifiedMaterial(image.material).SetVectorArray("_Targets", list); } 


How to use it, we omit. Let's say we have a manager with a list of points for the minimap. And you use it as you wish. Who subscriptions who singelton who statics. The main thing is not to forget to reset the list so that there are no unpleasant moments, and to drive everything into the shader.



image



The result of the work. As you can see, I added more and rotation. But in order to use it, icons should be made at a sufficient distance from each other in the atlas. I did not invent any complex cut-off algorithms in the shader, it is much easier to make all this a texture.



The same free Y, I decided to use as a corner. I had to tinker with it separately, the reason was higher, namely the inversion of the Y coordinate. It was because of the rotation that I decided to use everything as it is.



 fixed a = _Targets[i].y; fixed resultX = tX; fixed resultY = tY; if (a != 0) { fixed x0 = minX + (sizeX * 0.5); fixed y0 = minY + (sizeY * 0.5); resultX = x0 + (tX - x0) * cos(a) - (tY - y0) * sin(a); resultY = y0 + (tY - y0) * cos(a) + (tX - x0) * sin(a); } 


Now the shader is ready. The final touch is softening the alpha channel at the borders. I used interpolation, from affa to barrier. So the picture turned out better.



findings



Test for the article. I did not do full compliance. It is meaningless, and therefore the content is different. But to understand the difference, this is more than enough. The next stage, when the hands reach, I want to transfer tile inventory to a similar system.



I will lay out test codes and profiler results. Filling sprites.



  protected virtual void Start () { tests = new List<RectTransform>(); float x = 10; float y = 10; for (int i = 0; i < 256; i++) { int idx = Random.Range(0, prefabs.Count); GameObject obj = Instantiate(prefabs[idx].gameObject); RectTransform t = obj.GetComponent<RectTransform>(); t.SetParent(parent); t.localPosition = new Vector3(startPosition.x + x, startPosition.y + y, 0); if (useRotation) { t.rotation = Quaternion.Euler(0, 0, Random.Range(0, 360)); } x += step; if (i % 20 == 0) { y += step; x = 0; } tests.Add(t); } } 


Filling through the proxy class for the shader

item.type = Random.Range (0, 64); means icon type



  void Start () { tests = new List<Item2>(); float x = 10; float z = 10; for (int i = 0; i < 256; i++) { Item2 item = new Item2(); item.position = new Vector3(x, 0, z); if (useRotation) { item.rotation = Random.Range(0, 360); } item.type = Random.Range(0, 64); x += step; if (i % 20 == 0) { z += step; x = 0; } tests.Add(item); } } 


Shader profiler (3 more cubes on stage)



image



Profiler for sprites (on stage 3 more image as prefabs)



image



and finally, a masked example:



image



UPD.

Difference with and without shader

image

One of the comments where the author wanted to generate a mesh was very disappointing. Apparently the author does not understand that shaders are able to calculate the lighting of the shadow of reflection, etc. What for them should count primitive arithmetic expressions? Instead, he decided to load the CPU.



In addition to the question about the alpha. So I got soft borders

image



PS "If you can count something on the GPU, do it"

Source: https://habr.com/ru/post/335524/



All Articles