Unity中在纹理上绘制图形

Unity中在纹理上绘制图形

支持绘制图形:

  • 圆形(实心-空心)
  • 方形(实心-空心)
  • 直线/虚线
  • 纹理(贴花)
  • 文字

使用如下:

创建3D Plane 并添加 Test.cs脚本

using UnityEngine;

public class Test : MonoBehaviour
{
    public Texture2D decal;
    public Font font;
    Texture2D wallTexture;
    // 定义一个图片副本
    Texture2D DrawTexture;
    RaycastHit hit;
    private void Start()
    {
        // 获取墙贴图
        wallTexture = GetComponent<MeshRenderer>().material.mainTexture as Texture2D;
        // 备份一个墙的图片
        DrawTexture = Instantiate(wallTexture) as Texture2D;
        // 将备份赋值回去,做为修改对象
        GetComponent<MeshRenderer>().material.mainTexture = DrawTexture;
    }
    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            if (Physics.Raycast(ray, out hit))
            {
                if (hit.collider.name == "Plane")
                {
                    Vector2 hitPos = hit.textureCoord;
                    Vector2Int pos = new Vector2Int((int)(hitPos.x * DrawTexture.width), (int)(hitPos.y * DrawTexture.height));
                    // 绘制填充圆
                    //DecalManager.DrawCircleFill(DrawTexture, pos, 50, Color.red);
                    // 绘制圆环
                    DecalManager.DrawCircle(DrawTexture, pos, 50, 3, Color.blue);
                    // 绘制填充矩形
                    //DecalManager.DrawRectangleFill(DrawTexture, pos + Vector2Int.one * -50, pos + Vector2Int.one * 50, Color.cyan);
                    // 绘制矩形
                    DecalManager.DrawRectangle(DrawTexture, pos + Vector2Int.one * -50, pos + Vector2Int.one * 50, 3, Color.cyan);
                    // 绘制直线
                    DecalManager.DrawLine(DrawTexture, pos + Vector2Int.one * -50, pos + Vector2Int.one * 50, 3, Color.cyan);
                    // 绘制纹理(贴花)
                    DecalManager.DrawTex(DrawTexture, decal, pos);
                    // 绘制文字
                    DecalManager.DrawText(DrawTexture, pos, "HelloWorld", font, Color.yellow);
                }
            }
        }
    }
}

绘图插件代码:

/*
 * 在纹理上绘制图形 
 */
using UnityEngine;
public class DecalManager
{
    #region 圆
    /// <summary>
    /// 在纹理上绘制一个实行圆
    /// </summary>
    /// <param name="texture">要绘制的纹理</param>
    /// <param name="Center">圆点纹理像素坐标</param>
    /// <param name="r">圆像素半径</param>
    /// <param name="color">颜色</param>
    public static void DrawCircleFill(Texture2D texture, Vector2Int Center, int r, Color color)
    {
        for (int x = Center.x - r; x <= Center.x + r; x++)
        {
            for (int y = Center.y - r; y <= Center.y + r; y++)
            {
                if (InCircle(x, y, Center, r))
                {
                    texture.SetPixel(x, y, color);
                }
            }
        }
        texture.Apply();
    }
    /// <summary>
    /// 在纹理上绘制一个圆不带填充
    /// </summary>
    /// <param name="texture">要绘制的纹理</param>
    /// <param name="Center">圆点纹理像素坐标</param>
    /// <param name="r">圆半径纹理像素</param>
    /// <param name="border">线宽度</param>
    /// <param name="color">绘制颜色</param>
    public static void DrawCircle(Texture2D texture, Vector2Int Center, int r, int border, Color color)
    {
        for (int x = Center.x - r; x <= Center.x + r; x++)
        {
            for (int y = Center.y - r; y <= Center.y + r; y++)
            {
                if (InCircle(x, y, Center, r) && !InCircle(x, y, Center, r - border))
                {
                    texture.SetPixel(x, y, color);
                }
            }
        }
        texture.Apply();
    }
    #endregion
    #region 方形
    /// <summary>
    /// 在纹理上绘制一个实心方形
    /// </summary>
    /// <param name="texture">要绘制的纹理</param>
    /// <param name="ld">方形左下角坐标纹理坐标</param>
    /// <param name="ru">方形方向右上角纹理坐标</param>
    /// <param name="color">填充颜色</param>
    public static void DrawRectangleFill(Texture2D texture, Vector2Int ld, Vector2Int ru, Color color)
    {
        for (int x = ld.x; x <= ru.x; x++)
        {
            for (int y = ld.y; y <= ru.y; y++)
            {
                if (InRectangle(x, y, ld, ru))
                {
                    texture.SetPixel(x, y, color);
                }
            }
        }
        texture.Apply();
    }
    /// <summary>
    /// 在纹理上绘制一个没有填充的方形
    /// </summary>
    /// <param name="texture">要绘制的纹理</param>
    /// <param name="ld">左下角纹理坐标</param>
    /// <param name="ru">右上角纹理坐标</param>
    /// <param name="border">方形边界宽度</param>
    /// <param name="color">绘制的颜色</param>
    public static void DrawRectangle(Texture2D texture, Vector2Int ld, Vector2Int ru, int border, Color color)
    {
        for (int x = ld.x; x <= ru.x; x++)
        {
            for (int y = ld.y; y <= ru.y; y++)
            {
                if (InRectangle(x, y, ld, ru) && !InRectangle(x, y, ld + Vector2Int.one * border, ru - Vector2Int.one * border))
                {
                    texture.SetPixel(x, y, color);
                }
            }
        }
        texture.Apply();
    }
    #endregion
    #region 线
    /// <summary>
    /// 在纹理上绘制一条直线
    /// </summary>
    /// <param name="texture">要被绘制的纹理</param>
    /// <param name="start">起点纹理坐标</param>
    /// <param name="end">终点纹理坐标</param>
    /// <param name="border">线宽度</param>
    /// <param name="color">绘制的颜色</param>
    public static void DrawLine(Texture2D texture, Vector2Int start, Vector2Int end, int border, Color color)
    {
        Vector2Int vector = Vector2Int.zero;
        border /= 2;
        for (int x = start.x; x <= end.x; x++)
        {
            for (int y = start.y; y <= end.y; y++)
            {
                vector.x = x;
                vector.y = y;
                if (InLine(vector, start, end, border))
                {
                    texture.SetPixel(x, y, color);
                }
            }
        }
        texture.Apply();
    }
    /// <summary>
    /// 在纹理上绘制一条虚线
    /// </summary>
    /// <param name="texture">纹理画布</param>
    /// <param name="start">起点</param>
    /// <param name="end">终点</param>
    /// <param name="radius">线宽度</param>
    /// <param name="color">线颜色</param>
    /// <param name="gap">线间距</param>
    public static void DrawDottedLine(Texture2D texture, Vector2Int start, Vector2Int end, int radius, Color color, int gap = 2)
    {
        Vector2 center = start;
        while (center != end)
        {
            center = Vector2.MoveTowards(center, end, radius * 2 + gap);
            for (int x = (int)(center.x - radius); x <= center.x + radius; x++)
            {
                for (int y = (int)(center.y - radius); y <= center.y + radius; y++)
                {
                    if (InCircle(x, y, new Vector2Int((int)center.x, (int)center.y), radius))
                    {
                        texture.SetPixel(x, y, color);
                    }
                }
            }
        }
        texture.Apply();
    }
    #endregion
    #region 贴花
    /// <summary>
    /// 在纹理上填充一个纹理
    /// </summary>
    /// <param name="texture">要处理的纹理</param>
    /// <param name="source">贴花纹理</param>
    /// <param name="Center">贴花位置</param>
    public static void DrawTex(Texture2D texture, Texture2D source, Vector2Int Center)
    {
        for (int i = 0; i < source.width; i++)
        {
            for (int j = 0; j < source.height; j++)
            {
                // 得到texture对应source应该出现的每个像素点
                int x = Center.x - source.width / 2 + i;
                int y = Center.y - source.height / 2 + j;

                // 获取texture每个像素点的颜色
                Color texColor = texture.GetPixel(x, y);
                // 获取source的对应每个像素点的颜色
                Color sourceColor = source.GetPixel(i, j);
                // 融合
                if (sourceColor.a > 0.1f)
                {
                    //Color r = texColor * sourceColor;
                    texture.SetPixel(x, y, sourceColor);
                }
            }
        }
        //将修改融合的图片应用
        texture.Apply();
    }
    #endregion
    #region 文字
    /// <summary>
    /// 在纹理上绘制文字
    /// </summary>
    /// <param name="texture">要被处理的纹理</param>
    /// <param name="Center">绘制的起点(文字将从左->右,上->下方渲染)</param>
    /// <param name="text">要绘制的文字</param>
    /// <param name="font">文字使用的字体(动态字体)</param>
    /// <param name="color">文字使用的颜色</param>
    /// <param name="fontSize">文字尺寸</param>
    /// <param name="style">文字使用的样式</param>
    public static void DrawText(Texture2D texture, Vector2Int Center, string text, Font font, Color color, int fontSize = 24, FontStyle style = FontStyle.Normal)
    {
        Texture2D source = TextToTexture(font, fontSize, style, text, color, new Color(0, 0, 0, 0));
        DrawTex(texture, source, Center);
    }
    #endregion
    #region Utility
    /// <summary>
    /// 纹理像素点是否在纹理坐标系指定圆内
    /// </summary>
    /// <param name="pixelCoordinate">像素坐标</param>
    /// <param name="circleCenter">像素圆中心</param>
    /// <param name="r">像素圆半径</param>
    /// <returns></returns>
    private static bool InCircle(int x, int y, Vector2Int circleCenter, int r)
    {
        return Mathf.Pow(x - circleCenter.x, 2) + Mathf.Pow(y - circleCenter.y, 2) < Mathf.Pow(r, 2);
    }
    /// <summary>
    /// 纹理像素是否在纹理方形内
    /// </summary>
    /// <param name="x">x坐标</param>
    /// <param name="y">y坐标</param>
    /// <param name="ld">左下角坐标</param>
    /// <param name="ru">右上角左边</param>
    /// <returns></returns>
    private static bool InRectangle(int x, int y, Vector2Int ld, Vector2Int ru)
    {
        if (x >= ld.x && x <= ru.x && y >= ld.y && y <= ru.y)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
    private static bool InLine(Vector2Int target, Vector2Int start, Vector2Int end, int border)
    {
        float length = Vector2.Distance(target, start);
        var angle = Vector2.Angle(target - start, end - start) * Mathf.Deg2Rad;
        float value = Mathf.Abs(Mathf.Sin(angle) * length);
        if (value < border)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
    /// <summary>
    /// 将文本转纹理
    /// </summary>
    /// <param name="font">文本使用的字体</param>
    /// <param name="fontSize">文本大小</param>
    /// <param name="fontStyle">文本样式</param>
    /// <param name="text">要绘制的纹理</param>
    /// <param name="textColor">纹理颜色</param>
    /// <returns></returns>
    private static Texture2D TextToTexture(Font font, int fontSize, FontStyle fontStyle, string text, Color textColor, Color bgColor)
    {
        // 字体贴图不可读,需要创建一个新的可读的
        font.RequestCharactersInTexture(text, fontSize, fontStyle);
        var fontTexture = (Texture2D)font.material.mainTexture;
        var readableFontTexture = new Texture2D(fontTexture.width, fontTexture.height, fontTexture.format, fontTexture.mipmapCount, true);
        Graphics.CopyTexture(fontTexture, readableFontTexture);

        // 文字信息
        int spaceGap = 10;            // 空格
        int widthGap = 1;             // 字间间隔偏移量
        int heightGap = 1;            // 行间间隔偏移量

        int texWidth = 0;
        int texHeight = 0;
        int widthTemp = 0;
        int heightTemp = 0;
        int lineCount = 1;          // 行数量
        CharacterInfo ci;
        foreach (var @char in text)
        {
            var b = font.GetCharacterInfo(@char, out ci, fontSize, fontStyle);
            //print(@char + ":" + b + ":" + ci.glyphWidth + ":" + ci.glyphHeight);
            heightTemp = heightTemp > ci.glyphHeight ? heightTemp : ci.glyphHeight;
            if (@char == ' ')
            {
                widthTemp += spaceGap;
            }
            else if (@char == '\n')
            {
                // 换行
                texWidth = texWidth > widthTemp ? texWidth : widthTemp;
                texHeight += heightTemp + heightGap;
                widthTemp = 0;
                heightTemp = 0;
                lineCount++;
            }
            else
            {
                widthTemp += ci.glyphWidth + widthGap;
            }
        }
        texWidth = texWidth > widthTemp ? texWidth : widthTemp;
        texHeight = texHeight > heightTemp ? texHeight + heightTemp : heightTemp;
        // 创建返回的Texture
        var textTexture = new Texture2D(texWidth, texHeight, TextureFormat.ARGB32, true);
        // 背景颜色
        Color[] bgColors = new Color[texWidth * texHeight];
        for (int i = 0; i < bgColors.Length; i++)
        {
            bgColors[i] = bgColor;
        }
        textTexture.SetPixels(bgColors);
        // 调整偏移量
        heightTemp = texHeight / lineCount;
        int drawOffsetX = 0;
        int drawOffsetY = texHeight - heightTemp;

        // // 逐个字符绘制
        foreach (var @char in text.ToCharArray())
        {
            var b = font.GetCharacterInfo(@char, out CharacterInfo info, fontSize, fontStyle);

            if (@char == ' ')
            {
                drawOffsetX += spaceGap;
                continue;
            }

            if (@char == '\n')
            {
                // 换行
                drawOffsetX = 0;
                drawOffsetY -= heightTemp;
                continue;
            }

            int charWidth, charHeight;// 字符宽高
            Color[] charColor;// 字符颜色,数组内颜色的顺序为从左至右,从下至上

            if (info.uvTopLeft.x < info.uvBottomRight.x)// 处理被垂直翻转的字符
            {
                charWidth = info.glyphWidth;
                charHeight = info.glyphHeight;

                charColor = readableFontTexture.GetPixels((int)(readableFontTexture.width * info.uvTopLeft.x), (int)(readableFontTexture.height * info.uvTopLeft.y), charWidth, charHeight);

                for (int j = 0; j < charHeight; j++)
                {
                    for (int i = 0; i < charWidth; i++)
                    {
                        if (charColor[j * charWidth + i].a != 0)
                        {
                            // 从上往下画,把字符颠倒过来
                            textTexture.SetPixel(drawOffsetX + i, drawOffsetY + charHeight - j, textColor);
                        }
                    }
                }
            }
            else// 处理被顺时针旋转90度的字符
            {
                charWidth = info.glyphHeight;
                charHeight = info.glyphWidth;

                charColor = readableFontTexture.GetPixels((int)(readableFontTexture.width * info.uvBottomRight.x), (int)(readableFontTexture.height * info.uvBottomRight.y), charWidth, charHeight);

                for (int j = 0; j < charHeight; j++)
                {
                    for (int i = 0; i < charWidth; i++)
                    {
                        if (charColor[j * charWidth + i].a != 0)
                        {
                            // 旋转
                            textTexture.SetPixel(drawOffsetX + charHeight - j, drawOffsetY + i, textColor);
                        }
                    }
                }
            }
            // 更新偏移
            drawOffsetX += charWidth + widthGap;
        }
        textTexture.Apply();
        return textTexture;
    }
    #endregion
}

运行效果:

纹理中绘制图形

希望可以帮各位伙伴,若有Bug请评论,谢谢

posted @ 2023-03-24 11:41  镜子-眼泪  阅读(531)  评论(1)    收藏  举报