【Unity功能集】TextureShop纹理工坊(二)图层(上)

项目源码:在终章发布

图层

PS中,图层的概念贯穿始终(了解PS图层),他可以称作PS最基础也是最强大的特性之一。

那么,在TextureShop中,我们的第一个任务也即是设计并完成图层的功能。

TextureLayer

首先,定义TextureLayer类,此即为图层类,一个TextureLayer类对象代表一个图层实例:

    /// <summary>
    /// 图层
    /// </summary>
    public sealed class TextureLayer
    {
    
    }

可见性

基于图层的可见性(也即是人眼可见),我们需要每一个图层都是能够独立渲染的,所以图层需要携带一个渲染器,在此我决定使用RawImage来渲染,同时Texture2D对象作为渲染源,Material对象作为渲染材质:

    /// <summary>
    /// 图层
    /// </summary>
    public sealed class TextureLayer
    {
        private Material _material;
        private RawImage _target;
        private Texture2D _texture;
    }

这三者应是图层的内部逻辑对象,不需对外,也不能对外公开,所以他们为private的。

激活性

基于图层的可激活、可隐藏性(也即是可激活、可隐藏图层),我们加入一个属性:

    /// <summary>
    /// 图层
    /// </summary>
    public sealed class TextureLayer
    {
        private Material _material;
        private RawImage _target;
        private Texture2D _texture;
        
        /// <summary>
        /// 是否激活图层
        /// </summary>
        public bool IsShow
        {
            get
            {
                return _target.enabled;
            }
            set
            {
                _target.enabled = value;
            }
        }
    }

可编辑性

基于图层的可编辑性(也即是可编辑颜色、可编辑区域等),我们会发现如下的问题:

当我们的图层尺寸为512x512时,显示一张大于512x512尺寸的图像,显示效果如下:

在这里插入图片描述
超出512x512范围的图像内容(黑色区域)将被裁切!

而实际上,这部分内容不仅要保留,还需要在某些情况下显示出来,比如向右拖动图层时,左侧被黑色区域挡住的内容将显现出来,而中间的原显示内容将移动到右侧黑色区域中不可见。

由此,我们再次提出2个概念。

1.绘画区域,也即是可见的512x512区域被称作绘画区域,只有此区域是可见、可编辑、可导出的。
2.绘画板,也即是黑色区域被称作绘画板,默认2048*2048,存储了整张图像的所有颜色数据。

为此,定义一个用于表示画板的类Plate

    /// <summary>
    /// 绘画板
    /// </summary>
    public sealed class Plate
    {
        private Color[,] _colors;

        /// <summary>
        /// 宽度
        /// </summary>
        public int Width { get; private set; }
        /// <summary>
        /// 高度
        /// </summary>
        public int Height { get; private set; }
    }

二维数组_colors存储了整个画板中的颜色数据。

绘画区域、绘画板

绘画区域、绘画板都将单独用一个Plate(画板)表示,他们是独立存在的:

    /// <summary>
    /// 图层
    /// </summary>
    public sealed class TextureLayer
    {
    	//绘画板
        private Plate _plate;
        //绘画区域
        private Plate _region;
    }

绘画区域、绘画板的关系大概就是如下这样:
在这里插入图片描述

绘画区域锚点

此时,我们的图层在渲染图像时,会将绘画区域中的内容填充到渲染源(Texture2D),再绘制到屏幕上。

绘画区域中的内容,又是根据绘画区域所在的位置和尺寸,到整个绘画板中去提取颜色数据。

那么,如果给绘画区域一个锚点,则移动图层的功能,便可以直接实现为移动绘画区域锚点

    /// <summary>
    /// 图层
    /// </summary>
    public sealed class TextureLayer
    {
    	    /// <summary>
      		/// 绘画区域在绘画板中的偏移值(锚点)
      		/// </summary>
     	    public Vector2Int Offset { get; private set; }
		}

锚点(0,0),则代表左下角,锚点(1,1),则代表右上角。

在这里插入图片描述
在这里插入图片描述

导入图像

图层的绘画板作为其唯一存储颜色数据的目标,我们要实现导入一张外部图片,自动生成一个图层的功能,只需要读取外部图片,并填充到绘画板(_plate)中即可:

    /// <summary>
    /// 绘画板
    /// </summary>
    public sealed class Plate
    {
        private Color[,] _colors;

        /// <summary>
        /// 宽度
        /// </summary>
        public int Width { get; private set; }
        /// <summary>
        /// 高度
        /// </summary>
        public int Height { get; private set; }

        /// <summary>
        /// 绘画板
        /// </summary>
        /// <param name="width">宽度</param>
        /// <param name="height">高度</param>
        /// <param name="texture">导入的图像</param>
        public Plate(int width, int height, Texture2D texture = null)
        {
            _colors = new Color[width, height];
            if (texture != null)
            {
                if (texture.isReadable)
                {
                	//计算图片尺寸与绘画板尺寸的关系,使得图片始终于绘画板中居中
                    int offsetX = (width - texture.width) / 2;
                    if (offsetX < 0) offsetX = 0;
                    int offsetY = (height - texture.height) / 2;
                    if (offsetY < 0) offsetY = 0;

                    for (int x = 0; x < texture.width; x++)
                    {
                        for (int y = 0; y < texture.height; y++)
                        {
                            int pixelX = x + offsetX;
                            int pixelY = y + offsetY;
                            if (pixelX < width && pixelY < height)
                            {
                                _colors[pixelX, pixelY] = texture.GetPixel(x, y);
                            }
                        }
                    }
                }
                else
                {
                    Debug.LogError($"图像 {texture.name} 必须设置为可读写的,请在检视器面板勾选 Read/Write Enabled!");
                }
            }
            Width = width;
            Height = height;
        }
    }

同理的,在图层(TextureLayer)的构造方法中完成这一切:

    /// <summary>
    /// 图层
    /// </summary>
    public sealed class TextureLayer
    {
        private RectTransform _rectTransform;
        private Material _material;
        private RawImage _target;
        private Texture2D _texture;
        private Plate _plate;
        private Plate _region;

        /// <summary>
        /// 图层名称
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// 是否显示图层
        /// </summary>
        public bool IsShow
        {
            get
            {
                return _target.enabled;
            }
            set
            {
                _target.enabled = value;
            }
        }
        /// <summary>
        /// 绘画区域在画板中的偏移值
        /// </summary>
        public Vector2Int Offset { get; private set; }

        /// <summary>
        /// 图层
        /// </summary>
        /// <param name="name">图层名称</param>
        /// <param name="paintAreaWidth">绘画区域宽度</param>
        /// <param name="paintAreaHeight">绘画区域高度</param>
        /// <param name="plateWidth">绘画板宽度</param>
        /// <param name="plateHeight">绘画板高度</param>
        /// <param name="parent">图层所属父级</param>
        /// <param name="texture">导入的图像</param>
        /// <param name="offset">偏移值</param>
        public TextureLayer(string name, int paintAreaWidth, int paintAreaHeight, int plateWidth, int plateHeight, RectTransform parent, Texture2D texture, Vector2Int offset = default)
        {
        	//创建图层的渲染对象
            _rectTransform = Utility.CreateRectTransform(name, parent, true);
            //创建材质
            _material = new Material(Utility.TextureLayerShader);
            _material.hideFlags = HideFlags.HideAndDontSave;
            //创建渲染器
            _target = _rectTransform.gameObject.AddComponent<RawImage>();
            _target.raycastTarget = true;
            _target.material = _material;
            //创建渲染源
            _texture = new Texture2D(paintAreaWidth, paintAreaHeight, TextureFormat.RGBA32, false);
            _texture.SetPixels(new Color[paintAreaWidth * paintAreaHeight]);
            _texture.wrapMode = TextureWrapMode.Clamp;
            _texture.name = Name;
            //创建绘画板、绘画区域
            _plate = new Plate(plateWidth, plateHeight, true, texture);
            _region = new Plate(paintAreaWidth, paintAreaHeight, false);
            //设置渲染源,此处没有采用直接设置为RawImage.texture的方式,因为在渲染Shader中,有一些我们自己的处理
            _material.SetTexture("_PaintTex", _texture);
            //设置初始偏移值(锚点),使得图像居中
            Offset = (offset != default) ? offset : new Vector2Int((_plate.Width - _region.Width) / 2, (_plate.Height - _region.Height) / 2);

            Name = name;
        }
    }

到这里,由于篇幅问题,图层(上)篇的内容暂时告一段落。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

神码编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值