Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion Docs/pure-base-shader-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,27 @@ The model-specific properties are:
| `PureBase/PBR` | `_NormalMap`, `_NormalScale`, `_Metallic`, `_Roughness`, `_UseUnityStandardDiffuseBrightness` |
| `PureBase/Hybrid` | `_NormalMap`, `_NormalScale`, `_Metallic`, `_Roughness`, `_UseUnityStandardDiffuseBrightness` |

PBR and Hybrid use byte-identical property declarations. `_Roughness` clamps from `0.002` to `1`.
PBR and Hybrid use byte-identical property declarations. `_Roughness` is a ShaderLab `Float` backed by `SC_float`, with default `0.5`, public perceptual range `[0.089, 1]`, and exact `[SCRange(0.089,1)]` metadata. It remains ordered between `_Metallic` and `_UseUnityStandardDiffuseBrightness`, and the complete PBR and Hybrid property declarations, including this metadata, remain byte-identical.

### PBR and Hybrid roughness contract

`_Roughness` stores perceptual roughness `p`, not academic roughness `p^2`. PBR and Hybrid use one shared runtime clamp, `clamp(p, 0.089, 1)`, before creating their shared BRDF data. The clamped value feeds every roughness-sensitive path:

- Direct GGX evaluates `roughnessSquared = p^2` and then reaches `roughnessFourth = p^4` in the direct evaluator for both `ForwardBase` and `ForwardAdd`.
- Unity Standard GI and reflection-probe setup derive `Smoothness = 1 - p` from the same clamped value before `LightingStandard_GI`; the resulting indirect contribution uses the same shared BRDF data.
- PBR and Hybrid `Meta` fragments create the same shared BRDF data, so Meta/lightmapping uses the same floor through its squared-roughness rule.

The floor is `0.089` because the direct evaluator's fourth-power term must remain above the IEEE-754 binary16 minimum positive normal. Specifically, $0.089^4 = 0.000062742241 > 2^{-14} = 0.00006103515625$. Unity URP uses related FP16 protections in its [`BRDF.hlsl`](https://github.com/Unity-Technologies/Graphics/blob/e6595ee2d83c8b02dab6e58abba0ff285c0c80ed/Packages/com.unity.render-pipelines.universal/ShaderLibrary/BRDF.hlsl): its BRDF initialization protects squared roughness with `HALF_MIN_SQRT` and the square of that value with `HALF_MIN`. Unity Core's [`CommonMaterial.hlsl`](https://github.com/Unity-Technologies/Graphics/blob/cdc941e1378729b1ca1fafb175151ac3d781ebb0/Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonMaterial.hlsl) also documents that zero or excessively small analytical-light roughness is invalid or can alias. These are related numerical protections, not a claim that URP is supported or that Pure Base implements the URP BRDF.

Built-in Standard's internal `0.002` clamp is a different parameterization. It clamps academic roughness after perceptual roughness has been squared, whereas Pure Base's public `_Roughness` is perceptual `p`. The installed Unity `2022.3.22f1` Built-in Standard source therefore does not define Pure Base's public floor; the same numeral represents a different quantity.

### Existing-material compatibility

- No material migration command or bulk serialized rewrite is added. A stored value below `0.089` remains stored as-is until a user explicitly edits or otherwise rewrites the material.
- At runtime, every stored `_Roughness` below `0.089` evaluates as `0.089`, so direct lighting, Unity Standard GI/reflection, and Meta/lightmapping produce the same roughness result as an input of `0.089`.
- Stored values at or above `0.089` retain their public ordering and roughness meaning. The public default remains `0.5`; shader names, property names, types, and pass ownership remain unchanged.

This roughness-floor change does not implement the Issue #13 visibility approximation, Issue #14 multiple-scattering compensation, specular anti-aliasing, or full Unity Standard BRDF parity. It also does not change the ownership, placement, or behavior of `_UseUnityStandardDiffuseBrightness`.

### Direct diffuse brightness ABI and semantics

Expand Down
18 changes: 17 additions & 1 deletion Docs/technical-information.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,23 @@ Pure Base は Shader-Core を動かすための最小構成の土台です。多

`PureBase/Toon` は、追加で `_NormalMap` と `_NormalScale` を公開します。

`PureBase/PBR` と `PureBase/Hybrid` は、法線マップ用の項目に加えて `_Metallic`、`_Roughness`、`_UseUnityStandardDiffuseBrightness` を公開します。両者の公開項目定義は完全に同一です。粗さは `0.002` から `1` の範囲に制限されます。
`PureBase/PBR` と `PureBase/Hybrid` は、法線マップ用の項目に加えて `_Metallic`、`_Roughness`、`_UseUnityStandardDiffuseBrightness` を公開します。このメタデータを含む両者の公開項目定義は完全に同一です。`_Roughness` は `SC_float` を基にした ShaderLab の `Float` で、初期値は `0.5`、公開する知覚粗さの範囲は `[0.089, 1]`、メタデータは正確に `[SCRange(0.089,1)]` です。公開順序は `_Metallic` と `_UseUnityStandardDiffuseBrightness` の間のままです。

### PBR と Hybrid の知覚粗さ下限

`_Roughness` が保持するのは学術的な粗さ `p^2` ではなく、知覚粗さ `p` です。PBR と Hybrid は共通の BRDF データを作る前に、1つの共有ランタイムクランプ `clamp(p, 0.089, 1)` を使います。このクランプ後の値は、粗さに依存するすべての経路へ渡されます。

- 直接 GGX は `roughnessSquared = p^2` を計算し、`ForwardBase` と `ForwardAdd` の直接評価で `roughnessFourth = p^4` まで計算します。
- Unity Standard の GI と反射プローブの準備では、同じクランプ後の値から `Smoothness = 1 - p` を作って `LightingStandard_GI` に渡します。その後の間接光評価も同じ BRDF データを使います。
- PBR と Hybrid の `Meta` フラグメントは同じ BRDF データを作るため、Meta/ライトマップも二乗粗さの規則を通じて同じ下限を使います。

下限を `0.089` とするのは、直接評価の4乗項を IEEE-754 binary16 の最小正規化正数より大きく保つためです。具体的には、$0.089^4 = 0.000062742241 > 2^{-14} = 0.00006103515625$ です。Unity URP も [`BRDF.hlsl`](https://github.com/Unity-Technologies/Graphics/blob/e6595ee2d83c8b02dab6e58abba0ff285c0c80ed/Packages/com.unity.render-pipelines.universal/ShaderLibrary/BRDF.hlsl) で関連する FP16 保護を使っており、BRDF 初期化時に二乗粗さを `HALF_MIN_SQRT`、その値の二乗を `HALF_MIN` で保護します。Unity Core の [`CommonMaterial.hlsl`](https://github.com/Unity-Technologies/Graphics/blob/cdc941e1378729b1ca1fafb175151ac3d781ebb0/Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonMaterial.hlsl) も、粗さがゼロまたは過度に小さい分析ライト用粗さは無効またはエイリアシングの原因になると説明しています。これは関連する数値保護を示すものであり、URP を対応対象にすることや、Pure Base が URP の BRDF を実装することを意味しません。

Built-in Standard 内部の `0.002` クランプは、別のパラメーター化です。これは知覚粗さを二乗した後の学術的な粗さに対して適用されます。一方、Pure Base の公開 `_Roughness` は知覚粗さ `p` です。そのため、インストール済み Unity `2022.3.22f1` の Built-in Standard ソースにある値は Pure Base の公開下限を定義せず、同じ数値でも表している量が異なります。

マテリアル移行コマンドや、保存値を一括書き換えする処理は追加しません。`0.089` 未満の保存値は、ユーザーが明示的に編集または別の方法でマテリアルを書き換えるまで、そのまま保存されます。ただし実行時には、直接光、Unity Standard の GI/反射、Meta/ライトマップのすべてで `0.089` として評価されます。`0.089` 以上の保存値は公開順序と粗さの意味を維持し、公開初期値も `0.5` のままです。

この変更には Issue #13 の可視性近似、Issue #14 の多重散乱補償、スペキュラアンチエイリアシング、Unity Standard との完全な BRDF 一致は含まれません。`_UseUnityStandardDiffuseBrightness` の所有範囲、配置、動作も変更しません。

### PBR と Hybrid の直接拡散反射の輝度

Expand Down
18 changes: 17 additions & 1 deletion Docs/technical-information.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,23 @@ All four shaders expose these common properties:

`PureBase/Toon` additionally exposes `_NormalMap` and `_NormalScale`.

`PureBase/PBR` and `PureBase/Hybrid` expose the same normal-map properties plus `_Metallic`, `_Roughness`, and `_UseUnityStandardDiffuseBrightness`. Their public property declarations are byte-identical. Roughness is clamped from `0.002` to `1`.
`PureBase/PBR` and `PureBase/Hybrid` expose the same normal-map properties plus `_Metallic`, `_Roughness`, and `_UseUnityStandardDiffuseBrightness`. Their public property declarations, including this metadata, are byte-identical. `_Roughness` is a ShaderLab `Float` backed by `SC_float`, with default `0.5`, public perceptual range `[0.089, 1]`, and exact `[SCRange(0.089,1)]` metadata. It remains ordered between `_Metallic` and `_UseUnityStandardDiffuseBrightness`.

### PBR and Hybrid perceptual roughness floor

`_Roughness` stores perceptual roughness `p`, not academic roughness `p^2`. PBR and Hybrid use one shared runtime clamp, `clamp(p, 0.089, 1)`, before creating their shared BRDF data. The clamped value feeds every roughness-sensitive path:

- Direct GGX evaluates `roughnessSquared = p^2` and then reaches `roughnessFourth = p^4` in the direct evaluator for both `ForwardBase` and `ForwardAdd`.
- Unity Standard GI and reflection-probe setup derive `Smoothness = 1 - p` from the same clamped value before `LightingStandard_GI`; the resulting indirect contribution uses the same shared BRDF data.
- PBR and Hybrid `Meta` fragments create the same shared BRDF data, so Meta/lightmapping uses the same floor through its squared-roughness rule.

The floor is `0.089` because the direct evaluator's fourth-power term must remain above the IEEE-754 binary16 minimum positive normal. Specifically, $0.089^4 = 0.000062742241 > 2^{-14} = 0.00006103515625$. Unity URP uses related FP16 protections in its [`BRDF.hlsl`](https://github.com/Unity-Technologies/Graphics/blob/e6595ee2d83c8b02dab6e58abba0ff285c0c80ed/Packages/com.unity.render-pipelines.universal/ShaderLibrary/BRDF.hlsl): its BRDF initialization protects squared roughness with `HALF_MIN_SQRT` and the square of that value with `HALF_MIN`. Unity Core's [`CommonMaterial.hlsl`](https://github.com/Unity-Technologies/Graphics/blob/cdc941e1378729b1ca1fafb175151ac3d781ebb0/Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonMaterial.hlsl) also documents that zero or excessively small analytical-light roughness is invalid or can alias. These are related numerical protections, not a claim that URP is supported or that Pure Base implements the URP BRDF.

Built-in Standard's internal `0.002` clamp is a different parameterization. It clamps academic roughness after perceptual roughness has been squared, whereas Pure Base's public `_Roughness` is perceptual `p`. The installed Unity `2022.3.22f1` Built-in Standard source therefore does not define Pure Base's public floor; the same numeral represents a different quantity.

No material migration command or bulk serialized rewrite is added. A stored value below `0.089` remains stored as-is until a user explicitly edits or otherwise rewrites the material, but it evaluates as `0.089` at runtime in direct lighting, Unity Standard GI/reflection, and Meta/lightmapping. Stored values at or above `0.089` retain their public ordering and roughness meaning, and the public default remains `0.5`.

This change does not implement the Issue #13 visibility approximation, Issue #14 multiple-scattering compensation, specular anti-aliasing, or full Unity Standard BRDF parity. It does not change the ownership, placement, or behavior of `_UseUnityStandardDiffuseBrightness`.

### PBR and Hybrid direct-diffuse brightness

Expand Down
11 changes: 10 additions & 1 deletion Shaders/Common/pbr_brdf.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ struct PureBasePbrBrdfData
half roughnessSquared;
};

/// <summary>Defines the shared rounded-up perceptual-roughness floor as a compile-time half value.</summary>
static const half PureBasePbrPerceptualRoughnessFloor = 0.0890;

/// <summary>Clamps perceptual roughness to 0.089; the rounded-up floor protects p^4 above the IEEE binary16 minimum normal and aligns with Unity URP HALF_MIN_SQRT/HALF_MIN initialization.</summary>
half PureBasePbrClampPerceptualRoughness(half perceptualRoughness)
{
return clamp(perceptualRoughness, PureBasePbrPerceptualRoughnessFloor, 1.0);
}

/// <summary>Returns a finite unit direction and maps zero-length directions to zero.</summary>
/// <param name="direction">The direction to normalize.</param>
/// <returns>A finite unit direction or zero.</returns>
Expand All @@ -49,7 +58,7 @@ PureBasePbrBrdfData PureBasePbrCreateBrdf(half3 albedo, half metallic, half roug
{
PureBasePbrBrdfData brdf;
half clampedMetallic = saturate(metallic);
brdf.roughness = clamp(roughness, 0.002, 1.0);
brdf.roughness = PureBasePbrClampPerceptualRoughness(roughness);
brdf.roughnessSquared = brdf.roughness * brdf.roughness;
brdf.diffuseColor = saturate(albedo) * (1.0 - clampedMetallic);
brdf.specularColor = lerp(half3(0.04, 0.04, 0.04), saturate(albedo), clampedMetallic);
Expand Down
3 changes: 2 additions & 1 deletion Shaders/Models/pbr.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@ SurfaceOutputStandard SCModelCreateStandardSurface(SCShadingData shadingData)
surface.Normal = PureBasePbrSafeNormalize(shadingData.N);
surface.Emission = 0;
surface.Metallic = saturate(_Metallic);
surface.Smoothness = 1.0 - clamp(_Roughness, 0.002, 1.0);
half clampedPerceptualRoughness = PureBasePbrClampPerceptualRoughness(_Roughness);
surface.Smoothness = 1.0 - clampedPerceptualRoughness;
surface.Occlusion = 1;
surface.Alpha = 1;
return surface;
Expand Down
2 changes: 1 addition & 1 deletion Shaders/PureBaseHybrid_properties.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ SC_Texture2D(_NormalMap, "bump", [], "Normal Map", "")
SC_SamplerState(sampler_NormalMap)
SC_float(_NormalScale, 1, [SCRange(0,2)], "Normal Scale", "")
SC_float(_Metallic, 0, [SCRange(0,1)], "Metallic", "")
SC_float(_Roughness, 0.5, [SCRange(0.002,1)], "Roughness", "")
SC_float(_Roughness, 0.5, [SCRange(0.089,1)], "Roughness", "")
SC_uint(_UseUnityStandardDiffuseBrightness, 0, [SCToggle], "Unity Standard Diffuse Brightness", "")
2 changes: 1 addition & 1 deletion Shaders/PureBasePBR_properties.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ SC_Texture2D(_NormalMap, "bump", [], "Normal Map", "")
SC_SamplerState(sampler_NormalMap)
SC_float(_NormalScale, 1, [SCRange(0,2)], "Normal Scale", "")
SC_float(_Metallic, 0, [SCRange(0,1)], "Metallic", "")
SC_float(_Roughness, 0.5, [SCRange(0.002,1)], "Roughness", "")
SC_float(_Roughness, 0.5, [SCRange(0.089,1)], "Roughness", "")
SC_uint(_UseUnityStandardDiffuseBrightness, 0, [SCToggle], "Unity Standard Diffuse Brightness", "")
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ public sealed partial class PureBaseRenderingModeContractTests
private const string UnityStandardDiffuseBrightnessPropertySourcePattern =
@"SC_uint\s*\(\s*_UseUnityStandardDiffuseBrightness\s*,\s*0(?:\.0+)?\s*,\s*\[\s*SCToggle\s*\]\s*,\s*""Unity Standard Diffuse Brightness""\s*,\s*""""\s*\)";

/// <summary>Identifies the exact public PBR and Hybrid perceptual-roughness declaration.</summary>
private const string PbrRoughnessPropertySource =
"SC_float(_Roughness, 0.5, [SCRange(0.089,1)], \"Roughness\", \"\")";

/// <summary>Lists the public product shaders and their complete visible property ABI.</summary>
private static readonly ProductContract[] Products =
{
Expand Down Expand Up @@ -377,6 +381,33 @@ private static void AssertProductShaderAbi(ProductContract product, Shader shade
);
AssertStencilPropertyAbi(product, shader);
AssertUnityStandardDiffuseBrightnessAbi(product, shader);
AssertPbrRoughnessAbi(product, shader);
}

/// <summary>Requires the stable PBR and Hybrid roughness ABI and its byte-identical source mirror.</summary>
/// <param name="product">The product ABI under test.</param>
/// <param name="shader">The imported product shader.</param>
private static void AssertPbrRoughnessAbi(ProductContract product, Shader shader)
{
bool supportsPbrRoughness = product.shaderName == "PureBase/PBR"
|| product.shaderName == "PureBase/Hybrid";
int roughnessIndex = shader.FindPropertyIndex("_Roughness");
if (!supportsPbrRoughness)
{
Assert.That(roughnessIndex, Is.EqualTo(-1), product.shaderName + " must not expose _Roughness.");
return;
}

Assert.That(roughnessIndex, Is.GreaterThan(shader.FindPropertyIndex("_Metallic")));
Assert.That(roughnessIndex, Is.LessThan(shader.FindPropertyIndex("_UseUnityStandardDiffuseBrightness")));
Assert.That(shader.GetPropertyType(roughnessIndex), Is.EqualTo(ShaderPropertyType.Float));
Assert.That(shader.GetPropertyDefaultFloatValue(roughnessIndex), Is.EqualTo(0.5f));
Assert.That(shader.GetPropertyDescription(roughnessIndex), Is.EqualTo("Roughness"));
CollectionAssert.AreEqual(new[] { "SCRange(0.089,1)" }, shader.GetPropertyAttributes(roughnessIndex));
StringAssert.Contains(PbrRoughnessPropertySource, File.ReadAllText(product.propertySourcePath));
string pbr = File.ReadAllText("Packages/jp.penguin.purebase/Shaders/PureBasePBR_properties.hlsl");
string hybrid = File.ReadAllText("Packages/jp.penguin.purebase/Shaders/PureBaseHybrid_properties.hlsl");
Assert.That(hybrid, Is.EqualTo(pbr), "PBR and Hybrid property declarations must remain byte-identical.");
}

/// <summary>Requires the PBR and Hybrid Integer toggle ABI while preserving its absence from Unlit and Toon.</summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ public void ToonLightingOwnershipKeepsBinaryDirectTwoBandShaderCoreLightmapsAndF
AssertToonHelperAndModelContracts(toon, helper);
AssertBirpHostForwardAddAndLightmapContracts(host, shaderCoreLighting);
AssertPbrAndHybridLightingOwnership(pbr, pbrBrdf, hybrid);
AssertPbrRoughnessFloorOwnership(pbr, pbrBrdf);
AssertLightingPhaseOrder(host);
OpenLitSourceContractAssertions.AssertOpenLitFallbackPrecedesNormalization(helper);
}
Expand Down
Loading