【Unity/ShaderGraph】CADライクな断面表示

↓のような表示をするため紆余曲折あったので、共有します
![ezgif.com-gif-maker (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1317891/fd6aa4a6-51b6-a4dc-39b9-67dfafa6b63c.gif)

22/0722 断面にハッチングができるようになりました
データのみ先に更新します。記事は後程更新予定です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1317891/4930112d-9603-af6f-a697-826338963d1f.png)

自分のレベルは
・シェーダー言語さっぱり分からない
・Unity歴は1年
・今回初めてShaderGraphを触った
です。このくらいの人向けの記事です(なのでもっといい方法があるかも)

データはこちら
22/0722 リンク切れ修正、データ更新しました

https://1drv.ms/u/s!Aut71f_-JhpUk-oL-pJ18TEBiG5SSw?e=sfmNZI

───────

# 目的
・切断部に面がある(ように見える)断面表示
・サブメッシュのあるオブジェクトに対応するため、1シェーダーで断面表示をする
・ぴったり重なった面や多少のめり込みに対応する

# 環境
Unity2019.4.2(おそらくこれ以降も可)
20220722　データのほうは　2020.3.22f1に移行しました
今回はライトの情報を取得したいので、URPです

# 前知識

ShaderGraphの概要と使い方は↓の動画を見てください
<iframe width="560" height="315" src="https://www.youtube.com/embed/3TXHMS8xFJ0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

# 方針決め
「Unity 断面表示」などで検索すると、↓がヒットします

https://edom18.hateblo.jp/entry/2017/03/08/090400

https://assetstore.unity.com/packages/vfx/shaders/crosssection-93478

これらは深度バッファなどを利用してクリッピングし、その後断面部分を塗っている…ようです。
切断部に面を張っていないのが参考になりました。
しかしこれらは「マスク用」「断面用」のように、複数のシェーダーで組みになっていて、
今回は1つのシェーダーにしたいので、使えませんでした。


また、切断部に色をつけないのであれば、ディソルブシェーダーというジャンルが
断面表示に近い感じです。クリッピングにはアルファを使っているみたいです。
この方法は1つのマテリアルでも使えます。

https://baba-s.hatenablog.com/entry/2018/01/30/230000


以上から、クリッピングにはアルファを使い、
切断部を単色塗りつぶしをすることにしました。
# 試作(Unlit)
はじめPBRのライティングを無効にする方法がわからず、
Unlitにライティングを追加することにしました。

<details><summary>試作の過程</summary><div>

(参考1)https://qiita.com/o_s_t/items/c546962bbd9f064c9908
UnlitからPBRを再現している方がいました(すごい)。
これを全部見ていくと、PBRの処理がよく理解できそうです。

(参考2)https://blogs.unity3d.com/jp/2019/07/31/custom-lighting-in-shader-graph-expanding-your-graphs-in-2019/
こちらは公式で、ライトの情報を取得する方法が書いてあります。
これは後で断面のライティングをするために使います。

(参考3)https://noahbannister.blog/2019/09/23/unity-lwrp-pbr-shader-node/
こちらは通常のPBR処理を呼び出しています。
今回はこちらを使用。
このままのコードではエラーが出たため、修正
SHADERGRAPH_PREVIEWを使って、プレビュー用の値を設定してやらないといけないみたいです

```ruby:test.hlsl
void LightweightPBR_float
(float3 Albedo,
	float Metallic,
	float3 Specular,
	float Smoothness,
	float Occlusion,
	float3 Emission,
	float Alpha,
	float3 PositionWS,
	float3 NormalWS,
	float3 ViewDirectionWS,
	float FogCoord,
	float3 VertexLighting,
	float3 BakedGI,
	out float4 fragOut)
{
#if SHADERGRAPH_PREVIEW
	fragOut = float4 (1, 1, 1, 1);
#else
	InputData inputData;
	inputData.positionWS = PositionWS;
	inputData.normalWS = NormalWS;
	inputData.viewDirectionWS = ViewDirectionWS;
	inputData.shadowCoord = TransformWorldToShadowCoord(PositionWS);// GetShadowCoord(GetVertexPositionInputs(PositionWS));
	inputData.fogCoord = FogCoord;
	inputData.vertexLighting = VertexLighting;
	inputData.bakedGI = BakedGI;
	fragOut = UniversalFragmentPBR(inputData, Albedo, Metallic, Specular, Smoothness, Occlusion, Emission, Alpha);
#endif
}
```
</div></details>
結果がこちら

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1317891/55a0abdf-9630-e56b-76e0-215e5a36ad48.png)
おおよそ最終版と同じ見た目ですが、他のオブジェクトからの影を受けられていません。

https://nemlog.nem.social/blog/36759

↑こちらの記事を参考すれば実現できそうですが、
この辺りでPBRもmetallic、normal、smoothneesを0にし、emissionを指定すればUnlitのようになることがわかり、PBRにまき直しました。


# 最終版(PBR)
やっていることは
1.スクリプトから断面用プレーンのtransformをマテリアルに送る
2.アルファで切断、
3.断面部から見える裏面をmetallic、normal、smoothneesは0にして真っ黒に
4.断面色＋偽のライティングをemissionに設定
です。

まず、スクリプトです。
[ExecuteInEditMode]と書くとplay時以外にも実行してくれます。
スクリプトからレンダラー経由でマテリアルの情報を書き換えると
インスタンス化(マテリアルがオブジェクトごとに独立)されるので注意。

```ruby:断面指示.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[ExecuteInEditMode]
public class 断面指示 : MonoBehaviour
{
    public Renderer[] レンダラー;
    void Update()
    {
        for (int i=0;i< レンダラー.Length; i++)
        {
            Material[] M = レンダラー[i].materials;
            for(int j = 0; j < M.Length; j++)
            {
                M[j].SetVector("_DANMEN_Pos", transform.position);
                M[j].SetVector("_DANMEN_Vec", transform.up);
            }
            レンダラー[i].materials = M;

        }
    }
}
```

![10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1317891/d33a5eb4-b437-a6a9-dc40-ccf57451664e.png)

スクリプトからパラメーターを変更したい場合は、Referenceの名前を"_パラメーター"などに変更しておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1317891/2500b6e7-6a21-6758-a1e8-36e0edd5d3a7.png)

次にグラフの説明です

![1＿1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1317891/19fed56c-5287-6986-48c4-39723b9acb8f.png)



####①重なったメッシュが汚くなるため、法線内側方向に目立たない範囲で縮める
![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1317891/73cb921c-f689-b6b3-387d-daeea3de53ab.png)
![ezgif.com-gif-maker (2).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1317891/887aa5da-31b1-4556-f719-8087b2db36bd.gif)

####②通常のアルベドのテクスチャ処理
![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1317891/4878f078-b32d-7918-74db-f1873c568468.png)
####③断面部分にライティングをするための処理
![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1317891/f9c2e6c5-67e4-1017-e71b-1529e1df8e4b.png)
いろいろ厳密には違うんでしょうが、自分にはこれが限界でした。
通常だとオブジェクトのノーマルを入れるところに、スクリプトから渡した断面用プレーンのtransformを使ってライティングを行います。
使い勝手をよくするため、断面色をベースカラーにするか、別の色にするか、偽ライティングを有効にするか切り替えできるようにしています。
ライトの情報は↓の記事よりカスタムファンクションを作成しました。

https://blogs.unity3d.com/jp/2019/07/31/custom-lighting-in-shader-graph-expanding-your-graphs-in-2019/

![9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1317891/24a19bcb-6820-c76f-0bd9-7448803d21c3.png)
ライティングの結果、オブジェクトの影が落ちています。
GIも使っているので、照り返しがあるとリアルな面になります。
(positionは裏面のものなので、なんとなくそれっぽいだけですが)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1317891/65a71dff-a887-c0d5-6e4e-bb8c380c1cb1.png)
ただし、他のオブジェクトからの影は落とせないです。
実際にはここに面がないためです(なにか方法があれば教えてください)。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1317891/c03f7b7b-f365-5fa2-059f-4da2baa4a736.png)

####④通常のノーマル処理
テクスチャがある時とない時でBranchで切り替え。
(もっとスマートな方法があるかも)
![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1317891/697eb24a-b76b-9261-5e46-275f1da93b4e.png)
####⑤通常のオクルージョン処理
あっているはず…
![6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1317891/3129c2d7-41c7-12d3-021f-174754a47a87.png)
####⑥断面位置でカットする処理
![7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1317891/bed35f48-9a1a-fbaa-1319-76a9e52f7829.png)
スクリプトから渡した断面用プレーンのtransformを使い、プレーンのどちら側にあるかをベクトルの内積を使って求め、アルファを0or1に切り替えます。

#####⑦表、裏面(断面部)で切り替える
↑で作ったノードをつなぎます
![8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1317891/af017a20-f4ec-6d3c-852a-3f9d55338961.png)

これで完成です

#まとめ
今回初めてShaderGraphを触りましたが、使いやすかったです。
そもそものシェーダーができる範囲がとても広く、見た目だけでなくもはや機能のような
物を作れると分かり勉強になりました。

また今回Qiitaで記事を書くのが初めてなんですが、そこそこ時間がかかり、
丁寧な記事を書く先人に一層感謝しました。

以上です。
ありがとうございました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1317891/7beb411a-2850-5a15-68aa-4096b76b3adc.png)