Search Unity

Resolved Issue with image rotation in Compute Shader

Discussion in 'Shaders' started by UserNobody, Apr 2, 2021.

  1. UserNobody

    UserNobody

    Joined:
    Oct 3, 2019
    Posts:
    144
    Hey everyone,
    I have implemented a rotate image Compute Shader. The image is rotating fine, although for some reason there are right side pixels on the left side of the image.

    I use this image as an input (source) image.

    (I re-scaled the original image down for this website, so it wouldn't take so much space)

    The image width is 652 and height is 817

    as.png

    When I process this image in my Compute Shader I get the following result:



    This is the Compute Shader code I use:

    Code (CSharp):
    1. Texture2D<float4> source;
    2. RWStructuredBuffer<float4> pixels;
    3.  
    4. int angle;
    5. int width;
    6. int height;
    7.  
    8. [numthreads(8, 8, 1)]
    9. void Rotate(uint3 id : SV_DispatchThreadID)
    10. {
    11.     float radians = angle * (3.14159274 * 2 / 360);
    12.  
    13.     int w = width;
    14.     int h = height;
    15.  
    16.     float s = sin(radians);
    17.     float c = cos(radians);
    18.  
    19.     float2x2 r2 = float2x2(c, -s, s, c);
    20.  
    21.     float2 center = float2(w, h) / 2, pos = mul(r2, id - center) + center;
    22.  
    23.     if (min(pos.x, pos.y) < 0 || max(pos.x - w, pos.y - h) >= 0)
    24.     {
    25.         pixels[id.x + id.y * w] = float4(0, 0, 0, 0);
    26.     }
    27.     else
    28.     {
    29.         pixels[id.x + id.y * w] = source[pos.xy];
    30.     }
    31. }
    And this is the C# code

    Code (CSharp):
    1. void Start()
    2. {
    3.    kernel = shader.FindKernel("Rotate");
    4.  
    5.    pixels = new Color[source.width * source.height];
    6.  
    7.    pixelsBuffer = new ComputeBuffer(pixels.Length, sizeof(float) * 4);
    8.    pixelsBuffer.SetData(pixels);
    9.  
    10.    shader.SetBuffer(kernel, "pixels", pixelsBuffer);
    11.    shader.SetTexture(kernel, "source", source);
    12.  
    13.    shader.SetInt("width", source.width);
    14.    shader.SetInt("height", source.height);
    15.  
    16.    threadGX = Mathf.CeilToInt(source.width / 8f);
    17.    threadGY = Mathf.CeilToInt(source.height / 8f);      
    18.  
    19.    shader.SetInt("angle", 45);
    20.  
    21.    shader.Dispatch(kernel, threadGX, threadGY, 1);
    22.    UpdateRender();
    23. }
    Can someone please help me solve this issue? Thank you
     
  2. UserNobody

    UserNobody

    Joined:
    Oct 3, 2019
    Posts:
    144
    I changed the condition a bit. I added the bold marked code
    Code (CSharp):
    1. if (min(pos.x, pos.y) < 0 || max(pos.x - w, pos.y - h) >= 0 [B]|| id.x >= width || id.y >= height[/B])
    2. {
    3.     pixels[id.x + id.y * w] = float4(0, 0, 0, 0);
    4. }
    5. else
    6. {
    7.     pixels[id.x + id.y * w] = source[pos.xy];
    8. }
    And now those pixels are kind of gone, however I'm not sure what is happening completely, but it seems that the entire image is shifted by 4 pixels to the right. I rotated the image 90 degrees and as can be seen in the below image the left side edge pixels are gone. There's something wrong with my code but I can't find the source of it... :/

     
  3. UserNobody

    UserNobody

    Joined:
    Oct 3, 2019
    Posts:
    144
    Well I found the issue. The considering the image size, the width could not be divided by 2 properly, so if I ceil the x thread group count I get 4 pixels more than the actual width, if floor rounded 4 less than width. So that's why there's that gap. :/ Not sure how to fix this tho
     
  4. UserNobody

    UserNobody

    Joined:
    Oct 3, 2019
    Posts:
    144
    Changing the numthreads to 1, 1, 1 and setting the threadgroup count to width and height does fixes the problem, although it's not really efficient way...
     
  5. Magnilum

    Magnilum

    Joined:
    Jul 1, 2019
    Posts:
    241
    Hi, I had the same issue but with a different problem. I don't know if you have found an other solution but what I have seen to solve this issue is limiting the compute shader to the width and height of your image.

    You can keep the number of threadgroup at [8,8,1] and keep the Mathf.CeilToInt(source.width / 8f) and Mathf.CeilToInt(source.height/ 8f), this is correct.

    You can also use GetKernelThreadGroupSizes to be more flexible if you are interested in.

    The only thing you have to add is the limit to your compute shader.

    Here is the code:

    Code (CSharp):
    1. Texture2D<float4> source;
    2. RWStructuredBuffer<float4> pixels;
    3. int angle;
    4. int width;
    5. int height;
    6. [numthreads(8, 8, 1)]
    7. void Rotate(uint3 id : SV_DispatchThreadID)
    8. {
    9.     // This limit the compute shader to the size of your image
    10.     if (id.x > width - 1 || id.y > height - 1)
    11.         return;
    12.  
    13.     float radians = angle * (3.14159274 * 2 / 360);
    14.     int w = width;
    15.     int h = height;
    16.     float s = sin(radians);
    17.     float c = cos(radians);
    18.     float2x2 r2 = float2x2(c, -s, s, c);
    19.     float2 center = float2(w, h) / 2, pos = mul(r2, id - center) + center;
    20.     if (min(pos.x, pos.y) < 0 || max(pos.x - w, pos.y - h) >= 0)
    21.     {
    22.         pixels[id.x + id.y * w] = float4(0, 0, 0, 0);
    23.     }
    24.     else
    25.     {
    26.         pixels[id.x + id.y * w] = source[pos.xy];
    27.     }
    28. }
    I don't know if you have to keep the - 1 but try with and without.

    I hope it will work for you.
     
    Last edited: Oct 30, 2023