Unity 取色板

1、 简介

功能分区为四部分:

  • 色相选择Slider
  • 饱和度和明度选择区域
  • Preview
  • 十六进制颜色区域

在这里,介绍一下颜色空间吧:

1.1 RGB

RGB色彩模式是工业界的一种颜色标准,是通过对红(R)、绿(G)、蓝(B)三个颜色通道的变化以及它们相互之间的叠加来得到各式各样的颜色的,RGB即是代表红、绿、蓝三个通道的颜色,这个标准几乎包括了人类视力所能感知的所有颜色,是运用最广的颜色系统之一。

1.2 HSV

HSV(Hue, Saturation, Value)是根据颜色的直观特性由A. R. Smith在1978年创建的一种颜色空间, 也称六角锥体模型(Hexcone Model)。HSV颜色模型是指H、S、V三维颜色空间中的一个可见光子集,它包含某个颜色域的所有颜色。
每一种颜色都是由色相(Hue,简H),饱和度(Saturation,简S)和色明度(Value,简V)所表示的。这个模型中颜色的参数分别是:色调(H),饱和度(S),亮度(V)。
色调H参数表示色彩信息,即所处的光谱颜色的位置。该参数用一角度量来表示,取值范围为0°~360°。若从红色开始按逆时针方向计算,红色为0°,绿色为120°,蓝色为240°。它们的补色是:黄色为60°,青色为180°,紫色为300°;
饱和度S:取值范围为0.0~1.0;
亮度V:取值范围为0.0(黑色)~1.0(白色)。

1.3 Hexadecimal

十六进制颜色对照表:https://www.ysdaima.com/tools/hexbiao
转换原理,可参见:https://blog.csdn.net/f_957995490/article/details/120727626

2、功能实现

2.1 Slider

背景图自己P了一张,然后Slider的value为h,s=1,v=1,算出色板右上角的颜色:

imageSliderXYBG.material.color = Color.HSVToRGB(1 - v, 1, 1);

2.2 SliderXY

2.2.1 渐变色

以Shader的方式实现,其中有个问题,算出来的颜色值需要转为liner

Shader "GradientXY"
{
    Properties
    {
        _Color("Color", Color) = (1,1,1,1)
    }

    SubShader
    {
        Tags 
        { 
            "RenderType" = "Transparent" 
            "Queue" = "Transparent"
            "IgnoreProjector" = "True" 
            "RenderPipeline" = "UniversalPipeline"
        }

        Pass
        {
            Name "ForwardLit"
            Tags { "LightMode" = "UniversalForward" }

            Blend SrcAlpha OneMinusSrcAlpha
            ZWrite Off
            Cull Off

            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile _ _MAIN_LIGHT_SHADOWS

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            struct Attributes
            {
                float4 positionOS : POSITION;
                float2 uv : TEXCOORD0;
                half4  color : COLOR;
            };

            struct Varyings
            {
                float4 positionCS : SV_POSITION;
                float2 uv : TEXCOORD0;
                half4  color : COLOR;
                float3 positionWS : TEXCOORD1;
            };

            CBUFFER_START(UnityPerMaterial)
                half4 _Color;
            CBUFFER_END

            Varyings vert(Attributes v)
            {
                Varyings o;
                VertexPositionInputs vertexInput = GetVertexPositionInputs(v.positionOS.xyz);
                o.positionCS = vertexInput.positionCS;
                o.uv = v.uv;             
                o.color = v.color * _Color;
                o.positionWS = vertexInput.positionWS;
                return o;
            }

            half4 gamma2liner(half4 gamma)
            {
                return gamma * (gamma * (gamma * 0.305306011h + 0.682171111h) + 0.012522878h);
            }

            half4 frag(Varyings i) : SV_Target
            {
                half4 colorWhite = half4(1,1,1,1);
                half4 colorBlack = half4(0,0,0,1);
                half4 color0 = lerp(colorWhite, _Color, i.uv.x);
                color0 = lerp(colorBlack, color0, i.uv.y);
                color0 = gamma2liner(color0);
                return color0;
            }   
            ENDHLSL
        }
    }
}

2.2.2 SliderXY实现

使用IPointerDownHandler, IDragHandler处理鼠标事件,UnityEvent处理回调事件

using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;

public class SliderXY : MonoBehaviour, IPointerDownHandler, IDragHandler
{
    [SerializeField]
    private Vector2 value = Vector2.zero;

    private RectTransform self;
    private RectTransform handle;

    private UnityEvent<Vector2> callback = new UnityEvent<Vector2>();
    private Vector4 boundary = Vector4.zero;

    public UnityEvent<Vector2> onValueChanged
    {
        get 
        {
            return callback;
        }
    }

    public void OnDrag(PointerEventData eventData)
    {
        OnMouseMoving(eventData.position);
    }

    public void OnPointerDown(PointerEventData eventData)
    {
        OnMouseMoving(eventData.position);
    }

    private void Awake()
    {
        self = transform.GetComponent<RectTransform>();
        handle = transform.Find("Handle").GetComponent<RectTransform>();

        boundary = new Vector4(
            self.position.x - self.rect.width * 0.5f,
            self.position.x + self.rect.width * 0.5f,
            self.position.y - self.rect.height * 0.5f,
            self.position.y + self.rect.height * 0.5f
        );
    }

    private void OnMouseMoving(Vector2 pos) 
    {
        pos.x = Mathf.Clamp(pos.x, boundary[0], boundary[1]);
        pos.y = Mathf.Clamp(pos.y, boundary[2], boundary[3]);
        handle.position = pos;

        CalculateValue(pos);
        OnValueChanged();
    }

    private void CalculateValue(Vector2 mousePos)
    {
        value.x = mousePos.x - self.position.x + self.rect.width * 0.5f;
        value.y = mousePos.y - self.position.y + self.rect.height * 0.5f;
        value.x /= self.rect.width;
        value.y /= self.rect.height;
    }

    private void OnValueChanged()
    {
        callback.Invoke(value);
    }

    public void SetValue(Vector2 value)
    {
        this.value = value;
        Vector2 pos = value;
        pos.x *= self.rect.width;
        pos.y *= self.rect.height;
        pos.x = pos.x - self.rect.width *0.5f + self.position.x;
        pos.y = pos.y - self.rect.height *0.5f + self.position.y;
        handle.position = pos;
    }

    public Vector2 GetValue()
    {
        return value;
    }
}

2.3 Hexadecimal

使用color.ToHexString()处理Color转Hex,ColorUtility.TryParseHtmlString处理Hex转Color

3、整体处理逻辑

using TMPro;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.UI;

public class Palette : MonoBehaviour
{
    private Slider slider;
    private Image imageSliderXYBG;
    private SliderXY sliderXY;
    private TMP_InputField inputFieldHex;
    private Image imagePreview;

    private void Awake()
    {
        slider = transform.Find("Slider").GetComponent<Slider>();
        imageSliderXYBG = transform.Find("SliderXY/Background").GetComponent<Image>();
        sliderXY = transform.Find("SliderXY").GetComponent<SliderXY>();
        inputFieldHex = transform.Find("Hexadecimal/InputField (TMP)").GetComponent<TMP_InputField>();
        imagePreview = transform.Find("Preview").GetComponent<Image>();
    }

    private void Start()
    {
        slider.onValueChanged.AddListener(v =>
        {
            imageSliderXYBG.material.color = Color.HSVToRGB(1 - v, 1, 1);
            OnColorChanged(GetTargetColor());
        });

        sliderXY.onValueChanged.AddListener(v =>
        {
            OnColorChanged(GetTargetColor(v));
        });

        inputFieldHex.onEndEdit.AddListener(v =>
        {
            UnityEngine.ColorUtility.TryParseHtmlString("#" + v, out Color color);
            OnColorChanged(color);
        });

        slider.onValueChanged.Invoke(slider.value);
    }

    private Color GetTargetColor()
    {
        Vector2 value = sliderXY.GetValue();
        return GetTargetColor(value);
    }

    private Color GetTargetColor(Vector2 value)
    {
        return Color.HSVToRGB(1 - slider.value, value.x, value.y);
    }

    private void OnColorChanged(Color color)
    {
        inputFieldHex.text = color.ToHexString()[..6];
        imagePreview.color = color;
		//dosomthing
    }
}
功能介绍: 取色,取到的颜色可以通过ColorChanged事件参数e.Color获得,还可以在控件里把current变量公开出来 实现过程: 控件由很多色块组成。目前固定尺寸6x36,你可以自己修改尺寸。 控件分层是这样的(从最底层到最上层): 1.控件绘图面2.色块3.网格4.边框5.光标 在Paint事件中按上面顺序绘制2-5。 绘制网格很简单,根据色块大小隔一定距离画一道横(竖)线 绘制色块道理差不多,先获取当前坐标(行,列)的颜色(根据你自己定义的调色盘计算出来),然后填充一个方块,转到处理下一个坐标,直到全部行列都处理完。 然后绘制所有色块 从表面上看,是通过鼠标移动,选取每个色块获得颜色。其实不然。那样做,我就需要保存每个色块的颜色信息,白白浪费空间。我的实现方法是通过鼠标位置得知当前鼠标所在色块的坐标(行,列),然后用上面的颜色算法直接得到该色块的颜色,一句话搞定(Point pt是鼠标位置)。 标移动时会绘制光标,为了减少性能开销,不能直接Refresh()/Invalidate()控件,使用Invalidate(Rectangle)来重绘被鼠标弄脏的那个区域。所以用了两个小矩形保存旧光标和新光标的区域,然后在鼠标事件中更新(和鼠标取色一起)。 然后鼠标移动事件里重绘时稍微把区域扩大点(避免留下难看的边框)。 最后一行「OnColorChanged();是用来引发颜色改变事件。把事件写出来基本就完成了。 然后再完善下属性之类的,就可以在你的程序里使用了。 程序简单适合新手学习使用。 注意: 开发环境为Visual Studio 2010
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

末零

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

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

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

打赏作者

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

抵扣说明:

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

余额充值