【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();
            }
        }
    }

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

相关推荐
天人合一peng8 小时前
unity 生成标记根据背景色标记变色
unity·游戏引擎
天人合一peng12 小时前
unity 生成标记根据背景色变色为明显的颜色
unity·游戏引擎
魔士于安12 小时前
Unity 超市总动员 超市收银台 超市货架 超市购物手推车 超市常见商品
游戏·unity·游戏引擎·贴图·模型
CandyU212 小时前
Unity —— 数据持久化
unity·游戏引擎
zh路西法12 小时前
【Unity实现Oneshot胶卷显形】游戏窗口化与Win32API的使用
游戏·unity·游戏引擎
迪捷软件13 小时前
显控系统虚拟仿真的工程化路径
游戏引擎·cocos2d
凡情17 小时前
android隐私合规检测
android·unity
小贺儿开发17 小时前
Unity3D 本地 Stable Diffusion 文生图效果演示
人工智能·unity·stable diffusion·文生图·ai绘画·本地化
Swift社区18 小时前
传统游戏引擎 vs 鸿蒙 System 架构
架构·游戏引擎·harmonyos
mxwin1 天前
Unity Shader 半透明物体为什么不能写入深度缓冲?
unity·游戏引擎·shader