サークル獏の佐藤敏 Unityとか備忘録

サークル獏の佐藤敏がUnityとかで知ったTipsを書いておく備忘録です。

【教えてください】頂点シェーダで非表示を決定→フラグメントシェーダーでdiscardする適切な方法

Unityの頂点シェーダで非表示を決定→フラグメントシェーダーでdiscardするときの、適切&エレガントな方法を教えてくださいm(__)m

なんでそんなことをしたくなったか:前半

f:id:VinSatoo:20181012232526p:plain
こちらは我らがImagineGirlsのR1N4 v2さんにUTS 2.0.5Test版を適用したものである。設定は割と単純で、眉毛貫通とかもしていない。

github.com

f:id:VinSatoo:20181012232158p:plain
f:id:VinSatoo:20181012232823p:plain
これに、R1N4さんの顔のUVを参考にしつつ作ったOutline_Samplerを適用するとこんな感じになる。
口元にあった変な線が消えた。
ついでに顔の輪郭線も強弱ができた。
(カラダは別マテリアルなのでそのまま)

なんでそんなことをしたくなったか:後半

f:id:VinSatoo:20181012232936p:plain
ところがここに、自作のアレなBlendShapeを適用するとまた変な線がでてきてしまう。
(口の下のところ)
恐らく原因はBlenderでBlendShapeを作ったとき、ポリゴンの配置がヘンになってしまったからだ。
しかしモデリング技術がアレなのはどうにもならないし、トゥーン系の表現ならこの線さえ消えてくれればどうにか妥協できないこともない。

f:id:VinSatoo:20181012233143p:plain
ちなみにこの線、Outline由来なのは間違いない(Outline_Colorの色を変えるとそれに従って変わる)。
だが、Outline_Widthを0にしてもこのように残ってしまう。
画像を見てもわかる通り、顔の他の輪郭線は消えたのに口の周りだけ線が残る。

ムリヤリどうにかしてみる

そこで、「UCTS_Outline.cginc」をのぞいてみる(以前のバージョンでは「MyUCTS_Outline.cginc」だった模様)。
散々試行錯誤した結果、以下のようにしたらうまくいった。
(うまくいった結果を先に貼っておく)
f:id:VinSatoo:20181012233512p:plain

(以下の改造は自己責任でお願いします。また、この改造の内容についてUTS公式さんなどに問い合わせないでくださいm(__)m )

VertexOutputに、1つ値を追加する。

struct VertexOutput {
                float4 pos : SV_POSITION;
                float2 uv0 : TEXCOORD0;
                float3 normalDir : TEXCOORD1;
                float3 tangentDir : TEXCOORD2;
                float3 bitangentDir : TEXCOORD3;
				fixed4 disableFlag : Color; //これを加える
            };

vert関数の後半をこのように変える。

#ifdef _OUTLINE_NML
                //v.2.0.4.3 baked Normal Texture for Outline
				if (Set_Outline_Width <= 0) { //ここから
					o.disableFlag.r = 1;
					return o;
				}
				o.disableFlag.r = 0; //ここまでが追加された内容
				o.pos = UnityObjectToClipPos(lerp(float4(v.vertex.xyz + v.normal*Set_Outline_Width,1), float4(v.vertex.xyz + _BakedNormalDir*Set_Outline_Width,1),_Is_BakedNormal));
#elif _OUTLINE_POS

frag関数の冒頭のこのように変える。

			float4 frag(VertexOutput i) : SV_Target{
				if (i.disableFlag.r == 1) {//ここから
					discard;
				} //ここまで

これで、Set_Outline_Widthが0以下のときは頂点シェーダでdisableFlag.rに1が入る。
そしてフラグメントシェーダの冒頭でdisableFlag.rが1ならdiscardで表示が破棄される。
(Set_Outline_Widthはアレコレ計算した結果の主線の太さらしい)
f:id:VinSatoo:20181012232936p:plainf:id:VinSatoo:20181012233512p:plain
(before→afterで再掲)
その結果、↑のようにヘンな線が消えてくれる。

もうちょっと綺麗な適切な書き方はないのか

しかし色とナンの関係もないのにColorセマンティクスをムリヤリ加え、それをフラグとして使って値を放り込むというのは気持ちが悪い。
また、if文はシェーダーを遅くする原因というのも聞いたことがある。
frag関数のほうはともかく、vert関数までifを使っていいものかという疑問が残る。
そもそも頂点シェーダで「このケースは描画破棄」とできれば一番ラクなのだが……

というわけで、シェーダに詳しい方、こういうケースでもっとエレガントな書き方があれば教えてくださいm(__)m