@forgeax/engine-shader 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +99 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/ShaderRegistry.d.ts +208 -0
- package/dist/ShaderRegistry.d.ts.map +1 -0
- package/dist/__tests__/atmosphere-background.test.d.ts +2 -0
- package/dist/__tests__/atmosphere-background.test.d.ts.map +1 -0
- package/dist/__tests__/atmosphere-cubemap-orientation.test.d.ts +2 -0
- package/dist/__tests__/atmosphere-cubemap-orientation.test.d.ts.map +1 -0
- package/dist/__tests__/atmosphere-preetham-numeric.test.d.ts +2 -0
- package/dist/__tests__/atmosphere-preetham-numeric.test.d.ts.map +1 -0
- package/dist/__tests__/atmosphere-preetham-red.test.d.ts +2 -0
- package/dist/__tests__/atmosphere-preetham-red.test.d.ts.map +1 -0
- package/dist/__tests__/atmosphere-preetham.test.d.ts +2 -0
- package/dist/__tests__/atmosphere-preetham.test.d.ts.map +1 -0
- package/dist/__tests__/atmosphere-three-r184-reference.test.d.ts +2 -0
- package/dist/__tests__/atmosphere-three-r184-reference.test.d.ts.map +1 -0
- package/dist/__tests__/bloom-fxaa-tonemap.unit.test.d.ts +2 -0
- package/dist/__tests__/bloom-fxaa-tonemap.unit.test.d.ts.map +1 -0
- package/dist/__tests__/builtin-texture-sampling-contract.test.d.ts +2 -0
- package/dist/__tests__/builtin-texture-sampling-contract.test.d.ts.map +1 -0
- package/dist/__tests__/default-standard-pbr-alpha.unit.test.d.ts +2 -0
- package/dist/__tests__/default-standard-pbr-alpha.unit.test.d.ts.map +1 -0
- package/dist/__tests__/deferred-lighting-ssao.test.d.ts +2 -0
- package/dist/__tests__/deferred-lighting-ssao.test.d.ts.map +1 -0
- package/dist/__tests__/fog-oracle.unit.test.d.ts +2 -0
- package/dist/__tests__/fog-oracle.unit.test.d.ts.map +1 -0
- package/dist/__tests__/fog-orthographic-ray.unit.test.d.ts +2 -0
- package/dist/__tests__/fog-orthographic-ray.unit.test.d.ts.map +1 -0
- package/dist/__tests__/fog-producer-matrix.integration.test.d.ts +2 -0
- package/dist/__tests__/fog-producer-matrix.integration.test.d.ts.map +1 -0
- package/dist/__tests__/hdrp-ssao-compile.test.d.ts +2 -0
- package/dist/__tests__/hdrp-ssao-compile.test.d.ts.map +1 -0
- package/dist/__tests__/ibl-irradiance.unit.test.d.ts +2 -0
- package/dist/__tests__/ibl-irradiance.unit.test.d.ts.map +1 -0
- package/dist/__tests__/ibl-sampling.unit.test.d.ts +2 -0
- package/dist/__tests__/ibl-sampling.unit.test.d.ts.map +1 -0
- package/dist/__tests__/lighting-punctual.unit.test.d.ts +2 -0
- package/dist/__tests__/lighting-punctual.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-artifact-dedup.unit.test.d.ts +2 -0
- package/dist/__tests__/material-artifact-dedup.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-builtins.unit.test.d.ts +2 -0
- package/dist/__tests__/material-builtins.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-contract.unit.test.d.ts +2 -0
- package/dist/__tests__/material-contract.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-derived-builtins.integration.test.d.ts +2 -0
- package/dist/__tests__/material-derived-builtins.integration.test.d.ts.map +1 -0
- package/dist/__tests__/public-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/public-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/scene-temporal.unit.test.d.ts +2 -0
- package/dist/__tests__/scene-temporal.unit.test.d.ts.map +1 -0
- package/dist/__tests__/shader-registry-transaction.unit.test.d.ts +2 -0
- package/dist/__tests__/shader-registry-transaction.unit.test.d.ts.map +1 -0
- package/dist/__tests__/shader.unit.test.d.ts +2 -0
- package/dist/__tests__/shader.unit.test.d.ts.map +1 -0
- package/dist/__tests__/sprite-lit-shader.test.d.ts +2 -0
- package/dist/__tests__/sprite-lit-shader.test.d.ts.map +1 -0
- package/dist/__tests__/sprite-variants.unit.test.d.ts +2 -0
- package/dist/__tests__/sprite-variants.unit.test.d.ts.map +1 -0
- package/dist/__tests__/taa-resolve.unit.test.d.ts +2 -0
- package/dist/__tests__/taa-resolve.unit.test.d.ts.map +1 -0
- package/dist/__tests__/transparent-pbr.unit.test.d.ts +2 -0
- package/dist/__tests__/transparent-pbr.unit.test.d.ts.map +1 -0
- package/dist/__tests__/vertex-color-variant.unit.test.d.ts +2 -0
- package/dist/__tests__/vertex-color-variant.unit.test.d.ts.map +1 -0
- package/dist/errors.d.ts +96 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +560 -0
- package/dist/index.mjs.map +1 -0
- package/dist/material/artifact-registry.d.ts +31 -0
- package/dist/material/artifact-registry.d.ts.map +1 -0
- package/dist/material/artifact-types.d.ts +13 -0
- package/dist/material/artifact-types.d.ts.map +1 -0
- package/dist/material-schemas.d.ts +8 -0
- package/dist/material-schemas.d.ts.map +1 -0
- package/dist/register-default-sprite-lit.d.ts +40 -0
- package/dist/register-default-sprite-lit.d.ts.map +1 -0
- package/dist/register-default-standard-pbr-skin.d.ts +34 -0
- package/dist/register-default-standard-pbr-skin.d.ts.map +1 -0
- package/dist/tonemap.d.ts +13 -0
- package/dist/tonemap.d.ts.map +1 -0
- package/dist/types.d.ts +47 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +58 -0
- package/src/ShaderRegistry.ts +519 -0
- package/src/__tests__/atmosphere-background.test.ts +33 -0
- package/src/__tests__/atmosphere-cubemap-orientation.test.ts +68 -0
- package/src/__tests__/atmosphere-preetham-numeric.test.ts +122 -0
- package/src/__tests__/atmosphere-preetham-red.test.ts +52 -0
- package/src/__tests__/atmosphere-preetham.test.ts +132 -0
- package/src/__tests__/atmosphere-three-r184-reference.test.ts +122 -0
- package/src/__tests__/bloom-fxaa-tonemap.unit.test.ts +31 -0
- package/src/__tests__/builtin-texture-sampling-contract.test.ts +56 -0
- package/src/__tests__/default-standard-pbr-alpha.unit.test.ts +19 -0
- package/src/__tests__/deferred-lighting-ssao.test.ts +114 -0
- package/src/__tests__/fog-oracle.unit.test.ts +132 -0
- package/src/__tests__/fog-orthographic-ray.unit.test.ts +62 -0
- package/src/__tests__/fog-producer-matrix.integration.test.ts +77 -0
- package/src/__tests__/hdrp-ssao-compile.test.ts +185 -0
- package/src/__tests__/ibl-irradiance.unit.test.ts +43 -0
- package/src/__tests__/ibl-sampling.unit.test.ts +40 -0
- package/src/__tests__/lighting-punctual.unit.test.ts +66 -0
- package/src/__tests__/manifest.fixture.json +22 -0
- package/src/__tests__/material-artifact-dedup.unit.test.ts +21 -0
- package/src/__tests__/material-builtins.unit.test.ts +111 -0
- package/src/__tests__/material-contract.unit.test.ts +79 -0
- package/src/__tests__/material-derived-builtins.integration.test.ts +30 -0
- package/src/__tests__/public-surface.unit.test.ts +9 -0
- package/src/__tests__/scene-temporal.unit.test.ts +32 -0
- package/src/__tests__/shader-registry-transaction.unit.test.ts +116 -0
- package/src/__tests__/shader.unit.test.ts +1909 -0
- package/src/__tests__/sprite-lit-shader.test.ts +240 -0
- package/src/__tests__/sprite-variants.unit.test.ts +209 -0
- package/src/__tests__/taa-resolve.unit.test.ts +32 -0
- package/src/__tests__/transparent-pbr.unit.test.ts +21 -0
- package/src/__tests__/vertex-color-variant.unit.test.ts +52 -0
- package/src/atmosphere-background.wgsl +80 -0
- package/src/atmosphere-cubemap.wgsl +72 -0
- package/src/atmosphere-ibl.wgsl +127 -0
- package/src/atmosphere-preetham.wgsl +214 -0
- package/src/bloom-blur.wgsl +89 -0
- package/src/bloom-bright.wgsl +50 -0
- package/src/bloom-composite.wgsl +54 -0
- package/src/brdf.wgsl +25 -0
- package/src/common.wgsl +523 -0
- package/src/default-standard-pbr-skin.wgsl +538 -0
- package/src/default-standard-pbr.wgsl +794 -0
- package/src/errors.ts +169 -0
- package/src/fog.wgsl +27 -0
- package/src/fxaa.wgsl +259 -0
- package/src/hdrp-cluster-forward.wgsl +186 -0
- package/src/hdrp-deferred-lighting.wgsl +69 -0
- package/src/hdrp-ssao.wgsl +174 -0
- package/src/ibl-brdf-lut.wgsl +92 -0
- package/src/ibl-equirect-to-cube.wgsl +56 -0
- package/src/ibl-irradiance.wgsl +89 -0
- package/src/ibl-prefilter.wgsl +101 -0
- package/src/ibl-sampling.wgsl +58 -0
- package/src/ibl-shared.wgsl +118 -0
- package/src/index.ts +166 -0
- package/src/lighting-directional.wgsl +471 -0
- package/src/lighting-punctual.wgsl +274 -0
- package/src/material/artifact-registry.ts +78 -0
- package/src/material/artifact-types.ts +26 -0
- package/src/material-schemas.ts +48 -0
- package/src/msdf-text.wgsl +208 -0
- package/src/pbr-temporal.wgsl +73 -0
- package/src/register-default-sprite-lit.ts +87 -0
- package/src/register-default-standard-pbr-skin.ts +74 -0
- package/src/shadow-pcf.wgsl +120 -0
- package/src/shadow_caster.wgsl +54 -0
- package/src/skybox.wgsl +75 -0
- package/src/sprite-lit.wgsl +307 -0
- package/src/sprite.wgsl +365 -0
- package/src/taa-resolve.wgsl +129 -0
- package/src/tbn.wgsl +54 -0
- package/src/tonemap.ts +13 -0
- package/src/tonemap.wgsl +177 -0
- package/src/types.ts +59 -0
- package/src/unlit.wgsl +186 -0
package/src/errors.ts
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// @forgeax/engine-shader/errors — ShaderError + factories (physically isolated from the
|
|
2
|
+
// same-named class in @forgeax/engine-shader-compiler, but shape-aligned 1:1 / charter
|
|
3
|
+
// proposition 5: consistent abstraction).
|
|
4
|
+
//
|
|
5
|
+
// Shape rules (plan-strategy §S-7 / D-R7 / OQ-2 close):
|
|
6
|
+
// - ShaderError extends Error, with the 5-field top-level projection
|
|
7
|
+
// {code, lineNum, linePos, message, hint} (MVP-2.3) + 3 internal fields
|
|
8
|
+
// {expected, detail, name}.
|
|
9
|
+
// - ShaderErrorCode is a closed 4-member union imported from @forgeax/engine-types
|
|
10
|
+
// (SSOT single-source policy).
|
|
11
|
+
//
|
|
12
|
+
// Physical isolation — this file does **not** import from
|
|
13
|
+
// @forgeax/engine-shader-compiler; the type / factories are 1:1 mirrored but
|
|
14
|
+
// independently implemented (guarded by the AC-06 triple-grep gate; charter
|
|
15
|
+
// proposition 1: progressive disclosure).
|
|
16
|
+
|
|
17
|
+
import type { ShaderErrorCode } from '@forgeax/engine-types';
|
|
18
|
+
|
|
19
|
+
export type { ShaderErrorCode };
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Path-specific `detail` shape for `manifest-malformed` / `shader-not-found`.
|
|
23
|
+
* `reason` is supplemental prose (it does not replace `hint`).
|
|
24
|
+
*/
|
|
25
|
+
export interface ShaderErrorDetail {
|
|
26
|
+
readonly reason?: string | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* Machine-consumable list of currently-registered material-shader identifiers.
|
|
29
|
+
* Populated by `materialShaderNotFound()` factory so AI users can enumerate
|
|
30
|
+
* available shaders via `err.detail.registeredShaderIds` for autocomplete /
|
|
31
|
+
* fuzzy-match recovery without parsing the human-formatted `err.expected`
|
|
32
|
+
* string (charter P3 structured failure). `err.expected` still carries the
|
|
33
|
+
* pre-formatted `"ShaderRegistry has registered identifiers: [...]"` message
|
|
34
|
+
* for human-readable log output — the two channels are complementary.
|
|
35
|
+
*/
|
|
36
|
+
readonly registeredShaderIds?: readonly string[] | undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface ShaderErrorInit {
|
|
40
|
+
readonly code: ShaderErrorCode;
|
|
41
|
+
readonly expected: string;
|
|
42
|
+
readonly hint: string;
|
|
43
|
+
readonly message: string;
|
|
44
|
+
readonly lineNum?: number | undefined;
|
|
45
|
+
readonly linePos?: number | undefined;
|
|
46
|
+
readonly detail?: ShaderErrorDetail | undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Structured shader error (runtime path, aligned with build-time
|
|
51
|
+
* `@forgeax/engine-shader-compiler.ShaderError`).
|
|
52
|
+
*
|
|
53
|
+
* **5 surface fields** (MVP-2.3 top-level projection, AI-user consumption path):
|
|
54
|
+
* - `.code` — member of the closed `ShaderErrorCode` union (4 variants)
|
|
55
|
+
* - `.message` — display text (`Error` base-class field, populated at construction)
|
|
56
|
+
* - `.hint` — actionable recovery guidance (charter proposition 3:
|
|
57
|
+
* machine-readable hint > prose)
|
|
58
|
+
* - `.lineNum` / `.linePos` — error source location (undefined on the runtime
|
|
59
|
+
* path; only populated by the build-time compile-failed path; the field is
|
|
60
|
+
* retained here for shape alignment).
|
|
61
|
+
*
|
|
62
|
+
* **3 internal fields**:
|
|
63
|
+
* - `.name` = `'ShaderError'` (debug label)
|
|
64
|
+
* - `.expected` — expected-state description (symmetric with RhiError)
|
|
65
|
+
* - `.detail` — path-specific extras (`reason` prose)
|
|
66
|
+
*
|
|
67
|
+
* **Do not `new` this directly** — construct via the 2 factory helpers
|
|
68
|
+
* (`manifestMalformed` / `shaderNotFound`); the runtime path of this loop does
|
|
69
|
+
* not trigger compile-failed / init-failed (those two paths belong to the
|
|
70
|
+
* build-time `ShaderError` shape in `@forgeax/engine-shader-compiler`).
|
|
71
|
+
*/
|
|
72
|
+
export class ShaderError extends Error {
|
|
73
|
+
override readonly name: 'ShaderError' = 'ShaderError';
|
|
74
|
+
readonly code: ShaderErrorCode;
|
|
75
|
+
readonly expected: string;
|
|
76
|
+
readonly hint: string;
|
|
77
|
+
readonly lineNum: number | undefined;
|
|
78
|
+
readonly linePos: number | undefined;
|
|
79
|
+
readonly detail: ShaderErrorDetail | undefined;
|
|
80
|
+
|
|
81
|
+
constructor(init: ShaderErrorInit) {
|
|
82
|
+
super(init.message);
|
|
83
|
+
this.code = init.code;
|
|
84
|
+
this.expected = init.expected;
|
|
85
|
+
this.hint = init.hint;
|
|
86
|
+
this.lineNum = init.lineNum;
|
|
87
|
+
this.linePos = init.linePos;
|
|
88
|
+
this.detail = init.detail;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// === factory helpers (2 variants for the runtime path) ============================
|
|
93
|
+
|
|
94
|
+
/** `manifest-malformed`: manifest.json schema validation failed (4 fields missing or JSON unparseable). */
|
|
95
|
+
export function manifestMalformed(args: {
|
|
96
|
+
readonly message: string;
|
|
97
|
+
readonly hint: string;
|
|
98
|
+
readonly reason?: string | undefined;
|
|
99
|
+
}): ShaderError {
|
|
100
|
+
return new ShaderError({
|
|
101
|
+
code: 'manifest-malformed',
|
|
102
|
+
expected: 'manifest.json parses + every entry has {hash, wgsl, glsl, bindings}',
|
|
103
|
+
message: args.message,
|
|
104
|
+
hint: args.hint,
|
|
105
|
+
...(args.reason !== undefined ? { detail: { reason: args.reason } } : {}),
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** `shader-not-found`: ShaderRegistry.get(hash) hash miss. */
|
|
110
|
+
export function shaderNotFound(args: {
|
|
111
|
+
readonly hash: string;
|
|
112
|
+
readonly hint: string;
|
|
113
|
+
}): ShaderError {
|
|
114
|
+
return new ShaderError({
|
|
115
|
+
code: 'shader-not-found',
|
|
116
|
+
expected: `manifest.entries contains entry with hash '${args.hash}'`,
|
|
117
|
+
message: `ShaderRegistry: hash '${args.hash}' not present in manifest`,
|
|
118
|
+
hint: args.hint,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* `material-shader-not-found`: `ShaderRegistry.findMaterialArtifact(identifier)`
|
|
124
|
+
* miss. Returned by the runtime path when a `MaterialAsset.payload.materialShader`
|
|
125
|
+
* `::`-routed identifier (or any registered identifier) has no matching entry
|
|
126
|
+
* in the registry. AI-user recovery: the `.hint` cites the
|
|
127
|
+
* `forgeax::default-standard-pbr` reservation + the
|
|
128
|
+
* `installMaterialArtifact(...)` API path so the caller can register the missing
|
|
129
|
+
* shader at engine boot (feat-20260523-shader-template-instance-split M5 / T05).
|
|
130
|
+
*
|
|
131
|
+
* feat-20260528-material-shader-registration-unification M3 / w12 +
|
|
132
|
+
* feat-20260624-sprite-lit F-P1 fix: dual-channel exposure of the registered
|
|
133
|
+
* identifier set. `.expected` carries the pre-formatted string
|
|
134
|
+
* `"ShaderRegistry has registered identifiers: [id1, id2, ...]"` for human /
|
|
135
|
+
* log output; `.detail.registeredShaderIds` carries the raw `readonly string[]`
|
|
136
|
+
* for AI users to enumerate via property access without regex-parsing the
|
|
137
|
+
* message (charter P3 structured failure).
|
|
138
|
+
*/
|
|
139
|
+
export function materialShaderNotFound(args: {
|
|
140
|
+
readonly identifier: string;
|
|
141
|
+
readonly hint: string;
|
|
142
|
+
readonly expected: string[];
|
|
143
|
+
}): ShaderError {
|
|
144
|
+
return new ShaderError({
|
|
145
|
+
code: 'material-shader-not-found',
|
|
146
|
+
expected: `ShaderRegistry has registered identifiers: [${args.expected.join(', ')}]`,
|
|
147
|
+
message: `ShaderRegistry: material shader identifier '${args.identifier}' not registered`,
|
|
148
|
+
hint: args.hint,
|
|
149
|
+
detail: { reason: `identifier=${args.identifier}`, registeredShaderIds: args.expected },
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// === Result<T, E> ====================================================================
|
|
154
|
+
//
|
|
155
|
+
// Round 3 fix-up F-P0-1: Result is re-exported from @forgeax/engine-rhi so the
|
|
156
|
+
// `.unwrap()` / `.unwrapOr()` method-chain surface (AGENTS.md §Adapter /
|
|
157
|
+
// Device IDE-friendly template + 50+ JSDoc @example anchors) is identical
|
|
158
|
+
// across the @forgeax/engine-rhi + @forgeax/engine-shader public surfaces (charter
|
|
159
|
+
// proposition 5: consistent abstraction). The previous local duplicate
|
|
160
|
+
// (plain field-only Result) caused TS structural-mismatch when ShaderRegistry
|
|
161
|
+
// values flowed into RHI-typed signatures.
|
|
162
|
+
|
|
163
|
+
export {
|
|
164
|
+
err,
|
|
165
|
+
ok,
|
|
166
|
+
type Result,
|
|
167
|
+
type ResultErr,
|
|
168
|
+
type ResultOk,
|
|
169
|
+
} from '@forgeax/engine-rhi';
|
package/src/fog.wgsl
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#define_import_path forgeax_view::fog
|
|
2
|
+
|
|
3
|
+
// Shared analytic scene-radiance Fog. Producers provide a world-space ray;
|
|
4
|
+
// this module owns the density integration and linear color contract.
|
|
5
|
+
#import forgeax_view::common::{FogViewParams, FogRay}
|
|
6
|
+
|
|
7
|
+
const FOG_Q_EPSILON : f32 = 1e-6;
|
|
8
|
+
const FOG_EXP_LIMIT : f32 = 80.0;
|
|
9
|
+
|
|
10
|
+
fn fogOpticalDepth(params : FogViewParams, ray : FogRay) -> f32 {
|
|
11
|
+
let rho0 = params.density * exp(clamp(-params.heightFalloff * ray.origin.y, -FOG_EXP_LIMIT, FOG_EXP_LIMIT));
|
|
12
|
+
let q = params.heightFalloff * ray.direction.y;
|
|
13
|
+
var tau = rho0 * ray.distance;
|
|
14
|
+
if abs(q) >= FOG_Q_EPSILON {
|
|
15
|
+
let exponent = clamp(-q * ray.distance, -FOG_EXP_LIMIT, FOG_EXP_LIMIT);
|
|
16
|
+
tau = rho0 * (1.0 - exp(exponent)) / q;
|
|
17
|
+
}
|
|
18
|
+
return max(tau, 0.0);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
fn apply_fog(params : FogViewParams, ray : FogRay, color : vec4<f32>) -> vec4<f32> {
|
|
22
|
+
let tau = fogOpticalDepth(params, ray);
|
|
23
|
+
let transmittance = exp(-tau);
|
|
24
|
+
let opacity = params.maxOpacity * (1.0 - transmittance);
|
|
25
|
+
let mixed = mix(color.rgb, params.color, opacity);
|
|
26
|
+
return vec4<f32>(mixed, color.a);
|
|
27
|
+
}
|
package/src/fxaa.wgsl
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
#define_import_path forgeax_view::fxaa
|
|
2
|
+
|
|
3
|
+
// @forgeax/engine-shader - fxaa.wgsl
|
|
4
|
+
// (feat-20260528-fxaa-post-processing / w4; algorithm rewritten
|
|
5
|
+
// feat-20260531-fxaa-shader-correctness-fix).
|
|
6
|
+
//
|
|
7
|
+
// FXAA 3.11 fragment shader -- faithful port of the canonical Simon
|
|
8
|
+
// Rodriguez reference (same lineage as Bevy's WGSL port), adapted to
|
|
9
|
+
// forgeax naga_oil conventions. Hardcoded "High" quality preset: 12-step
|
|
10
|
+
// edge search with compile-time step multipliers (no UBO, no #ifdef quality
|
|
11
|
+
// branches).
|
|
12
|
+
//
|
|
13
|
+
// Vertex stage: imports the SSOT fullscreen_triangle() from
|
|
14
|
+
// forgeax_view::common (single large triangle, research Finding 1).
|
|
15
|
+
//
|
|
16
|
+
// Fragment stage: computes perceptual luma via rgb2luma (sqrt of Rec.601
|
|
17
|
+
// weighted sum -- matches the canonical reference), performs a directional
|
|
18
|
+
// edge search, then blends along the edge with a sub-pixel aliasing term.
|
|
19
|
+
// Texture dimensions are obtained via textureDimensions() for resize-agnostic
|
|
20
|
+
// behavior (AC-08).
|
|
21
|
+
//
|
|
22
|
+
// SAMPLING: uses textureSampleLevel (explicit LOD 0) with a linear sampler,
|
|
23
|
+
// NOT textureLoad. Explicit-LOD sampling is permitted in non-uniform control
|
|
24
|
+
// flow (WebGPU spec 20.1 only bars *implicit*-derivative textureSample), so
|
|
25
|
+
// the edge-search loops are legal while still getting bilinear filtering --
|
|
26
|
+
// the bilinear taps at fractional UVs are what actually do the sub-texel
|
|
27
|
+
// blend that anti-aliases the edge.
|
|
28
|
+
//
|
|
29
|
+
// All offsets are kept in UV space: texel steps are scaled by
|
|
30
|
+
// inverseScreenSize (= 1/dims) before being added to a [0,1] UV. The prior
|
|
31
|
+
// implementation added texel-unit offsets directly to UVs (and squared
|
|
32
|
+
// texelSize in the sub-pixel term), sampling ~half a screen away and gouging
|
|
33
|
+
// holes in shaded surfaces -- the bug this rewrite fixes.
|
|
34
|
+
//
|
|
35
|
+
// Bindings (group 0):
|
|
36
|
+
// @binding(0) screenTexture : texture_2d<f32> -- sampled LDR input
|
|
37
|
+
// @binding(1) samp : sampler -- linear filterable sampler
|
|
38
|
+
// linearLdrColorDomain: FXAA samples and writes linear LDR values; display
|
|
39
|
+
// encoding happens only at the final output surface.
|
|
40
|
+
|
|
41
|
+
#import forgeax_view::common::FullscreenOutput
|
|
42
|
+
#import forgeax_view::common::fullscreen_triangle
|
|
43
|
+
#import forgeax_view::common::linearToSrgbOetf
|
|
44
|
+
|
|
45
|
+
const EDGE_THRESHOLD_MIN: f32 = 0.0312;
|
|
46
|
+
const EDGE_THRESHOLD_MAX: f32 = 0.125;
|
|
47
|
+
const SUBPIXEL_QUALITY: f32 = 0.75;
|
|
48
|
+
const ITERATIONS: i32 = 12;
|
|
49
|
+
|
|
50
|
+
@group(0) @binding(0) var screenTexture: texture_2d<f32>;
|
|
51
|
+
@group(0) @binding(1) var samp: sampler;
|
|
52
|
+
|
|
53
|
+
fn rgb2luma(rgb: vec3<f32>) -> f32 {
|
|
54
|
+
return sqrt(dot(rgb, vec3<f32>(0.299, 0.587, 0.114)));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
fn sampleColor(uv: vec2<f32>) -> vec3<f32> {
|
|
58
|
+
return textureSampleLevel(screenTexture, samp, uv, 0.0).rgb;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
fn sampleLuma(uv: vec2<f32>) -> f32 {
|
|
62
|
+
return rgb2luma(textureSampleLevel(screenTexture, samp, uv, 0.0).rgb);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// High-preset edge-search step multipliers (canonical FXAA 3.11 QUALITY
|
|
66
|
+
// table for the 12-tap variant): steps 0..4 = 1.0, then ramps to 8.0.
|
|
67
|
+
fn qualityStep(i: i32) -> f32 {
|
|
68
|
+
if i < 5 {
|
|
69
|
+
return 1.0;
|
|
70
|
+
}
|
|
71
|
+
if i == 5 {
|
|
72
|
+
return 1.5;
|
|
73
|
+
}
|
|
74
|
+
if i < 10 {
|
|
75
|
+
return 2.0;
|
|
76
|
+
}
|
|
77
|
+
if i == 10 {
|
|
78
|
+
return 4.0;
|
|
79
|
+
}
|
|
80
|
+
return 8.0;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@vertex
|
|
84
|
+
fn vs_main(@builtin(vertex_index) vertex_index: u32) -> FullscreenOutput {
|
|
85
|
+
return fullscreen_triangle(vertex_index);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@fragment
|
|
89
|
+
fn fs_main(in: FullscreenOutput) -> @location(0) vec4<f32> {
|
|
90
|
+
let dims = vec2<f32>(textureDimensions(screenTexture));
|
|
91
|
+
let inverseScreenSize = 1.0 / dims;
|
|
92
|
+
let uv = in.uv;
|
|
93
|
+
|
|
94
|
+
let centerColor = sampleColor(uv);
|
|
95
|
+
let lumaCenter = rgb2luma(centerColor);
|
|
96
|
+
|
|
97
|
+
// Lumas of the 4 direct neighbours.
|
|
98
|
+
let lumaDown = sampleLuma(uv + vec2<f32>(0.0, 1.0) * inverseScreenSize);
|
|
99
|
+
let lumaUp = sampleLuma(uv + vec2<f32>(0.0, -1.0) * inverseScreenSize);
|
|
100
|
+
let lumaLeft = sampleLuma(uv + vec2<f32>(-1.0, 0.0) * inverseScreenSize);
|
|
101
|
+
let lumaRight = sampleLuma(uv + vec2<f32>(1.0, 0.0) * inverseScreenSize);
|
|
102
|
+
|
|
103
|
+
let lumaMin = min(lumaCenter, min(min(lumaDown, lumaUp), min(lumaLeft, lumaRight)));
|
|
104
|
+
let lumaMax = max(lumaCenter, max(max(lumaDown, lumaUp), max(lumaLeft, lumaRight)));
|
|
105
|
+
let lumaRange = lumaMax - lumaMin;
|
|
106
|
+
|
|
107
|
+
// Not on an edge (or in a near-uniform region): pass the centre through.
|
|
108
|
+
if lumaRange < max(EDGE_THRESHOLD_MIN, lumaMax * EDGE_THRESHOLD_MAX) {
|
|
109
|
+
return vec4<f32>(linearToSrgbOetf(centerColor), 1.0);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Lumas of the 4 corners.
|
|
113
|
+
let lumaDownLeft = sampleLuma(uv + vec2<f32>(-1.0, 1.0) * inverseScreenSize);
|
|
114
|
+
let lumaUpRight = sampleLuma(uv + vec2<f32>(1.0, -1.0) * inverseScreenSize);
|
|
115
|
+
let lumaUpLeft = sampleLuma(uv + vec2<f32>(-1.0, -1.0) * inverseScreenSize);
|
|
116
|
+
let lumaDownRight = sampleLuma(uv + vec2<f32>(1.0, 1.0) * inverseScreenSize);
|
|
117
|
+
|
|
118
|
+
let lumaDownUp = lumaDown + lumaUp;
|
|
119
|
+
let lumaLeftRight = lumaLeft + lumaRight;
|
|
120
|
+
|
|
121
|
+
let lumaLeftCorners = lumaDownLeft + lumaUpLeft;
|
|
122
|
+
let lumaDownCorners = lumaDownLeft + lumaDownRight;
|
|
123
|
+
let lumaRightCorners = lumaDownRight + lumaUpRight;
|
|
124
|
+
let lumaUpCorners = lumaUpRight + lumaUpLeft;
|
|
125
|
+
|
|
126
|
+
// Estimate horizontal vs vertical gradient.
|
|
127
|
+
let edgeHorizontal =
|
|
128
|
+
abs(-2.0 * lumaLeft + lumaLeftCorners) +
|
|
129
|
+
abs(-2.0 * lumaCenter + lumaDownUp) * 2.0 +
|
|
130
|
+
abs(-2.0 * lumaRight + lumaRightCorners);
|
|
131
|
+
let edgeVertical =
|
|
132
|
+
abs(-2.0 * lumaUp + lumaUpCorners) +
|
|
133
|
+
abs(-2.0 * lumaCenter + lumaLeftRight) * 2.0 +
|
|
134
|
+
abs(-2.0 * lumaDown + lumaDownCorners);
|
|
135
|
+
|
|
136
|
+
let isHorizontal = edgeHorizontal >= edgeVertical;
|
|
137
|
+
|
|
138
|
+
// Lumas on the two sides of the edge.
|
|
139
|
+
let luma1 = select(lumaLeft, lumaDown, isHorizontal);
|
|
140
|
+
let luma2 = select(lumaRight, lumaUp, isHorizontal);
|
|
141
|
+
let gradient1 = luma1 - lumaCenter;
|
|
142
|
+
let gradient2 = luma2 - lumaCenter;
|
|
143
|
+
|
|
144
|
+
let is1Steepest = abs(gradient1) >= abs(gradient2);
|
|
145
|
+
let gradientScaled = 0.25 * max(abs(gradient1), abs(gradient2));
|
|
146
|
+
|
|
147
|
+
// One-texel step perpendicular to the edge, in UV space.
|
|
148
|
+
var stepLength = select(inverseScreenSize.x, inverseScreenSize.y, isHorizontal);
|
|
149
|
+
|
|
150
|
+
var lumaLocalAverage = 0.0;
|
|
151
|
+
if is1Steepest {
|
|
152
|
+
stepLength = -stepLength;
|
|
153
|
+
lumaLocalAverage = 0.5 * (luma1 + lumaCenter);
|
|
154
|
+
} else {
|
|
155
|
+
lumaLocalAverage = 0.5 * (luma2 + lumaCenter);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Shift the UV by half a texel toward the edge.
|
|
159
|
+
var currentUv = uv;
|
|
160
|
+
if isHorizontal {
|
|
161
|
+
currentUv.y += stepLength * 0.5;
|
|
162
|
+
} else {
|
|
163
|
+
currentUv.x += stepLength * 0.5;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Walk along the edge in both directions until the luma delta exceeds the
|
|
167
|
+
// local gradient (= we left the edge) or we run out of iterations.
|
|
168
|
+
let offsetStep = select(
|
|
169
|
+
vec2<f32>(stepLength, 0.0),
|
|
170
|
+
vec2<f32>(0.0, stepLength),
|
|
171
|
+
isHorizontal,
|
|
172
|
+
);
|
|
173
|
+
let edgeStep = select(
|
|
174
|
+
vec2<f32>(0.0, inverseScreenSize.y),
|
|
175
|
+
vec2<f32>(inverseScreenSize.x, 0.0),
|
|
176
|
+
isHorizontal,
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
var uv1 = currentUv - edgeStep;
|
|
180
|
+
var uv2 = currentUv + edgeStep;
|
|
181
|
+
|
|
182
|
+
var lumaEnd1 = sampleLuma(uv1) - lumaLocalAverage;
|
|
183
|
+
var lumaEnd2 = sampleLuma(uv2) - lumaLocalAverage;
|
|
184
|
+
|
|
185
|
+
var reached1 = abs(lumaEnd1) >= gradientScaled;
|
|
186
|
+
var reached2 = abs(lumaEnd2) >= gradientScaled;
|
|
187
|
+
var reachedBoth = reached1 && reached2;
|
|
188
|
+
|
|
189
|
+
if !reached1 {
|
|
190
|
+
uv1 -= edgeStep;
|
|
191
|
+
}
|
|
192
|
+
if !reached2 {
|
|
193
|
+
uv2 += edgeStep;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if !reachedBoth {
|
|
197
|
+
var i = 1;
|
|
198
|
+
loop {
|
|
199
|
+
if i >= ITERATIONS || reachedBoth {
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
let q = qualityStep(i);
|
|
203
|
+
if !reached1 {
|
|
204
|
+
lumaEnd1 = sampleLuma(uv1) - lumaLocalAverage;
|
|
205
|
+
reached1 = abs(lumaEnd1) >= gradientScaled;
|
|
206
|
+
}
|
|
207
|
+
if !reached2 {
|
|
208
|
+
lumaEnd2 = sampleLuma(uv2) - lumaLocalAverage;
|
|
209
|
+
reached2 = abs(lumaEnd2) >= gradientScaled;
|
|
210
|
+
}
|
|
211
|
+
reachedBoth = reached1 && reached2;
|
|
212
|
+
if !reached1 {
|
|
213
|
+
uv1 -= edgeStep * q;
|
|
214
|
+
}
|
|
215
|
+
if !reached2 {
|
|
216
|
+
uv2 += edgeStep * q;
|
|
217
|
+
}
|
|
218
|
+
i += 1;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Distances to each end of the edge, along the edge axis.
|
|
223
|
+
let distance1 = select(uv.y - uv1.y, uv.x - uv1.x, isHorizontal);
|
|
224
|
+
let distance2 = select(uv2.y - uv.y, uv2.x - uv.x, isHorizontal);
|
|
225
|
+
|
|
226
|
+
let isDirection1 = distance1 < distance2;
|
|
227
|
+
let distanceFinal = min(distance1, distance2);
|
|
228
|
+
let edgeThickness = distance1 + distance2;
|
|
229
|
+
let pixelOffsetRaw = -distanceFinal / edgeThickness + 0.5;
|
|
230
|
+
|
|
231
|
+
// Only blend if the centre luma is on the correct side of the edge --
|
|
232
|
+
// guards against haloing.
|
|
233
|
+
let isLumaCenterSmaller = lumaCenter < lumaLocalAverage;
|
|
234
|
+
let correctVariation1 = (lumaEnd1 < 0.0) != isLumaCenterSmaller;
|
|
235
|
+
let correctVariation2 = (lumaEnd2 < 0.0) != isLumaCenterSmaller;
|
|
236
|
+
let correctVariation = select(correctVariation2, correctVariation1, isDirection1);
|
|
237
|
+
let finalOffset = select(0.0, pixelOffsetRaw, correctVariation);
|
|
238
|
+
|
|
239
|
+
// Sub-pixel anti-aliasing: weight by the local 3x3 luma contrast.
|
|
240
|
+
let lumaAverage =
|
|
241
|
+
(1.0 / 12.0) *
|
|
242
|
+
(2.0 * (lumaDownUp + lumaLeftRight) + lumaLeftCorners + lumaRightCorners);
|
|
243
|
+
let subPixelOffset1 = clamp(abs(lumaAverage - lumaCenter) / lumaRange, 0.0, 1.0);
|
|
244
|
+
let subPixelOffset2 = (-2.0 * subPixelOffset1 + 3.0) * subPixelOffset1 * subPixelOffset1;
|
|
245
|
+
let subPixelOffsetFinal = subPixelOffset2 * subPixelOffset2 * SUBPIXEL_QUALITY;
|
|
246
|
+
|
|
247
|
+
let pixelOffset = max(finalOffset, subPixelOffsetFinal);
|
|
248
|
+
|
|
249
|
+
// Apply the perpendicular offset, in UV space.
|
|
250
|
+
var finalUv = uv;
|
|
251
|
+
if isHorizontal {
|
|
252
|
+
finalUv.y += pixelOffset * stepLength;
|
|
253
|
+
} else {
|
|
254
|
+
finalUv.x += pixelOffset * stepLength;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
let finalColor = sampleColor(finalUv);
|
|
258
|
+
return vec4<f32>(linearToSrgbOetf(finalColor), 1.0);
|
|
259
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// hdrp-cluster-forward.wgsl — HDRP cluster-forward punctual light evaluation.
|
|
2
|
+
// feat-20260608-cluster-lighting M4 / w17.
|
|
3
|
+
//
|
|
4
|
+
// The cluster module owns membership and raw payload decoding only. Punctual
|
|
5
|
+
// BRDF, range, cone, PCF, and fail-open behavior live in lighting-punctual.
|
|
6
|
+
|
|
7
|
+
#define_import_path forgeax_hdrp::cluster_forward
|
|
8
|
+
|
|
9
|
+
#import forgeax_pbr::lighting_punctual::{evalPoint, evalSpot, evalSpotShadowed}
|
|
10
|
+
#import forgeax_view::common::{view}
|
|
11
|
+
#ifdef POINT_SHADOW_AVAILABLE
|
|
12
|
+
#import forgeax_pbr::lighting_punctual::{evalPointShadowed}
|
|
13
|
+
#endif
|
|
14
|
+
|
|
15
|
+
#ifdef CLUSTER_FORWARD_AVAILABLE
|
|
16
|
+
|
|
17
|
+
const KIND_POINT: u32 = 0u;
|
|
18
|
+
const KIND_SPOT: u32 = 1u;
|
|
19
|
+
|
|
20
|
+
// LightSlot is the byte-frozen 64B std430/std140 transport contract.
|
|
21
|
+
// [0..2] position, [3] invRangeSquared
|
|
22
|
+
// [4..6] colorTimesIntensity, [7] cosInner
|
|
23
|
+
// [8..10] direction, [11] cosOuter
|
|
24
|
+
// [12] raw kind u32, [13] shadow identity i32
|
|
25
|
+
// [14..15] point near/far f32 bits; spot keeps these lanes zero.
|
|
26
|
+
struct LightSlot {
|
|
27
|
+
position : vec4<f32>,
|
|
28
|
+
color : vec4<f32>,
|
|
29
|
+
direction : vec4<f32>,
|
|
30
|
+
kind_and_shadow : vec4<u32>,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const LIGHTSLOT_BYTE_SIZE: u32 = 64u;
|
|
34
|
+
|
|
35
|
+
struct ClusterUniform {
|
|
36
|
+
grid : vec4<u32>,
|
|
37
|
+
near_far_log : vec4<f32>,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
#if STORAGE_BUFFER_AVAILABLE == true
|
|
41
|
+
@group(2) @binding(3) var<storage, read> light_data : array<LightSlot, 256>;
|
|
42
|
+
@group(2) @binding(4) var<storage, read> cluster_grid : array<u32>;
|
|
43
|
+
@group(2) @binding(5) var<storage, read> light_index_list : array<u32>;
|
|
44
|
+
#else
|
|
45
|
+
@group(2) @binding(3) var<uniform> light_data_uniform : array<LightSlot, 128>;
|
|
46
|
+
#endif
|
|
47
|
+
@group(2) @binding(6) var<uniform> cluster_uniform : ClusterUniform;
|
|
48
|
+
|
|
49
|
+
fn get_ssao_intensity() -> f32 {
|
|
50
|
+
return cluster_uniform.near_far_log.w;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
fn view_z_to_z_slice(
|
|
54
|
+
view_z : f32,
|
|
55
|
+
grid_z : u32,
|
|
56
|
+
near : f32,
|
|
57
|
+
far : f32,
|
|
58
|
+
log_far_over_near : f32,
|
|
59
|
+
) -> u32 {
|
|
60
|
+
if (view_z >= -near) {
|
|
61
|
+
return 0u;
|
|
62
|
+
}
|
|
63
|
+
let slice = floor(log(-view_z / near) / log_far_over_near * f32(grid_z));
|
|
64
|
+
let u_slice = u32(slice);
|
|
65
|
+
if (u_slice >= grid_z) {
|
|
66
|
+
return grid_z - 1u;
|
|
67
|
+
}
|
|
68
|
+
return u_slice;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
fn ndc_position_to_cluster(
|
|
72
|
+
ndc : vec3<f32>,
|
|
73
|
+
view_z : f32,
|
|
74
|
+
grid_x : u32,
|
|
75
|
+
grid_y : u32,
|
|
76
|
+
grid_z : u32,
|
|
77
|
+
near : f32,
|
|
78
|
+
far : f32,
|
|
79
|
+
log_far : f32,
|
|
80
|
+
) -> vec3<u32> {
|
|
81
|
+
let cx = clamp(u32(floor((ndc.x * 0.5 + 0.5) * f32(grid_x))), 0u, grid_x - 1u);
|
|
82
|
+
let cy = clamp(u32(floor((ndc.y * 0.5 + 0.5) * f32(grid_y))), 0u, grid_y - 1u);
|
|
83
|
+
let cz = view_z_to_z_slice(view_z, grid_z, near, far, log_far);
|
|
84
|
+
return vec3(cx, cy, cz);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Decode one kind first, then delegate the complete evaluation to the shared
|
|
88
|
+
// punctual owner. Unknown kinds are explicitly zero contribution.
|
|
89
|
+
fn evaluate_cluster_light(
|
|
90
|
+
light : LightSlot,
|
|
91
|
+
world_pos : vec3<f32>,
|
|
92
|
+
normal : vec3<f32>,
|
|
93
|
+
view_dir : vec3<f32>,
|
|
94
|
+
base_color : vec3<f32>,
|
|
95
|
+
metallic : f32,
|
|
96
|
+
alpha_sq : f32,
|
|
97
|
+
f0 : vec3<f32>,
|
|
98
|
+
) -> vec3<f32> {
|
|
99
|
+
let kind = light.kind_and_shadow.x;
|
|
100
|
+
if (kind == KIND_POINT) {
|
|
101
|
+
#ifdef POINT_SHADOW_AVAILABLE
|
|
102
|
+
let layer = bitcast<i32>(light.kind_and_shadow.y);
|
|
103
|
+
if (layer >= 0) {
|
|
104
|
+
return evalPointShadowed(
|
|
105
|
+
light.position.xyz, light.color.xyz, light.position.w,
|
|
106
|
+
world_pos, normal, view_dir, base_color, metallic, alpha_sq, f0,
|
|
107
|
+
layer,
|
|
108
|
+
bitcast<f32>(light.kind_and_shadow.z),
|
|
109
|
+
bitcast<f32>(light.kind_and_shadow.w),
|
|
110
|
+
0.005, 0.05,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
#endif
|
|
114
|
+
return evalPoint(
|
|
115
|
+
light.position.xyz, light.color.xyz, light.position.w,
|
|
116
|
+
world_pos, normal, view_dir, base_color, metallic, alpha_sq, f0,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
if (kind == KIND_SPOT) {
|
|
120
|
+
let tile = bitcast<i32>(light.kind_and_shadow.y);
|
|
121
|
+
if (tile >= 0) {
|
|
122
|
+
return evalSpotShadowed(
|
|
123
|
+
light.position.xyz, light.direction.xyz, light.color.xyz,
|
|
124
|
+
// LightSlot packs cosInner in color.w and cosOuter in direction.w;
|
|
125
|
+
// keep the evaluator's named cone order intact at the decode boundary.
|
|
126
|
+
light.color.w, light.direction.w, light.position.w,
|
|
127
|
+
world_pos, normal, view_dir, base_color, metallic, alpha_sq, f0,
|
|
128
|
+
view.spotLightViewProj[tile], tile, 0.005, 0.05,
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
return evalSpot(
|
|
132
|
+
light.position.xyz, light.direction.xyz, light.color.xyz,
|
|
133
|
+
light.color.w, light.direction.w, light.position.w,
|
|
134
|
+
world_pos, normal, view_dir, base_color, metallic, alpha_sq, f0,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
return vec3<f32>(0.0);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
fn evaluate_cluster_lights(
|
|
141
|
+
ndc : vec3<f32>,
|
|
142
|
+
view_z : f32,
|
|
143
|
+
world_pos : vec3<f32>,
|
|
144
|
+
normal : vec3<f32>,
|
|
145
|
+
view_dir : vec3<f32>,
|
|
146
|
+
base_color : vec3<f32>,
|
|
147
|
+
metallic : f32,
|
|
148
|
+
alpha_sq : f32,
|
|
149
|
+
f0 : vec3<f32>,
|
|
150
|
+
) -> vec3<f32> {
|
|
151
|
+
let gx = cluster_uniform.grid.x;
|
|
152
|
+
let gy = cluster_uniform.grid.y;
|
|
153
|
+
let gz = cluster_uniform.grid.z;
|
|
154
|
+
let near = cluster_uniform.near_far_log.x;
|
|
155
|
+
let far = cluster_uniform.near_far_log.y;
|
|
156
|
+
let log_far = cluster_uniform.near_far_log.z;
|
|
157
|
+
var total_radiance = vec3<f32>(0.0);
|
|
158
|
+
|
|
159
|
+
#if STORAGE_BUFFER_AVAILABLE == true
|
|
160
|
+
let cluster_idx = ndc_position_to_cluster(ndc, view_z, gx, gy, gz, near, far, log_far);
|
|
161
|
+
let cluster_linear = cluster_idx.z * gy * gx + cluster_idx.y * gx + cluster_idx.x;
|
|
162
|
+
let grid_offset = cluster_linear * 2u;
|
|
163
|
+
let list_offset = cluster_grid[grid_offset];
|
|
164
|
+
let list_count = cluster_grid[grid_offset + 1u];
|
|
165
|
+
for (var i = 0u; i < list_count; i = i + 1u) {
|
|
166
|
+
let light = light_data[light_index_list[list_offset + i]];
|
|
167
|
+
total_radiance += evaluate_cluster_light(
|
|
168
|
+
light, world_pos, normal, view_dir, base_color, metallic, alpha_sq, f0,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
#else
|
|
172
|
+
let light_count = min(cluster_uniform.grid.w, 128u);
|
|
173
|
+
for (var i = 0u; i < 128u; i = i + 1u) {
|
|
174
|
+
if (i >= light_count) {
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
total_radiance += evaluate_cluster_light(
|
|
178
|
+
light_data_uniform[i], world_pos, normal, view_dir,
|
|
179
|
+
base_color, metallic, alpha_sq, f0,
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
#endif
|
|
183
|
+
return total_radiance;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
#endif // CLUSTER_FORWARD_AVAILABLE
|