One pixel horizontal divider lines flicker with Unity ScrollRect?

1.8k Views Asked by At

I have some GameObjects inside of a ScrollRect (really inside of a content object which the ScrollRect translates).

When the localPosition of the content object is moved, the child objects move as expected but the horizontal divider present at the bottom of each child object doesn't appear in a constant way. Sometimes the lines appear thicker or thinner and "shimmer" while scrolling.

I'd like the horizontal lines not to visibly change. Any ideas on how to do it?

enter image description here

3

There are 3 best solutions below

0
On

I ran into this question while trying to solve a similar issue myself, so I figured I'd share my solution.

Assuming you want to restrict a ScrollRect to whole pixels, you can do something like this:

public class PixelPerfectScrollRect : ScrollRect
{
    protected override void LateUpdate ()
    {
        base.LateUpdate();
        ensurePixelPerfectScroll();
    }

    void ensurePixelPerfectScroll ()
    {
        float diff = content.rect.height - viewport.rect.height;
        float normalizedPixel = 1 / diff;
        verticalNormalizedPosition = Mathf.Ceil(verticalNormalizedPosition / normalizedPixel) * normalizedPixel;
        // can also do the same for horizontalNormalizedPosition, using rect.width instead of rect.height
    }
}

Attach this to a GameObject and use it the same way you'd use a ScrollRect. This also assumes that every UI unit is one pixel; you'll have to scale normalizedPixel if that isn't the case for your game.

This works because ScrollRects can move their content RectTransforms up to diff pixels away from their local-space origin. If ScrollRects used pixel values we could just round those to ints, but since ScrollRects deal in normalized positions, we need to normalize our desired pixel values. If, in "pixel space," you move from 0 to diff in increments of 1, in "normalized space" you need to move from 0 to 1 in increments of 1/diff. (You're dividing the entire space by diff.) Therefore we need to make sure our normalized value is rounded to the nearest multiple of 1/diff.

1
On

I faced the same issue. So I tried to normalize content position relative to the screen. I convert world position to the screen position, round it to snap to pixels positions and convert it back to world position. It updates content position every frame

public class PixelPerfectScrollRect : ScrollRect
{
    public Camera Camera; // or use Camera.main


    protected override void LateUpdate()
    {
        base.LateUpdate();
        EnsurePixelPerfectScroll();
    }

    void EnsurePixelPerfectScroll ()
    {
        if (!Camera || !content)
        {
            return;
        }

        var pos = content.position;
        var screenPoint = Camera.WorldToScreenPoint(pos);

        screenPoint.y = Mathf.Round(screenPoint.y);
        screenPoint.x = Mathf.Round(screenPoint.x);
        pos = Camera.ScreenToWorldPoint(screenPoint);
        content.position = pos;
    }
}
1
On

I believe you can you use the Canvas.pixelPerfect property to force UI elements to adhere to pixelperfect standards.