はじめに
クロマキーに関してはもう既に色々なアセットがあるとは思うのですが、uWindowCapture で使えるクロマキーシェーダがほしいとの要望を受けたので作ってみました。
デモ
任意の色相・彩度・明度の範囲を抜くことが出来ます。下の例ではピンク色を背景色に指定しています。影もレンダラで指定されていれば出力されます。
こちらは uWindowCapture で FaceRig のウィンドウを持ってきた形です。FaceRig ではウェブカメラ経由の出力もサポートしていますが、とりあえず uWindowCapture との組み合わせデモということで...。
環境
- Unity 2018.2.12f1
ダウンロード
Release から一番新しいバージョンの .unitypackage をダウンロードして自プロジェクトに展開してください。
使い方
シェーダの種類
4 種類のシェーダが用意されています。
- ChromaKey/Unlit/Cutout
- ChromaKey/Unlit/Transparent
- ChromaKey/Standard/Cutout
- ChromaKey/Standard/Transparent
Unlit 系はライティングがなされないシェーダで、Standard 系は Surface Shader 相当のライティングが行われるものになります。Cutout 系は AlphaTest
の描画キューでアルファテストで背景色を抜いたもの、Transparent 系は Transparent
の描画キューでアルファテストに加え境界を半透明に抜いて滑らかにする処理が入っています。次のスクリーンショットは上が Cutout、下が Transparent になります。
また、全てのシェーダで共通して ShadowCaster
パスによって切り抜いた形の影が落ちるようになっているので、要らない場合は MeshRenderer
の Cast Shadows
を OFF にしてください。大体のユースケースでは Unlit/Transparent
で良いと思います。
インスペクタ
マテリアルにシェーダを適用すると、インスペクタは通常のマテリアルに関係する Material
セクションと、クロマキー処理に関係する Chroma Key
セクションに別れます。Chroma Key
セクションは共通なので、例として Unlit/Transparent
を見てみます。
Color
でクロマキーで抜きたい背景色を選びます。次に HSV 領域での範囲を Hue Range
、Saturation Range
、Brightness Range
で指定します。概念的には次のように HSV 空間を切り取る感じになります。
コツとしてはクロマキー元になる映像次第ですが Hue をなるべく絞り、Saturation や Brightness をそこそこ広めに取ると良いと思います。
制約
Standard の Transparent のみ、ZWrite
の ON/OFF の切り替えのフラグがセットできないためプロパティに出ていません。欲しい場合は ColorMask 0
した Pass を自前で追加してください。
コード解説
クロマキー処理は次のサイトを参考に分岐なしの実装になっています。
クロマキー関連処理は ChromaKey.cginc
にまとめて書いてあります。
inline float3 ChromaKeyRGB2HSV(float3 rgb) { float4 k = float4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); float4 p = lerp(float4(rgb.bg, k.wz), float4(rgb.gb, k.xy), step(rgb.b, rgb.g)); float4 q = lerp(float4(p.xyw, rgb.r), float4(rgb.r, p.yzx), step(p.x, rgb.r)); float d = q.x - min(q.w, q.y); float e = 1e-10; return float3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); }
こうして RGB 色空間から HSV 色空間に変換できた値を使って色相、彩度、明度それぞれで範囲内に収まっているかのチェックを行います。
float4 _ChromaKeyColor; float _ChromaKeyHueRange; float _ChromaKeySaturationRange; float _ChromaKeyBrightnessRange; inline float3 ChromaKeyCalcDiffrence(float4 col) { float3 hsv = ChromaKeyRGB2HSV(col); float3 key = ChromaKeyRGB2HSV(_ChromaKeyColor); return abs(hsv - key); } inline float3 ChromaKeyGetRange() { return float3(_ChromaKeyHueRange, _ChromaKeySaturationRange, _ChromaKeyBrightnessRange); } inline void ChromaKeyApplyCutout(float4 col) { float3 d = ChromaKeyCalcDiffrence(col); if (all(step(0.0, ChromaKeyGetRange() - d))) discard; } inline void ChromaKeyApplyAlpha(inout float4 col) { float3 d = ChromaKeyCalcDiffrence(col); if (all(step(0.0, ChromaKeyGetRange() - d))) discard; col.a *= saturate(length(d / ChromaKeyGetRange()) - 1.0); }
Cutout のシェーダでは ChromaKeyApplyCutout()
を、Transparent のシェーダでは ChromaKeyApplyAlpha()
を通過するようになっています。ChromaKey_Unlit.cginc
のフラグメントシェーダを見てみます。
fixed4 frag(v2f i) : SV_Target { float4 col = tex2D(_MainTex, i.uv) * _Color; #ifdef CHROMA_KEY_ALPHA ChromaKeyApplyAlpha(col); #else ChromaKeyApplyCutout(col); #endif UNITY_APPLY_FOG(i.fogCoord, col); return col; }
頂点シェーダも含めほとんどのコードは通常のシェーダと同じです。カラーの計算の際に 1 行だけ挟んでアルファテストまたは透明度の計算を行っている形です。なので、自分のシェーダに組み込みたいときは ChromaKey.cginc
をインクルードして、フラグメントシェーダのカラー出力部にいずれかの関数を挟めば良い形になっています。
おわりに
一般的なグリーンバックでの映像ソース用途だけでなく、uWindowCapture との組み合わせといった特殊なものでも色々と面白いものも出来るかも知れないので、ぜひ何か作った際は教えてください。