话说前段时间一个朋友找到我,说是让做个能够改变拍摄到汽车车身颜色的Demo,具体需求就是:
1.打开摄像机拍摄车体,点击车身能够拾取到车身的颜色;
2.将车身上颜色和拾取到的颜色相同的部位颜色改变为指定的颜色;
额..听上去优点绕口,不过对于程序员来说不是太难理解的啦,对吧!就是两点功能,拾取颜色和替换颜色而已啦!
一.拾取颜色
首先进行分析下,我们拾取颜色是经过鼠标或者手指点击需要拾取颜色的部位,然后读取点击部位的像素点进行存储,以便接下来进行颜色相似度判断,到这里拾取颜色的问题基本就简化了,拾取颜色我们一般用Texture2D里面的ReadPixels()方法,然后将颜色以Sprite.Create()的方式显示出来
拾取颜色的主要代码如下:
下面展示下拾取颜色主体功能代码:
IEnumerator ScreenShot() { m_texture = new Texture2D(xCount, yCount, TextureFormat.RGB24, false); yield return new WaitForEndOfFrame(); m_texture.ReadPixels( new Rect( (int)Input.mousePosition.x - (int)(xCount / 2), (int)Input.mousePosition.y - (int)(yCount / 2), xCount, yCount), 0, 0);//获取鼠标点击部位的像素点 m_texture.Apply(); pickColorImage.sprite = Sprite.Create(m_texture, new Rect(0, 0, xCount, yCount), Vector2.zero); //将拾取到的颜色以Sprite的形式显示出来并保存 orgCarColor.color = pickColorImage.sprite.texture.GetPixel(1,1); btnSetColorImage.sprite = whiteCircle; btnSetColorImage.color = pickColorImage.sprite.texture.GetPixel(1,1); } public void OnDrag(PointerEventData eventData) //鼠标点击拖动图片 { Vector3 gloalMousePos; if (isPick && RectTransformUtility.ScreenPointToWorldPointInRectangle(this.transform as RectTransform, eventData.position, eventData.pressEventCamera, out gloalMousePos)) { mouse.transform.position = gloalMousePos; StartCoroutine(ScreenShot()); } } public void OnPointerDown(PointerEventData eventData) //鼠标点击处设置图片的位置 { Vector3 gloalMousePos; if (isPick && RectTransformUtility.ScreenPointToWorldPointInRectangle(this.transform as RectTransform, eventData.position, eventData.pressEventCamera, out gloalMousePos)) { mouse.transform.position = gloalMousePos; StartCoroutine(ScreenShot()); } }
二.替换颜色
替换颜色牵扯到RGB和HSV转换问题,RGB和HSV之间转换有参考公式(RGB和HSV转换公式);
HSV颜色空间
HSV(hue,saturation,value)颜色空间的模型对应于圆柱坐标系中的一个圆锥形子集,圆锥的顶面对应于V=1。它包含RGB模型中的R=1,G=1,B=1三个面,所代表的颜色较亮。色彩H由绕V轴的旋转角给定。红色对应于角度0°,绿色对应于角度120°,蓝色对应于角度240°。在HSV颜色模型中,每一种颜色和它的补色相差180°。饱和度S取值从0到1,所以圆锥顶面的半径为1。HSV颜色模型所代表的颜色域是CIE色度图的一个子集,这个模型中饱和度为百分之百的颜色,其纯度一般小于百分之百。在圆锥的顶点(即原点)处,V=0,H和S无定义,代表黑色。圆锥的顶面中心处S=0,V=1,H无定义,代表白色。从该点到原点代表亮度渐暗的灰色,即具有不同灰度的灰色。对于这些点,S=0,H的值无定义。可以说,HSV模型中的V轴对应于RGB颜色空间中的主对角线。在圆锥顶面的圆周上的颜色,V=1,S=1,这种颜色是纯色。HSV模型对应于画家配色的方法。画家用改变色浓和色深的方法从某种纯色获得不同色调的颜色,在一种纯色中加入白色以改变色浓,加入黑色以改变色深,同时加入不同比例的白色,黑色即可获得各种不同的色调。
HSV颜色空间可以用一个圆锥空间模型来描述。
从 RGB 到HSV 的转换
设 (r, g, b) 分别是一个颜色的红、绿和蓝坐标,它们的值是在 0 到 1 之间的实数。设 max 等价于 r, g 和 b 中的最大者。设min 等于这些值中的最小者。要找到在 HSV 空间中的 (h, s, v) 值,这里的 h ∈ [0, 360)是角度的色相角,而 s, v ∈ [0,1] 是饱和度和亮度,计算为:
max=max(R,G,B)
min=min(R,G,B)
if R = max, H = (G-B)/(max-min)
if G = max, H = 2 + (B-R)/(max-min)
if B = max, H = 4 + (R-G)/(max-min)
H = H * 60
if H < 0, H = H + 360
V=max(R,G,B)
S=(max-min)/max
h 的值通常规范化到位于 0 到 360°之间。而 h = 0 用于 max = min 的(就是灰色)时候而不是留下 h 未定义。
上面我们讲过了HSV的颜色空间,下面回到具体的问题,我们要将与拾取到的颜色相似的部位颜色更改为指定颜色,说到这里有人可能会说:“我直接遍历一下所有的颜色值和拾取到的颜色对比,相同的话就更改为指定颜色不就行了吗?”额...这是个解决思路,我已经试过这个方案,不过结果让人大跌 眼镜,有兴趣可以试试看哈,嗯;
好了,有点罗嗦了,直接上解决方案啦!
颜色转换不能用C#来写的,消耗太大,所以要用Shader来实现,Shader参见:
Shader "MyShader/PointColorEffect" { Properties { _MainTex("Texture", 2D) = "white" {} _MyColor("MyColor",COLOR)=(1,1,1,1) _TargetColor("TargetColor", Color) = (1,0,0) _Near("Near", Range(0, 0.5)) = 0.1 _AddValue("AddValue",Range(0,1))=0.2 _Alpha("Alpha",range(0,1))=1 } SubShader { Cull Off ZWrite Off ZTest Always Lighting Off Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; float3 RGBConvertToHSV(float3 rgb) { float R = rgb.x/255,G = rgb.y/255,B = rgb.z/255; float3 hsv; float max1=max(R,max(G,B)); float min1=min(R,min(G,B)); float del_max = max1 - min1; hsv.z = max1; if (del_max == 0) { hsv.x = 0; hsv.y = 0; } else { hsv.y = del_max / max1; float del_R = (((max1 - R) / 6) + (del_max / 2)) / del_max; float del_G = (((max1 - G) / 6) + (del_max / 2)) / del_max; float del_B = (((max1 - B) / 6) + (del_max / 2)) / del_max; if (R == max1)hsv.x = del_B - del_G; else if (G == max1)hsv.x = (1 / 3) + del_R - del_B; else if (B == max1)hsv.x = (2 / 3) + del_G - del_R; if (hsv.x < 0)hsv.x += 1; if (hsv.x > 1)hsv.x -= 1; } return hsv; } float3 HSVConvertToRGB(float3 hsv) { float R,G,B; //float3 rgb; if( hsv.y == 0 ) { /*R=G=B=hsv.z;*/ R = hsv.z * 255; G = hsv.z * 255; B = hsv.z * 255; } else { float var_r, var_g, var_b; float var_h = hsv.x * 6; if (var_h == 6)var_h = 0; int var_i = (int)var_h;//把var_h转化为整数var_i; float var_1 = hsv.z*(1 - hsv.y); float var_2 = hsv.z*(1 - hsv.y*(var_h - var_i)); float var_3 = hsv.z*(1 - hsv.y*(1 - (var_h - var_i))); if (var_i == 0) { var_r = hsv.z; var_g = var_3; var_b = var_1; } else if (var_i == 1) { var_r = var_2; var_g = hsv.z; var_b = var_1; } else if (var_i == 2) { var_r = var_1; var_g = hsv.z; var_b = var_3; } else if (var_i == 3) { var_r = var_1; var_g = var_2; var_b = hsv.z; } else if (var_i == 4) { var_r = var_3; var_g = var_1; var_b = hsv.z; } else { var_r = hsv.z; var_g = var_1; var_b = var_2; } R = var_r * 255; G = var_g * 255; B = var_b * 255; } return float3(R,G,B); } fixed GetHue(fixed3 rgb) { fixed hue = 0; fixed minValue = min(rgb.r, min(rgb.g, rgb.b)); fixed maxValue = max(rgb.r, max(rgb.g, rgb.b)); fixed delta = maxValue - minValue; if (delta != 0) { if (maxValue == rgb.r) { hue = (rgb.g - rgb.b) / delta; } else if (maxValue == rgb.g) { hue = 2.0 + (rgb.b - rgb.r) / delta; } else { hue = 4.0 + (rgb.r - rgb.g) / delta; } hue /= 6.0; if (hue < 0) { hue += 1.0; } } return hue; } v2f vert (appdata v) { v2f o; o.vertex = mul(UNITY_MATRIX_MVP, v.vertex); o.uv = v.uv; return o; } sampler2D _MainTex; fixed3 _TargetColor; fixed _Near; fixed3 _MyColor; float _AddValue; float _Alpha; fixed4 frag (v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.uv); fixed4 tarCol= tex2D(_MainTex, i.uv); if(_Alpha==1) { fixed distance = GetHue(col.rgb) - GetHue(_TargetColor); if (distance > 0.5) { distance = 1.0 - distance; } else if (distance < -0.5) { distance = 1.0 + distance; } else { distance = abs(distance); } if (distance <= _Near) { //tarCol.r = _MyColor.r; //tarCol.g = _MyColor.g; //tarCol.b = _MyColor.b; fixed3 curHSV=RGBConvertToHSV(col.rgb); curHSV.x=RGBConvertToHSV(_MyColor).x+_AddValue; tarCol=fixed4(HSVConvertToRGB(curHSV),_Alpha); } } else if(_Alpha==0.5||_Alpha==-0.5) { fixed distance = RGBConvertToHSV(col.rgb).y - RGBConvertToHSV(_TargetColor).y; if (distance > 0.5) { distance = 1.0 - distance; } else if (distance < -0.5) { distance = 1.0 + distance; } else { distance = abs(distance); } if (distance <= _Near&&_Alpha==0.5) //黑色 { fixed3 curHSV=RGBConvertToHSV(col.rgb); curHSV.x=RGBConvertToHSV(col.rgb).x; curHSV.y=RGBConvertToHSV(col.rgb).y; curHSV.z=_AddValue; tarCol=fixed4(HSVConvertToRGB(curHSV),0); } if (distance <= _Near&&_Alpha==-0.5) //白色 { fixed3 curHSV=RGBConvertToHSV(col.rgb); curHSV.x=RGBConvertToHSV(col.rgb).x; curHSV.y=0; curHSV.z=_AddValue; tarCol=fixed4(HSVConvertToRGB(curHSV),0); } } else if(_Alpha==0) { tarCol= tex2D(_MainTex, i.uv); } return tarCol; } ENDCG } } }
HSVToRGB和RGBToHSV直接套用公式;
接着我们用屏幕后期处理来实现颜色转换(指定Shader中的一些属性就可以了):
private void OnRenderImage(RenderTexture source, RenderTexture destination) { this.Material.SetColor("_TargetColor", this.TargetColor); this.Material.SetFloat("_Near", this.Near); this.Material.SetColor("_MyColor", this.MyColor); this.Material.SetFloat("_AddValue", this.AddValue); this.Material.SetFloat("_Alpha", this.Alpha); Graphics.Blit(source, destination, this.Material); }
三.效果图
源碼qq群里自取