1. 程式人生 > >Unity 為隊伍設置不同顏色的shader

Unity 為隊伍設置不同顏色的shader

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: 我的第一反應是提取與紅色相近的顏色,然後變到綠色就可以了,代碼如下: 技術分享
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
嘗試著去優化一下,像下面這樣做一下插值,用顏色的RGB空間距離來判斷距離 技術分享
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