【Unity】Samplerが消える問題

# はじめに
本記事はAdvent Calendar 2022の7日目の記事です。

https://qiita.com/advent-calendar/2022/unity

## 環境
* Unity 2021.3.11f1
* URP
* RenderDoc

# Samplerには制限がある
問題の前に前提の話をします。

Samplerは最大で16個までしか使えません。
```HLSL
float4 frag (v2f i) : SV_Target
{
    float4 col = tex2D(_MainTex0, i.uv);

    col += tex2D(_MainTex1, i.uv);
    col += tex2D(_MainTex2, i.uv);
    col += tex2D(_MainTex3, i.uv);
    // _MainTex4~14まである
    col += tex2D(_MainTex15, i.uv);    
    // 17枚目のテクスチャはまだ使わない
    // col += tex2D(_MainTex16, i.uv);
    return col;
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608797/75418836-f3e7-ec10-8060-13274ac3e016.png)
全16枚のテクスチャがしっかりと読み込まれています。

ここで17枚目のテクスチャを使ってみます。
```HLSL
float4 frag (v2f i) : SV_Target
{
    float4 col = tex2D(_MainTex0, i.uv);

    col += tex2D(_MainTex1, i.uv);
    col += tex2D(_MainTex2, i.uv);
    col += tex2D(_MainTex3, i.uv);
    // _MainTex4~14まである
    col += tex2D(_MainTex15, i.uv);    
    // 17枚目を使う
    col += tex2D(_MainTex16, i.uv);
    return col;
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608797/efeaa8cf-91f8-1653-d0df-a00c076316f2.png)
テクスチャの読み込みに失敗しました。

> Shader error in 'Unlit/Test': maximum ps_4_0 sampler register index (16) exceeded at

エラー文ではSamplerが16を超えた的な内容が出ます。

# 何が問題なのか
Samplerの数＝テクスチャの枚数
といった感じでShaderを書いていると16枚以上テクスチャを使えないことになります。

# 解決方法
ですがSamplerは使い回して使うことができるので1つのSamplerで何枚ものテクスチャを出すことができます。
これを利用すればSamplerは16個、Textureは128個まで使うことができます。

```HLSL
Texture2D _MainTex0;
SamplerState sampler_MainTex0;

Texture2D _MainTex1;
Texture2D _MainTex2;
// _MainTex3~_MainTex15まである
Texture2D _MainTex16;

float4 frag (v2f i) : SV_Target
{
    float4 col = _MainTex0.Sample(sampler_MainTex0, i.uv);
    col += _MainTex1.Sample(sampler_MainTex0, i.uv);
    col += _MainTex2.Sample(sampler_MainTex0, i.uv);
    // _MainTex3~_MainTex15まで上と同じような処理
    col += _MainTex16.Sample(sampler_MainTex0, i.uv);
    // これでTextureを0~16で17枚使用してる

    return col;
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608797/d10ae9bf-2da2-a3e5-1b22-f54db55b8e2e.png)
しっかりとテクスチャが17枚読み込まれています。

# しかし…
`SamplerState`が消える？ことがあります。
```HLSL
Texture2D _MainTex0;
SamplerState sampler_MainTex0;

Texture2D _MainTex1;

float4 frag (v2f i) : SV_Target
{
    float4 col=(float4)0;
    float4 col2=(float4)0;

    col = _MainTex0.Sample(sampler_MainTex0, i.uv);
    col2=_MainTex1.Sample(sampler_MainTex0, i.uv);

    // colは使わずcol2だけを返す
    return col2;
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608797/6530b034-8703-7be7-137c-4a3dca76cced.png)

最終的に返す値は`col2`なので`col`の分の計算がすっ飛ばされて結果的に`sampler_MainTex0`が消えてエラーを出します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608797/8082d468-378b-11a7-4130-8e82fca02e38.png)
`col`を返すとき`SamplerState`はしっかりと残っています。

`return`までの計算でその`SamplerState`を使わないことが問題なので
```HLSL
float4 frag (v2f i) : SV_Target
{
    float4 col=(float4)0;
    float4 col2=(float4)0;

    col = _MainTex0.Sample(sampler_MainTex0, i.uv);
    col2=_MainTex1.Sample(sampler_MainTex0, i.uv);

    // col,col2どっちも計算に使う
    col2 = col2+col;
    return col2;
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608797/70356e1d-861e-bcca-7821-c4ce3db20031.png)
このように`return`まででちゃんと数値を使ってあげればエラーはなくなります。

しかし、このままだと`#if`して分岐するプログラムなどを書いたときに`SamplerState`が消えてエラーが出る可能性があります。

# 解決策
エラー文を確認すると
> Shader error in 'Unlit/Test': Fragment program 'frag': Unrecognized sampler 'sampler_maintex0' - does not match any texture and is not a recognized inline name (should contain filter and wrap modes)
'Unlit/Test' のシェーダー エラー: フラグメント プログラム 'frag': 認識されないサンプラー 'sampler_maintex0' - どのテクスチャとも一致せず、認識されたインライン名ではありません (フィルター モードとラップ モードを含める必要があります)

フィルターモードとラップモードを含める必要があるといわれるので素直に追加します。
```HLSL
Texture2D _MainTex0;
// これをやめて
// SamplerState sampler_MainTex0;

// こんな感じにする
SamplerState sampler_linear_clamp_MainTex0;

Texture2D _MainTex1;

float4 frag (v2f i) : SV_Target
{
    float4 col=(float4)0;
    float4 col2=(float4)0;

    col = _MainTex0.Sample(sampler_linear_clamp_MainTex0, i.uv);
    col2=_MainTex1.Sample(sampler_linear_clamp_MainTex0, i.uv);

    // colは使わずcol2だけを返す
    return col2;
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608797/ce0f07b4-75a2-6998-93e3-64eaab32ef62.png)
無事にエラーが消えます。

フィルター
* Point
* Linear
* Trilinear

ラップ
* Clamp
* Repeat
* Mirror
* MirrorOnce
を`SamplerState`の変数名に書き足せば使えます。

これらの名前が入っていればいいので
* sampler_linear_clamp_MainTex0(上で使ってた名前)
* samplerlinearclamp_MainTex0(_を一部消した)
* samplerclamp_MainTex0_linear(linearを最後に書いた)
* ClAmP_MainTex0_linear(大文字小文字がぐちゃぐちゃ)

でも問題なく動きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608797/9f62f709-7dae-2473-470a-87bf2f4a2c19.png)
デバッグ結果にも含まれています。

# まとめ
`SamplerState`が消えてしまう問題をフィルターモードとラップモードを書くことで回避しました。
消えないテクニックが存在するかShaderの書き方が悪いのか分かりませんが現状はこの方法でこの問題を私は乗り越えています。

これをしなくても回避する方法がありましたら教えて頂きたいです。