【Unity功能集】TextureShop纹理工坊(四)取色板

项目源码:在终章发布

取色板

取色板,也既是颜色选择器,方便我们人眼选择颜色的工具。

在二维图像处理程序中,这应该是最最基本的工具之一,那么在TextureShop中,我们首先要实现的就是它。

PS取色板

我们先来看看PS的取色板(较复杂,同时支持RGBHSVLab颜色空间):

在这里插入图片描述

Unity编辑器取色板

再来看看Unity编辑器的取色板(相对简单,也支持RGBHSV颜色空间):

在这里插入图片描述

TextureShop取色板

界面设计

回到我们的TextureShop,取色板的界面经过设计后呈如下模样:

在这里插入图片描述
众所周知,设计取色板的初衷就是为了方便人眼选择颜色,而针对人眼相对友好的颜色空间HSV,所以我们必须要在界面上展现出颜色的HSV值,并使得用户能够根据HSV值选择颜色。

H取色区域

通过了解HSV颜色空间(了解HSV颜色空间),得知HSVH值为色相,也即是决定了颜色的最终种类(是红还是绿),所以取色板面板中,竖形选取区域即为H取色区域

在这里插入图片描述

H取色区域(竖形选取区域):
1.只能方向取值(图中白色横条代表了取值所在位置);
2.取值类型为H值;
3.H取值范围:上 - 下 (1 - 0);

SV取色区域

H为色相,当色相相同的情况下:

S值决定了该颜色的饱和度,从完全不饱和(灰白色)到完全饱和(正常颜色)之间,取值范围为0-1;
V值决定了该颜色的亮度,从完全不亮(黑色)到完全亮(白色)之间,取值范围为0-1;

那么在取色板面板中,矩形选取区域即可做为SV取色区域(因为他在横竖方向上分别可取2个值):

在这里插入图片描述

SV取色区域(矩形选取区域):
1.只能横、竖方向取值(图中白色圆点代表了取值所在位置);
2.取值类型为S、V值;
3.S取值范围:左 - 右 (0 - 1);
4.V取值范围:上 - 下 (1 - 0);

HSV联合取色逻辑

综上所述,H值既是取色的首选,用户通过在H取色区域选定一个颜色种类后,再在SV取色区域选取(也可称为调节)该颜色的饱和度、亮度,达到微调颜色的目的。

也即是:

H取色区域:选取颜色种类。
SV取色区域:微调颜色饱和度、亮度。

事实上,所有的取色板基于HSV空间的取色逻辑几乎都是这样实现的,回看PS的取色板:

在这里插入图片描述

右边竖形选取区域即为H取色区域,左边矩形选取区域即为SV取色区域

H取色区域选取了红色(H=0)后;

SV取色区域即可调节红色的饱和度、亮度;

越左边饱和度越低,越接近灰白色
越右边饱和度越高,越接近原色(这里为红色);
越上边亮度越高,颜色越
越下边亮度越低,颜色越

H取色区域Shader

事实上,搞清楚了实现原理之后,后面的编码部分就极其简单了,比方H取色区域的渲染Shader,只需要取uv坐标的纵坐标H值,然后输出颜色即可:

Shader "UI/TextureShop/ColorPickerH"
{
			......

			//片元处理方法
			fixed4 frag(FragData IN) : SV_Target
			{
				//直接取uv坐标的纵坐标作为H值
				half h = IN.texcoord.y;
				//使:S=1(原色),V=1(最亮)
				//将此HSV颜色值转换为RGB颜色值
				half3 rgb = HSVToRGB(half3(h, 1, 1));
				half4 color = half4(rgb.r, rgb.g, rgb.b, 1);
				//输出颜色
				return color;
			}

			......
}

完成后效果如下:

在这里插入图片描述

SV取色区域Shader

SV取色区域同理,只需要取uv坐标的横坐标S值,取uv坐标的纵坐标V值,然后输出颜色即可。

至于H值,SV取色区域需要外部传入当前的H值,也即是H取色区域当前的取值需要传给SV取色区域

Shader "UI/TextureShop/ColorPickerSV"
{
			Properties
			{
				//可供外部传入的H值
				[HideInInspector] _H("H", float) = 0
			}

			......

			//片元处理方法
			fixed4 frag(FragData IN) : SV_Target
			{
				//获取外部传入的H值
				half h = _H;
				//直接取uv坐标的横坐标作为S值
				half s = IN.texcoord.x;
				//直接取uv坐标的纵坐标作为V值
				half v = IN.texcoord.y;
				//将此HSV颜色值转换为RGB颜色值
				half3 rgb = HSVToRGB(half3(h, s, v));
				half4 color = half4(rgb.r, rgb.g, rgb.b, 1);
				//输出颜色
				return color;
			}

			......
}

完成后效果如下:

在这里插入图片描述

到此,取色板的核心逻辑也就差不多了然于胸了,接下来是C#部分。

ColorPicker

编写一个类ColorPicker,用于完成整个取色板的逻辑驱动,主要包含如下步骤:

1.检测用户在H取色区域的输入,获取用户选取的H值;
2.将H值传入SV取色区域
3.检测用户在SV取色区域的输入,获取用户选取的S、V值;
4.将H、S、V值实时显示在取色面板中;
5.将H、S、V值转换为R、G、B值后,实时显示在取色面板中;
6.在当前区域显示用户当前选取的最终颜色;
7.当用户手动输入了H、S、V值或R、G、B值后,同步更新H取色区域SV取色区域中的选取图标位置;
8.当用户点击确认按钮,取色完毕,返回用户选取的最终颜色。

H取色区域检测输入

H取色区域的检测输入我使用了ICanvasRaycastFilter接口来实现,定义了一个类PickBoard来实现该接口:

    /// <summary>
    /// 拾取板
    /// </summary>
    [DisallowMultipleComponent]
    public sealed class PickBoard : MonoBehaviour, ICanvasRaycastFilter
    {
        /// <summary>
        /// 图片背景
        /// </summary>
        public Image Board;
        /// <summary>
        /// 拾取点(H取色时的白色横条,SV取色时的白色圆点)
        /// </summary>
        public RectTransform Point;
        /// <summary>
        /// 是否忽略拾取点的X坐标(H取色时应当忽略)
        /// </summary>
        public bool IsIgnoreX = false;
        /// <summary>
        /// 拾取事件(每次取色时触发)
        /// </summary>
        public UnityEvent<Vector2> PickEvent;

        private RectTransform _rectTransform;

        /// <summary>
        /// 平面布局位置组件
        /// </summary>
        private RectTransform Rect
        {
            get
            {
                if (_rectTransform == null)
                {
                    _rectTransform = GetComponent<RectTransform>();
                }
                return _rectTransform;
            }
        }
        
        public bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera)
        {
            if (Input.GetMouseButton(0))
            {
                Vector2 ui = Utility.ScreenPointToUGUIPoint(sp, eventCamera, Rect);
                if (IsIgnoreX) ui.x = 0;
                Point.anchoredPosition = ui;

                Vector2 uv = Utility.ScreenPointToUVPoint(sp, eventCamera, Rect);
                PickEvent?.Invoke(uv);
            }
            return true;
        }
        /// <summary>
        /// 设置拾取的UV坐标(用户手动输入HSV值时,更新选取图标位置)
        /// </summary>
        /// <param name="uv">UV坐标</param>
        public void SetUV(Vector2 uv)
        {
            float x = (uv.x - Rect.pivot.x) * Rect.sizeDelta.x;
            float y = (uv.y - Rect.pivot.y) * Rect.sizeDelta.y;
            Vector2 ui = new Vector2(x, y);
            if (IsIgnoreX) ui.x = 0;
            Point.anchoredPosition = ui;
        }
    }

SV取色区域检测输入

SV取色区域的检测输入同上。

更新取色面板

回到ColorPicker类中,我准备将剩下的逻辑都统称为更新取色面板(将H值传入SV取色区域、显示HSV值、显示RGB值、显示当前颜色、更新取色区域的图标位置等),当且仅当取色面板为脏状态时,才会在下一帧触发更新取色面板的行为:

    /// <summary>
    /// 取色板
    /// </summary>
    [DisallowMultipleComponent]
    public sealed class ColorPicker : MonoBehaviour
    {
    	//是否为脏状态
        private bool _isDirty = false;
        //是否以HSV值为主,否则以RGB值为主
        private bool _isUseHSV = true;

        private void Update()
        {
            if (_isDirty)
            {
                RefreshCurrentColor();
                _isDirty = false;
            }
        }

        /// <summary>
        /// 更新取色面板
        /// </summary>
        private void RefreshCurrentColor()
        {
        	//以HSV为主(用户输入了HSV值),将HSV转换后,覆盖RGB值
            if (_isUseHSV)
            {
                Current = Color.HSVToRGB(H, S, V);
                R = (int)(Current.r * 255);
                G = (int)(Current.g * 255);
                B = (int)(Current.b * 255);
                A = (int)(Current.a * 255);
            }
            //以RGB为主(用户输入了RGB值),将RGB转换后,覆盖HSV值
            else
            {
                Current = new Color(R / 255f, G / 255f, B / 255f, A / 255f);
                Color.RGBToHSV(Current, out float h, out float s, out float v);
                H = h;
                S = s;
                V = v;
            }

			//关闭UI控件的输入检测
            _isMonitor = false;
            //同步更新H取色区域的选取图标位置
            PickBoard_H.SetUV(new Vector2(0, H));
            //同步更新SV取色区域的选取图标位置
            PickBoard_SV.SetUV(new Vector2(S, V));
            //将H值传入SV取色区域
            PickBoard_SV.Board.material.SetFloat("_H", H);
            //将HSV、RGBA值更新到UI控件
            InputField_H.text = H.ToString("F2");
            InputField_S.text = S.ToString("F2");
            InputField_V.text = V.ToString("F2");
            InputField_R.text = R.ToString();
            InputField_G.text = G.ToString();
            InputField_B.text = B.ToString();
            InputField_A.text = A.ToString();
            //更新当前选取的颜色
            Img_CurrentColor.color = Current;
            //重新开启UI控件的输入检测
            _isMonitor = true;
        }
    }

那么接下来,需要定义HSV、RGBA的UI控件属性值

    /// <summary>
    /// 取色板
    /// </summary>
    [DisallowMultipleComponent]
    public sealed class ColorPicker : MonoBehaviour
    {
        /// <summary>
        /// 拾取板H
        /// </summary>
        public PickBoard PickBoard_H;
        /// <summary>
        /// 拾取板SV
        /// </summary>
        public PickBoard PickBoard_SV;
        /// <summary>
        /// 输入框H
        /// </summary>
        public InputField InputField_H;
        /// <summary>
        /// 输入框S
        /// </summary>
        public InputField InputField_S;
        /// <summary>
        /// 输入框V
        /// </summary>
        public InputField InputField_V;
        /// <summary>
        /// 输入框R
        /// </summary>
        public InputField InputField_R;
        /// <summary>
        /// 输入框G
        /// </summary>
        public InputField InputField_G;
        /// <summary>
        /// 输入框B
        /// </summary>
        public InputField InputField_B;
        /// <summary>
        /// 输入框A
        /// </summary>
        public InputField InputField_A;
        /// <summary>
        /// 当前拾取的颜色显示框
        /// </summary>
        public Image Img_CurrentColor;

        private float _h = 0;
        private int _r = 0;
        
        /// <summary>
        /// HSV分量H
        /// </summary>
        public float H
        {
            get
            {
                return _h;
            }
            set
            {
            	//限制H值在0-1之间
                _h = Mathf.Clamp01(value);
                //H值一旦改变,取色板切换为脏状态
                _isDirty = true;
                //且表明在更新取色板时,以HSV值为主
                _isUseHSV = true;
            }
        }

		//S、V属性同理......

        /// <summary>
        /// RGB分量R
        /// </summary>
        public int R
        {
            get
            {
                return _r;
            }
            set
            {
            	//限制R值在0-1之间
                _r = Mathf.Clamp(value, 0, 255);
                //R值一旦改变,取色板切换为脏状态
                _isDirty = true;
                //且表明在更新取色板时,以RGB值为主
                _isUseHSV = false;
            }
        }
        
        //G、B、A属性同理......
	}

到此,TextureShop的简易取色板便完成了,我们来看看最终效果:

请添加图片描述

功能介绍: 取色,取到的颜色可以通过ColorChanged事件参数e.Color获得,还可以在控件里把current变量公开出来 实现过程: 控件由很多色块组成。目前固定尺寸6x36,你可以自己修改尺寸。 控件分层是这样的(从最底层到最上层): 1.控件绘图面2.色块3.网格4.边框5.光标 在Paint事件中按上面顺序绘制2-5。 绘制网格很简单,根据色块大小隔一定距离画一道横(竖)线 绘制色块道理差不多,先获取当前坐标(行,列)的颜色(根据你自己定义的调色盘计算出来),然后填充一个方块,转到处理下一个坐标,直到全部行列都处理完。 然后绘制所有色块 从表面上看,是通过鼠标移动,选取每个色块获得颜色。其实不然。那样做,我就需要保存每个色块的颜色信息,白白浪费空间。我的实现方法是通过鼠标位置得知当前鼠标所在色块的坐标(行,列),然后用上面的颜色算法直接得到该色块的颜色,一句话搞定(Point pt是鼠标位置)。 标移动时会绘制光标,为了减少性能开销,不能直接Refresh()/Invalidate()控件,使用Invalidate(Rectangle)来重绘被鼠标弄脏的那个区域。所以用了两个小矩形保存旧光标和新光标的区域,然后在鼠标事件中更新(和鼠标取色一起)。 然后鼠标移动事件里重绘时稍微把区域扩大点(避免留下难看的边框)。 最后一行「OnColorChanged();是用来引发颜色改变事件。把事件写出来基本就完成了。 然后再完善下属性之类的,就可以在你的程序里使用了。 程序简单适合新手学习使用。 注意: 开发环境为Visual Studio 2010
### 大模型对齐微调DPO方法详解 #### DPO简介 直接偏好优化(Direct Preference Optimization, DPO)是一种用于改进大型语言模型行为的技术,该技术通过结合奖励模型训练和强化学习来提升训练效率与稳定性[^1]。 #### 实现机制 DPO的核心在于它能够依据人类反馈调整模型输出的概率分布。具体来说,当给定一对候选响应时,DPO试图使更受偏好的那个选项具有更高的生成概率。这种方法不仅简化了传统强化学习所需的复杂环境设置,而且显著增强了模型对于多样化指令的理解能力和执行精度[^2]。 #### PAI平台上的实践指南 为了便于开发者实施这一先进理念,在PAI-QuickStart框架下提供了详尽的操作手册。这份文档覆盖了从环境配置直至完成整个微调流程所需的一切细节,包括但不限于数据准备、参数设定以及性能评估等方面的内容。尤其值得注意的是,针对阿里云最新发布的开源LLM——Qwen2系列,文中给出了具体的实例说明,使得即使是初次接触此类工作的用户也能顺利上手。 ```python from transformers import AutoModelForCausalLM, Trainer, TrainingArguments model_name_or_path = "qwen-model-name" tokenizer_name = model_name_or_path training_args = TrainingArguments( output_dir="./results", per_device_train_batch_size=8, num_train_epochs=3, ) trainer = Trainer( model_init=lambda: AutoModelForCausalLM.from_pretrained(model_name_or_path), args=training_args, train_dataset=train_dataset, ) # 假设已经定义好了train_dataset trainer.train() ``` 这段代码片段展示了如何使用Hugging Face库加载预训练模型并对其进行微调的过程。虽然这里展示的例子并不完全对应于DPO的具体实现方式,但它提供了一个基础模板供进一步定制化开发之用[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

神码编程

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

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

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

打赏作者

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

抵扣说明:

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

余额充值