Shaderのマテリアルプロパティを見やすくしてみよう

UnityでShaderを書いてますでしょうか。
書いた後は他の方に使いやすいようにマテリアルプロパティをカスタマイズはしていますでしょうか？
しなくても良いけど、した方が良い場合もあります。
私は今がそんな時です！なので色々と調べました！
ここに忘備録として弄り方を残しておきます。

# 前準備
CustomShaderGUIの基本的な事、適応の仕方はこちらのHoloMoto様の記事を読みましょう。

https://redhologerbera.hatenablog.com/entry/2023/01/04/213458

まずはShaderにCustomEditorを定義させて
ShaderGui用のファイルを制作して下さい。


また説明用として下記のようなシェーダーをAmplify Shader Editorで制作しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1071164/2e903b15-ccfe-27b5-6fc8-d805a46b41f2.png)
EmissionのUVをボロノイで取る事ができます。

例えばこのような設定にすれば、こんな感じになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1071164/839e2742-ee62-7922-d229-becbbec85017.png)



ちなみにASEでシェーダーを制作した時は
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1071164/c0514cf9-ff1c-ab3d-e4bd-e66696ca54a2.png)
この項目に設定したクラス名をかけば適応されますし、横の回転矢印マークを押せば通常に戻ります。


## オリジナルと同じマテリアルプロパティを作る
まずは通常のマテリアルプロパティと同じ状態を作りましょう。
私はそれを作るのが面倒くさくて今まで触ろうともしませんでしたが
今は便利なものがあります！
そうChatGPTです。

ChatGPTに対して
```
UnityのShaderGUIについて、協力をして下さい。

下記のコードのShaderについて、ShaderGuiを制作しようと思います。
MaterialPropertyのコード部分を書いてください。
しかし、 base.OnGUI()は使用しないで下さい。
～～～～以下ShaderのProperties部分をコピペ～～～～
```
と聞いてみましょう。

きっとコードが帰ってくると思います。
それを必要部分( `public override void OnGUI`より以下)にコピペましょう。
これで準備が出来ました。

:::note
23/8/29現在だと
ChatGPT3.5は余計な処理を入れてしまいます。(聞き方が悪いのもあると思う)
ChatGPT4.0の方が素直なコードを出してくれました。
:::




# 見た目を整えよう
見やすさは正義です。
設定個所のカテゴリー分けをする事で、設定のしやすさは格段に上がります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1071164/115651ed-85e0-7393-b88b-c05fbc99277d.png)
ChatGPTに書いてもらったコードは、上記のようなマテリアルプロパティになりました。
テクスチャのUVを弄る[Tiling]と[Offset]が無くなってますが、これは後で設定をしましょう。

***

まずは、質感設定とボロノイ設定でカテゴリー分けをしましょう。
使用するのは、`EditorGUILayout.VerticalScope`です。
```
using (new EditorGUILayout.VerticalScope("HelpBox"))
{
        //プロパティコードを入れる
}
```
`EditorGUILayout.VerticalScope` は、Unityエディタ拡張の中で縦方向のGUIレイアウトを制御するためのものです。このスコープ内でGUI要素を配置すると、それらは垂直方向に自動的に並べられます。

変数に"HelpBox"使用する事によって、中に入れたコードの範囲の情報を箱に収めたような感じに出来ます。

~~~
using (new EditorGUILayout.VerticalScope("HelpBox"))
{
    // 質感設定描写プロパティコード
}

EditorGUILayout.Space(); //スペースを開ける

using (new EditorGUILayout.VerticalScope("HelpBox"))
{
    // ボロノイ描写プロパティコード
}
~~~

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1071164/18685dcc-f044-99a2-54bc-5a4e3bcad9c7.png)
というわけで、このように分けてみました。

参考URL

https://hacchi-man.hatenablog.com/entry/2019/12/20/002444#EditorGUILayoutVerticalScope

https://hacchi-man.hatenablog.com/entry/2020/04/05/220000

## StandardShaderを真似る

みんな知っているStandardShaderのプロパティはこんな感じですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1071164/31498ac4-c450-c3be-5db4-600b01194e01.png)

* テクスチャ設定とカラーなどの調節設定が一列になっている。
* Metalicにテクスチャを入れると、スライダーが消えて設定できなくなる。
* Normal Mapにテクスチャを入れると、Scaleの設定が出てくる。
* UVのTillingとOffsetがまとめて設定が出来る。
などが特徴かと思います。

今現在のChatGPTに出したマテリアルプロパティは下記のような状態です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1071164/d698315f-9bb4-6187-7716-7b6fd9d3869b.png)
コードはこちら
```
materialEditor.TexturePropertySingleLine(new GUIContent("Main Texture"), _MainTex, _MainColor);
materialEditor.RangeProperty(_Metalic, "Metalic");
materialEditor.TexturePropertySingleLine(new GUIContent("Metalic Texture"), _MetalicTex);
materialEditor.RangeProperty(_Smoothness, "Smoothness");
materialEditor.TexturePropertySingleLine(new GUIContent("Normal Texture"), _NormalTex, _NormalScale);
```
見た目は似たような感じになってますが、StandardShaderのようにはまだなっていません。
ここから変更を加えていきましょう。

***

まずはこちら、テクスチャ関連を表示する時に使えるコード一覧と、その表示見本になります。
```
materialEditor.TextureProperty(mainTex , "TextureProperty" );
            
materialEditor.TexturePropertySingleLine(new GUIContent("TexturePropertySingleLine"), mainTex, color);
            
materialEditor.TexturePropertyTwoLines(new GUIContent("TexturePropertyTwoLines"), mainTex, color , new GUIContent("Smoothness"),  glossiness);
            
materialEditor.TexturePropertyWithHDRColor(new GUIContent("TexturePropertyWithHDRColor"), mainTex, color, true);
            
GUILayout.Label("TextureScaleOffsetProperty"); //文字の表示
materialEditor.TextureScaleOffsetProperty(mainTex);
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1071164/ecbad4b0-52e4-4b65-31cd-8ecf481f0fd8.png)
こちらを使ってプロパティのUIを整えていきましょう。

***

…というわけで見た目が同じようになるよう編集し、読みやすくした後のコードはこちら
```
GUILayout.Label("Main Maps");

materialEditor.TexturePropertySingleLine(
                new GUIContent("Main Texture"), _MainTex,
                _MainColor);

materialEditor.TexturePropertySingleLine(
                new GUIContent("Metalic"), _MetalicTex,
                _Metalic);

materialEditor.ShaderProperty(_Smoothness ,
                new GUIContent("Smoothness") ,
                2 );

materialEditor.TexturePropertySingleLine(
                new GUIContent("Normal Texture"), _NormalTex,
                _NormalScale);

```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1071164/385ffee8-0ff3-5b60-26fc-9c03cead3dcb.png)
* *_MainTex* と *_NormalTex* はChatGPTが出してくれた一行表示のまま。
* Metalicはテクスチャと *_Metaric* を一列表示に。
* *_Smoothness* は *ShaderProperty* の3番目の引数に数字(int)を入れる事でインデントさせてます

これでStandardShaderのような配置になりました。
<details><summary>インデントについて</summary>

これインデントできることをUNITYのドキュメントには書いてないんですよね。


https://docs.unity3d.com/ScriptReference/MaterialEditor.ShaderProperty.html


https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/Inspector/MaterialEditor.cs#L1796


こっちを見ると、存在している。

</details>



## テクスチャ導入に伴うプロパティの変化
ここから、テクスチャを入れるとプロパティが変化する仕組みを追加していきましょう。
まずはNormalTexturから。

プロパティに条件演算子を使用する事で、テクスチャの有無で分岐をさせる事が可能になります。

```
materialEditor.TexturePropertySingleLine(new GUIContent("Normal Texture"), _NormalTex,
              _NormalTex.textureValue != null ? _NormalScale : null );

```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1071164/9ba989f1-2794-178e-adad-8f5c9e108bd6.png)

` _NormalTex.textureValue != null `これでテクスチャが有無がわかります。
ある場合は、*_NomalScale* を表示、無い場合は *null* で無を表示させます。

***

Metalicの仕様はちょっと複雑で、
* テクスチャが入ってないと、Metalicの強さを数値で決めれる。
* テクスチャに入っていると、Metalicの強さが設定できなくなり
内部計算の関係で 1.0 が入る。

これをコードに書くとこうなります。

```
bool isMetallicTex = _MetalicTex.textureValue;
materialEditor.TexturePropertySingleLine(
                new GUIContent("Metalic"), _MetalicTex,
                isMetallicTex ? null : _Metalic);

if(isMetallicTex){
    _Metalic.floatValue = 1f;
}
```
1. `_Metalic.textureValue` を取得し、テクスチャの有無を *isMetallicTex* というbool変数に格納します。
2. 条件演算子を使用して、テクスチャがある時は *_Metalic* の強さを設定できないように *null* を入れ、
テクスチャがない時は数値を入れる事が出来るようにします。
3. if文で、テクスチャがある時には *_Metalic* に 1.0 を入れる。

<details><summary>StandardShaderとの仕様違い</summary>

StandardShaderでは、Metalicの数字を設定した後に(例えば0.5とか)テクスチャを入れるとMetalicの数値が消え、
テクスチャを外すと数値が元に(0.5に)戻りますが、上記のコードだと戻りません。
これはStandardShaderがテクスチャの有無で数値を変更するのではなく、
プロパティの変数自体を変えているためになります。
</details>




テクスチャのスケールとオフセットを編集するためのプロパティを表示するためには下記のメソッドを使用します。

```
materialEditor.TextureScaleOffsetProperty(_MainTex);
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1071164/2b10f479-e4ab-ff3f-62dc-a04f5932762e.png)

## Vectorプロパティを分解する

ボロノイのプロパティを編集していきます。
まずは今現在はこのようなプロパティになっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1071164/99db2707-64ec-44e6-1c2d-efb220054ae9.png)

```
materialEditor.TexturePropertySingleLine(new GUIContent("Emission Texture"),
                    _EmissionTex, _EmissonColor);
                    
materialEditor.VectorProperty(_Voronoi_UV, "Voronoi UV");

materialEditor.ShaderProperty(_EnableVonoroi, "Enable Vonoroi");

```
この Voronoi UV の項目については
X：ボロノイのアングル
Y：ボロノイのスケール
Z：ボロノイ隙間距離
W：ボロノイの表示比率(０～１)
になってます。
これをそれぞれに設定を出来るようにしていきましょう。

出来たコードはこちら
```
EditorGUI.BeginChangeCheck();
Vector4 VoronoiVal = EditorGUILayout.Vector2Field("Voronoi UV", _Voronoi_UV.vectorValue);

VoronoiVal.z = EditorGUILayout.FloatField ("Voronoi size", _Voronoi_UV.vectorValue.z );

VoronoiVal.w = EditorGUILayout.Slider("Voronoi" , _Voronoi_UV.vectorValue.w , 0 , 1 );

if (EditorGUI.EndChangeCheck())
{
    _Voronoi_UV.vectorValue = VoronoiVal;
}
```
通常はVectorプロパティを使用した場合、XYだけを使用していたとしてもVectorなのでxyzwの項目が必ず表示されてしまいます。
それを、XYだけを表示・編集が出来るようにしましょう。
しかし、それをするには`materialEditor`を使用するのではなく、`EditorGUILayout`を使用しなくてはなりません。
`EditorGUILayout`を使用した場合、見た目は作れるのですがマテリアル変数( *_Voronoi_UV*)の値を変更する事が出来ません。

そこで`EditorGUI.BeginChangeCheck()`メソッドを使用し、この後のGUI要素に変更が加えられたかどうかをチェックします。
`if(EditorGUI.EndChangeCheck())`で更新後に *_Voronoi_UV*に値を代入しています。

https://docs.unity3d.com/ja/560/ScriptReference/EditorGUI.BeginChangeCheck.html


* `Vector4 VoronoiVal = EditorGUILayout.Vector2Field("Voronoi UV", _Voronoi_UV.vectorValue);`

`Vector2Field`は、ラベルとともにVector2の入力フィールドをInspectorに表示します。しかし、ここで扱っているのはVector4型の変数なので、このメソッドはその先頭2つの要素（XとY）のみを変更することができます。
同時に、最終的に_Voronoi_UVに代入する変数 VoronoiVal を設定してます。


* `VoronoiVal.z = EditorGUILayout.FloatField ("Voronoi size", _Voronoi_UV.vectorValue.z );`

`FloatField`は、浮動小数点数の入力フィールドを表示します。ここでは、_Voronoi_UVのZ要素を変更するための入力フィールドを提供しています。

* `VoronoiVal.w = EditorGUILayout.Slider("Voronoi" , _Voronoi_UV.vectorValue.w , 0 , 1 );`

`Slider`は、指定された最小値と最大値の間で値をスライドさせて選択するためのスライダーを提供します。ここでは、*_Voronoi_UV* のW要素を0から1の間で変更するためのスライダーを表示しています。


そして、上記の設定が変更された場合に `_Voronoi_UV.vectorValue = VoronoiVal;` が実行されマテリアルに代入されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1071164/8222fe38-286a-61b3-f36b-e1ead3526f40.png)



***

ボロノイを使用しない場合はボロノイを設定するプロパティを隠していた方が見た目的にも良いでしょう

というわけで、次のコードがプロパティの表示をON OFFする為のコードです。

```
materialEditor.ShaderProperty(_EnableVonoroi, "Enable Vonoroi");

if(_EnableVonoroi.floatValue == 1){

    using (new EditorGUILayout.VerticalScope("Box")){

            ///上記のVoronoiコードが入る

    }
}

```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1071164/bd1bf9a8-de96-92fa-8149-4947e0946b18.png)

* `materialEditor.ShaderProperty(_EnableVonoroi, "Enable Vonoroi");`
*_EnableVonoroi* のプロパティを表示します。Shaderコード側でToggle表示にしています。

* `if(_EnableVonoroi.floatValue == 1)`
上記で表示された *_EnableVonoroi* の値が1の場合にのみ、GUI要素を表示します。

* `using (new EditorGUILayout.VerticalScope("Box"))`
VerticalScopeはもう説明済みになりますが、ここではBOXを指定してカテゴリーを分けて見やすくしています。


***

Render Queue・Instance GPU ・Double Sided Global Illuminationの項目を表示させるには
下記のコードを追加します。

```
materialEditor.RenderQueueField();
materialEditor.EnableInstancingField();
materialEditor.DoubleSidedGIField();
```



# その他にとても参考になるサイト


https://docs.unity3d.com/ja/current/ScriptReference/MaterialEditor.html

https://docs.unity3d.com/ja/current/ScriptReference/EditorGUILayout.html

StandardShaderのカスタムGUIコード

https://github.com/TwoTailsGames/Unity-Built-in-Shaders/blob/master/Editor/StandardShaderGUI.cs


警告文などの表示に

https://bluebirdofoz.hatenablog.com/entry/2022/04/19/231327


https://zenn.dev/kumas/books/325ed71592f6f5/viewer/a9af77

https://sleepygamersmemo.blogspot.com/2017/12/unity-custom-editors-layout.html