替球隊換顏色-2D Sprite Color Mask

因為開發中的遊戲有個需求,希望能讓玩家每次打比賽都能對上一些不同球隊的隊伍

所以希望能夠不增加太多資源下,做出可以更換任意顏色隊服的功能.

找了一下網路資料,大致歸類兩種方法 :

方法一 : 在script中copy 原本的那一份2D texture , 並將要替換的顏色擷取下來,置換成想要的顏色

(參考連結 : https://forum.unity3d.com/threads/best-easiest-way-to-change-color-of-certain-pixels-in-a-single-sprite.223030/

方法二 : 客製化shader ,在GPU中動態的置換顏色.

原本使用方法一, 原因是它可以任何資源都不增加, 只耗到memory 的內存空間, 且也不用寫shader.但是,我錯了...Orz. 對於靜態圖片而言,這種方法置換顏色是OK的. 但是我的case是這些2D sprite 會有animation, 你不只需要轉換顏色而已,還要script 產生animation clip 和 controller. 這太瘋狂我無法承受 ...

所以只好選用第二種方法,乖乖去寫shader...

方法二的詳細概念是為你要置換的顏色準備一份mask texture, 指定到你客製化的shader中,再利用變數 mask_color 更改成你需要的color.

以下是方法二的做法 :

首先我們將原2D 材質圖進PS 進行mask 的製作. 我主要將上衣跟帽子的部分挑出,當成是可以置換顏色的部分. 

  • 前後背景取樣衣服跟帽子

  • 利用顏色範圍的功能選取衣服跟帽子

  • 貼至另一圖層

這邊注意的是,選取的部分要全白(RGB值 =0) ,非選取部分全黑(RGB值 = 255)

我是將原選取圖在做一次容忍度=0的選取,將選取的範圍再一次塗白! 

OK, 我們回到Unity寫Shader惹....這邊不會講Shader的基本,只針對怎麼處理做描述

基礎部分可以找找Unity Shader Lab 網路文章會有詳細介紹.

新增一個Shader, 取名 sprite-teamcolor ,我是先拿原本處理sprite的shader - sprite-default shader

copy 內容下來改的.  右圖是sprite-default的原始CG code.(部分截圖)

 

我們宣告 _Mask 2D texture 和___MaskColor 兩個變數在Properties block.

_Mask 是個2D texture ,就是準備要放進我們之前準備的mask texture.

__MaskColor 則是可以讓我們指定要置換的顏色的設定.

我們一樣在 Pass CG block 中宣告同名的對應變數

在這之前先回到Unity , Create Material - "TeamColor" ,把 shader 指定成sprite-teamcolor, 並且把 所做的Mask圖指定進去. 

首先,先來段以前當工程師時常講的口號
"先講求不傷身體,再來講求藥效"
我們先改一段只要能夠將原本sprite 顯現出來的CG code 就好
 
這段是在片元著色器階段,先利用 tex2D 的function將材質座標(uv_map)對應的材質顏色取出, 我們有兩張材質圖,一張是原sprite所屬的2D texture, 而另一張是我們剛做好的 Mask 圖. 我們都取出存成 _Main_color 和 _Mask_var
但是這階段我們先不用_Mask_var
 
color_phase0 只取用_Main_color 和原 shader 的_Color變數混色的結果

color_phase0 所輸出的是原圖

 

我們來解釋一下接下來怎麼做....
基本上的想法是用mask 圖中的白色部分將原圖的部分剃除,接著用mask color 來取代即可
如果寫C# code 會是很簡單的for + if / else 就解決了, 但是我們現在在GPU的pipeline 渲染流水線中
一切的data都用加減乘除來做才行,那...該怎麼做呢?
 
先有一個background knowledge, 就是RGBA的表示範圍是介於 0~1 之間(RGB全0代表黑, 全1代表白)
所以可以想成需要產出兩張圖,一張是原圖但要扣除mask的部分,另一張是mask部分加上mask color的圖
兩張一起混色(相乘) 即可,但需要注意的是 不能混色的部分要留白 也就是RGB為1 ,這樣跟另一張圖相乘混色時
就會採用對方的顏色了! 這個怎麼做? 我們是用顏色反差的概念,也就是 (1.0 - 原RGB值)得到反差顏色
這樣一方面有顏色資訊的部分仍可保持資訊,需要mask的部分可以轉全0或全1(也就是全黑或全白) 來跟另一張圖做剔除或取色
 
以下就是step by step ,我們先做將原圖做mask部分的剔除
color_phase1 就是原圖color 跟mask 做相減,因為 mask 需要剔除部分為白色,也就是1 , 所以mask部分RGB會呈現負值,經過saturate後
補回 0 ,也就是黑色 ,看下圖,帽子跟上衣都為黑色了!
  • 但這需要做一次反差,將要處理mask的部分改正回1,所以我們做一次反差

  • 反差的圖長這樣 (color_phase2)

接下來,我們處理另一張圖, 這張是mask部分跟mask_color 做混色

  • color_phase3結果

  • 為了方面辨識,我們將mask_color調成紅色, 可以看到有mask的部分改紅色了

恩...mask部分被我們取到了,但其他部分是黑色的,也就是為0, 依樣我們做反差,轉成1的資訊

color_phase4的結果

終於到最後一步了,將兩張圖相乘混色! 因為我們都將需要的資訊轉成1或其反差值,所以相乘不會丟掉該有的資訊!我將phase5 , phase6一併寫出.

  • color_phase5 結果

  • 再做一次反差, Bingo ! 顏色置換完成!!

Bingo!! Sprite mask color 被我們做出來啦 ^^
最後,整理一下CG code ,不要被人笑我們寫code醜 ~~