Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

[RESOLVED] Compute Shader that Calculate the Fill Color in a Texture

Discussion in 'Shaders' started by Shadownildo, Oct 5, 2021.

  1. Shadownildo

    Shadownildo

    Joined:
    May 31, 2018
    Posts:
    10
    Hellot there!
    I have some problem, i'm developing some study game and in there i have a Particle Effect that works like the game Splatoon , so to simplify, in the Terrain shader, i have One Main Texture, and one Mask Texture that goes on top the Main Texture. So where i Paint is in the Mask. ( The Black Texture)
    this part is working Fine.
    imagem_2021-10-05_095909.png



    Now, to Find the Points the player have, i'm using a Compute Shader, that get the Mask Texture, and find for example:

    How much blue is in ? 10%
    How much pink is in ? 5%
    blue win's, like that

    The problem is : It's not differentiating the colors, and it's giving me the wrong percentage, I'll pass down the Compute Shader code and how I consume it

    This is the Compute Shader
    Code (CSharp):
    1.  
    2. // Each #kernel tells which function to compile; you can have many kernels
    3. #pragma kernel CSMain
    4. #pragma kernel CSInit
    5. //SamplerState _LinearClamp;
    6. Texture2D<float4> image;  // The Mask
    7. float4 reference; // Color reference
    8. RWStructuredBuffer<uint> compute_buffer; //buffer
    9. //RWTexture2D<float4> Result;
    10.  
    11. [numthreads(64, 1, 1)]
    12. void CSInit(uint3 id : SV_DispatchThreadID)
    13. {
    14.     compute_buffer[id.x] = 0.0;
    15. }
    16.  
    17. [numthreads(8,8,1)]
    18. void CSMain (uint3 id : SV_DispatchThreadID)
    19. {
    20.         float fit = dot(image[id.xy].xyz, reference.xyz);
    21.         float refMagSq = dot(reference.xyz, reference.xyz);
    22.         float threshold = 0.5f;
    23.         float sqrt_3 = 1.73205080757f;
    24.         if ((fit / refMagSq) / sqrt_3 <= threshold) {
    25.             InterlockedAdd(compute_buffer[0], 1);
    26.         }
    27. }
    28.  

    This is how i consume , it's a little extense , the main method is GetPoints

    Code (CSharp):
    1.   public ComputeShader compute_shader;
    2.     [HideInInspector]
    3.     public uint[] data;
    4.     protected ComputeBuffer compute_buffer;
    5.  
    6.     private RenderTexture render_texture;
    7.     int handle_init;
    8.     int handle_main;
    9.     float percent;
    10.  
    11.     Dictionary<Colors, string> ListPercentual;
    12.  
    13.  
    14.     void Start()
    15.     {
    16.         ListPercentual = new Dictionary<Colors, string>();
    17.  
    18.      
    19.         render_texture = tex.getMask();
    20.         handle_init = compute_shader.FindKernel("CSInit");
    21.         handle_main = compute_shader.FindKernel("CSMain");
    22.         compute_buffer = new ComputeBuffer(1, sizeof(uint));
    23.         data = new uint[1];
    24.  
    25.         compute_shader.SetTexture(handle_main, "image", render_texture);
    26.         compute_shader.SetBuffer(handle_main, "compute_buffer", compute_buffer);
    27.         compute_shader.SetBuffer(handle_init, "compute_buffer", compute_buffer);
    28.     }
    29.  
    30.     void Update()
    31.     {
    32.         var list = GetColorsInserted();
    33.         foreach(var item in list)
    34.         {
    35.             var color = GetValueFromListColors(item);
    36.             var value = GetPoints(color);
    37.  
    38.             if (ListPercentual.ContainsKey(item))
    39.                 ListPercentual[item] = value;
    40.             else
    41.                 ListPercentual.Add(item, value);
    42.         }
    43.  
    44.         ShowPoints();
    45.     }
    46.  
    47.     private void ShowPoints()
    48.     {
    49.         foreach( var item in ListPercentual)
    50.         {
    51.             switch (item.Key)
    52.             {
    53.                 case Colors.Blue:   PointBlue.text   = item.Value; break;
    54.                 case Colors.Red:    PointRed.text    = item.Value; break;
    55.                 case Colors.Green:  PointGreen.text  = item.Value; break;
    56.                 default: break;
    57.  
    58.             }
    59.         }      
    60.     }
    61.  
    62.     private string GetPoints(Color reference)
    63.     {
    64.         compute_shader.SetVector("reference", reference);
    65.         data = new uint[1];
    66.         compute_buffer.SetData(data);
    67.         compute_shader.Dispatch(handle_init, 64, 1, 1);
    68.         compute_shader.Dispatch(handle_main, render_texture.width / 8, render_texture.height / 8, 1);
    69.         compute_buffer.GetData(data);
    70.         uint result = data[0];
    71.         var percent = 100.0f - ((float)result / ((float)render_texture.width * (float)render_texture.height)) * 100.0f;
    72.         return percent.ToString() + "%";
    73.     }
    74.  
    75.     void OnDestroy()
    76.     {
    77.         if (compute_buffer != null )
    78.         {
    79.             compute_buffer.Release();
    80.             compute_buffer = null;
    81.         }
    82.     }
     
  2. JohnE4D

    JohnE4D

    Joined:
    Jul 1, 2021
    Posts:
    5
    First, it seems the compute buffer is only size 1 in your c# code, but you are executing the init function 64 times. The value of id.x on line 14 of the compute shader will vary from 0-63, and write the value 0 to a lot of memory beyond the size of the buffer, which might cause things to misbehave and lead to incorrect results

    Second, I dont think it is a good idea to do an interlocked add for every pixel of the image. I could be wrong, but I think you would have better luck having the output buffer have as many uints (or a smaller data type) as you have pixels. You could then run the same calculation and write either 1 or 0 to the buffer at the index of corresponding with that pixel, then return that and sum it on the cpu side
     
    Shadownildo likes this.
  3. Olmi

    Olmi

    Joined:
    Nov 29, 2012
    Posts:
    1,553
    I would suggest you first create a very cut down prototype of the critical part (color counting), and then try to figure out what is exactly going wrong. Now there's all that extra stuff that will just confuse you.

    If I remember correctly this is pretty similar to that Unity Wiki article on how to create a histogram system with compute shaders. See link: https://en.wikibooks.org/wiki/Cg_Programming/Unity/Computing_Color_Histograms. It also uses that atomic InterlockedAdd operation. I think it should be quite ok to do it that way.

    @JohnE4D I think OP's idea is to get an easy to use value, just a single color count. There is also less to transfer back to CPU side. Although I have not profiled to see how much difference it would make to transfer that data. (1 vs 65k or such.)

    Anyway I can try to take a look at that tomorrow if I got time.
     
  4. Shadownildo

    Shadownildo

    Joined:
    May 31, 2018
    Posts:
    10



    If i understand correctly ,
    First i need to set the Init to or 1 in hsls file, or the Init to 64 ,

    And in second a don't know if i understand, you can give a briefly example ?
     
  5. Shadownildo

    Shadownildo

    Joined:
    May 31, 2018
    Posts:
    10

    Yes , i try to do this in the CPU side, but is too slow, so i'm trying to do this in GPU, It's Kinda Works, but he didn't Filter, it is geting the Percentual of the Paint Surface, no matter the color

    Like ,
    i Have blue 20%
    and red 10%

    Instead to give me 20% when i pass Blue as reference or 10% whe i pass Red, it's giving me 30%. no matter the color,
     
  6. JohnE4D

    JohnE4D

    Joined:
    Jul 1, 2021
    Posts:
    5
    Code (CSharp):
    1. [numthreads(64, 1, 1)]
    2. void CSInit(uint3 id : SV_DispatchThreadID)
    3. {
    4.     compute_buffer[id.x] = 0.0;
    5. }
    Code (CSharp):
    1.  
    2. compute_shader.Dispatch(handle_init, 64, 1, 1);
    3.  
    So this code asks the gpu to run the internal bit of the shader 64 times. It would be the equivalent of
    Code (CSharp):
    1.  
    2. uint[] compute_buffer = new uint[1]
    3. for (int i = 0; i < 64; i++) {
    4. compute_buffer[i] = 0.0;
    5. }
    6.  
    This is an invalid access, you will try to put things in the array at index 3 when the array is only one element large.

    Instead, I would recommend not initializing the buffer on the cpu side and getting rid of the first kernel altogether. Compute shaders have overhead associated with them which is only offset when they get to do many of the same operation in parallel. Setting a single value to zero will be faster as part of your cpu side setup, rather then running a separate kernel once that only does that.

    The code to do this on the c# side is a very small modification:
    Code (CSharp):
    1.  
    2. private string GetPoints(Color reference)
    3.     {
    4.         compute_shader.SetVector("reference", reference);
    5.         data = new uint[1] { 0 };
    6.         compute_buffer.SetData(data);
    7.         compute_shader.Dispatch(handle_main, render_texture.width / 8, render_texture.height / 8, 1);
    8.         compute_buffer.GetData(data);
    9.         uint result = data[0];
    10.         var percent = 100.0f - ((float)result / ((float)render_texture.width * (float)render_texture.height)) * 100.0f;
    11.         return percent.ToString() + "%";
    12. }
    13.  
    This makes sure data only contains 0, then is sent to the gpu with SetData()

    Ill take @Olmi suggestion and focus on getting the implementation to work without changing the interlock. Im not understanding what your doing with the math. If you know the mask is only going to be one color or blank, you should be able to just use ==

    Code (CSharp):
    1.  
    2. #pragma kernel CSMain
    3.  
    4. Texture2D<float4> image;  // The Mask
    5. float4 reference; // Color reference
    6. RWStructuredBuffer<uint> compute_buffer; //buffer
    7. //RWTexture2D<float4> Result;
    8.  
    9. [numthreads(8,8,1)]
    10. void CSMain (uint3 id : SV_DispatchThreadID)
    11. {
    12.         if (reference == image[id.xy]) {
    13.             InterlockedAdd(compute_buffer[0], 1);
    14.         }
    15. }
     
    Shadownildo likes this.
  7. Shadownildo

    Shadownildo

    Joined:
    May 31, 2018
    Posts:
    10

    Thank you so much! Works!!! i only have to change a little the Compute Shader

    Code (CSharp):
    1.  
    2. #pragma kernel CSMain
    3.  
    4. Texture2D<float4> image;  // The Mask
    5. float4 reference; // Color reference
    6. RWStructuredBuffer<uint> compute_buffer; //buffer
    7. //RWTexture2D<float4> Result;
    8.  
    9. [numthreads(8, 8, 1)]
    10. void CSMain(uint3 id : SV_DispatchThreadID)
    11. {
    12.     if (all(reference ==image[id.xy])) {
    13.         InterlockedAdd(compute_buffer[0], 1);
    14.     }
    15. }[/code
    16.  
    17.  
    18.  
    without the "all" give errors, BUT, it Works, thank you for the explanations!!
     
  8. Shadownildo

    Shadownildo

    Joined:
    May 31, 2018
    Posts:
    10