Search Unity

Black and white 1 style overlapping influence border

Discussion in 'Scripting' started by Lethn, Jul 25, 2021.

  1. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    I have been thinking about this stupid problem for years now and was struggling a lot to wrap my head around it. I have a generated mesh that acts as a border and I'm generally pretty happy with the effect but one thing I was dealing with was how to prevent it from overlapping. I looked up all sorts of shader related weirdness to see if I could deal with the problem that way but the problem is a lot of the solutions were either 2D, ridiculously complicated metaball solutions that didn't fit the comparatively simple mesh that was being generated.

    Then I had a thought, one thing I do have to my advantage is that with the way this influence ring is generated is we know where the vertices are going to be placed before hand since it's all generated through code. So shouldn't it be possible to detect which vertices are overlapping and then delete them? I realise I'm asking for something potentially complicated to be explained to me but I've been working on this damn problem for about 3 or so years now and I'm not stopping until I solve it lol. I've been able to solve most of the issues I've been dealing with myself aside from a few specific maths problems which people here have been a great help on for my game. I'm just constantly having to wrap my head around this damn influence border.

    Bgolus helped me out awhile back with the initial mesh generation and it works great but I'd love to refine the effect finally.

    https://forum.unity.com/threads/ste...ws-the-height-of-terrain.802230/#post-5360478

    1.png

    2.png

    For the record, the influence rings have sphere collider triggers on them and I've been using them for a lot of my mechanics. I'm wondering if it's possible that I could check which vertices are within a collider and then delete them to prevent the overlapping problem.
     
  2. ADNCG

    ADNCG

    Joined:
    Jun 9, 2014
    Posts:
    994
    If it's all circles, it's as simple as checking if the distance between the XZ center of a given circle and the vertex's XZ world position is less than the radius of the circle being tested against.
     
  3. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    How would you delete the specific vertices that fail this check though? I think that's my main question in this case, I'm pretty new to mesh generation in runtime.
     
    Last edited: Jul 25, 2021
  4. ADNCG

    ADNCG

    Joined:
    Jun 9, 2014
    Posts:
    994
    You can update the vertex/triangle arrays at runtime freely. You could re-generate the arrays every frame, testing against all your rings to decide whether or not certain verts should not be generated. Or you could do that at key moments, depending on the logic of your game and what's actually needed. You could even split your mesh into a bunch of quads and then you can do individual testing.

    Keep in mind that discarding the verts is not a perfect solution. When you remove the verts, you'll create an empty space where there originally was a segment. There's a chance that this gap will coincide perfectly with where the other ring begins, but more often than not, you'll end up with tiny gaps between your rings. The lower the resolution of the rings, the larger the gaps. If that's OK for you, then by all means, go for it!

    If not, then to fill that gap, you'd need to come up with a method to find the intersection points between a line and a circle. Then, add new verts at that intersection point. And I'm sure you'd run into even more problems down the road that I am unable to foresee.



    Your problem interested me and I wanted to give it a shot at a shader approach, for my own education.

    If you're interested, here's what I ended up with :



    Basically I create a texture for each rings and I store the position of all the other rings into it, every frame. Then, my shader iterates over that texture to calculate the distance between the fragments and the rings. If that distance is less than the radius of the ring being tested, then the alpha is simply 0.

    I'm using a 4x4 texture, meaning I can support up to 16 rings, but you could increase that, should you need it.
    Code (CSharp):
    1.  
    2. private void Awake()
    3. {
    4.     ringsMap = new Texture2D(4, 4, TextureFormat.RGBAFloat, false);
    5.     meshRenderer.material.SetTexture("_RingsMap", ringsMap);
    6. }
    7.  
    8. private void LateUpdate()
    9. {
    10.     MapRings();
    11. }
    12.  
    13. private void MapRings()
    14. {
    15.     List<Color> pixels = new List<Color>();
    16.  
    17.     for (int i = 0; i < ringsMap.width * ringsMap.height; i++)
    18.     {
    19.         if (i < rings.Length && rings[i] != this)
    20.         {
    21.             Vector3 ringPosition = rings[i].transform.position;
    22.             pixels.Add(new Vector4(ringPosition.x, ringPosition.z, rings[i].radius));
    23.         }
    24.         else
    25.         {
    26.             pixels.Add(Color.clear);
    27.         }
    28.     }
    29.  
    30.     ringsMap.SetPixels(pixels.ToArray());
    31.     ringsMap.Apply();
    32. }
    33.  

    Here's the shader
    graph.png
    Code (CSharp):
    1. Out = 1;
    2. float2 texelSize = float2(1 / TexSize.x, 1 / TexSize.y);
    3.  
    4. for (int x = 0; x < TexSize.x; x++)
    5. {
    6.     for (int y = 0; y < TexSize.y; y++)
    7.     {
    8.         float2 uv = (float2(x,  y) + 0.5) * texelSize;
    9.         float4 data = SAMPLE_TEXTURE2D(Tex, SS, uv);
    10.         float xDist = pow(abs(data.x - Position.x), 2);
    11.         float yDist = pow(abs(data.y - Position.y), 2);
    12.         Out = (xDist + yDist < data.z * data.z) ? 0 : Out;
    13.     }
    14. }
    cf.png
     
    Last edited: Jul 26, 2021
  5. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    Holy crap @ADNCG that's just the effect I was looking for, I had been researching this problem for years and you just solved it within a day LOL. I'll need to study all of this properly including the mesh generation code again, I had been asking all over the forum about this as well.
     
    chelnok and ADNCG like this.
  6. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    For the record, yes it's mainly a visual effect, I can get away with having overlapping sphere colliders as they are just designed to indicate whether you are within the influence ring or not, my mind is blown. Going to have to experiment with this and see what happens but thank you for helping me out with this, you and @bgolus finally helped me solve this damn problem. I had wondered if deleting the vertices would be a solution but you came up with a much better shader implementation.

    I wonder why there's so little information on this kind of technique? By the way, how exactly have you made this 4x4 texture? Is it just a blank colour or something more sophisticated?
     
  7. ADNCG

    ADNCG

    Joined:
    Jun 9, 2014
    Posts:
    994
    Generated at runtime like this
    Code (CSharp):
    1. ringsMap = new Texture2D(4, 4, TextureFormat.RGBAFloat, false);
    The texture format is important since you want to retain the precision regarding the position of the rings.

    Essentially, the required data for each ring is packed into its own pixel.
    You know how a color has a red, green, blue and an alpha channel? Well, instead of being data that represents a color, it's data that represents a ring.

    It maps like this
    r => the x position of the ring
    g => the z position of the ring
    b => the radius of the ring
    a => wasted - no data needed

    The shader then reads every pixel, extracts the data and uses it to determine the distance from the fragment.

    Does that make more sense?
     
    Lethn likes this.
  8. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    Sort of but this is a tricky concept for me I'll admit, are these calculations being done based on the rotation/position of the camera but automatically due to it being a shader?
     
  9. ADNCG

    ADNCG

    Joined:
    Jun 9, 2014
    Posts:
    994
    No, none of that sorcery!

    When a mesh is rendered, it's broken down into fragments, tiny pieces of the triangles representing the mesh. You can get the world position of a fragment with the "Position" node in shadergraph.

    In the ideal world, we'd pass a vector3 array to the shader, containing the world position of the rings, and their radiuses. The shader would then compare the fragment's position to every rings and determine if the fragment should be visible or not. In your case, if a fragment is inside of another ring, then no, it shouldn't be visible.

    However, Shadergraph does not support vector arrays, but it supports textures! So, instead of passing an array, we pass a texture that represents the array.

    For instance, we have a ring at position (3, 0, 6) with a radius of 1 meter. We aren't interested in the Y position for this use case. We could represent that ring in the texture this way :
    Code (CSharp):
    1. Color color = new Color(3f, 6f, 1f);
    2.  
    3. // or
    4.  
    5. Color color = new Color(ring.position.x, ring.position.z, ring.radius);
    6.  
    7. // We ignore the alpha channel since we only need 3 channels for our data
    We then assign this to the first pixel in our texture, and so on for every other rings.

    This way, we have sent our data over to the shader, in the form of a texture.
     
    Lethn likes this.
  10. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    Wait, so to try and break this down, what you did was essentially trick the shader graph into reading vectors and implemented the distance check between the generated circles again within the shader and deleting the result to prevent overlapping? That's actually insane, I would have never have figured this out myself in a million years.

    I was talking about this with someone I know in discord and they showed me a visual of the equation that you're implementing in the shader graph, let me know if this is right. Should also dramatically help others figuring all this out because it helped me.

    https://www.analyzemath.com/Geometry/circles_problems.html
     
    Last edited: Jul 26, 2021
  11. ADNCG

    ADNCG

    Joined:
    Jun 9, 2014
    Posts:
    994
    It's more like "hiding" the result rather than deleting, but yeah, that's spot on! :)

    No, there's no need for that. To know if a point is inside of a circle, all you have to know is if the distance between the point's position and the center of the circle is less than the radius of the circle.

    The fragment is just iterating over every circles and doing this distance check. It's that stupid simple. No further step.
     
    Lethn likes this.
  12. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    Thanks again, this has been a huge help and I'm sure this will help out a lot of other people who have been trying to get this kind of iconic effect working properly, I had no idea there were so many steps. Think I've just about got a proper grasp of it now.

    The real question is now why the hell haven't the Unity staff implemented vectors for shader graph to make this all easier? lol.
     
    ADNCG likes this.
  13. ADNCG

    ADNCG

    Joined:
    Jun 9, 2014
    Posts:
    994
    Glad to help, this was an educating experience for me as well. Good luck with your game :)

    It's a good question and beyond my understanding for the time being :)
     
    Lethn likes this.
  14. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    By "vectors" do you mean arrays? Because basically all shaders do is manipulate vectors.

    Part of the reason why arrays aren't supported is because doing for loops in a node graph is a huge nightmare and 90% of node graphs for materials / shaders just punt on loops or provide custom nodes that do the loop w/o needing it explicitly supported by the graph. The other reason is because Unity doesn't support arrays as material properties. Never has, so they didn't add support for it for Shader Graph either.

    That doesn't mean Unity shaders don't support arrays, they do. You just can't have them as serialized properties of the material.

    To get around that you just add the array as a uniform in the shader code itself. For Shader Graph that requires using a Custom Function that points to an .hlsl file.

    Alternatively, you can use a texture like @ADNCG suggested, which can actually have some minor advantages, like the ability to change the number of elements in the "array" by changing out the texture with one with a different resolution, and get the max number of elements from the texture's size.


    One note about this approach. Your mesh is not a perfect circle. It can't be, because it's a mesh. So there'll always be a little bit of a visible overlap or a gap between overlapping rings.
     
    Lethn and ADNCG like this.
  15. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    ADNCG likes this.
  16. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    @ADNCG if you could explain to me how you set up your rings array and list that would be great, I've been doing some poking at the code and I managed to get it setup without any major errors. I think the problem might be the pixels list because the MapRings function doesn't seem to be doing anything on my end despite updating every frame when I add the colliders to the rings array in runtime?

    1.png

    Thought it would be a good idea to show a screenshot of where I'm at because I suspect I'm misinterpreting the code somewhere again. I've already made a modification to make it so that the radius matches my generated border, I made the pixels list public just to have a look at what it was doing.
     
  17. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    Had a derp moment after re-reading your post and realised I shouldn't even be using the sphere colliders for the check because we're running checks on the damn vertex fragments. Could still use some help though in understanding where I'm messing things up.
     
  18. ADNCG

    ADNCG

    Joined:
    Jun 9, 2014
    Posts:
    994
    Right, so essentially all you need is a way to obtain the position of the center of all the rings and their radiuses.

    I'm trying to make sense of your setup from the screenshot. From my understanding, you have a RingMapper class on all of your rings and that class maintains a reference to all the other rings? I think that's a bit chaotic, having to maintain relationship with all the rings from every ring.

    Perhaps have one class responsible for maintaining a reference to all the rings and have the rings query the information from that class, if you want to maintain the logic on the rings class. Or, have rings be nothing but data and that that other class iterate over the rings and perform the logic instead. Or you could have a static list of rings inside of the rings class, and have each instance responsible for adding and removing itself.

    At the end of the day, those are implementation details. If you're happy with your solution, disregard my advice.
    I think I'd need to see your code, I'm having trouble understanding what the problem is from the screenshot. Could you post your entire RingsMapper class?
     
  19. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class RingMapper : MonoBehaviour
    6.  
    7. {
    8.     public Texture2D ringsMap;
    9.     public MeshRenderer meshRenderer;
    10.     public MeshGeneratedInfluenceBorder[] rings;
    11.     public List<Color> pixels;
    12.     public float radius;
    13.  
    14.     public void Awake()
    15.  
    16.     {
    17.         ringsMap = new Texture2D(4, 4, TextureFormat.RGBAFloat, false);
    18.         meshRenderer.material.SetTexture("_RingsMap", ringsMap);
    19.     }
    20.  
    21.     private void Update()
    22.  
    23.     {
    24.         MapRings();
    25.         radius = GetComponent<MeshGeneratedInfluenceBorder>().radius;
    26.     }
    27.  
    28.     private void MapRings()
    29.  
    30.     {
    31.         pixels = new List<Color>();
    32.  
    33.         for (int i = 0; i < ringsMap.width * ringsMap.height; i++)
    34.  
    35.         {
    36.             if (i < rings.Length && rings[i] != this)
    37.  
    38.             {
    39.                 Vector3 ringPosition = rings[i].transform.position;
    40.                 pixels.Add(new Vector4(ringPosition.x, ringPosition.z, rings[i].radius));
    41.                 Debug.Log(rings[i].radius);
    42.             }
    43.  
    44.             else
    45.  
    46.             {
    47.                 pixels.Add(Color.clear);
    48.             }
    49.         }
    50.  
    51.         ringsMap.SetPixels(pixels.ToArray());
    52.         ringsMap.Apply();
    53.     }
    54.  
    55.  
    56. }
    57.  
    I'm 99% sure my shader code and graph is correct, I've triple checked that, so it's likely just how I've set up your code. I'm getting a general understanding of how it all works though which is nice I just need to know how to work it all with the prefabs now, it's some really weird maths. I'm trying to get it all to work in runtime as my rings are added.
     
    Last edited: Aug 1, 2021
  20. ADNCG

    ADNCG

    Joined:
    Jun 9, 2014
    Posts:
    994
    Code (CSharp):
    1. if (i < rings.Length && rings[i] != this)
    Well, for one "rings != this" is invalid because your rings are "MeshGeneratedInfluenceBorder" type and you're checking against "RingMapper" type.

    The code above works for me because my rings list is of the same type than the class I've put this code in. Both are type of "Ring" so I can actually compare them. For you, you could compare gameobjects instead, since you have your MeshGeneratedInfluenceBorder and RingMapper components on the same GOs. Or your list could be of type RingMapper.

    The reason behind that line is that the current ring should not be added to its own rings map because the shader would have no way to know that this ring is the current ring, and thus wouldn't be able to treat it differently. It would simply consider it as another ring laid right on top of the current, with the same radius, and wouldn't render properly.

    Code (CSharp):
    1. Vector3 ringPosition = rings[i].transform.position;
    I'm not sure how you setup your mesh generation for your rings but if the transform.position is not at the center of the ring, you should update the line above to reflect the actual center of the ring. If it is, you can disregard what I said.

    Also, the radius in your code "RingMapper" class isn't used at all. You're assigning its value but never reading it. You're reading the radius straight from the MeshGeneratedInfluenceBorder, since that's the type of your list.

    I think there might also be a problem on the shader side. With the problems above, it shouldn't work properly but I think you should be able to see at least some kind of behaviour. Can you post your graph and the custom node as well?

    And just in case, are you certain that the materials of your rings are actually using the shader?

    edit : Here's the script I used when testing. Add it to a few gameobject in a blank scene. Make sure you add your material to the meshrenderer that it'll create and you should be able to test to see if your shader is actually the problem.

    Just in case, that code isn't good code. It's working code I used for testing. Don't use calls like FindObjectsOfType every frame.

    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEngine;
    3.  
    4. [RequireComponent(typeof(MeshFilter))]
    5. [RequireComponent(typeof(MeshRenderer))]
    6. public class Ring : MonoBehaviour
    7. {
    8.     [SerializeField]
    9.     private float radius = 2f;
    10.     [SerializeField]
    11.     private float height = 0.4f;
    12.     [SerializeField]
    13.     private int resolution = 32;
    14.     private MeshRenderer meshRenderer;
    15.     private Texture2D ringsMap;
    16.     private Mesh mesh;
    17.  
    18.     private void Awake()
    19.     {
    20.         ringsMap = new Texture2D(4, 4, TextureFormat.RGBAFloat, false);
    21.         meshRenderer = GetComponent<MeshRenderer>();
    22.         meshRenderer.material.SetTexture("_RingsMap", ringsMap);
    23.     }
    24.  
    25.     private void Start()
    26.     {
    27.         mesh = new Mesh();
    28.         GetComponent<MeshFilter>().mesh = mesh;
    29.         RedrawMesh();
    30.     }
    31.  
    32.     private void RedrawMesh()
    33.     {
    34.         List<Vector3> verts = new List<Vector3>();
    35.         List<int> triangles = new List<int>();
    36.  
    37.         for (int i = 0; i < resolution; i++)
    38.         {
    39.             float angle = i * Mathf.PI * 2f / resolution;
    40.  
    41.             Vector3 pos = new Vector3(Mathf.Cos(angle) * radius, 0f, Mathf.Sin(angle) * radius);
    42.  
    43.             verts.Add(pos);
    44.             verts.Add(pos + Vector3.up * height);
    45.         }
    46.  
    47.         for (int i = 0; i < resolution; i++)
    48.         {
    49.             int index = i * 2;
    50.             triangles.Add(index);
    51.             triangles.Add(index + 3);
    52.             triangles.Add(index + 2);
    53.  
    54.             triangles.Add(index + 1);
    55.             triangles.Add(index + 3);
    56.             triangles.Add(index);
    57.         }
    58.  
    59.         for (int i = triangles.Count - 6; i < triangles.Count; i++)
    60.         {
    61.             triangles[i] %= verts.Count;
    62.         }
    63.  
    64.         mesh.SetVertices(verts);
    65.         mesh.SetTriangles(triangles, 0);
    66.     }
    67.  
    68.     private void LateUpdate()
    69.     {
    70.         RedrawMesh();
    71.         MapRings();
    72.     }
    73.  
    74.     private void MapRings()
    75.     {
    76.         List<Color> pixels = new List<Color>();
    77.  
    78.         Ring[] rings = FindObjectsOfType<Ring>();
    79.  
    80.         for (int i = 0; i < ringsMap.width * ringsMap.height; i++)
    81.         {
    82.             if (i < rings.Length && rings[i] != this)
    83.             {
    84.                 Vector3 ringPosition = rings[i].transform.position;
    85.                 pixels.Add(new Color(ringPosition.x, ringPosition.z, rings[i].radius));
    86.             }
    87.             else
    88.             {
    89.                 pixels.Add(Color.clear);
    90.             }
    91.         }
    92.  
    93.         ringsMap.SetPixels(pixels.ToArray());
    94.         ringsMap.Apply();
    95.     }
    96. }
     
    Last edited: Aug 1, 2021
  21. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    This has turned into one hell of a project so I'm documenting everything properly for later, just to be on the safe side I also took screenshots of what's going on in the graph inspector because I wouldn't be surprised if there were issues happening there. As I've never used that before compared to the shader graph itself.

    2.png 3.png 4.png 5.png 6.png

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class RingMapper : MonoBehaviour
    6.  
    7. {
    8.     public Texture2D ringsMap;
    9.     public RingMapper[] rings;
    10.     public List<Color> pixels;
    11.     public MeshRenderer meshRenderer;
    12.     public float radius;
    13.  
    14.     public void Awake()
    15.  
    16.     {
    17.         ringsMap = new Texture2D(4, 4, TextureFormat.RGBAFloat, false);
    18.         meshRenderer = GetComponent<MeshRenderer>();
    19.         meshRenderer.material.SetTexture("_RingsMap", ringsMap);
    20.     }
    21.  
    22.     private void Update()
    23.  
    24.     {
    25.         MapRings();
    26.         radius = GetComponent<MeshGeneratedInfluenceBorder>().radius;
    27.  
    28.     }
    29.  
    30.     private void MapRings()
    31.  
    32.     {
    33.         pixels = new List<Color>();
    34.         rings = FindObjectsOfType<RingMapper>();
    35.  
    36.         for (int i = 0; i < ringsMap.width * ringsMap.height; i++)
    37.  
    38.         {
    39.             if (i < rings.Length && rings[i] != this)
    40.  
    41.             {
    42.                 Vector3 ringPosition = rings[i].transform.position;
    43.                 pixels.Add(new Vector4(ringPosition.x, ringPosition.z, rings[i].radius));
    44.             }
    45.  
    46.             else
    47.  
    48.             {
    49.                 pixels.Add(Color.clear);
    50.             }
    51.         }
    52.  
    53.         ringsMap.SetPixels(pixels.ToArray());
    54.         ringsMap.Apply();
    55.     }
    56.  
    57.  
    58. }
    59.  
     
  22. ADNCG

    ADNCG

    Joined:
    Jun 9, 2014
    Posts:
    994
    @Lethn Any chance you haven't set the reference name to _RingsMap in shadergraph? When you highlight the property, inside of the graph inspector, you should see a ref name

    edit : Nvm, just saw your screenshot.

    Also, inside of the graph inspector => Graph settings, surface should be set to transparent.

    edit : Nvm again..
     
    Last edited: Aug 1, 2021
  23. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    That's exactly why I included those screenshots because I figured that would come up :D

    Edit: Damn thought it might an issue with the way I named the Texture2D because the capitalisation was wrong but that didn't seem to do anything.
     
  24. ADNCG

    ADNCG

    Joined:
    Jun 9, 2014
    Posts:
    994
    The issue seems to be with HDRP. Not sure why yet... Investigating...

    I'll get back to you
     
  25. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    Thanks a lot, that's interesting, worth documenting as well just for people to browse through since this technique is barely discussed, I hadn't realised you were using LWRP.
     
  26. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    The "world space" in HDRP is camera relative. You need to get the absolute world space position by taking the Position node's output and using a Transform node to convert from World to Absolute World.
     
    Lethn and ADNCG like this.
  27. ADNCG

    ADNCG

    Joined:
    Jun 9, 2014
    Posts:
    994
    Ah yeah, that did it! Conveniently the position node has absolute world built in already, it seems.

    Was hoping you were still watching the thread
     
  28. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    1.png 2.png 3.png 4.png

    This is what I've done so far I suspect I messed up the world position settings somewhere or there's maybe something else I need to add to get rid of that overlap finally. It's sort of doing it but in a very weird way so it's almost there. I tried placing two buildings to try and mess things up just to make sure I wasn't going mental.
     
  29. ADNCG

    ADNCG

    Joined:
    Jun 9, 2014
    Posts:
    994
    I think your shader is fine now.

    It seems like an issue with the rings not being aware of every other rings, only considering one. It's my understanding that you serialized a list and then dragged and dropped every rings in there. I'd check there first, make sure the rings really are aware of all existing rings.
     
  30. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class RingMapper : MonoBehaviour
    6.  
    7. {
    8.     public Texture2D ringsMap;
    9.     public MeshRenderer meshRenderer;
    10.     public float radius;
    11.  
    12.     private void Awake()
    13.  
    14.     {
    15.         ringsMap = new Texture2D(4, 4, TextureFormat.RGBAFloat, false);
    16.         meshRenderer = GetComponent<MeshRenderer>();
    17.         meshRenderer.material.SetTexture("_RingsMap", ringsMap);
    18.     }
    19.  
    20.     private void Update()
    21.  
    22.     {
    23.         MapRings();
    24.         radius = GetComponent<MeshGeneratedInfluenceBorder>().radius;
    25.     }
    26.  
    27.     private void MapRings()
    28.  
    29.     {
    30.         List <Color>pixels = new List<Color>();
    31.         RingMapper[] rings = FindObjectsOfType<RingMapper>();
    32.  
    33.         for (int i = 0; i < ringsMap.width * ringsMap.height; i++)
    34.  
    35.         {
    36.             if (i < rings.Length && rings[i] != this)
    37.  
    38.             {
    39.                 Vector3 ringPosition = rings[i].transform.position;
    40.                 pixels.Add(new Color(ringPosition.x, ringPosition.z, rings[i].radius));
    41.             }
    42.  
    43.             else
    44.  
    45.             {
    46.                 pixels.Add(Color.clear);
    47.             }
    48.         }
    49.  
    50.         ringsMap.SetPixels(pixels.ToArray());
    51.         ringsMap.Apply();
    52.     }
    53.  
    54.  
    55. }
    56.  
    Well I rewrote the code to make it as close to @ADNCG 's example as possible but I'm coming up with blanks right now, I don't know why it would be behaving the way it is unless I've done something weird with the shader again. The only possibility I could think of is maybe it has something to do with the prefabs being instantiated during runtime? Doubtful because it's all local code now aside from the FindObjectsOfType.

    5.png
     
  31. ADNCG

    ADNCG

    Joined:
    Jun 9, 2014
    Posts:
    994
    @Lethn If you print the rings.Length inside the MapRings method, does it return the same number of rings than you actually have?
     
  32. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    Oh interesting! It prints 1 like you thought, I wonder if it's a better option in this case to have them all add to a global empty and then read the length of the rings off a list from that? Should the array have updated with the FindObjectsOfType?
     
  33. ADNCG

    ADNCG

    Joined:
    Jun 9, 2014
    Posts:
    994
    @Lethn
    Yeah, it should. Means not all your rings have the component on them.
     
  34. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    Oh wait my mistake, my brain wasn't working when I looked at this, I hadn't instantiated my new buildings at runtime to check, there are three ring classes being detected, just to be clear and show a screenshot of what print is putting out.

    1.png

    As near as I can work out, what's happening is the check is running fine on the very first influence ring and on the third ring it kind of breaks somehow. The second ring is detected and doesn't have the first ring going through it but none of the overlapping is dealt with.
     
    Last edited: Aug 2, 2021
  35. ADNCG

    ADNCG

    Joined:
    Jun 9, 2014
    Posts:
    994
    Can you replace your MapRings method with this and see if the number of rings packed is effectively the total number of rings - 1 and is consistent across all instances?
    Code (CSharp):
    1. private void MapRings()
    2. {
    3.     List<Color> pixels = new List<Color>();
    4.     RingMapper[] rings = FindObjectsOfType<RingMapper>();
    5.  
    6.     int validCount = 0;
    7.     for (int i = 0; i < ringsMap.width * ringsMap.height; i++)
    8.     {
    9.         if (i < rings.Length && rings[i] != this)
    10.         {
    11.             Vector3 ringPosition = rings[i].transform.position;
    12.             pixels.Add(new Color(ringPosition.x, ringPosition.z, rings[i].radius));
    13.             validCount++;
    14.         }
    15.         else
    16.         {
    17.             pixels.Add(Color.clear);
    18.         }
    19.     }
    20.  
    21.     print(gameObject.GetInstanceID() + " : " + validCount + " rings packed in the texture.");
    22.     ringsMap.SetPixels(pixels.ToArray());
    23.     ringsMap.Apply();
    24. }
    edit: I added GetInstanceID() to the print call. Use that instead please.
     
    Last edited: Aug 2, 2021
  36. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
  37. ADNCG

    ADNCG

    Joined:
    Jun 9, 2014
    Posts:
    994
    @Lethn Starting to run out of ideas... Can you post your mesh gen code from the other class please?
     
  38. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class MeshGeneratedInfluenceBorder : MonoBehaviour
    6.  
    7. {
    8.     public int sides = 16;
    9.     public float radius = 5f;
    10.     public float top = 1f;
    11.     public float bottom = -0.5f;
    12.  
    13.     public Terrain terrain;
    14.     Mesh mesh;
    15.  
    16.     Vector3[] vertices;
    17.     Vector2[] uvs;
    18.     int[] tris;
    19.  
    20.     private void Update()
    21.  
    22.     {
    23.         CreateMesh();
    24.         GetComponent<SphereCollider>().radius = radius;
    25.     }
    26.  
    27.  
    28.     private void CreateMesh()
    29.  
    30.     {
    31.         terrain = GameObject.FindGameObjectWithTag("Terrain").GetComponent<Terrain>();
    32.  
    33.         if (mesh == null)
    34.  
    35.         {
    36.             var meshFilter = GetComponent<MeshFilter>();
    37.             if (meshFilter == null)
    38.  
    39.             {
    40.                 return;
    41.             }
    42.  
    43.             mesh = new Mesh();
    44.             meshFilter.sharedMesh = mesh;
    45.             GetComponent<SphereCollider>().radius = radius;
    46.  
    47.         }
    48.  
    49.         vertices = new Vector3[sides * 2 + 2];
    50.         uvs = new Vector2[sides * 2 + 2];
    51.         tris = new int[sides * 2 * 3];
    52.  
    53.         Vector3 center = transform.position;
    54.  
    55.         for (int i = 0; i <= sides; i++)
    56.  
    57.         {
    58.             float radAngle = 2f * Mathf.PI / sides * i;
    59.             float s = Mathf.Sin(radAngle);
    60.             float c = Mathf.Cos(radAngle);
    61.  
    62.  
    63.             Vector3 pos = new Vector3(c * radius, 0f, s * radius);
    64.  
    65.             float terrainHeight = terrain.SampleHeight(pos + center);
    66.  
    67.             vertices[i * 2 + 0] = new Vector3(pos.x, terrainHeight + bottom - center.y, pos.z);
    68.             vertices[i * 2 + 1] = new Vector3(pos.x, terrainHeight + top - center.y, pos.z);
    69.  
    70.             float u = (float)i / (sides);
    71.             uvs[i * 2 + 0] = new Vector2(u, 0f);
    72.             uvs[i * 2 + 1] = new Vector2(u, 1f);
    73.         }
    74.  
    75.         for (int i = 0; i < sides; i++)
    76.  
    77.         {
    78.             tris[i * 6 + 0] = i * 2 + 0;
    79.             tris[i * 6 + 1] = i * 2 + 1;
    80.             tris[i * 6 + 2] = i * 2 + 2;
    81.  
    82.             tris[i * 6 + 3] = i * 2 + 2;
    83.             tris[i * 6 + 4] = i * 2 + 1;
    84.             tris[i * 6 + 5] = i * 2 + 3;
    85.         }
    86.  
    87.         mesh.vertices = vertices;
    88.         mesh.uv = uvs;
    89.         mesh.triangles = tris;
    90.         mesh.RecalculateBounds();
    91.     }
    92.  
    93. }
    94.  
    The only thing I could think of is maybe it's something to do with the way the for loop itself is handling things? because the rings array seems to be working fine. I'm suspecting it might be the if check within the for loop.
     
    Last edited: Aug 2, 2021
  39. ADNCG

    ADNCG

    Joined:
    Jun 9, 2014
    Posts:
    994
    It's a hail mary at this point but can you try to replace the custom function code with this flattened version?
    Code (CSharp):
    1. Out = 1;
    2. float2 texelSize = float2(1 / TexSize.x, 1 / TexSize.y);
    3.  
    4. for (int i = 0; i < TexSize.x * TexSize.y; i++)
    5. {
    6.     int x = i % TexSize.x;
    7.     int y = i / TexSize.x;
    8.     float2 uv = (float2(x,  y) + 0.5) * texelSize;
    9.     float4 data = SAMPLE_TEXTURE2D(Tex, SS, uv);
    10.     float xDist = pow(abs(data.x - Position.x), 2);
    11.     float yDist = pow(abs(data.y - Position.y), 2);
    12.     Out = (xDist + yDist < data.z * data.z) ? 0 : Out;
    13. }
     
  40. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    Holy crap it worked!

    1.png

    Okay let's go through what happened now LOL.
     
  41. ADNCG

    ADNCG

    Joined:
    Jun 9, 2014
    Posts:
    994
    Well man, I'll give you that, you don't give up easily. Great quality for a game developer to have.

    Now, why nested loops didn't work for you is beyond my understanding. Congrats on getting it working.
     
  42. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    lol thanks I think part of it may be because I had been working on this for 3 years and there was no way in hell I was going to stop, we should probably ask around about why this happened so that we can know what's going on. To be fair, I don't think I've updated Unity for awhile so I wonder if something is going on with that.
     
    ADNCG likes this.
  43. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    That effect looks absolutely awesome now, I'm honestly surprised this hasn't been investigated as a technique beforehand. I think I'll compare the two bits of custom function code to see what you did and get a clearer understanding.

    2.png
     
    Last edited: Aug 2, 2021
  44. ADNCG

    ADNCG

    Joined:
    Jun 9, 2014
    Posts:
    994
    Yeah, it looks good =)
    You can represent a grid with a 1D array, provided you know its width and height. That was a way to remove the nested loops, which I believed could be the cause of the issue. I'm still confused. There's a chance you had a typo in the other, but it looks fine from the screenshot.

    It bet it's the graphics API. I'm using metal and you DX11, but it's just speculation. I'm not qualified to say for sure.
     
  45. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    Okay, when my brain has calmed down and I've backed all this up I'll do some testing on the API to see if your theory is correct that way we can conclude the thread and everyone knows exactly what's going on. I'm also going to do some experimenting now we know what's up and look at making the code more efficient, I have some ideas.
     
  46. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    Small update! I've just made some changes to the original code so that it is hopefully more efficient, instead of arrays I've used lists and I've decided to put a ring manager empty in the scene for the other newly created rings to help them keep track of everything without any expensive checks. They simply copy the list created from the empty and update themselves that way, since the ring manager is always in the scene that means they only have to do a search once for a specific tag.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class RingMapper : MonoBehaviour
    6.  
    7. {
    8.     public Texture2D ringsMap;
    9.     public MeshRenderer meshRenderer;
    10.     public float radius;
    11.     public GameObject ringsManagerEmpty;
    12.     public List<RingMapper> rings;
    13.  
    14.     private void Awake()
    15.  
    16.     {
    17.         ringsMap = new Texture2D(4, 4, TextureFormat.RGBAFloat, false);
    18.         meshRenderer = GetComponent<MeshRenderer>();
    19.         meshRenderer.material.SetTexture("_RingsMap", ringsMap);
    20.         ringsManagerEmpty = GameObject.FindGameObjectWithTag("RingsManagerEmpty"); // Does one search for a single empty manager within the scene rather than trying to find several script components when a new ring is created
    21.         RingMapper ringMapper = GetComponent<RingMapper>();
    22.         ringsManagerEmpty.GetComponent<RingsManager>().ringMappers.Add(ringMapper);
    23.         rings = ringsManagerEmpty.GetComponent<RingsManager>().ringMappers;
    24.  
    25.     }
    26.  
    27.     private void Update()
    28.  
    29.     {
    30.         MapRings();
    31.         radius = GetComponent<MeshGeneratedInfluenceBorder>().radius;
    32.     }
    33.     private void MapRings()
    34.     {
    35.         List<Color> pixels = new List<Color>();
    36. //      RingMapper[] rings = FindObjectsOfType<RingMapper>();
    37.  
    38.         int validCount = 0;
    39.         for (int i = 0; i < ringsMap.width * ringsMap.height; i++)
    40.         {
    41.             if (i < rings.Count && rings[i] != this) // Changed rings.Length to rings.Count so the code accesses the new list
    42.             {
    43.                 Vector3 ringPosition = rings[i].transform.position;
    44.                 pixels.Add(new Color(ringPosition.x, ringPosition.z, rings[i].radius));
    45.                 validCount++;
    46.             }
    47.             else
    48.             {
    49.                 pixels.Add(Color.clear);
    50.             }
    51.         }
    52.  
    53.         ringsMap.SetPixels(pixels.ToArray());
    54.         ringsMap.Apply();
    55.     }
    56. }
    57.  
    Feel free to check my work, so far everything seems to be working exactly as it did before on the front end, I'm also experimenting with the shader itself and will be checking the API weirdness next. It's nice to know that I've actually gained an understanding of this damn code so I can make changes myself.
     
    Yoreki and ADNCG like this.
  47. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    Okay, some more good news as we're peeling away the issues now and it looks like you were right with your theory @ADNCG . I implemented the exact same shader function you gave me before hand and instead of DirectX11 I used Vulkan as when I switched to OpenGL it gave me a helpful warning about that and told me to use Vulkan instead. I wonder why that's such an issue with DirectX? That's fascinating, that's taken away a lot of the mystery of the problems I've been having with this shader now.

    I might just use Vulkan API by default anyway because I had been doing some learning on that previously and it's much more compatible with Linux which I do want to make my game available for out of the box.
     
  48. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    6.png

    Screenshot proof of it working, Vulkan enabled, original custom function in, I reckon I could probably tweak that gap problem now I have a better understanding, might just take some blending.
     
    Last edited: Aug 7, 2021
    ADNCG likes this.
  49. IliqNikushev

    IliqNikushev

    Joined:
    Feb 1, 2017
    Posts:
    22
    10 / 10 This is exactly what i was looking for :3

    it was a bit hard for me to get things going (following mix n match of screenshots made it work on URP ) and added some 'optimization' based on the distance to the camera

    in my current game i do not use a terrain so i removed the Tarrain sampling of the height and just use Height 0
    Instead of having the circles extruded Up/down i extrude them left/right (making them flat)
    I changed the Manager to be a Grouping mechanism

    You can assign a group to each circle and it will check with all other circles in the group - hey am i colliding with you

    there is a significant drop of frames when you have about 512+ circles even with the list of circles optimization
    Essentially its running a 512x512 check (hey, am i colliding with you) every frame
    In my game i have about 3k circles at a given time, but all scattered at different distances
    So i made the Update function happen less frequent, based on the distance to the camera
    It can be adjusted in many other ways (circle not in view for example, but by distance worked in my case)

    upload_2022-1-16_13-20-14.png
    Circles getting merged

    upload_2022-1-16_13-9-10.png
    512 flat circles

    I created it into an asset package and uploading it to the asset store, hope you guys don't mind :)
    it should be available within 20 days

    Thanks a lot guys! I wish i had found this sooner :)
     
    Lethn likes this.
  50. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    It's surprising how little information there is on this technique which is why I made this thread and also linked to the other mesh generation part of the influence circle. I think easily the most mind boggling part is the fact that you have to trick the engine into believing one type of data is another and I believe that's something that should be reported as a missing feature or bug that you can't do that in a normal way within the shader editor. Good job getting a 2D version set up for yourself.

    Another shader effect I enjoy quite a bit is fog of war as that's a game mechanic very widely used but not explored a lot but I should be able to get something set up on my own just about and I may make an up to date post about it so people don't have to look at 2004 posts with javascript code on it. Setting up a proper thread for selection boxes might also be another idea to look at.

    Edit: Edited the title to make sure it was more relevant because that was me making topics when I was in problem solving mode.
     
    IliqNikushev likes this.