项目源码:在终章发布


索引

  • 取色板
  • PS取色板
  • Unity编辑器取色板
  • TextureShop取色板
  • 界面设计
  • H取色区域
  • SV取色区域
  • HSV联合取色逻辑
  • H取色区域Shader
  • SV取色区域Shader
  • ColorPicker
  • H取色区域检测输入
  • SV取色区域检测输入
  • 更新取色面板


取色板

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

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

PS取色板

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

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

Unity编辑器取色板

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

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

TextureShop取色板

界面设计

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

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


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

H取色区域

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

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

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

SV取色区域

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

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

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

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

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

HSV联合取色逻辑

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

也即是:

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

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

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

右边竖形选取区域即为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;
			}

			......
}

完成后效果如下:

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

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;
			}

			......
}

完成后效果如下:

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

到此,取色板的核心逻辑也就差不多了然于胸了,接下来是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的简易取色板便完成了,我们来看看最终效果:

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