【Unity功能集】TextureShop纹理工坊(五)选区

项目源码:在终章发布

索引

选区

选区,也既是在当前选中图层中,已选择的编辑区域,我们后续的所有图像编辑操作,都将针对此选中的区域(了解PS选区)。

此功能是TextureShop的核心功能,毕竟我们后续的所有操作都将针对选区展开,那么本章我们将探讨如何实现这个功能点。

PS选区

我们先来看看PS中的选区:

选区功能点提炼

TextureShop选区功能将完全参照PS选区来实现,所以我们首先来提炼PS选区的功能特点:

1.选区为一个方形区域;

2.选区内部为无色透明(也即是中间镂空);

3.选区边框为流动的虚线;

如上即为实现选区后的功能点,如果你了解unity shader,这三个功能点对你来说并不难。

TextureShop选区

那么现在,让我们来循序展开吧。

方形区域

首先我们要实现的是代表已选取的方形区域,实现的方式依然是借助shader

众所周知,在图形学领域,可用来代表已选取(未选取)这样一个开关属性的参数,通常是用图像的alpha通道来表示,如下图像,白色区域(alpha=1)即可代表已选取区域,棋盘格区域(alpha=0)即可代表未选取区域:

shader中实现它时,首先需要传入这张代表选区状态的图像:

c 复制代码
Shader "Hidden/TextureShop/Region"
{
	Properties
	{
		[HideInInspector] _RegionTex("区域纹理", 2D) = "black" {}
	}
}

然后在片元处理方法中实现对已选取(未选取)区域的甄别:

c 复制代码
		fixed4 frag(FragData IN) : SV_Target
		{
			//读取代表选区状态的图像数据
			half texA = tex2D(_RegionTex, IN.texcoord).a;
			//texA=1 为已选取区域,输出半透明白色
			//texA=0 为未选取区域,输出纯透明白色
			half4 color = half4(1, 1, 1, texA * 0.5);
			return color;
		}

看看效果(如何构建这个代表选区状态的图像数据,将在后面讲解):

很明显,选区的方形区域效果是OK了。

中间镂空

接下来我们要实现选区的内部区域不可见(中间镂空),为此,我们引入参数边框大小,方形区域四周需要显示的部分称作边框,由边框大小来决定其尺寸,而除去边框以外的部分即为中间区域,都将不可见:

c 复制代码
Shader "Hidden/TextureShop/Region"
{
	Properties
	{
		[HideInInspector] _RegionTex("区域纹理", 2D) = "black" {}
		[HideInInspector] _BrushSize("边框大小", int) = 1	
	}
}

在片元处理方法中:

c 复制代码
		fixed4 frag(FragData IN) : SV_Target
		{
			float2 uv = IN.texcoord;

			half texA = tex2D(_RegionTex, uv).a;

			//基准alpha值
			half alpha = 1;
			//遍历边框四周颜色
			for (int i = -_BrushSize; i <= _BrushSize; i++)
			{
				for (int j = -_BrushSize; j <= _BrushSize; j++)
				{
					//将四周的alpha值,累乘到基准alpha值
					//实现的效果如下:
					//如果四周的alpha值都为1(已选取),则最终基准alpha值也为1(表示此区域为中间区域)
					//如果四周的alpha值存在0(未选取),则最终基准alpha值也为0(表示此区域为边框区域)
					float2 borderUV = float2(uv.x + i * _RegionTex_TexelSize.x, uv.y + j * _RegionTex_TexelSize.y);
					half borderA = tex2D(_RegionTex, borderUV).a;
					alpha *= borderA;
				}
			}

			half4 color = half4(1, 1, 1, 1);
			//1 - alpha,将基准alpha值取反,中间区域将为0(输出纯透明色),边框区域将为1(输出白色)
			color.a = texA * (1 - alpha);
			return color;
		}

看看效果:

边框的流动虚线

接下来我们要实现选区边框区域的流动虚线,我们的第一想法肯定是使用黑白棋盘格图片来实现,为此,我们引入参数边框纹理(棋盘格图片),边框流动速度

c 复制代码
Shader "Hidden/TextureShop/Region"
{
	Properties
	{
		[HideInInspector] _RegionTex("区域纹理", 2D) = "black" {}
		[HideInInspector] _BrushSize("边框大小", int) = 1	
		[HideInInspector] _BorderTex("边框纹理", 2D) = "black" {}
		[HideInInspector] _BrushSpeed("边框流动速度", float) = 0.3	
	}
}

使用此边框纹理(棋盘格图片):

在片元处理方法中,将边框纹理(棋盘格图片)输出到边框区域,并随时间流动uv:

c 复制代码
		fixed4 frag(FragData IN) : SV_Target
		{
			//......

			//half4 color = half4(1, 1, 1, 1);
			//将原本的边框颜色(白色),改为读取边框纹理颜色,并随时间流动,流动方向:左上角到右下角
			float2 realUV = float2(uv.x - _Time.x * _BrushSpeed, uv.y + _Time.x * _BrushSpeed);
			half4 color = tex2D(_BorderTex, realUV * 20);
			
			//......
		}

看看效果:

效果有点奇怪,由于棋盘格图片是斜着向右下方移动的,所以每移动一个单位,就会有翻滚一下的效果(图片边缘),因为我们的棋盘格是正方格的图片。

所以需要改进棋盘格图片,使用如下的图片作为边框纹理

再次运行看效果:

OK,没问题了,边框纹理与边框流动方向相同后,流动的虚线效果更加顺眼。

SelectedRegion类

选区的展现效果实现后,接下来新建一个类SelectedRegion,用它来驱动选区逻辑,构建并传入代表选区状态的图像到shader中:

csharp 复制代码
    /// <summary>
    /// 选区
    /// </summary>
    public sealed class SelectedRegion
    {
        private RectTransform _rectTransform;
        private Material _material;
        private RawImage _target;
        private Texture2D _texture;
        private bool _isTextureDirty = false;
        
        /// <summary>
        /// 选区
        /// </summary>
        /// <param name="width">选区宽度</param>
        /// <param name="height">选区高度</param>
        /// <param name="parent">选区所属父级</param>
        /// <param name="borderTex">选区边框纹理</param>
        public SelectedRegion(int width, int height, RectTransform parent, Texture2D borderTex)
        {
        	//创建选区实体(用于显示选区流动的虚线效果)
            _rectTransform = Utility.CreateRectTransform("SelectedRegion", parent, true);
            //创建选区材质(使用我们上文创建的shader)
            _material = new Material(Utility.RegionShader);
            _material.hideFlags = HideFlags.HideAndDontSave;
            //使用RawImage作为渲染器
            _target = _rectTransform.gameObject.AddComponent<RawImage>();
            _target.raycastTarget = false;
            _target.material = _material;
            //构建选区状态图像
            _texture = new Texture2D(width, height, TextureFormat.RGBA32, false);
            _texture.SetPixels(new Color[width * height]);
            _texture.wrapMode = TextureWrapMode.Clamp;
            _texture.name = "SelectedRegion";
            //传入选区状态图像到shader
            _material.SetTexture("_RegionTex", _texture);
            //传入边框纹理到shader(上文的棋盘格图片)
            _material.SetTexture("_BorderTex", borderTex);
            //标记选区状态为脏的,将触发选区更新
            _isTextureDirty = true;
        }
    }
选择选区

矩形选区可由左上角右下角两个点位描述其位置信息,所以我们如此编写选择选区的方法:

csharp 复制代码
    /// <summary>
    /// 选区
    /// </summary>
    public sealed class SelectedRegion
    {
        private Vector2Int _leftUp = new Vector2Int(-1, -1);
        private Vector2Int _rightDown = new Vector2Int(-1, -1);

        /// <summary>
        /// 选择选区【矩形选取】
        /// </summary>
        /// <param name="leftUp">区域左上角</param>
        /// <param name="rightDown">区域右下角</param>
        /// <param name="isImmediatelyUpdate">是否立即更新选区</param>
        internal void SetRegion(Vector2Int leftUp, Vector2Int rightDown, bool isImmediatelyUpdate = false)
        {
            _leftUp = leftUp;
            _rightDown = rightDown;
            _isTextureDirty = true;

            if (isImmediatelyUpdate)
            {
            	//更新选区状态
                OnUpdate();
            }
        }
    }
更新选区
csharp 复制代码
    /// <summary>
    /// 选区
    /// </summary>
    public sealed class SelectedRegion
    {
        /// <summary>
        /// 是否选取任意区域
        /// </summary>
        public bool IsSelected { get; private set; } = false;
        
        /// <summary>
        /// 选区更新(OnUpdate帧更新方法由TextureShop编辑器统一调用)
        /// </summary>
        public void OnUpdate()
        {
        	//如果本帧内选区状态为脏的,将触发更新一次
            if (_isTextureDirty)
            {
                _isTextureDirty = false;

                int count = 0;
                for (int i = 0; i < _texture.width; i++)
                {
                    for (int j = 0; j < _texture.height; j++)
                    {
                        if (i >= _leftUp.x && i <= _rightDown.x && j <= _leftUp.y && j >= _rightDown.y)
                        {
                        	//将处于选区(左上角-右下角)内的像素设置为白色(代表已选取)
                            _texture.SetPixel(i, j, Color.white);
                            count += 1;
                        }
                        else
                        {
                        	//将未处于选区(左上角-右下角)内的像素设置为无色(代表未选取)
                            _texture.SetPixel(i, j, Color.clear);
                        }
                    }
                }
                //是否存在任意已选取的区域
                IsSelected = count > 0;

				//更新选区状态图像,此图像已在构造方法中传递给shader,其将依据此状态图像更新选区展示效果
                _texture.Apply();
            }
        }
    }

那么至此,矩形选区的功能就实现了。

相关推荐
ue星空2 小时前
虚幻引擎结构之UWorld
游戏引擎·虚幻
ue星空2 小时前
虚幻引擎结构之ULevel
游戏引擎·虚幻
向宇it2 小时前
【从零开始入门unity游戏开发之——unity篇01】unity6基础入门开篇——游戏引擎是什么、主流的游戏引擎、为什么选择Unity
开发语言·unity·c#·游戏引擎
神洛华4 小时前
Y3地图制作1:水果缤纷乐、密室逃脱
编辑器·游戏引擎·游戏程序
向宇it7 小时前
【从零开始入门unity游戏开发之——C#篇26】C#面向对象动态多态——接口(Interface)、接口里氏替换原则、密封方法(`sealed` )
java·开发语言·unity·c#·游戏引擎·里氏替换原则
m0_7482517219 小时前
Android webview 打开本地H5项目(Cocos游戏以及Unity游戏)
android·游戏·unity
benben04419 小时前
Unity3D仿星露谷物语开发7之事件创建动画
unity·游戏引擎
林枫依依1 天前
Unity2021.3.16f1可以正常打开,但是Unity2017.3.0f3却常常打开闪退或者Unity2017编辑器运行起来就闪退掉
unity
虾球xz1 天前
游戏引擎学习第57天
学习·游戏引擎