Unity实现调色板
🍀 Unity实现调色板
Unity自带的调色板是如何实现的?RGB和色彩空间中的HSV有什么关系?
RGB
RGB分别对应的是Red(红)、Green(绿)、Blue(蓝),又称三原色光。通过对红、绿、蓝三个颜色通道(每个颜色通道都存放着图像中颜色元素的信息)的变化以及它们相互之间的叠加来得到各式各样的颜色的。即将这三种颜色在不同比例和强度上混合,可以得到其他各种颜色。
红、绿、蓝三个颜色通道每种色各分为256阶亮度,0时最暗,255时最亮。所以三色灰度都为0时,是最暗的黑色调;三色灰度都为255时,是最亮的白色调。
世界上任何一种颜色的“颜色空间”都可定义成一个固定的数字或变量。RGB(红、绿、蓝)只是众多颜色空间的一种。采用这种编码方法,每种颜色都可用三个变量来表示-红色绿色以及蓝色的强度。
HSB(HSV)
RGB是对机器友好的编码方式,想要知道色彩的明亮程度,有一种更友好的编码方式就是HSB和HSL。
HSB还有另一种对等的说法即HSV。
HSB分别代表以下含义:
H(Hub):色相。在0~360°的标准色轮上,色相是按位置度量的。黑色和白色无色相。
S(Saturation):饱和度。表示色彩的纯度,为0时为灰色。白、黑和其他灰色色彩都没有饱和度的。在最大饱和度时,每一色相具有最纯的色光。取值范围0~100%。
B(Brightness/Value):亮度。为0时即为黑色。
调色板的实现
首先我们需要明确每一部分都指代的是什么。当颜色改变时,paint模块会相应的获取到颜色的值并改变;hue就是HSV中的H(色相);Saturation中的横轴是HSV中的S(饱和度),纵轴是HSV中的V(亮度)。
unity有RGB和HSV之间互相转换的方法,我们可以直接使用,将模块的HSV值转换成RGB,或者是传入一个默认的RGB色值,定位调色板中的具体位置。
private Color HSVToRGB(Vector4 hsv)
{
var color = Color.HSVToRGB(hsv.x, hsv.y, hsv.z);
color.a = hsv.w;
return color;
}
public void RGBToHSV(Color color)
{
Color.RGBToHSV(color, out currentColorHSV.x, out currentColorHSV.y, out currentColorHSV.z);
// 更新调色板的位置
}
Hue
我们需要通过hue模块中滑块的位置来转换RGB色值
currentColorHSV
是一个初始化为private Vector4 currentColorHSV = new Vector4(0, 1, 1, 1);
的坐标,x代表hue的位置,y代表S,z代表V,w代表透明度。
private Color GetHue(float y)
{
var hueHSV = new Vector4(y, currentColorHSV.y, currentColorHSV.z, 1);
return HSVToRGB(hueHSV);
}
alpha
该模块的处理方式和Hue模块类似,只需要传入alpha模块中滑块的位置即可
private Color GetAlpha(float y)
{
var hueHSV = new Vector4(currentColorHSV.x, currentColorHSV.y, currentColorHSV.z, y);
return HSVToRGB(hueHSV);
}
Saturation
该模块内部包含了一个能够滑动的图标,我们需要给这个图标规定能够滑动的范围limitBounds
limitBounds
的值需要根据Saturation的大小来设置,由于此处的Saturation为一个边长为256的正方形,所以设置的边界值如下
然后我们需要获取滑块的位置,此处需要进行坐标的转换
public Camera m_camera;
public RectTransform content;
public Vector3 contentPoint;
public Vector4 limitBounds;
public ScrollRect.ScrollRectEvent onValueChanged;
private RectTransform rect;
private Vector3 screenPoint;
public override void OnPointerDown(PointerEventData eventData)
{
screenPoint = m_camera.WorldToScreenPoint(transform.position);
var world = m_camera.ScreenToWorldPoint(new Vector3(eventData.position.x, eventData.position.y, screenPoint.z));
contentPoint = rect.InverseTransformPoint(world);
contentPoint.x = Mathf.Max(limitBounds.x, Mathf.Min(limitBounds.y, contentPoint.x));
contentPoint.y = Mathf.Max(limitBounds.z, Mathf.Min(limitBounds.w, contentPoint.y));
content.anchoredPosition = contentPoint;
onValueChanged.Invoke(contentPoint);
}
public void OnDrag(PointerEventData eventData)
{
screenPoint = m_camera.WorldToScreenPoint(transform.position);
var world = m_camera.ScreenToWorldPoint(new Vector3(eventData.position.x, eventData.position.y, screenPoint.z));
contentPoint = rect.InverseTransformPoint(world);
contentPoint.x = Mathf.Max(limitBounds.x, Mathf.Min(limitBounds.y, contentPoint.x));
contentPoint.y = Mathf.Max(limitBounds.z, Mathf.Min(limitBounds.w, contentPoint.y));
content.anchoredPosition = contentPoint;
onValueChanged.Invoke(contentPoint);
}
然后在colorpick组件中,我们需要获取该点的位置,同时转换成为RGB,更新即可。
// 根据Saturation中滑块的位置,转换成为HSV
private Vector2 GetSaturationHSV(Vector2 point)
{
var hsv = new Vector2();
hsv.x = point.x / saturation.rectTransform.sizeDelta.x + 0.5f;
hsv.y = point.y / saturation.rectTransform.sizeDelta.y + 0.5f;
return hsv;
}
// 根据HSV值得到滑块的位置,相当于一个逆向操作
private Vector2 GetSaturationPoint(Vector4 hsv)
{
var point = new Vector2();
point.x = (hsv.y - 0.5f) * saturation.rectTransform.sizeDelta.x;
point.y = (hsv.z - 0.5f) * saturation.rectTransform.sizeDelta.y;
return point;
}
// 更新Saturation模块中滑块的位置
public void UpdateSaturationPoint(Vector4 hsv)
{
var saturationPoint = GetSaturationPoint(hsv);
scrollRectSaturation.content.anchoredPosition = saturationPoint;
}
最后,我们不要忘记加上对对滑块值改变的监听事件。
那么如果已知一个值,如何通过传入的值将整个面板的滑块进行重新定位呢?
只需要调用RGBToHSV
函数即可,最后为什么要调用一遍OnHueClick
的原因是该事件监听的是Hue中滑块的移动,即Hue滑块值的改变。那么这个值在传入RGB色值转换成为HSV后不一定会改变,导致Saturation
模块不会进行刷新操作,所以可以直接调用一遍监听事件。
public void RGBToHSV(Color color)
{
Color.RGBToHSV(color, out currentColorHSV.x, out currentColorHSV.y, out currentColorHSV.z);
scrollbarHue.value = 1 - currentColorHSV.x;
OnHueClick(scrollbarHue.value);
}
paint
只需要在HSV改变的时候将转换后的RGB给paint模块即可
private void PaintChange()
{
paint.color = HSVToRGB(currentColorHSV);;
}