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. Dismiss Notice

Artifacts when drawing background with dynamically generated 2d texture

Discussion in 'UI Toolkit' started by echekanskiy, Aug 17, 2020.

  1. echekanskiy

    echekanskiy

    Joined:
    Aug 16, 2020
    Posts:
    16
    Hi. I am trying to draw programatically generated texture using SkiaSharp using visual element.
    Texture contains alpha channel.
    I am setting texture as VisualElement background. Unfortunately I observing picture like this:
    upload_2020-8-17_18-14-24.png
    As you can see, there are black outline on circle borders.
    For example, exported image in Pain.NET looks like this:
    upload_2020-8-17_18-16-30.png
    so SkiaSharp generates "clean" texture.

    Any information I can provide to diagnose and fix this issue?
     
  2. antoine-unity

    antoine-unity

    Unity Technologies

    Joined:
    Sep 10, 2015
    Posts:
    733
    Hello,

    There isn't anything obvious from what you are describing. Can you maybe submit a bug in Unity with the code that produces this image or maybe share the exported texture here before it's passed to the Visual Element, so we can try to identify where the issue comes from.
     
  3. echekanskiy

    echekanskiy

    Joined:
    Aug 16, 2020
    Posts:
    16
    Code to draw animated circle I used:
    Code (CSharp):
    1. using SkiaSharp;
    2. using UnityEngine.UIElements.Experimental;
    3.  
    4. namespace UnityEngine.UIElements
    5. {
    6.     public class SkiaCanvas : VisualElement
    7.     {
    8.         public new class UxmlFactory : UxmlFactory<SkiaCanvas>
    9.         {
    10.         }
    11.  
    12.         SKImageInfo info;
    13.         SKSurface surface;
    14.         SKCanvas canvas;
    15.         SKShader shader;
    16.         SKPaint gradientPaint;
    17.         Texture2D texture;
    18.         TextureFormat format;
    19.         ValueAnimation<float> animation;
    20.  
    21.         public SkiaCanvas()
    22.         {
    23.             info = new SKImageInfo(256, 256, SKColorType.RgbaF32);
    24.             surface = SKSurface.Create(info);
    25.             canvas = surface.Canvas;
    26.  
    27.             shader = SKShader.CreateSweepGradient(
    28.                 new SKPoint(info.Rect.MidX, info.Rect.MidY),
    29.                 new[] {SKColors.Gray, SKColors.WhiteSmoke},
    30.                 null
    31.             );
    32.  
    33.             gradientPaint = new SKPaint
    34.             {
    35.                 Shader = shader,
    36.                 StrokeWidth = 30,
    37.                 Style = SKPaintStyle.Stroke,
    38.                 IsAntialias = true
    39.             };
    40.             canvas.DrawCircle(128, 128, 98, gradientPaint);
    41.             texture = new Texture2D(info.Width, info.Height, TextureFormat.RGBAFloat, false, true);
    42.             style.backgroundImage = Background.FromTexture2D(texture);
    43.          
    44.             animation = experimental.animation
    45.                 .Start(0, Mathf.PI * 2, 1000, UpdateTexture)
    46.                 .KeepAlive()
    47.                 .Ease(Easing.Linear)
    48.                 .OnCompleted(() => { animation.Start(); });
    49.         }
    50.  
    51.         void UpdateTexture(VisualElement thisElement, float angle)
    52.         {
    53.             canvas.Clear();
    54.             canvas.ResetMatrix();
    55.             canvas.SetMatrix(SKMatrix.CreateRotation(-angle, 128, 128));
    56.             canvas.DrawCircle(128, 128, 98, gradientPaint);
    57.             var pixmap = surface.PeekPixels();
    58.             texture.LoadRawTextureData(pixmap.GetPixels(), pixmap.RowBytes * pixmap.Height);
    59.             texture.Apply(false, false);
    60.             MarkDirtyRepaint();
    61.         }
    62.     }
    63. }
    Also I uploaded skia libraries to easily drop them in project and execute example.
    In case you are not okay downloading and executing some random dlls from random sources, link for nuget with SkiaSharp - https://www.nuget.org/packages/SkiaSharp/2.80.1.
    My packages:
    Code (JavaScript):
    1. {
    2.   "dependencies": {
    3.     "com.unity.2d.sprite": "1.0.0",
    4.     "com.unity.collab-proxy": "1.2.16",
    5.     "com.unity.ide.rider": "1.1.4",
    6.     "com.unity.ide.vscode": "1.1.4",
    7.     "com.unity.test-framework": "1.1.9",
    8.     "com.unity.textmeshpro": "2.0.1",
    9.     "com.unity.timeline": "1.2.10",
    10.     "com.unity.ugui": "1.0.0",
    11.     "com.unity.ui.builder": "1.0.0-preview.3",
    12.     "com.unity.ui.runtime": "0.0.4-preview",
    13.     "com.unity.modules.ai": "1.0.0",
    14.     "com.unity.modules.androidjni": "1.0.0",
    15.     "com.unity.modules.animation": "1.0.0",
    16.     "com.unity.modules.assetbundle": "1.0.0",
    17.     "com.unity.modules.audio": "1.0.0",
    18.     "com.unity.modules.cloth": "1.0.0",
    19.     "com.unity.modules.director": "1.0.0",
    20.     "com.unity.modules.imageconversion": "1.0.0",
    21.     "com.unity.modules.imgui": "1.0.0",
    22.     "com.unity.modules.jsonserialize": "1.0.0",
    23.     "com.unity.modules.particlesystem": "1.0.0",
    24.     "com.unity.modules.physics": "1.0.0",
    25.     "com.unity.modules.physics2d": "1.0.0",
    26.     "com.unity.modules.screencapture": "1.0.0",
    27.     "com.unity.modules.terrain": "1.0.0",
    28.     "com.unity.modules.terrainphysics": "1.0.0",
    29.     "com.unity.modules.tilemap": "1.0.0",
    30.     "com.unity.modules.ui": "1.0.0",
    31.     "com.unity.modules.uielements": "1.0.0",
    32.     "com.unity.modules.umbra": "1.0.0",
    33.     "com.unity.modules.unityanalytics": "1.0.0",
    34.     "com.unity.modules.unitywebrequest": "1.0.0",
    35.     "com.unity.modules.unitywebrequestassetbundle": "1.0.0",
    36.     "com.unity.modules.unitywebrequestaudio": "1.0.0",
    37.     "com.unity.modules.unitywebrequesttexture": "1.0.0",
    38.     "com.unity.modules.unitywebrequestwww": "1.0.0",
    39.     "com.unity.modules.vehicles": "1.0.0",
    40.     "com.unity.modules.video": "1.0.0",
    41.     "com.unity.modules.vr": "1.0.0",
    42.     "com.unity.modules.wind": "1.0.0",
    43.     "com.unity.modules.xr": "1.0.0"
    44.   }
    45. }
    46.  
     

    Attached Files:

  4. SimonDufour

    SimonDufour

    Unity Technologies

    Joined:
    Jun 30, 2020
    Posts:
    515
    Hi,

    From my experience, theses "outline" artifact happens when the background color (even if the alpha is 0) is mixed with the side of the foreground during interpolation.

    You can try to fill the background with a transparent red to see if the outline changes color.

    Once confirmed, there are two ways to handle the issue:
    • You need to overdraw the color so that it fill a few pixels were the alpha =0
    • Or you make the image rendering pixel perfect, so guarantee that there is no interpolation.
    The second one is usually harder to achieve, especially considering the different resolutions, scale factor and aspect ratio that the clients can have.
     
  5. echekanskiy

    echekanskiy

    Joined:
    Aug 16, 2020
    Posts:
    16
    Hi. Thanks for reply.

    So, looks like that is really and issue with interpolation. When I am trying to change background color, that outline changes color as well.

    Disabling AA in Skia generates pixel-perfect image, obviously with pixel stairs, but no outline anymore.

    And I don't clearly understand what is the "You need to overdraw the color so that it fill a few pixels were the alpha =0". Can you please explain a little bit more?
     
  6. SimonDufour

    SimonDufour

    Unity Technologies

    Joined:
    Jun 30, 2020
    Posts:
    515
    Great, at least we know where the issue is coming from. Basically, you don't want an abrupt transition in alpha values at the same place as a transition in the color.

    An example: (image take from and unrelated thread of stack overflow)


    Because of the interpolation, color on the edge are blended. If one is (0, 0, 0, 1) and the other is (1,1,1,0), you may obtain (0.5, 0.5, 0.5, 0,5) or (0.25, 0.25, 0.25, 0.75) or a proportionally coherent value base on where the interpolation exactly occurs on the graphic card. Because of this, the white should have never been shown without interpolation, but you can see a bit of gray on the edge resulting from the blending.
     
    Last edited: Aug 18, 2020
    echekanskiy likes this.
  7. SimonDufour

    SimonDufour

    Unity Technologies

    Joined:
    Jun 30, 2020
    Posts:
    515
    You can possibly draw the circle with a first SKPaint having a slightly larger brush and the color set to the same RGB, but a value of 0 alpha.

    I personally never used the library, there is probably a way to achieve the same effect with a single SKPaint.
     
    echekanskiy likes this.
  8. echekanskiy

    echekanskiy

    Joined:
    Aug 16, 2020
    Posts:
    16
    Thanks a lot for reply. Will try your advices.
    Also, its interesting that if I am saving image as PNG in memory and loading it to texture - there is no outline anymore:
    Code (CSharp):
    1.             texture.LoadImage(surface.Snapshot().Encode(SKEncodedImageFormat.Png, 100).ToArray());
    Not sure what magic happens behind.
     
    SimonDufour likes this.
  9. echekanskiy

    echekanskiy

    Joined:
    Aug 16, 2020
    Posts:
    16
    Hey, so digging with this alpha problem, I found one simple and fast solution - added SKAlphaType.Unpremul.
    Code (CSharp):
    1. info = new SKImageInfo(256, 256, SKColorType.Rgba8888, SKAlphaType.Unpremul);
    Now alpha channel processed correctly and I have perfect shapes.

    Thanks for help.
     
    SimonDufour likes this.