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

news2024/12/18 22:18:11

项目源码:后期发布

索引

  • 图层
    • 渲染绘画区域
    • 图层Shader
  • 编辑器
    • 编辑模式
    • 新建图层
    • 设置当前图层
    • 上、下移动图层
    • 删除图层
    • 图层快照

图层

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

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

渲染绘画区域

接上篇,为了实现渲染绘画区域的逻辑,我们添加一个控制变量:

    /// <summary>
    /// 图层
    /// </summary>
    public sealed class TextureLayer
    {
    		//图层是否为脏的,将触发重新生成渲染源
            private bool _isTextureDirty = false;
    }

然后,在帧轮询方法OnUpdate中,检测并重新生成渲染源:

    /// <summary>
    /// 图层
    /// </summary>
    public sealed class TextureLayer
    {
        /// <summary>
        /// 图层更新
        /// </summary>
        public void OnUpdate()
        {
        	//当图层改变时(被标记为脏的)
            if (_isTextureDirty)
            {
                _isTextureDirty = false;

                for (int x = 0; x < _texture.width; x++)
                {
                    for (int y = 0; y < _texture.height; y++)
                    {
                    	//通过偏移值(锚点),从绘画板中提取颜色,填充到_texture(渲染源)中
                        Color bColor = _plate.GetColor(x + Offset.x, y + Offset.y);
                        _texture.SetPixel(x, y, bColor);
                    }
                }

                _texture.Apply();
            }
        }
    }

_isTextureDirty变量的控制至关重要,它使得即便在同一帧多次改变了图层数据,也仅仅只会进行一次生成渲染源,而不是图层每改变一次便触发一次生成渲染源,那将带来极大的性能开销。

图层Shader

再者是图层持有的Shader TextureLayer.shader,在TextureLayer类的构造方法中,他已将渲染源设置给了Shader:

 _material.SetTexture("_PaintTex", _texture);

那么在TextureLayer.shader中将是我们自行编写的渲染逻辑,只不过此时我们没有额外的处理,直接原封不动的输出了渲染源中的颜色:

Shader "Hidden/TextureShop/TextureLayer"
{
	Properties
	{
		[PerRendererData] _MainTex("Sprite Texture", 2D) = "white" {}
		[HideInInspector] _PaintTex("图层纹理", 2D) = "white" {}
	}

	SubShader
	{
		Tags
		{
			"Queue" = "Transparent"
			"IgnoreProjector" = "True"
			"RenderType" = "Transparent"
			"PreviewType" = "Plane"
			"CanUseSpriteAtlas" = "True"
		}

		Cull Off
		Lighting Off
		ZWrite Off
		ZTest[unity_GUIZTestMode]
		Blend SrcAlpha OneMinusSrcAlpha

		Pass
		{
			Name "Default"

			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma target 2.0

			#include "UnityCG.cginc"
			#include "UnityUI.cginc"

			struct VertData
			{
				float4 vertex   : POSITION;
				fixed4 color : COLOR;
				float2 texcoord : TEXCOORD0;
				UNITY_VERTEX_INPUT_INSTANCE_ID
			};

			struct FragData
			{
				float4 vertex   : SV_POSITION;
				fixed4 color : COLOR;
				float2 texcoord  : TEXCOORD0;
				float4 worldPosition : TEXCOORD1;
				UNITY_VERTEX_OUTPUT_STEREO
			};

			sampler2D _PaintTex;

			FragData vert(VertData IN)
			{
				FragData OUT;
				UNITY_SETUP_INSTANCE_ID(IN);
				UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
				OUT.worldPosition = IN.vertex;
				OUT.vertex = UnityObjectToClipPos(OUT.worldPosition);
				OUT.texcoord = IN.texcoord;
				OUT.color = IN.color;
				return OUT;
			}

			fixed4 frag(FragData IN) : SV_Target
			{
				//原封不动输出颜色
				half4 color = tex2D(_PaintTex, IN.texcoord);
				return color;
			}
			ENDCG
		}
	}
}

编辑器

我们需要定义一个最顶层的控制器类,也将作为我们整个程序的入口,TextureShop的编辑器:

    /// <summary>
    /// TextureShop编辑器
    /// </summary>
    [DisallowMultipleComponent]
    public sealed class TextureShopEditor : MonoBehaviour
    {
    
	}

回顾图层的尺寸等参数,所有的图层应当拥有一样的尺寸大小,所以将他们加入到编辑器中,以公开到外部设置:

    /// <summary>
    /// TextureShop编辑器
    /// </summary>
    [DisallowMultipleComponent]
    public sealed class TextureShopEditor : MonoBehaviour
    {
        /// <summary>
        /// 绘画区域宽度
        /// </summary>
        public int PaintAreaWidth = 512;
        /// <summary>
        /// 绘画区域高度
        /// </summary>
        public int PaintAreaHeight = 512;
        /// <summary>
        /// 绘画板宽度
        /// </summary>
        public int PlateWidth = 2048;
        /// <summary>
        /// 绘画板高度
        /// </summary>
        public int PlateHeight = 2048;
	}

编辑模式

编辑模式目前暂定为如下几种:

编辑模式描述
不可进行任何编辑操作
移动选中选区时,移动选区内容,否则移动绘画板内容
选取以矩形方式选择选区
套索以套索方式选择选区
魔术棒以魔术棒方式选择选区
修剪进行高自由度修剪选区
仿制图章按住Alt锚定一片区域,然后在其他地方可复制该区域
画笔使用鼠标进行绘画
橡皮擦使用鼠标进行擦除
    /// <summary>
    /// TextureShop编辑器
    /// </summary>
    [DisallowMultipleComponent]
    public sealed class TextureShopEditor : MonoBehaviour
    {
        /// <summary>
        /// 编辑模式
        /// </summary>
        [SerializeField] private EditMode _mode = EditMode.None;
	}

    /// <summary>
    /// 编辑模式
    /// </summary>
    public enum EditMode
    {
        /// <summary>
        /// 无
        /// </summary>
        None,
        /// <summary>
        /// 移动
        /// </summary>
        Move,
        /// <summary>
        /// 选取
        /// </summary>
        Choose,
        /// <summary>
        /// 套索
        /// </summary>
        Noose,
        /// <summary>
        /// 魔术棒
        /// </summary>
        MagicWand,
        /// <summary>
        /// 修剪
        /// </summary>
        Trim,
        /// <summary>
        /// 仿制图章
        /// </summary>
        CloneStamp,
        /// <summary>
        /// 画笔
        /// </summary>
        Paint,
        /// <summary>
        /// 擦除
        /// </summary>
        Erase
    }

新建图层

新建图层时,传入尺寸、图像数据等参数(如果传入了图像,将该图像颜色数据载入新建的图层中,否则新建一个空图层):

    /// <summary>
    /// TextureShop编辑器
    /// </summary>
    [DisallowMultipleComponent]
    public sealed class TextureShopEditor : MonoBehaviour
    {
    	//图层渲染器放置的根节点
        private RectTransform _textureLayerRoot;
            
        /// <summary>
        /// 所有的图层
        /// </summary>
        public List<TextureLayer> TextureLayers { get; private set; } = new List<TextureLayer>();

        /// <summary>
        /// 新建一个图层
        /// </summary>
        /// <param name="layerName">图层名称</param>
        /// <param name="texture">图层图像</param>
        /// <param name="offset">偏移值</param>
        /// <returns>图层</returns>
        public TextureLayer NewTextureLayer(string layerName, Texture2D texture, Vector2Int offset = default)
        {
            layerName = string.IsNullOrEmpty(layerName) ? "新建图层" : layerName;
            TextureLayer textureLayer = new TextureLayer(layerName, PaintAreaWidth, PaintAreaHeight, PlateWidth, PlateHeight, _textureLayerRoot, texture, offset);
            TextureLayers.Add(textureLayer);
            return textureLayer;
        }
	}

设置当前图层

只有当前图层不为空,才能进一步编辑当前图层:

    /// <summary>
    /// TextureShop编辑器
    /// </summary>
    [DisallowMultipleComponent]
    public sealed class TextureShopEditor : MonoBehaviour
    {
        /// <summary>
        /// 当前选中的图层
        /// </summary>
        public TextureLayer CurrentLayer { get; private set; }

        /// <summary>
        /// 设置当前激活的图层
        /// </summary>
        /// <param name="layer">图层</param>
        public void SetCurrentLayer(TextureLayer layer)
        {
            if (CurrentLayer == layer)
                return;

            CurrentLayer = layer;
        }
	}

上、下移动图层

图层越靠下(在图层列表中越靠后),其显示层级越高,也即是能在视觉上挡住之前的图层:

    /// <summary>
    /// TextureShop编辑器
    /// </summary>
    [DisallowMultipleComponent]
    public sealed class TextureShopEditor : MonoBehaviour
    {
        /// <summary>
        /// 向上移动当前图层(显示层级降低)
        /// </summary>
        public void MoveLayerUpwards()
        {
            if (CurrentLayer != null)
            {
                int index = TextureLayers.IndexOf(CurrentLayer);
                if (index > 0)
                {
                    index -= 1;
                    TextureLayers.Remove(CurrentLayer);
                    TextureLayers.Insert(index, CurrentLayer);
                    //移动图层渲染器实体,改变其在父级中的位置
                    CurrentLayer.Entity.transform.SetSiblingIndex(index);
                }
            }
        }
        /// <summary>
        /// 向下移动当前图层(显示层级升高)
        /// </summary>
        public void MoveLayerDownwards()
        {
            if (CurrentLayer != null)
            {
                int index = TextureLayers.IndexOf(CurrentLayer);
                if (index < TextureLayers.Count - 1)
                {
                    index += 1;
                    TextureLayers.Remove(CurrentLayer);
                    TextureLayers.Insert(index, CurrentLayer);
                    CurrentLayer.Entity.transform.SetSiblingIndex(index);
                }
            }
        }
	}

删除图层

    /// <summary>
    /// TextureShop编辑器
    /// </summary>
    [DisallowMultipleComponent]
    public sealed class TextureShopEditor : MonoBehaviour
    {
        /// <summary>
        /// 删除一个图层
        /// </summary>
        /// <param name="layer">图层</param>
        public void DeleteTextureLayer(TextureLayer layer)
        {
            if (layer == null)
                return;

            if (CurrentLayer == layer)
            {
                SetCurrentLayer(null);
            }

			layer.Dispose();
            TextureLayers.Remove(layer);
        }
	}

图层快照

在进行某些操作时,如果用户未点击确认,而是点击了取消,则之前的操作将被还原,如下:

在这里插入图片描述

为了实现这个功能,我们引入图层快照(当然,更高级的应当引入命令模式,但这不在本系列的学习范畴内):

    /// <summary>
    /// 图层快照
    /// </summary>
    public sealed class TextureLayerSnapshot
    {
        internal Plate _plate;
        internal Plate _region;
        internal Vector2Int _offset;

        /// <summary>
        /// 图层快照
        /// </summary>
        public TextureLayerSnapshot(int paintAreaWidth, int paintAreaHeight, int plateWidth, int plateHeight)
        {
            _plate = new Plate(plateWidth, plateHeight, true);
            _region = new Plate(paintAreaWidth, paintAreaHeight, false);
            _offset = new Vector2Int((_plate.Width - _region.Width) / 2, (_plate.Height - _region.Height) / 2);
        }
        /// <summary>
        /// 释放图层快照资源
        /// </summary>
        public void Dispose()
        {
            if (_plate != null)
            {
                _plate.Dispose();
                _plate = null;
            }

            if (_region != null)
            {
                _region.Dispose();
                _region = null;
            }
        }
    }

任何图层,都可以为其创建快照,并在之后的某一时刻还原到快照,快照能够记录一个图层在某一时刻的完整状态:

    /// <summary>
    /// TextureShop编辑器
    /// </summary>
    [DisallowMultipleComponent]
    public sealed class TextureShopEditor : MonoBehaviour
    {
        /// <summary>
        /// 当前的图层快照
        /// </summary>
        public TextureLayerSnapshot CurrentSnapshot { get; private set; }

        /// <summary>
        /// 为当前图层创建快照
        /// </summary>
        public void CreateLayerSnapshot()
        {
            if (CurrentLayer != null)
            {
                CurrentLayer.OutputToSnapshot(CurrentSnapshot);
            }
        }
        /// <summary>
        /// 将当前图层还原到快照
        /// </summary>
        public void RestoreLayerSnapshot()
        {
            if (CurrentLayer != null)
            {
                CurrentLayer.InputOfSnapshot(CurrentSnapshot);
            }
        }
	}

至于图层如何创建快照,如何还原到快照,将由他自己定夺:

    /// <summary>
    /// 图层
    /// </summary>
    public sealed class TextureLayer
    {
        /// <summary>
        /// 输出到快照
        /// </summary>
        /// <param name="snapshot">快照</param>
        internal void OutputToSnapshot(TextureLayerSnapshot snapshot)
        {
            _plate.CopyTo(snapshot._plate);
            _region.CopyTo(snapshot._region);
            snapshot._offset = Offset;
        }
        /// <summary>
        /// 通过快照输入
        /// </summary>
        /// <param name="snapshot">快照</param>
        internal void InputOfSnapshot(TextureLayerSnapshot snapshot)
        {
            snapshot._plate.CopyTo(_plate);
            snapshot._region.CopyTo(_region);
            Offset = snapshot._offset;
            //图层为脏的,触发重新生成渲染源
            _isTextureDirty = true;
        }
    }

到此,图层的功能就实现得差不多了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2261811.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

云计算HCIP-OpenStack02

书接上回&#xff1a; 云计算HCIP-OpenStack01-CSDN博客 7.OpenStack核心服务 7.1Horizon&#xff1a;界面管理服务 Horizon提供了OpenStack中基于web界面的管理控制页面&#xff0c;用户或者是管理员都需要通过该服务进行OpenStack的访问和控制 界面管理服务需要依赖于keyston…

Word2Vec:将词汇转化为向量的技术

文章目录 Word2Vec来龙去脉分层Softmax负采样 Word2Vec 下面的文章纯属笔记&#xff0c;看完后不会有任何收获&#xff0c;如果想理解这两种优化技术&#xff0c;给大家推荐一篇博客&#xff0c;讲的很好&#xff1a; 详解-----分层Softmax与负采样 来龙去脉 word2vec,即将词…

电商商品详情API接口(item get)数据分析上货

电商商品详情API接口&#xff08;item get&#xff09;在数据分析与商品上货方面发挥着重要作用。以下是对这两个方面的详细探讨&#xff1a; 一、数据分析 数据源获取&#xff1a; 商品详情API接口提供了丰富的数据源&#xff0c;包括商品的标题、价格、库存、描述、图片、用…

如何将你的 Ruby 应用程序从 OpenSearch 迁移到 Elasticsearch

作者&#xff1a;来自 Elastic Fernando Briano 将 Ruby 代码库从 OpenSearch 客户端迁移到 Elasticsearch 客户端的指南。 OpenSearch Ruby 客户端是从 7.x 版 Elasticsearch Ruby 客户端分叉而来的&#xff0c;因此代码库相对相似。这意味着当将 Ruby 代码库从 OpenSearch 迁…

如何对 Java 项目简化接口设计提升开发效率

文章目录 摘要引言简洁接口设计的原则示例代码OrderProcessor 接口StandardOrderProcessor 实现类Order 数据类调用方代码&#xff1a;OrderService 模块之间的协作QA 环节总结参考资料 摘要 简洁的接口设计可以有效降低代码依赖与耦合度&#xff0c;提高代码的可维护性和扩展…

Python字符串及正则表达式(十):字符串常用操作、字符串编码转换

前言&#xff1a;在编程的世界里&#xff0c;字符串无处不在。它们是构建用户界面、存储数据、进行通信的基础元素。无论是财务系统的总账报表、电子游戏的比赛结果&#xff0c;还是火车站的列车时刻表&#xff0c;这些信息最终都需要以文本的形式呈现给用户。这些文本的背后&a…

JAVA爬虫获取1688关键词接口

以下是使用Java爬虫获取1688关键词接口的详细步骤和示例代码&#xff1a; 一、获取API接口访问权限 要使用1688关键词接口&#xff0c;首先需要获取API的使用权限&#xff0c;并了解接口规范。以下是获取API接口的详细步骤&#xff1a; 注册账号&#xff1a;在1688平台注册一…

【AIGC】与模型对话:理解与预防ChatGPT中的常见误解

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: AIGC | ChatGPT 文章目录 &#x1f4af;前言&#x1f4af;模型的工作原理和用户期望差异人工智能模型的基本工作原理认知上的局限与误解用户期望与模型实际能力的差距精确理解用户意图的重要性实际应用中的建议 &…

UE5制作倒计时功能

设置画布和文本 文本绑定 格式化时间 转到事件图表&#xff0c;计算时间&#xff0c;时间结束后面的事件可以按自己需求写 进入关卡蓝图&#xff0c;添加倒计时UI

Excel + Notepad + CMD 命令行批量修改文件名

注意&#xff1a;该方式为直接修改原文件的文件名&#xff0c;不会生成新文件 新建Excel文件 A列&#xff1a;固定为 renB列&#xff1a;原文件名称C列&#xff1a;修改后保存的名称B列、C列&#xff0c;需要带文件后缀&#xff0c;为txt文件就是.txt结尾&#xff0c;为png图片…

F5中获取客户端ip地址(client ip)

当F5设备对其原始设置上的所有IP地址使用NAT时&#xff0c;连接到poo成员&#xff08;nodes、backend servers&#xff09;的出站连接将是NAT IP地址。 pool 成员&#xff08;nodes、backend servers&#xff09;将无法看到真实的客户端 ip地址&#xff0c;因为看到的是F5上的…

什么是网络数据包分析?有什么特点?

网络数据包分析&#xff08;Packet Analysis&#xff09;&#xff0c;也被称为网络流量分析或抓包分析&#xff0c;是指通过捕获和检查在网络上传输的数据包来监控、诊断和评估网络性能及安全性的一种技术。这项技术可以用来识别网络问题、优化网络性能、检测安全威胁以及了解网…

大数据技术与应用——数据可视化(山东省大数据职称考试)

大数据分析应用-初级 第一部分 基础知识 一、大数据法律法规、政策文件、相关标准 二、计算机基础知识 三、信息化基础知识 四、密码学 五、大数据安全 六、数据库系统 七、数据仓库. 第二部分 专业知识 一、大数据技术与应用 二、大数据分析模型 三、数据科学 数据可视化 大…

window QT/C++ 与 lua交互(mingw + lua + LuaBridge + luasocket)

一、环境与准备工作 测试环境:win10 编译器:mingw QT版本:QT5.12.3 下载三种源码: LuaBridge源码:https://github.com/vinniefalco/LuaBridge LUA源码(本测试用的是5.3.5):https://www.lua.org/download.html luasocket源码:https://github.com/diegonehab/luasocket 目…

Docker在Ubuntu和CentOS系统下的安装

目录 1. 各版本平台支持情况2. 在Ubuntu系统下安装docker3. 常见报错4. Docker的镜像源修改5. Docker目录修改6. 在CentOS系统下安装docker 1. 各版本平台支持情况 &#xff08;1&#xff09;平台支持情况如下&#xff1a; Server 版本 桌面版本 2. 在Ubuntu系统下安装docker…

图形化界面MySQL(MySQL)(超级详细)

目录 1.官网地址 1.1在Linux直接点击NO thanks..... 1.2任何远端登录&#xff0c;再把jj数据库给授权 1.3建立新用户 优点和好处 示例代码&#xff08;MySQL Workbench&#xff09; 示例代码&#xff08;phpMyAdmin&#xff09; 总结 图形化界面 MySQL 工具大全及其功…

IP数据云查询IP归属地信息

互联网时代&#xff0c;我们每天都会面对大量的网站或App,但你们是否知晓&#xff0c;所有程序员进行程序或者系统的开发都离不开查询IP地址&#xff0c;这是由于对于每个安全的网站/软件来说&#xff0c;基础的服务日志&#xff0c;登录IP等就离不开IP归属地离线库&#xff0c…

PH热榜 | 2024-12-17

1. Eden 标语&#xff1a;一键用AI生成网页评论。 介绍&#xff1a;Eden是一款人工智能驱动的社交插件&#xff0c;只需点击表情符号就能在任何网页上评论。它能自动总结网页内容并生成个性化评论。 想调侃朋友、表达喜爱&#xff0c;还是快速评论几句&#xff1f;用Eden&…

Hadoop学习笔记(包括hadoop3.4.0集群安装)(黑马)

Hadoop学习笔记 0-前置章节-环境准备 0.1 环境介绍 配置环境&#xff1a;hadoop-3.4.0&#xff0c;jdk-8u171-linux-x64 0.2 VMware准备Linux虚拟机 0.2.1主机名、IP、SSH免密登录 1.配置固定IP地址&#xff08;root权限&#xff09; 开启master&#xff0c;修改主机名为…

ChatGPT Search开放:实时多模态搜索新体验

点击访问 chatTools 免费体验GPT最新模型&#xff0c;包括o1推理模型、GPT4o、Claude、Gemini等模型&#xff01; ChatGPT Search&#xff1a;功能亮点解析 本次更新的ChatGPT Search带来了多项令人瞩目的功能&#xff0c;使其在搜索引擎市场中更具竞争力。 1. 高级语音模式&…