凹みTips

C++、JavaScript、Unity、ガジェット等の Tips について雑多に書いています。

Unity でクロマキーシェーダを作ってみた

はじめに

クロマキーに関してはもう既に色々なアセットがあるとは思うのですが、uWindowCapture で使えるクロマキーシェーダがほしいとの要望を受けたので作ってみました。

デモ

任意の色相・彩度・明度の範囲を抜くことが出来ます。下の例ではピンク色を背景色に指定しています。影もレンダラで指定されていれば出力されます。

f:id:hecomi:20181020013024p:plain

こちらは uWindowCapture で FaceRig のウィンドウを持ってきた形です。FaceRig ではウェブカメラ経由の出力もサポートしていますが、とりあえず uWindowCapture との組み合わせデモということで...。

f:id:hecomi:20181020013041g:plain

環境

  • Unity 2018.2.12f1

ダウンロード

github.com

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 になります。

f:id:hecomi:20181021005632p:plain

f:id:hecomi:20181021005645p:plain

また、全てのシェーダで共通して ShadowCaster パスによって切り抜いた形の影が落ちるようになっているので、要らない場合は MeshRendererCast Shadows を OFF にしてください。大体のユースケースでは Unlit/Transparent で良いと思います。

インスペクタ

マテリアルにシェーダを適用すると、インスペクタは通常のマテリアルに関係する Material セクションと、クロマキー処理に関係する Chroma Key セクションに別れます。Chroma Key セクションは共通なので、例として Unlit/Transparent を見てみます。

f:id:hecomi:20181021004014p:plain

Color でクロマキーで抜きたい背景色を選びます。次に HSV 領域での範囲を Hue RangeSaturation RangeBrightness Range で指定します。概念的には次のように HSV 空間を切り取る感じになります。

f:id:hecomi:20181021010614p:plain

コツとしてはクロマキー元になる映像次第ですが Hue をなるべく絞り、Saturation や Brightness をそこそこ広めに取ると良いと思います。

制約

Standard の Transparent のみ、ZWrite の ON/OFF の切り替えのフラグがセットできないためプロパティに出ていません。欲しい場合は ColorMask 0 した Pass を自前で追加してください。

コード解説

クロマキー処理は次のサイトを参考に分岐なしの実装になっています。

www.laurivan.com

クロマキー関連処理は 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 との組み合わせといった特殊なものでも色々と面白いものも出来るかも知れないので、ぜひ何か作った際は教えてください。

tips.hecomi.com