見出し画像

立ち絵にソフトマスクをかける(Unityメモ)

 前回の続き。シェーダの勉強も兼ねて、立ち絵にソフトマスクをかけて表示する処理を実装しました。

立ち絵の背景を切り抜きたい

 立ち絵は挿絵風にするために背景込みで作っているのですが、絵をホーム画面でそのまま表示すると、背景もそのまま表示されます。ホームではキャラだけを表示したいです。
 人間がデジタル上で絵を描く場合は背景を透過したデータを入手できますが、Stable Diffusionに「依頼」する場合、少なくとも執筆時点で得られる絵は背景が透過されません。そこでキャラと背景の描かれた絵から背景を切り抜く必要があります。
 Unityの場合、背景を切り抜く方法はいろいろあります。
・マスク画像を作成しておき、SpriteRendererのスプライトマスクを使う
・立ち絵にアルファチャネル(ピクセルごとの不透明度の画像)を格納しておき、アルファチャネル込みで表示
・アルファチャネル用のマスク画像を別に与えてアルファブレンドする

 このうち、アルファブレンドするマスクのことをソフトマスクと呼ぶようです。普通のマスクは透過するかしないかの二値(ハードマスク?)なので、これとの対比なのでしょう。

マスク画像の作り方にもいくつか方法がありますが、今回は以前立ち絵から作ったデプスマップをそのまま使いました。レベル補正しておらず、遠景のアルファ値が0.0・キャラ部分が1.0のようになっていないので、キャラは半透明ぎみに表示されています。

スプライトマスク

 上の記事でも触れていますが、スプライトマスクの機能を使うと機能追加することなくマスク画像を指定して背景を切り抜けます。しかしUnityのスプライトマスクは二値マスクなので、境界がギザギザして表示されます。一方、アルファブレンドだと滑らかに背景を切り抜いてキャラを表示できます。

立ち絵にアルファチャネルを格納

 もっと分かりやすい方法として、立ち絵のデータを作る時点でピクセルごとの透明度をアルファチャネルに格納しておき、Unityでは単にアルファチャネル込みで画像を表示させる方法もあります。
 当初はこの方法を試したのですが、アルファチャネルを適用しないときに半透明の部分で画像が荒れることが分かったので、次の方法を採用することにしました。

左:アルファチャネル込みの画像を、テクスチャ設定のアルファソースを「なし」で読み込んだ場合。アルファ値の低い遠景の部分で表示が荒れている
右:作ったソフトマスク用シェーダを使い、マスク画像を適用しない場合(つまりアルファチャネルなしの画像をそのまま表示)。遠景も荒れていない。Noteでの画像だと分かりづらいけど…

ソフトマスク用シェーダを使ってアルファブレンドする

 アルファチャネル用の画像を別に与えてアルファブレンドする処理を自前で実装する方法です。Unityの場合はシェーダを使うと高速に描画されます。面倒そうなので避けていたのですが、結局はシェーダを作ることになりました。

ソフトマスク用シェーダを使う

シェーダーグラフで実装

 シェーダの実装では、UnityだとシェーダーグラフというGUIを使えるので多少楽でした。
 手順は以下の記事が参考になりました。古い記事なので今のシェーダーグラフと構造が違うのですが、シェーダの組み方の考え方が参考になりました。
後編から前編に飛ぶリンクが壊れているので、こちらで前編と後編の両方を貼っておきます。

 2022年現在のシェーダーグラフのGUIについては、以下の記事が参考になりました。

 作ったソフトマスク用シェーダは、外部から与えるプロパティを2つにしました。
・Mask:マスク画像のテクスチャ。マスク画像はアルファソースを「グレースケールから」に設定しておく。(グレースケールの)黒で表示画像を透過、白は表示画像を残す
・Rate:マスク適用度(白1.0とマスクの線形補間)。Rate=0.0でマスク適用なし。Rate=1.0で完全適用。

 あとMainTexというプロパティが表示画像に対応するのですが、この名前にしておくとUnity側で自動的に入力テクスチャとして設定してくれるようです。

作ったシェーダーグラフ

 作ったシェーダを適用すると、きれいに表示できました。マスク画像が完全な白黒ではないので、キャラの部分もやや透けています。


左:アルファチャネル込みの画像をそのまま表示
右:作ったソフトマスク用シェーダを使い、マスク画像を適用した場合

シェーダを呼び出すまで

 シェーダを作る作業より、画像にシェーダを適用するまでの実装が手間でした。
 シェーダを作っただけでは画像にソフトマスクをかけられません。シェーダをマテリアルに設定しておいて、マテリアルをスプライトレンダラに設定し、スプライトレンダラに表示したい画像のスプライトを設定することでようやくソフトマスクをかけることができます。
つまり、画像アセット→スプライト→立ち絵のスプライトレンダラ→マテリアル→シェーダの長い旅です。(UIのImageでも同様)
 ここでポイントとなるのが、マテリアルの扱いです。マテリアルはシェーダのパラメータを保持するクラス、という位置づけになりますが、Inspector上で設定したマテリアルは別のGameObjectと共有されます。そのため何も考えずに立ち絵のGameObjectを複数枚作ると、ある立ち絵のマスクを変更すると別の絵のマスクも変更されてしまいます。

プレハブでGameObjectごとマテリアルを複製

 ただし立ち絵1枚を表示するためのGameObjectをプレハブにしておけば、Instantiateするときに子要素も含めた複製が作られます。マテリアルも複製されるので、立ち絵を複数枚表示するときでも問題は出ません。
 というわけで、立ち絵を表示するためのGameObjectからプレハブを作成して保存しました。

プレハブのインスペクタ。InstantiateAsync後、未設定の箇所をスクリプトで設定

 スクリプトでプレハブから2枚の立ち絵オブジェクトを生成し、キャラごとにマスクの適用度を変えて表示してみました。右のキャラはマスクを弱く適用しています。

キャラごとにマスク適用度を変えて表示。左:Rate(マスク適用度)=1.0  右:Rate=0.4


この記事が気に入ったらサポートをしてみませんか?