[SOLVED] How can I count the number of pixels above a threshold with a shader?

So I have a 1024x1024 render texture that looks something like this, though obviously the red part changes

I need to count the number of red pixels
I tried doing this on the CPU with GetPixel() in a nested for loop but this is too slow.

How can I do this with a (compute?) shader instead?

Thanks :slight_smile:

Seems it’s very similar.
https://en.wikibooks.org/wiki/Cg_Programming/Unity/Computing_Color_Histograms

1 Like

I’ll take a look, thanks :slight_smile:

Thanks a lot, I was able to adapt this to work :slight_smile:

Here’s my shader for anyone else looking:

#pragma kernel CSMain
#pragma kernel CSInit

Texture2D<float4> InputImage;

RWStructuredBuffer<int> ResultBuffer;

[numthreads(1, 1, 1)]
void CSInit(uint3 id : SV_DispatchThreadID)
{
    ResultBuffer[0] = 0;
}


[numthreads(8, 8, 1)]
void CSMain(uint3 id : SV_DispatchThreadID)
{
    uint4 col = InputImage[id.xy];


    if(col.r > 0.4)
    {
        InterlockedAdd(ResultBuffer[0], 1);
    }
}

and here’s the C# script

public ComputeShader cShader;
ComputeBuffer cBuffer;
int[] analysisResult;
int kernalMain, kernalInit;
int AnalyseImage()
{
    kernalMain = cShader.FindKernel("CSMain");
    kernalInit = cShader.FindKernel("CSInit");
    cBuffer = new ComputeBuffer(1, sizeof(int));
    analysisResult = new int[1];

    cShader.SetTexture(kernalMain, "InputImage", rt);
    cShader.SetTexture(kernalInit, "InputImage", rt);
    cShader.SetBuffer(kernalMain, "ResultBuffer", cBuffer);
    cShader.SetBuffer(kernalInit, "ResultBuffer", cBuffer);

    cShader.Dispatch(kernalInit, 1, 1, 1);
    cShader.Dispatch(kernalMain, rt.width / 8, rt.height / 8, 1);

    cBuffer.GetData(analysisResult);

    cBuffer.Release();
    cBuffer = null;

    return analysisResult[0];
}

(rt is the RenderTexture with the image)

2 Likes

Links to source code in video description. Compute shader calculates area (ratio between counted pixels and total pixels):

Thanks for sharing! I had exactly the same problem (count white pixels) and the same idea, but no clue, how to do this with the shader. I had 25% load because of ReadPixels - now no remarkable load anymore.

i have follow this way and it is working perfectly but i am having a issue in some android Devices
ArgumentException: Kernel ‘CSMain’ not found
This is the exception i am getting
Help Needed

This is My Compute Shader

#pragma kernel CSMain

Texture2D<float4> image; // The Mask
float4 reference; // Color reference
RWStructuredBuffer<uint> compute_buffer; //buffer

[numthreads(8, 8, 1)]
void CSMain(uint3 id : SV_DispatchThreadID)
{
if (all(reference ==image[id.xy])) {
InterlockedAdd(compute_buffer[0], 1);
}
}

and here is the script

public void OnStart(RenderTexture mask)
{
    //ListPercentual = new Dictionary<Colors, string>();
      
    compute_shader = Instantiate(compute_shader);
      
  
   
    render_texture = mask;

    try
    {
        handle_main = compute_shader.FindKernel("CSMain");
    }
    catch (Exception e)
    {
        GameAnalytics.NewErrorEvent(GAErrorSeverity.Info , e.Message+"::"+SystemInfo.deviceModel);
        Console.WriteLine(e);
        throw;
    }
      
      
    compute_buffer = new ComputeBuffer(1, sizeof(uint));
    data = new uint[1];
    compute_shader.SetTexture(handle_main, "image", render_texture);
    compute_shader.SetBuffer(handle_main, "compute_buffer", compute_buffer);
}


public float GetPoints()
{
    compute_shader.SetVector("reference", reference);
    data = new uint[1] { 0 };
    compute_buffer.SetData(data);
    compute_shader.Dispatch(handle_main, render_texture.width / 8, render_texture.height / 8, 1);
    compute_buffer.GetData(data);
    uint result = data[0];
    percent = 100.0f - ((float)result / ((float)render_texture.width * (float)render_texture.height)) * 100.0f;
      

    return percent;

}

There is a huge problem with compute shader that 50% of Android Devices does not support Open GL ES 3.1 which is necessary for compute shaders. Also IOS does not support it altogether.
Looking for another way of doing this on mobile devices
Any Suggestions.

You may just write a box downsample shader instead.
First pass would count pixels passing the test and write output.
Next pass would simply output a sum of all pixels in a box (because they are counts after first pass).

And if you really want speed you could make your first pass write to a single channel 8-bit buffer (assuming your first box is no bigger than 255 pixels), then your second pass would write to a 16-bit buffer, and following passes to 32-bit buffers.
Until your final buffer is just 1x1 pixels.

Yeah, it’s more coding, but takes advantage of parallelism pretty well, so offers good performance and good compatibility (you may even give up on compute shaders and do it with pixel shaders, for as long as you have a 32-bit texture to write to)