Unreal Engineのマテリアル出力ピンSpecularとは何なのか(Specular値のチャート付き)

## 概要

Unreal EngineのマテリアルにあるSpecular出力ピン、何に使うんだっけ？となったので調べてみました。実際何も繋がなくても多くのマテリアルが表現できてしまいますし、デフォルトのまま運用されているマテリアルも多く見受けられます。

そこで、Adobe（旧Allegorithmic）のPBR GuideとUnreal Engineのシェーダコードを合わせて見ることで、その原理からSpecular出力ピンの意味に迫ってみようと思います。

PBR Guideはこちらです。先に読んで置くとシェーダコードが理解しやすくなります。（なぜか正しくないページに飛ばされたりログインを求められたりすることがありました。そういう時は何度か繰り返しクリックすると見られるようです）

Part 1
https://creativecloud.adobe.com/learn/substance-3d-designer/web/the-pbr-guide-part-1

Part 2
https://creativecloud.adobe.com/learn/substance-3d-designer/web/the-pbr-guide-part-2

## 先に結論

多くの場合は何も繋がなくても大丈夫ですが、物理的正確さのため値を出力することもできます。

Specular出力ピンは、ノンメタルの時のみ使用されるパラメータで、鏡面反射の強さを示します。デフォルト値は0.5であり、0.5は4%の鏡面反射を行う事を意味します。ノンメタルな物質は物質毎にその固有値が存在しています。それら固有値は多くの物質で2%から5%の間です。Specular出力ピンに対応する値は2%の場合0.25, 5%の場合0.625であり、基本的にこの範囲の値を設定すべきと言えます。

もし固有値がわからなかった場合は0.5のままにしておきます。

ノンメタルの鏡面反射色は常に灰色であり、Supecular出力ピンはカラーではなく1チャンネルです。

また、物理的には正しくないながら、Specular出力ピンに0を出力することで、一切の鏡面反射を起こさないフラグとして用いることもできます。


:::note info
メタルの場合、BaseColorにそれぞれの金属固有の鏡面反射色を設定するとされています。つまりメタルかノンメタルかに関わらず、鏡面反射色は全ての物質が実在の物質を測定した固有値があり、マテリアルではその値を設定するだけでよいということになります。
:::

## Specular値のチャート

Unreal Engine公式ページ内に、Specular値のチャートがあります。

https://dev.epicgames.com/documentation/ja-jp/unreal-engine/physically-based-materials-in-unreal-engine?application_version=5.4

PBR Guideの中にsRGB値のチャートがありますが、Unreal Engineの値との対応は以下の通りです。

|名前|sRGB値|リニア値|Specular出力ピンの値|
|--|--|--|--|
|プラスチック最低値|55|0.038204|0.47755|
|プラスチック最高値|64|0.051269|0.6408625|
|錆びた金属|52|0.03434|0.42925|
|水|43|0.021219|0.2652375|
|氷|41|0.022174|0.277175|
|ガラス|57|0.040915|0.5114375|

UnityのページにもsRGB値のチャートがあります。Unreal Engineの値との対応は以下の通りです。

|名前|sRGB値|リニア値|Specular出力ピンの値|
|--|--|--|--|
|石炭|52|0.03434|0.42925|
|ゴム|53|0.035601|0.4450125|
|泥|55|0.038204|0.47755|
|草|59|0.043735|0.5466875|
|レンガ|60|0.045186|0.564825|
|木材|80|0.08022|1.00275|
|コンクリート|63|0.049707|0.6213375|




出典は以下です。

https://docs.unity3d.com/Manual/StandardShaderMaterialCharts.html


## シェーダコードの確認

核心となるコードの１つは以下の形を取ります。

```HLSL:BasePassPixelShader.usf
	GBuffer.SpecularColor = ComputeF0(Specular, BaseColor, Metallic);
    ...
	GBuffer.DiffuseColor = BaseColor - BaseColor * Metallic;
```

メタルかノンメタルかに関わらず、'SpecularColor', 'DiffuseColor'という２つの値が求められ、以後のライティングの計算は共通になります。SpecularColorはSpecular出力ピンと名前が共通するようですが意味が異なります。SpecularColorは鏡面反射色、DiffuseColorは拡散反射色です。

まず読み解ける事は、メタル(Metallic=1)のDiffuseColorが計算により真っ黒になるということです。Metal/Roughness Workflow（Unrealが採用）ではDiffuseColorはメタルでは真っ黒とされるため、これはPBR Guideと符号します。また、ノンメタル(Metallic=0)ではBaseColorがDiffuseColorとして使われます。

その流れで拡散反射色は、メタルの場合BaseColor、ノンメタルの場合は灰色が用いられるはずです。ComputeF0の実装をみてみます。


```HLSL:ShadingCommon.ush
half DielectricSpecularToF0(half Specular)
{
	return half(0.08f * Specular);
}
...
half3 ComputeF0(half Specular, half3 BaseColor, half Metallic)
{
	return lerp(DielectricSpecularToF0(Specular).xxx, BaseColor, Metallic.xxx);
}
```
Dielectric(誘電体)とはノンメタルのことで、メタルの場合はBaseColor、ノンメタルの場合はSpecular出力ピンの値に0.08を掛けた値、つまり0.5が0.04(4%)に対応する灰色となることが確認できました。

また、モバイル用レンダラに興味深いコードがあります。

```HLSL:MobileBasePassPixelShader.usf
#if NONMETAL
	GBuffer.DiffuseColor = GBuffer.BaseColor;
	GBuffer.SpecularColor = 0.04;
#else
	GBuffer.SpecularColor = ComputeF0(GBuffer.Specular, GBuffer.BaseColor, GBuffer.Metallic);
	GBuffer.DiffuseColor = GBuffer.BaseColor - GBuffer.BaseColor * GBuffer.Metallic;
#endif
```
NONMETALマクロが1の時（Fully Roughチェックボックスを入れたときと思われます）は、SpecularColorが0.04に固定されていました。Specular出力ピンの値に関わらずSpecular出力ピンが常に0.5に固定される結果となりそうです。


Specular出力ピンに0を出力することで、鏡面反射の一切をなくすこともできます。所々に以下のようなコードがあります。
```HLSL:BRDF.ush
	// Anything less than 2% is physically impossible and is instead considered to be shadowing
	// Note: this is needed for the 'specular' show flag to work, since it uses a SpecularColor of 0
	float F90 = saturate( 50.0 * SpecularColor.g );
```
50は2%の逆数です。F90はspecularピン（メタルの場合はBaseColorピン）に0.25(2%)以上を出力する限り常に1、0を出力すると0になります。2%未満の物質はこの世に存在しないため、全ての物質のF90は1です。F90が1であるとは、いかなる物質も入射角が並行に近づくと鏡面反射率が100%に近づいていく事を示しています。しかし、specularピンに0を出力したときは例外としてF90が0になり一切鏡面反射が起こりません。常に影になる領域など、マテリアル作成上の意図で鏡面反射を切りたい場合に0を用います。

## GBufferの確認

SpecularはGBufferに格納されます。Buffer Visualization -> Overviewでは上段左から二番目に表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/66160/4b551504-142d-5470-f04e-cd01f5c9e620.png)

GBufferに格納されるということは、0から1の値にクランプされることを意味します。つまり反射率は0%から8%の範囲でのみ設定可能ということになります。