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

项目源码:在终章发布

索引

  • 图层
    • 渲染绘画区域
    • 图层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;
        }
    }

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

1、数据库概念和mysql表的管理

数据库概念 datebase&#xff1a;用来组织&#xff0c;存储&#xff0c;管理数据的仓库。 数据库的管理系统&#xff1a;DBMS&#xff08;用来实现对数据的有效组织、关系和存取的系统软件&#xff09; 关系型和非关系型数据库 关系型数据库&#xff1a;mysql、oracle 非关…

70 mysql 中事务的隔离级别

前言 mysql 隔离级别有四种 未提交读, 已提交读, 可重复度, 序列化执行 然后不同的隔离级别存在不同的问题 未提交读存在 脏读, 不可重复度, 幻觉读 等问题 已提交读存在 不可重复度, 幻觉读 等问题 可重复读存在 幻觉读 等问题 序列化执行 没有以上问题 然后 我们这里…

【FFmpeg】解封装 ① ( 封装与解封装流程 | 解封装函数简介 | 查找码流标号和码流参数信息 | 使用 MediaInfo 分析视频文件 )

文章目录 一、解封装1、封装与解封装流程2、解封装 常用函数 二、解封装函数简介1、avformat_alloc_context 函数2、avformat_free_context 函数3、avformat_open_input 函数4、avformat_close_input 函数5、avformat_find_stream_info 函数6、av_read_frame 函数7、avformat_s…

Nginx(Linux之Ubuntu)

1.1.什么是Nginx Nginx&#xff08;发音为"engine x"&#xff09;是由俄罗斯开发者Igor Sysoev创建的一款轻量级、高性能的Web服务器。它首次发布于2004年&#xff0c;如今已成为全球最受欢迎的Web服务器之一。Nginx以其卓越的性能和灵活性而闻名&#xff0c;适用于…

使用Docker启用MySQL8.0.11

目录 一、Docker减小镜像大小的方式 1、基础镜像选择 2、减少镜像层数 3、清理无用文件和缓存 4、优化文件复制&#xff08;COPY和ADD指令&#xff09; 二、Docker镜像多阶段构建 1、什么是dockers镜像多阶段构建 1.1 概念介绍 1.2 构建过程和优势 2、怎样在Dockerfil…

【微信小程序开发 - 3】:项目组成介绍

文章目录 项目组成介绍项目的基本组成结构小程序页面的组成部分JSON配置文件的作用app.json文件project.config.json文件sitemap.json文件页面的 .json 配置文件新建小程序页面修改项目首页 XWML模板XWML 和 HTML 的区别 WXSS样式WXSS 和 CSS 的区别 .js文件 项目组成介绍 项目…

springboot的项目创建和常用注解

创建springboot项目&#xff1a; 首先更改一下url&#xff0c;点击小齿轮改成https://start.aliyun.com/ 首先在选模块的时候选上SpringWeb&#xff0c;然后jdk1.8对应的springboot版本是2.6.13或者2.7.6 pom.xml: 用1.8的jdk&#xff0c;mybatis的包版本不能太高 <!-- …

flask_socketio 以继承 Namespace方式实现一个网页聊天应用

点击进入上一篇&#xff0c;可作为参考 实验环境 python 用的是3.11.11 其他环境可以通过这种方式一键安装&#xff1a; pip install flask3.1.0 Flask-SocketIO5.4.1 gevent-websocket0.10.1 -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple pip list 详情如下&am…

笔记本重装系统教程【详细教程】

一、装机前说明 各位有装机需求的伙伴&#xff0c;请根据自己的电脑配置选择合适操作系统&#xff0c;可以实现自己装机的伙伴&#xff0c;相信大家这点可以确认好。 ———————————————————————— 我的配置&#xff1a; 我的电脑是联想拯救者lenovoY7000…

uniapp入门 01创建项目模版

0安装 hbuilder x 标准版 1.创建模版工程 2.创建官方 案例工程 index.uvuewen 文件解析 <!-- 模版 标签 --> <template><view></view></template><!-- 脚本 --> <script>export default {data() {return {}},onLoad() {},methods:…

ARCGIS国土超级工具集1.2更新说明

ARCGIS国土超级工具集V1.2版本&#xff0c;功能已增加至47 个。在V1.1的基础上修复了若干使用时发现的BUG&#xff0c;新增了"矢量分割工具"菜单&#xff0c;同时增加及更新了了若干功能&#xff0c;新工具使用说明如下&#xff1a; 一、勘测定界工具栏更新界址点成果…

Vue3源码笔记阅读1——Ref响应式原理

本专栏主要用于记录自己的阅读源码的过程,希望能够加深自己学习印象,也欢迎读者可以帮忙完善。接下来每一篇都会从定义、运用两个层面来进行解析 定义 运用 例子:模板中访问ref(1) <template><div>{{str}}</div> </template> <script> impo…

[react] 优雅解决typescript动态获取redux仓库的类型问题

store.getState()是可以获取总仓库的 先拿到函数的类型 再用ReturnType<T> 它是 TypeScript 中的一个内置条件类型&#xff0c;用于获取某个函数类型 T 的返回值类型 代码 // 先拿总仓库的函数类型type StatefuncType typeof store.getState;//再拿函数类型T的返回值类…

【Qt】QWidget中的常见属性及其功能(一)

目录 一、 enabled 例子&#xff1a; 二、geometry 例子&#xff1a; window fram 例子 &#xff1a; 四、windowTiltle 五、windowIcon 例子&#xff1a; qrc机制 创建qrc文件 例子&#xff1a; qt中的很多内置类都是继承自QWidget的&#xff0c;因此熟悉QWidget的…

R语言的字符串操作

【图书推荐】《R语言医学数据分析实践》-CSDN博客 《R语言医学数据分析实践 李丹 宋立桓 蔡伟祺 清华大学出版社9787302673484》【摘要 书评 试读】- 京东图书 (jd.com) R语言医学数据分析实践-R语言的数据结构-CSDN博客 在R语言中&#xff0c;字符串是一种表示文本数据的数…

webGL硬核知识:图形渲染管渲染流程,各个阶段对应的API调用方式

一、图形渲染管线基础流程概述 WebGL 的图形渲染管线大致可分为以下几个主要阶段&#xff0c;每个阶段都有其特定的任务&#xff0c;协同工作将 3D 场景中的物体最终转换为屏幕上呈现的 2D 图像&#xff1a; 顶点处理&#xff08;Vertex Processing&#xff09;阶段&#xff1…

《深入浅出Apache Spark》系列⑤:Spark SQL的表达式优化

导读&#xff1a;随着数据量的快速增长&#xff0c;传统的数据处理方法难以满足对计算速度、资源利用率以及查询响应时间的要求。为了应对这些挑战&#xff0c;Spark SQL 引入了多种优化技术&#xff0c;以提高查询效率&#xff0c;降低计算开销。本文从表达式层面探讨了 Spark…

创建项目以及本地仓库和远程仓库并上传项目

创建项目以及本地仓库和远程仓库并上传项目 其详细流程如下&#xff1a; 1、本地创建项目 2、创建本地仓库&#xff08;若使用idea在创建项目时选择了创建.git本地仓库&#xff0c;则此步骤省略&#xff09; 进入到你需要上传的项目的目录下&#xff0c;右键找到Git Bah He…

快速解决oracle 11g中exp无法导出空表的问题

在一些生产系统中&#xff0c;有些时候我们为了进行oracle数据库部分数据的备份和迁移&#xff0c;会使用exp进行数据的导出。但在实际导出的时候&#xff0c;我们发现导出的时候&#xff0c;发现很多空表未进行导出。今天我们给出一个快速解决该问题的办法。 一、问题复现 我…

MySQL八股-MVCC入门

文章目录 当前读&#xff08;加锁&#xff09;快照读&#xff08;不加锁&#xff09;MVCC隐藏字段undo-log版本链A. 第一步B.第二步C. 第三步 readview MVCC原理分析RCA. 先来看第一次快照读具体的读取过程&#xff1a;B. 再来看第二次快照读具体的读取过程: RR隔离级别 当前读…