使用Texture2D创建圆角矩形

3
我正在尝试绘制圆角矩形,可以用作任何UI组件的纹理。我的目标是通过使用Texture2D类并使用SetPixels32函数设置像素来创建这个圆角纹理。我不想使用着色器来完成这个任务。
这篇文章使用了XNA而不是Unity来实现这一点。我把它移植到了Unity中,但边缘很锯齿。
下面是在Unity中的效果:

enter image description here

以下是移植后的代码:
public int width = 256;
public int height = 140;
public int borderThickness = 1;  //Cannot be < 1
//Border shadow cannot be more than Border Radius
public int borderRadius = 40; //Cannot be < 1
public int borderShadow = 2;
public List<Color32> backgroundColors = new List<Color32>();
public List<Color32> borderColors = new List<Color32>();
public float initialShadowIntensity = 5f;
public float finalShadowIntensity = 5f;


private Texture2D resultTex;
public RawImage display;

void Start()
{
    backgroundColors.Add(new Color32(171, 0, 0, 255));
    backgroundColors.Add(new Color32(9, 48, 173, 255));

    borderColors.Add(new Color32(111, 8, 99, 255));
    borderColors.Add(new Color32(171, 4, 161, 255));

    resultTex = RectangleCreator.
        CreateRoundedRectangleTexture(width, height, borderThickness,
        borderRadius, borderShadow, backgroundColors, borderColors,
        initialShadowIntensity, finalShadowIntensity);

    display.texture = resultTex;
    display.SetNativeSize();
}



public class RectangleCreator
{
    public static Texture2D CreateRoundedRectangleTexture(int width, int height, int borderThickness, int borderRadius, int borderShadow, List<Color32> backgroundColors, List<Color32> borderColors, float initialShadowIntensity, float finalShadowIntensity)
    {
        if (backgroundColors == null || backgroundColors.Count == 0) throw new ArgumentException("Must define at least one background color (up to four).");
        if (borderColors == null || borderColors.Count == 0) throw new ArgumentException("Must define at least one border color (up to three).");
        if (borderRadius < 1) throw new ArgumentException("Must define a border radius (rounds off edges).");
        if (borderThickness < 1) throw new ArgumentException("Must define border thikness.");
        if (borderThickness + borderRadius > height / 2 || borderThickness + borderRadius > width / 2) throw new ArgumentException("Border will be too thick and/or rounded to fit on the texture.");
        if (borderShadow > borderRadius) throw new ArgumentException("Border shadow must be lesser in magnitude than the border radius (suggeted: shadow <= 0.25 * radius).");

        Texture2D texture = new Texture2D(width, height, TextureFormat.ARGB32, false);
        Color32[] color = new Color32[width * height];

        for (int x = 0; x < texture.width; x++)
        {
            for (int y = 0; y < texture.height; y++)
            {
                switch (backgroundColors.Count)
                {
                    case 4:
                        Color32 leftColor0 = Color32.Lerp(backgroundColors[0], backgroundColors[1], ((float)y / (width - 1)));
                        Color32 rightColor0 = Color32.Lerp(backgroundColors[2], backgroundColors[3], ((float)y / (height - 1)));
                        color[x + width * y] = Color32.Lerp(leftColor0, rightColor0, ((float)x / (width - 1)));
                        break;
                    case 3:
                        Color32 leftColor1 = Color32.Lerp(backgroundColors[0], backgroundColors[1], ((float)y / (width - 1)));
                        Color32 rightColor1 = Color32.Lerp(backgroundColors[1], backgroundColors[2], ((float)y / (height - 1)));
                        color[x + width * y] = Color32.Lerp(leftColor1, rightColor1, ((float)x / (width - 1)));
                        break;
                    case 2:
                        color[x + width * y] = Color32.Lerp(backgroundColors[0], backgroundColors[1], ((float)x / (width - 1)));
                        break;
                    default:
                        color[x + width * y] = backgroundColors[0];
                        break;
                }

                color[x + width * y] = ColorBorder(x, y, width, height, borderThickness, borderRadius, borderShadow, color[x + width * y], borderColors, initialShadowIntensity, finalShadowIntensity);
            }
        }

        texture.SetPixels32(color);
        texture.Apply();
        return texture;
    }

    private static Color32 ColorBorder(int x, int y, int width, int height, int borderThickness, int borderRadius, int borderShadow, Color32 initialColor, List<Color32> borderColors, float initialShadowIntensity, float finalShadowIntensity)
    {
        Rect internalRectangle = new Rect((borderThickness + borderRadius), (borderThickness + borderRadius), width - 2 * (borderThickness + borderRadius), height - 2 * (borderThickness + borderRadius));


        Vector2 point = new Vector2(x, y);
        if (internalRectangle.Contains(point)) return initialColor;

        Vector2 origin = Vector2.zero;

        if (x < borderThickness + borderRadius)
        {
            if (y < borderRadius + borderThickness)
                origin = new Vector2(borderRadius + borderThickness, borderRadius + borderThickness);
            else if (y > height - (borderRadius + borderThickness))
                origin = new Vector2(borderRadius + borderThickness, height - (borderRadius + borderThickness));
            else
                origin = new Vector2(borderRadius + borderThickness, y);
        }
        else if (x > width - (borderRadius + borderThickness))
        {
            if (y < borderRadius + borderThickness)
                origin = new Vector2(width - (borderRadius + borderThickness), borderRadius + borderThickness);
            else if (y > height - (borderRadius + borderThickness))
                origin = new Vector2(width - (borderRadius + borderThickness), height - (borderRadius + borderThickness));
            else
                origin = new Vector2(width - (borderRadius + borderThickness), y);
        }
        else
        {
            if (y < borderRadius + borderThickness)
                origin = new Vector2(x, borderRadius + borderThickness);
            else if (y > height - (borderRadius + borderThickness))
                origin = new Vector2(x, height - (borderRadius + borderThickness));
        }

        if (!origin.Equals(Vector2.zero))
        {
            float distance = Vector2.Distance(point, origin);

            if (distance > borderRadius + borderThickness + 1)
            {
                return Color.clear;
            }
            else if (distance > borderRadius + 1)
            {
                if (borderColors.Count > 2)
                {
                    float modNum = distance - borderRadius;

                    if (modNum < borderThickness / 2)
                    {
                        return Color32.Lerp(borderColors[2], borderColors[1], (float)((modNum) / (borderThickness / 2.0)));
                    }
                    else
                    {
                        return Color32.Lerp(borderColors[1], borderColors[0], (float)((modNum - (borderThickness / 2.0)) / (borderThickness / 2.0)));
                    }
                }


                if (borderColors.Count > 0)
                    return borderColors[0];
            }
            else if (distance > borderRadius - borderShadow + 1)
            {
                float mod = (distance - (borderRadius - borderShadow)) / borderShadow;
                float shadowDiff = initialShadowIntensity - finalShadowIntensity;
                return DarkenColor(initialColor, ((shadowDiff * mod) + finalShadowIntensity));
            }
        }

        return initialColor;
    }

    private static Color32 DarkenColor(Color32 color, float shadowIntensity)
    {
        return Color32.Lerp(color, Color.black, shadowIntensity);
    }
}

我尝试通过替换来修复黑色锯齿边缘。
return Color32.Lerp(color, Color.black, shadowIntensity);

使用

return Color32.Lerp(color, Color.clear, shadowIntensity);

但这并没有用透明颜色使其平滑。它去掉了黑色,但让它更加锯齿状。

这是它的样子:

enter image description here

如何使纹理边缘更平滑?


1
Texture2D的FilterMode是什么?你可以将其设置为“point”再试一次吗,或者你已经尝试过了吗? - Cenkisabi
如果你想要深入研究,你可以实现这个着色器:https://steamcdn-a.akamaihd.net/apps/valve/2007/SIGGRAPH2007_AlphaTestedMagnification.pdf - Leo Bartkus
1
啊,我会尝试将分辨率加倍,并将材质设置为切割模式进行渲染。你也可能遇到了半像素问题或者原始代码库的解决方法。http://drilian.com/2008/11/25/understanding-half-pixel-and-half-texel-offsets/ - Leo Bartkus
@LeoBartkus 我会尝试将分辨率加倍。如果你有答案,你可以回答,因为我计划今天悬赏解决这个问题。加倍分辨率似乎是解决这个问题的方法。 - Programmer
我刚试了一下你的代码,但是没有看到问题。这种行为附加在什么样的物体上?我有一种感觉,这个问题可能与你的相机或视口设置有关。我也没有看到你截图中边缘周围的锯齿状伪影。 - Leo Bartkus
显示剩余3条评论
1个回答

2
当我将这段代码放到UICanvas上的RawImage中时,它看起来很好。这是使用Game Object -> UI -> Raw Image创建默认设置。
我只需将组件附加到rawimage并将Display属性设置为自身即可。

My hierarchy

My Results

Zoomed with pixel grid

如果你想让Unity进行一些反锯齿处理,你可以增加原始图像的分辨率并删除对display.SetNativeSize();的调用。但是这样你需要使用其变换组件来设置矩形的实际大小。
   using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class NewBehaviourScript : MonoBehaviour {


public int width = 256;
public int height = 140;
public int borderThickness = 1;  //Cannot be < 1
//Border shadow cannot be more than Border Radius
public int borderRadius = 40; //Cannot be < 1
public int borderShadow = 2;
public List<Color32> backgroundColors = new List<Color32>();
public List<Color32> borderColors = new List<Color32>();
public float initialShadowIntensity = 5f;
public float finalShadowIntensity = 5f;

public float resolutionmultiplier = 4f;


private Texture2D resultTex;
public RawImage display;

public RectTransform rt;

void Start()
{
    backgroundColors.Add(new Color32(171, 0, 0, 255));
    backgroundColors.Add(new Color32(9, 48, 173, 255));

    borderColors.Add(new Color32(111, 8, 99, 255));
    borderColors.Add(new Color32(171, 4, 161, 255));

    resultTex = RectangleCreator.
        CreateRoundedRectangleTexture(4, width, height, borderThickness,
        borderRadius, borderShadow, backgroundColors, borderColors,
        initialShadowIntensity, finalShadowIntensity);

    display.texture = resultTex;
    rt.sizeDelta = new Vector2(width, height);
}



public class RectangleCreator
{
    public static Texture2D CreateRoundedRectangleTexture(int resolutionmultiplier, int width, int height, int borderThickness, int borderRadius, int borderShadow, List<Color32> backgroundColors, List<Color32> borderColors, float initialShadowIntensity, float finalShadowIntensity)
    {
       // if (backgroundColors == null || backgroundColors.Count == 0) throw new ArgumentException("Must define at least one background color (up to four).");
      //  if (borderColors == null || borderColors.Count == 0) throw new ArgumentException("Must define at least one border color (up to three).");
      //  if (borderRadius < 1) throw new ArgumentException("Must define a border radius (rounds off edges).");
      //  if (borderThickness < 1) throw new ArgumentException("Must define border thikness.");
     //   if (borderThickness + borderRadius > height / 2 || borderThickness + borderRadius > width / 2) throw new ArgumentException("Border will be too thick and/or rounded to fit on the texture.");
     //   if (borderShadow > borderRadius) throw new ArgumentException("Border shadow must be lesser in magnitude than the border radius (suggeted: shadow <= 0.25 * radius).");

        width = width * resolutionmultiplier;
        height = height * resolutionmultiplier;

        Texture2D texture = new Texture2D(width, height, TextureFormat.ARGB32, false);
        Color32[] color = new Color32[width * height];

        for (int x = 0; x < texture.width; x++)
        {
            for (int y = 0; y < texture.height; y++)
            {
                switch (backgroundColors.Count)
                {
                    case 4:
                        Color32 leftColor0 = Color32.Lerp(backgroundColors[0], backgroundColors[1], ((float)y / (width - 1)));
                        Color32 rightColor0 = Color32.Lerp(backgroundColors[2], backgroundColors[3], ((float)y / (height - 1)));
                        color[x + width * y] = Color32.Lerp(leftColor0, rightColor0, ((float)x / (width - 1)));
                        break;
                    case 3:
                        Color32 leftColor1 = Color32.Lerp(backgroundColors[0], backgroundColors[1], ((float)y / (width - 1)));
                        Color32 rightColor1 = Color32.Lerp(backgroundColors[1], backgroundColors[2], ((float)y / (height - 1)));
                        color[x + width * y] = Color32.Lerp(leftColor1, rightColor1, ((float)x / (width - 1)));
                        break;
                    case 2:
                        color[x + width * y] = Color32.Lerp(backgroundColors[0], backgroundColors[1], ((float)x / (width - 1)));
                        break;
                    default:
                        color[x + width * y] = backgroundColors[0];
                        break;
                }

                color[x + width * y] = ColorBorder(x, y, width, height, borderThickness, borderRadius, borderShadow, color[x + width * y], borderColors, initialShadowIntensity, finalShadowIntensity);
            }
        }

        texture.SetPixels32(color);
        texture.Apply();
        return texture;
    }

    private static Color32 ColorBorder(int x, int y, int width, int height, int borderThickness, int borderRadius, int borderShadow, Color32 initialColor, List<Color32> borderColors, float initialShadowIntensity, float finalShadowIntensity)
    {
        Rect internalRectangle = new Rect((borderThickness + borderRadius), (borderThickness + borderRadius), width - 2 * (borderThickness + borderRadius), height - 2 * (borderThickness + borderRadius));


        Vector2 point = new Vector2(x, y);
        if (internalRectangle.Contains(point)) return initialColor;

        Vector2 origin = Vector2.zero;

        if (x < borderThickness + borderRadius)
        {
            if (y < borderRadius + borderThickness)
                origin = new Vector2(borderRadius + borderThickness, borderRadius + borderThickness);
            else if (y > height - (borderRadius + borderThickness))
                origin = new Vector2(borderRadius + borderThickness, height - (borderRadius + borderThickness));
            else
                origin = new Vector2(borderRadius + borderThickness, y);
        }
        else if (x > width - (borderRadius + borderThickness))
        {
            if (y < borderRadius + borderThickness)
                origin = new Vector2(width - (borderRadius + borderThickness), borderRadius + borderThickness);
            else if (y > height - (borderRadius + borderThickness))
                origin = new Vector2(width - (borderRadius + borderThickness), height - (borderRadius + borderThickness));
            else
                origin = new Vector2(width - (borderRadius + borderThickness), y);
        }
        else
        {
            if (y < borderRadius + borderThickness)
                origin = new Vector2(x, borderRadius + borderThickness);
            else if (y > height - (borderRadius + borderThickness))
                origin = new Vector2(x, height - (borderRadius + borderThickness));
        }

        if (!origin.Equals(Vector2.zero))
        {
            float distance = Vector2.Distance(point, origin);

            if (distance > borderRadius + borderThickness + 1)
            {
                return Color.clear;
            }
            else if (distance > borderRadius + 1)
            {
                if (borderColors.Count > 2)
                {
                    float modNum = distance - borderRadius;

                    if (modNum < borderThickness / 2)
                    {
                        return Color32.Lerp(borderColors[2], borderColors[1], (float)((modNum) / (borderThickness / 2.0)));
                    }
                    else
                    {
                        return Color32.Lerp(borderColors[1], borderColors[0], (float)((modNum - (borderThickness / 2.0)) / (borderThickness / 2.0)));
                    }
                }


                if (borderColors.Count > 0)
                    return borderColors[0];
            }
            else if (distance > borderRadius - borderShadow + 1)
            {
                float mod = (distance - (borderRadius - borderShadow)) / borderShadow;
                float shadowDiff = initialShadowIntensity - finalShadowIntensity;
                return DarkenColor(initialColor, ((shadowDiff * mod) + finalShadowIntensity));
            }
        }

        return initialColor;
    }

    private static Color32 DarkenColor(Color32 color, float shadowIntensity)
    {
        return Color32.Lerp(color, Color.black, shadowIntensity);
    }
}
}

Anti aliased version

编辑:
将纹理分辨率的宽度和高度增加到约2048和624,增加borderRadius到300。最后确保在游戏视图中未启用“低分辨率宽高比”。这会导致图像模糊。

enter image description here


我在第一张屏幕截图中得到了带有圆角矩形边缘的内容。 - Programmer
你的代码在屏幕空间中精确地排列像素,就像在XNA中一样。 - Leo Bartkus
等一下,你在游戏视图选项卡中的视口缩放比例设置为1倍了吗? - Leo Bartkus
不是的。在发布这篇文章时,那不是一个问题。我会在回到电脑时进行您建议的更改。 - Programmer
我简直不敢相信我在这上面浪费了这么多时间。在Unity中有一个名为“低分辨率纵横比”的新复选框,默认情况下启用。我只是禁用了它,在按照你的建议增加纹理大小后,它就正常工作了。我还必须将 borderRadius 更改为约 300,因为我改变了分辨率。现在很平滑。谢谢。 - Programmer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接