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
14 changes: 14 additions & 0 deletions Docs/pure-base-shader-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,20 @@ Unlit returns the base surface without host direct, baked, ambient, or environme

Toon evaluates a binary direct diffuse response from the surface normal and light direction. Its `ForwardBase` direction combines the Shader-Core direct aggregate with the first-order SH direction, and its ambient result selects a fixed bright or dark SH band from that direction. Shader-Core continues to provide the lightmap input; when Shader-Core supplies the lightmap aggregate, Toon does not synthesize an additional baked-light contribution. `ForwardAdd` contributes direct light only.

### OpenLit-derived Toon direction and SH bands

The module-free Toon host uses a bounded Pure Base adaptation of selected OpenLit 1.0.2 BIRP concepts. It retains Shader-Core's post-`light` aggregation boundary rather than copying OpenLit's `ComputeLights` or replacing Shader-Core light enumeration.

- The post-`light` direct aggregate weights each `light.color` with the OpenLit color-space luminance coefficients: `(0.22, 0.707, 0.071)` under `UNITY_COLORSPACE_GAMMA`, or `(0.0396819152, 0.458021790, 0.00609653955)` in Linear. Module changes made through the established `light` phase therefore remain part of the Toon aggregate.
- The scene direction adds the positive-Y first-order SH direction to that direct aggregate. The direction vector is `directAggregate + ((shAr.rgb + shAg.rgb + shAb.rgb) / 3)` with the Y component made positive, plus the fixed fallback `(0.001, 0.002, 0.001)`. The fallback is added before normalization, including for a nonzero aggregate. Exact-zero or nonfinite direction vectors use the fallback; finite near-cancellation residuals are normalized normally.
- In the supported normal BIRP scope, OpenLit `GetV` is the identity: the SH evaluator uses the selected scene direction without a camera or world-position dependency. Light Volumes, direction override, and other expanded OpenLit scope are not implemented.
- The bright band evaluates the unscaled identity-BIRP `V` using the L0/L2 base plus the L1 term along `V`. The dark band reuses the same L0/L2 base and evaluates L1 along the normalized SH RGB direction `normalize(shAr.rgb + shAg.rgb + shAb.rgb)`. An exact-zero or nonfinite SH direction contributes zero dark-band L1 so the result remains finite.
- Both bands are assembled before color-space conversion. Gamma converts both assembled bands with Unity's Linear-to-sRGB conversion; Linear leaves both assembled bands unconverted. The selected band remains the binary result of `step(0, dot(surfaceNormal, lightDirection))`.

These equations are Pure Base's narrow reimplementation of inspected OpenLit 1.0.2 concepts as used by lilToon 2.3.4. They do not copy upstream function bodies, add an OpenLit dependency, or imply an official lilToon/OpenLit association.

The ownership boundaries remain explicit: `ForwardAdd` publishes and uses only normalized direct aggregate direction when its squared length is greater than `0.000001`, otherwise zero; it adds no SH direction, fallback, or environment band. `LIGHTMAP_ON` and disabled Unity SH sampling preserve the direct direction but suppress Toon-generated SH bands, while Shader-Core owns lightmap decoding and Mixed/Subtractive handling. `sd.shadow` affects host-managed direct Toon radiance exactly once and never direction or SH evaluation. PBR, Hybrid, and Unlit retain their existing lighting paths and do not inherit Toon's OpenLit-derived helper.

### Toon direct-light visibility contract

For `PureBase/Toon`, the per-light `light.color` exposed to the `light` phase is the scene/direct light color multiplied by non-shadow distance, spot, and cookie attenuation. Unity effective visibility is published separately as `sd.shadow` before the `light` phase, so the same value is available to the `modifylight` and `shade` phases. This contract applies across the supported Unity light-kind branches, including directional, point, spot, point-cookie, and directional-cookie inputs.
Expand Down
12 changes: 12 additions & 0 deletions Docs/technical-information.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,18 @@ Hybrid は PBR の経路の中にある既存の2値化直接拡散反射の式

この固定されたホスト動作によって、公開項目、キーワード、パス、バリアント、依存関係は増えません。公開プロパティ ABI は変わらないため、既存のマテリアルを移行する必要はなく、自動的にこの動作を受け取ります。

### OpenLit 由来の Toon 動作と provenance

Pure Base の追加モジュールなし Toon ライティングは、lilToon 2.3.4 を通して確認した OpenLit 1.0.2 の Built-in Render Pipeline 向け概念のうち、限定した部分を適応したものです。Shader-Core の `light` 後の直接光集計境界を維持し、OpenLit の `ComputeLights` をコピーしたり、Shader-Core のライト列挙を置き換えたりしません。

- Toon は `light` フェーズ後の直接光集計を、色空間に応じた OpenLit の luminance で重み付けします。Gamma では `(0.22, 0.707, 0.071)`、Linear では `(0.0396819152, 0.458021790, 0.00609653955)` を使用します。そのため、既存の `light` フェーズでモジュールが変更した値も集計に含まれます。
- 方向はその集計、正の Y を持つ1次 SH の方向、固定 fallback `(0.001, 0.002, 0.001)` を加えてから正規化します。fallback は常に和へ含め、合計が完全なゼロまたは有限でない場合はこのベクトルを使います。有限な近相殺の残差は通常どおり正規化します。
- 通常の BIRP では、OpenLit の `GetV` に相当する動作はホストが選んだ方向をそのまま使う identity です。カメラやワールド座標への依存、Light Volumes、方向の上書き、その他の拡張された OpenLit 範囲はありません。
- 明るい SH 帯は unscaled な `V` による L0/L2 の基底と L1 を使います。暗い SH 帯は同じ L0/L2 の基底を再利用し、正規化した SH RGB 方向に沿って L1 を評価します。SH 方向がゼロまたは有限でない場合、暗い帯の L1 をゼロにして結果を有限に保ちます。
- Gamma では組み立て済みの両方の帯へ Unity の Linear-to-sRGB 変換を適用し、Linear では変換しません。明暗の選択は、表面法線とシーン方向の内積の符号による2値判定です。

`ForwardAdd` は直接光だけを扱います。直接光集計の二乗長が `0.000001` より大きい場合は正規化した方向を使い、それ以外ではゼロとし、SH、fallback、環境光の帯は加えません。ライトマップのデコードと Mixed/Subtractive の処理は Shader-Core が担当し、`LIGHTMAP_ON` または Unity の SH サンプリング無効時は Toon が生成する SH を抑制します。`sd.shadow` は Toon のホスト管理直接放射輝度へ1回だけ作用し、集計方向や SH には作用しません。PBR、Hybrid、Unlit の動作は変更されません。

### Toon の直接光と可視性の契約

`PureBase/Toon` の `light` 差し込み位置へ渡す各ライトの `light.color` は、シーンの直接光の色に、影以外の距離・スポット・クッキー減衰を乗じた値です。Unity のライト単位の実効可視性は `sd.shadow` として分離して公開され、`light` の前に設定されるため、`modifylight` と `shade` からも同じ値を参照できます。この分離は、対応する方向ライト、ポイントライト、スポットライト、ポイントクッキー、方向クッキーの各ライト分岐に適用されます。
Expand Down
12 changes: 12 additions & 0 deletions Docs/technical-information.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,18 @@ Hybrid retains its unchanged binary direct-diffuse equation inside the PBR path.

This fixed host behavior adds no public property, keyword, pass, variant, or dependency. The public property ABI is unchanged, so existing materials need no migration and receive the behavior automatically.

### OpenLit-derived Toon behavior and provenance

Pure Base's module-free Toon lighting is a bounded adaptation of selected OpenLit 1.0.2 BIRP concepts inspected through lilToon 2.3.4. It keeps Shader-Core's post-`light` direct-light aggregation and does not copy OpenLit's `ComputeLights` or replace Shader-Core light enumeration.

- Toon weights the post-`light` direct aggregate with color-space-specific OpenLit luminance: `(0.22, 0.707, 0.071)` for Gamma and `(0.0396819152, 0.458021790, 0.00609653955)` for Linear. The aggregate therefore continues to include module-authored `light` changes.
- Its direction combines that aggregate, positive-Y first-order SH, and the fixed fallback `(0.001, 0.002, 0.001)` before normalization. The fallback is always part of the sum; exact-zero or nonfinite totals fall back to that vector, while finite near-cancellation residuals remain normalizable.
- In normal BIRP, OpenLit `GetV` is represented by the identity direction used by the host. There is no camera/position dependency, Light Volumes behavior, direction override, or other expanded OpenLit scope.
- The bright SH band uses unscaled `V` for the L0/L2 base and L1. The dark band reuses that L0/L2 base and uses L1 along the normalized SH RGB direction. A zero or nonfinite SH direction contributes zero dark L1 to keep the result finite.
- Gamma applies Unity's Linear-to-sRGB conversion to both assembled bands; Linear does not convert them. Binary band selection uses the sign of the surface-normal and scene-direction dot product.

`ForwardAdd` is direct-only: it uses normalized direct aggregate direction above the `0.000001` squared-length threshold and zero otherwise, without SH, fallback, or environment contribution. Lightmap decoding and Mixed/Subtractive handling remain Shader-Core-owned, and Toon-generated SH is suppressed for `LIGHTMAP_ON` or disabled Unity SH sampling. `sd.shadow` changes direct Toon radiance once, never the aggregate direction or SH. PBR, Hybrid, and Unlit are unchanged.

### Toon direct-light visibility contract

For `PureBase/Toon`, `light.color` in the `light` phase is the scene/direct light color multiplied by non-shadow distance, spot, and cookie attenuation. Unity effective per-light visibility is exposed independently through `sd.shadow` before `light`, `modifylight`, and `shade`. The same split is used for the supported directional, point, spot, point-cookie, and directional-cookie light branches.
Expand Down
4 changes: 2 additions & 2 deletions NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ License
This project is licensed under the Apache-2.0 License - see the LICENSE file for details

Third-Party Lighting Concept Provenance
PureBase's minimal Toon lighting helper is a narrow adapted/reimplemented use of the identified lilToon 2.3.4/OpenLit 1.0.2 lighting concepts, including the corresponding SH bright/dark constants and operation structure. It is not a broad lilToon or OpenLit integration or a full upstream code copy. This acknowledgement does not imply endorsement, sponsorship, contributor status, or participation by lilToon or OpenLit developers.
Pure Base's minimal Toon lighting helper is a narrow, independently written reimplementation of selected OpenLit 1.0.2 BIRP concepts inspected through lilToon 2.3.4: color-space luminance, positive-Y SH direction and fallback handling, and the bright/dark SH term structure. The Pure Base adaptation retains Shader-Core's post-light aggregation and does not copy `ComputeLights` or any upstream function body. It adds no OpenLit or lilToon dependency and is not an official lilToon or OpenLit integration. This acknowledgement does not imply endorsement, sponsorship, contributor status, or participation by lilToon or OpenLit developers.

Inspected lilToon sources

- `Shader/Includes/openlit_core.hlsl`: `ComputeLightDirection`, `ShadeSH9ToonDouble`, and `ComputeLights`
- `Shader/Includes/openlit_core.hlsl`: `GetV`, `OpenLitLuminance`, `ComputeLightDirection`, `ShadeSH9ToonDouble`, `ComputeSHLightsAndDirection`, and `ComputeLights`
- `Shader/Includes/lil_common_functions.hlsl`: SH direction and bright/dark decomposition
- `Shader/Includes/lil_common_macro.hlsl`: ForwardAdd and lightmap ownership context

Expand Down
21 changes: 15 additions & 6 deletions Shaders/Common/birp_host.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
#define SCModelSelectAggregateLightDirection(directAggregateDirection, shAr, shAg, shAb) (dot(directAggregateDirection, directAggregateDirection) == 0 ? half3(0, 0, 0) : normalize(directAggregateDirection))
#endif

#ifndef SCModelEvaluateLightDirectionWeight
#define SCModelEvaluateLightDirectionWeight(lightColor) dot(lightColor, half3(0.333333, 0.333333, 0.333333))
#endif

/// <summary>Accumulates a BIRP light after the Shader-Core per-light phase.</summary>
void SCCalculateLight(inout SCLightData lightSum, inout SCShadingData sd, inout SCCustomData cd, SCVertexData vertex, SCLightData light)
{
Expand All @@ -40,17 +44,22 @@ void SCCalculateLight(inout SCLightData lightSum, inout SCShadingData sd, inout

__SC_PHASE_light__

lightSum.direction += light.direction * dot(light.color, half3(0.333333, 0.333333, 0.333333));
lightSum.direction += light.direction * SCModelEvaluateLightDirectionWeight(light.color);
lightSum.color += light.color * SCModelEvaluateDirectFactor(sd, light);
}

/// <summary>Publishes the aggregate light direction and applies the selected model's ambient SH response.</summary>
/// <summary>Publishes the fallback-inclusive Toon direct direction in ForwardBase and adds Toon SH only when Unity permits it.</summary>
void SCCalculateEnvironmentLight(inout SCLightData lightSum, inout half3 env, inout SCShadingData sd, inout SCCustomData cd, SCVertexData vertex, half4 shAr, half4 shAg, half4 shAb, half4 shBr, half4 shBg, half4 shBb, half4 shC)
{
#if defined(PUREBASE_TOON_MODEL_INCLUDED) && !defined(LIGHTMAP_ON)
sd.L = SCModelSelectAggregateLightDirection(lightSum.direction, unity_SHAr, unity_SHAg, unity_SHAb);
#if !defined(UNITY_PASS_FORWARDADD)
env += SCModelEvaluateAmbient(sd, unity_SHAr, unity_SHAg, unity_SHAb, unity_SHBr, unity_SHBg, unity_SHBb, unity_SHC);
#if defined(PUREBASE_TOON_MODEL_INCLUDED)
#if defined(UNITY_PASS_FORWARDADD)
sd.L = dot(lightSum.direction, lightSum.direction) > 0.000001 ? normalize(lightSum.direction) : half3(0, 0, 0);
#else
sd.L = SCModelSelectAggregateLightDirection(lightSum.direction, half4(0, 0, 0, 0), half4(0, 0, 0, 0), half4(0, 0, 0, 0));
#if !defined(LIGHTMAP_ON) && UNITY_SHOULD_SAMPLE_SH
sd.L = SCModelSelectAggregateLightDirection(lightSum.direction, shAr, shAg, shAb);
env += SCModelEvaluateAmbient(sd, shAr, shAg, shAb, shBr, shBg, shBb, shC);
#endif
#endif
#else
sd.L = SCModelSelectAggregateLightDirection(lightSum.direction, shAr, shAg, shAb);
Expand Down
61 changes: 50 additions & 11 deletions Shaders/Common/toon_lighting.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -26,28 +26,67 @@ half PureBaseToonEvaluateDirectFactor(float3 surfaceNormal, float3 lightDirectio
return step(0, dot(surfaceNormal, lightDirection));
}

/// <summary>Builds a finite Toon band direction from direct-light and spherical-harmonics aggregates.</summary>
/// <summary>Evaluates the OpenLit-derived direct-light luminance for the active Unity color-space branch.</summary>
float PureBaseToonLuminance(float3 rgb)
{
#if defined(UNITY_COLORSPACE_GAMMA)
return dot(rgb, float3(0.22, 0.707, 0.071));
#else
return dot(rgb, float3(0.0396819152, 0.458021790, 0.00609653955));
#endif
}

/// <summary>Builds a finite fallback-inclusive OpenLit Toon band direction from post-light direct and first-order SH aggregates.</summary>
float3 PureBaseToonComputeLightDirection(float3 directAggregateDirection, float4 shAr, float4 shAg, float4 shAb)
{
float3 shDirection = (shAr.xyz + shAg.xyz + shAb.xyz) / 3;
float3 directionVector = directAggregateDirection + float3(shDirection.x, abs(shDirection.y), shDirection.z);
if (dot(directionVector, directionVector) <= 0.000001)
float3 fallbackDirection = float3(0.001, 0.002, 0.001);
float3 directionVector = directAggregateDirection + float3(shDirection.x, abs(shDirection.y), shDirection.z) + fallbackDirection;
if (all(directionVector == 0) || !all(isfinite(directionVector)))
{
directionVector = float3(0.001, 0.002, 0.001);
directionVector = fallbackDirection;
}

return normalize(directionVector);
}

/// <summary>Evaluates the fixed bright and dark spherical-harmonics bands for a Toon surface.</summary>
/// <summary>Evaluates the OpenLit-derived L0/L2 SH base shared by the bright and dark Toon bands.</summary>
float3 PureBaseToonEvaluateShL0L2(float3 V, float4 shAr, float4 shAg, float4 shAb, float4 shBr, float4 shBg, float4 shBb, float4 shC)
{
float4 quadratic = V.xyzz * V.yzzx;
return float3(shAr.w, shAg.w, shAb.w)
+ float3(dot(shBr, quadratic), dot(shBg, quadratic), dot(shBb, quadratic))
+ shC.rgb * (V.x * V.x - V.y * V.y);
}

/// <summary>Evaluates a first-order SH term along the supplied direction.</summary>
float3 PureBaseToonEvaluateShL1(float3 direction, float4 shAr, float4 shAg, float4 shAb)
{
return float3(dot(shAr.rgb, direction), dot(shAg.rgb, direction), dot(shAb.rgb, direction));
}

/// <summary>Evaluates the finite dark-band L1 term along the summed first-order SH direction.</summary>
float3 PureBaseToonEvaluateDarkShL1(float4 shAr, float4 shAg, float4 shAb)
{
float3 shDirection = shAr.xyz + shAg.xyz + shAb.xyz;
if (all(shDirection == 0) || !all(isfinite(shDirection)))
{
return float3(0, 0, 0);
}

return PureBaseToonEvaluateShL1(normalize(shDirection), shAr, shAg, shAb);
}

/// <summary>Evaluates the OpenLit-derived bright and dark SH bands before selecting the Toon surface-facing band.</summary>
float3 PureBaseToonEvaluateTwoBandSh(float3 surfaceNormal, float3 L, float4 shAr, float4 shAg, float4 shAb, float4 shBr, float4 shBg, float4 shBb, float4 shC)
{
float3 E = L * 0.666666;
float4 quadratic = E.xyzz * E.yzzx;
float3 base = float3(shAr.w, shAg.w, shAb.w) + float3(dot(shBr, quadratic), dot(shBg, quadratic), dot(shBb, quadratic)) + shC.rgb * (E.x * E.x - E.y * E.y);
float3 linearTerm = float3(dot(shAr.xyz, E), dot(shAg.xyz, E), dot(shAb.xyz, E));
float3 bright = max(base + linearTerm, 0);
float3 dark = max(base - linearTerm, 0);
float3 base = PureBaseToonEvaluateShL0L2(L, shAr, shAg, shAb, shBr, shBg, shBb, shC);
float3 bright = base + PureBaseToonEvaluateShL1(L, shAr, shAg, shAb);
float3 dark = base + PureBaseToonEvaluateDarkShL1(shAr, shAg, shAb);
#if defined(UNITY_COLORSPACE_GAMMA)
bright = LinearToGammaSpace(bright);
dark = LinearToGammaSpace(dark);
#endif
return lerp(dark, bright, step(0, dot(surfaceNormal, L)));
}

Expand Down
3 changes: 2 additions & 1 deletion Shaders/Models/toon.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ half3 SCModelSelectMainLightDirection(SCVertexData vertex, half3 lightDirection)
return lightDirection;
}

/// <summary>Evaluates the supplied Unity spherical-harmonics coefficients as fixed bright and dark Toon bands.</summary>
/// <summary>Evaluates the supplied Unity spherical-harmonics coefficients as OpenLit-derived bright and dark Toon bands.</summary>
half3 SCModelEvaluateAmbient(SCShadingData shadingData, half4 shAr, half4 shAg, half4 shAb, half4 shBr, half4 shBg, half4 shBb, half4 shC)
{
return PureBaseToonEvaluateTwoBandSh(shadingData.N, shadingData.L, shAr, shAg, shAb, shBr, shBg, shBb, shC);
Expand Down Expand Up @@ -95,5 +95,6 @@ half4 SCModelAddSurfaceColor(SCShadingData shadingData, SCCustomData customData,
}

#define SCModelSelectAggregateLightDirection(directAggregateDirection, shAr, shAg, shAb) PureBaseToonComputeLightDirection(directAggregateDirection, shAr, shAg, shAb)
#define SCModelEvaluateLightDirectionWeight(lightColor) PureBaseToonLuminance(lightColor)

#endif
Loading