Unity 為隊伍設置不同顏色的shader
阿新 • • 發佈:2017-06-05
better surf title emp put unity3d mobile sub 4.3 在魔獸爭霸等一些遊戲中,我們通過模型的顏色就能很輕松的區分隊伍,如下:
實現的方法有很多,比如:
1,為不同隊伍各出一張不同顏色的貼圖(Hmmm,war3有的地圖可以容納12只隊伍,美術大大們會很[bu4] 感[da3] 謝[si3]你的)
2,額外用一張灰度圖標記要變色的區域,通過程序來操作這一塊區域變色(much better)
3,不需要任何額外的貼圖,通過分析原圖,直接改變需要變色的區域的顏色。
我們的目的是要實現第3種方法。
環境:Win 10,Unity 5.4.3f1
模型:https://www.assetstore.unity3d.com/en/#!/content/4696
下圖是萌萌噠的Kyle Robot,左圖(紅色)是原型,右圖(綠色)是我們要達成的目標:
不完美的嘗試1:
我的第一反應是提取與紅色相近的顏色,然後變到綠色就可以了,代碼如下:
嘗試著去優化一下,像下面這樣做一下插值,用顏色的RGB空間距離來判斷距離:
Shader "Custom/TeamMaskRgb" { Properties { _MainTex ("Base (RGB)", 2D) = "white" {} _From("From Color",Color) = (1,1,1,1) _To("To Color",Color) = (1,1,1,1) _RgbRange("Rgb Range",Range(0,1))=0.1 } SubShader { Tags { "RenderType"="Opaque" } LOD 150 CGPROGRAM #pragma surface surf Lambert noforwardadd sampler2D _MainTex; fixed4 _From; fixed4 _To; fixed _RgbRange; struct Input { float2 uv_MainTex; } ; void surf (Input IN, inout SurfaceOutput o) { fixed4 c = tex2D(_MainTex, IN.uv_MainTex); // 老師傅們都說不要用if,所以就改成step了! fixed con = step(abs(_From.r-c.r),_RgbRange); con = con + step(abs(_From.g-c.g),_RgbRange); con = con + step(abs(_From.b-c.b),_RgbRange); con = step(3,con); o.Albedo = lerp(c.rgb,c.rgb+_To-_From,con); o.Alpha = c.a; } ENDCG } Fallback "Mobile/VertexLit" }
但是,結果與我的設想卻不大一樣,小Kyle變成了: 這很怪異,Kyle身上的紅色並不純,而我們取色卻只能取一種,雖然我們給了一個允許的範圍,但RGB色彩模式的本質決定了我們取不到我們想要的所有紅色, 還會取到一些我們不想要的顏色。因為: Kyle肩膀處的紅色是接近RGB(255,106,89),我們以此顏色作為要改變的基色調。 Kyle膝蓋處的紅色是接近RGB(135,44,36),雖然數值上與肩膀處的紅色相差很大,但明顯是紅色的,這也是我們想要改變的部位。 Kyle兩眼中間的灰色接近RGB(171,171,173) 我們取_RgbRange=0.38. 那R值允許的範圍就是255-255*0.38<R<255+255*0.38,即158<R<352(大於255我們另外處理,不影響下面的結論),可以發現兩眼中間的灰色的R值在取色範圍 內,而膝蓋處的暗紅色卻不在範圍內。這就解釋了為什麽兩眼中間變綠了,而膝蓋為什麽沒變綠。 不完美的嘗試2
Shader "Custom/TeamMaskRgbEx1" { Properties { _MainTex ("Base (RGB)", 2D) = "white" {} _From ("From Color", Color) = (1,1,1,1) _To ("To Color", Color) = (1,1,1,1) _Range ("Range", Range (0.0, 2.0)) = 0.01 } SubShader { Tags { "RenderType"="Opaque" } LOD 150 CGPROGRAM #pragma surface surf Lambert noforwardadd sampler2D _MainTex; fixed4 _From; fixed4 _To; half _Range; struct Input { float2 uv_MainTex; } ; void surf (Input IN, inout SurfaceOutput o) { fixed4 c = tex2D(_MainTex, IN.uv_MainTex); fixed colorDistance = (c.r - _From.r)*(c.r - _From.r) + (c.g - _From.g)*(c.g - _From.g) + (c.b - _From.b)*(c.b - _From.b); o.Albedo = lerp(c.rgb,(_To.rgb - _From.rgb + c.rgb), saturate(1 - colorDistance / (_Range * _Range))); o.Alpha = c.a; } ENDCG } Fallback "Mobile/VertexLit" }
但效果卻依然不能讓我滿意: 那到底怎樣才能準確取到與紅色相近的顏色呢?答案是利用顏色的HSV模型。 HSV模型的定義以及其與RGB模型之間的轉換請參考:https://zh.wikipedia.org/wiki/HSL%E5%92%8CHSV%E8%89%B2%E5%BD%A9%E7%A9%BA%E9%97%B4 這裏不贅述了。 不完美的嘗試3 如果你了解了顏色的HSV模型,應該就會想到只要找到紅色色相(Hue)附近的顏色,然後改變他們就可以了,我也是這樣想的:
Shader "Custom/TeamMaskHue" { Properties { _Color ("Color", Color) = (1,1,1,1) _MainTex ("Albedo (RGB)", 2D) = "white" {} _From("From Color",Color) = (1,1,1,1) _To("To Color",Color) = (1,1,1,1) _FaultTolerant("hue fault-tolerant",range(0,359)) = 2 } SubShader { Tags { "RenderType"="Opaque" } LOD 200 CGPROGRAM #pragma surface surf Standard fullforwardshadows sampler2D _MainTex; struct Input { float2 uv_MainTex; } ; fixed4 _Color; fixed4 _From; fixed4 _To; float _FaultTolerant; float getHue(float3 col) { float r=col.r,g=col.g,b=col.b; float _max = max(r,max(g,b)); float _min = min(r,min(g,b)); float _gradient = _max - _min; float ret = 0; if(_max == r) { ret = (g-b)/_gradient; } else if(_max == g) { ret = 2 + (b-r)/_gradient; } else if(_max == b) { ret = 4 + (r-g)/_gradient; } ret = ret*60.0; if(ret<0) { ret = ret + 360; } return ret; } void surf (Input IN, inout SurfaceOutputStandard o) { fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; float hc = getHue(c.rgb); float hFrom = getHue(_From.rgb); float differ = abs(hc-hFrom); differ = lerp(differ,abs(differ-360),step(180,differ)); o.Albedo = lerp(c.rgb,c.rgb - _From.rgb + _To.rgb, step(differ,_FaultTolerant)); o.Alpha = c.a; } ENDCG } FallBack "Diffuse" }
Emmm,效果如下: 這是怎麽回事呢?難道用色相(Hue)也不能取到真正意義上相近的顏色嗎?那些該死的意料之外的綠色是怎麽回事呢? 上圖是HSV空間模型,從圖中可以很直觀的看到圓錐中心線附近的顏色呈灰色,也就是說不管哪種色相,在其飽和度過低的時候都會表現為灰色, 在其明暗度(Brightness)過低時會表現為黑色。 所以,為了準確找到與指定顏色看上去近似的顏色,我們還需要考慮飽和度(Saturation)和明暗度 (Brightness ,HSV 用Value表示)。 一般的,Hue十分接近,Saturation和Brightness的誤差各不超過0.7的顏色,它們看起來是相似的顏色。 最終代碼如下(含branch優化):
Shader "Custom/TeamMaskFinal" { Properties { _MainTex ("Base (RGB)", 2D) = "white" {} _From("From Color",Color) = (1,1,1,1) _To("To Color",Color) = (1,1,1,1) _HError("Hue Error",range(0,1)) = 0 // 允許的色相誤差 _SError("Saturation Error",range(0,1)) = 0 // 允許的飽和度誤差 _VError("Brightness Error",range(0,1)) = 0 // 允許的亮度誤差 } SubShader { Tags { "RenderType"="Opaque" } LOD 150 CGPROGRAM #pragma surface surf Lambert noforwardadd sampler2D _MainTex; fixed4 _From; fixed4 _To; fixed _HError; fixed _SError; fixed _VError; struct Input { float2 uv_MainTex; } ; fixed3 RGBtoHSV(fixed3 c) { fixed4 K = fixed4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); fixed4 p = lerp(fixed4(c.bg, K.wz), fixed4(c.gb, K.xy), step(c.b, c.g)); fixed4 q = lerp(fixed4(p.xyw, c.r), fixed4(c.r, p.yzx), step(p.x, c.r)); float d = q.x - min(q.w, q.y); float e = 1.0e-10; return fixed3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); } fixed3 HSVtoRGB(fixed3 c) { fixed4 K = fixed4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); fixed3 p = abs(frac(c.xxx + K.xyz) * 6.0 - K.www); return c.z * lerp(K.xxx, saturate(p - K.xxx), c.y); } void surf (Input IN, inout SurfaceOutput o) { fixed4 c = tex2D(_MainTex, IN.uv_MainTex); fixed3 cHSV = RGBtoHSV(c); fixed3 FromHSV= RGBtoHSV(_From); fixed3 ToHSV= RGBtoHSV(_To); fixed diffHue = abs(cHSV.x-FromHSV.x); diffHue = lerp(diffHue,abs(diffHue-1),step(0.5,diffHue)); fixed con = step(diffHue,_HError); con = con + step(abs(cHSV.y-FromHSV.y),_SError); con = con + step(abs(cHSV.z-FromHSV.z),_VError); con = step(2.5,con); fixed3 ret = cHSV + ToHSV - FromHSV; // ret.x = lerp(ret.x,ret.x-1,step(1,ret.x)); // ret.x = lerp(ret.x,ret.x+1,step(ret.x,0)); o.Albedo = lerp(c.rgb,HSVtoRGB(ret),fixed3(con,con,con)); o.Alpha = c.a; } ENDCG } Fallback "Mobile/VertexLit" }
最終效果圖: 用到RGB、HSV互相轉換的優化算法鏈接: http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl
Unity 為隊伍設置不同顏色的shader