@forgeax/engine-shader 0.1.20 → 0.1.23

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.
Files changed (74) hide show
  1. package/README.md +10 -0
  2. package/dist/ShaderRegistry.d.ts.map +1 -1
  3. package/dist/index.d.ts +6 -2
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.mjs +33070 -8
  6. package/dist/index.mjs.map +1 -1
  7. package/dist/ltc/provenance.d.ts +17 -0
  8. package/dist/ltc/provenance.d.ts.map +1 -0
  9. package/dist/ltc/tables.d.ts +22 -0
  10. package/dist/ltc/tables.d.ts.map +1 -0
  11. package/dist/material/artifact-types.d.ts +52 -0
  12. package/dist/material/artifact-types.d.ts.map +1 -1
  13. package/dist/material-schemas.d.ts +5 -0
  14. package/dist/material-schemas.d.ts.map +1 -1
  15. package/dist/register-default-sprite-lit.d.ts +3 -6
  16. package/dist/register-default-sprite-lit.d.ts.map +1 -1
  17. package/dist/types.d.ts +2 -1
  18. package/dist/types.d.ts.map +1 -1
  19. package/package.json +4 -4
  20. package/src/ShaderRegistry.ts +65 -0
  21. package/src/__tests__/bloom-fxaa-tonemap.unit.test.ts +30 -4
  22. package/src/__tests__/default-standard-pbr-alpha.unit.test.ts +7 -0
  23. package/src/__tests__/default-standard-pbr-transmission.unit.test.ts +138 -30
  24. package/src/__tests__/direct-light-layout.unit.test.ts +40 -0
  25. package/src/__tests__/ibl-irradiance.unit.test.ts +7 -4
  26. package/src/__tests__/ibl-sampling.unit.test.ts +3 -4
  27. package/src/__tests__/lighting-point-spot-volume.unit.test.ts +16 -2
  28. package/src/__tests__/lighting-punctual.unit.test.ts +61 -8
  29. package/src/__tests__/lighting-spot-volume-attenuation.unit.test.ts +1 -1
  30. package/src/__tests__/ltc-provenance.unit.test.ts +33 -0
  31. package/src/__tests__/manifest.fixture.json +33 -0
  32. package/src/__tests__/material-contract.unit.test.ts +61 -1
  33. package/src/__tests__/material-derived-builtins.integration.test.ts +27 -0
  34. package/src/__tests__/probe-lighting-composition.unit.test.ts +38 -0
  35. package/src/__tests__/rect-area-brdf.unit.test.ts +36 -0
  36. package/src/__tests__/reflection-probe-sampling.unit.test.ts +45 -0
  37. package/src/__tests__/scene-temporal.unit.test.ts +20 -0
  38. package/src/__tests__/shader.unit.test.ts +40 -15
  39. package/src/__tests__/shadow-pcss.unit.test.ts +131 -0
  40. package/src/__tests__/spot-cookie-composition.unit.test.ts +57 -0
  41. package/src/__tests__/spot-modifier-composition.unit.test.ts +19 -0
  42. package/src/__tests__/sprite-lit-shader.test.ts +24 -11
  43. package/src/__tests__/sprite-variants.unit.test.ts +5 -1
  44. package/src/__tests__/transmission-thickness-scale.unit.test.ts +7 -1
  45. package/src/__tests__/transparent-pbr.unit.test.ts +7 -0
  46. package/src/brdf.wgsl +115 -1
  47. package/src/common.wgsl +95 -91
  48. package/src/default-standard-pbr-skin.wgsl +108 -92
  49. package/src/default-standard-pbr.wgsl +189 -191
  50. package/src/fxaa.wgsl +27 -6
  51. package/src/ibl-irradiance.wgsl +4 -1
  52. package/src/ibl-sampling.wgsl +56 -2
  53. package/src/index.ts +23 -0
  54. package/src/lighting-directional.wgsl +161 -132
  55. package/src/lighting-probe.wgsl +51 -0
  56. package/src/lighting-punctual.wgsl +69 -29
  57. package/src/lighting-rect-area.wgsl +162 -0
  58. package/src/lighting-spot-modifiers.wgsl +93 -0
  59. package/src/ltc/provenance.ts +19 -0
  60. package/src/ltc/tables.ts +2831 -0
  61. package/src/material/artifact-types.ts +137 -0
  62. package/src/material-schemas.ts +12 -0
  63. package/src/register-default-sprite-lit.ts +3 -6
  64. package/src/scene-temporal.wgsl +14 -8
  65. package/src/shadow-pcf.wgsl +44 -1
  66. package/src/sprite-lit.wgsl +37 -53
  67. package/src/sprite.wgsl +1 -1
  68. package/src/standard-cluster.wgsl +274 -0
  69. package/src/tonemap.wgsl +8 -3
  70. package/src/types.ts +2 -1
  71. package/src/volume/volume-inject.wgsl +48 -38
  72. package/src/volume/volume-integrate.wgsl +47 -42
  73. package/dist/.tsbuildinfo +0 -1
  74. package/src/hdrp-cluster-forward.wgsl +0 -224
@@ -16,8 +16,10 @@
16
16
  // - sampleIblDiffuse(N, irradianceMap, irradianceSampler)
17
17
  // - sampleIblSpecular(N, V, roughness, F0, prefilterMap, prefilterSampler,
18
18
  // brdfLut, brdfLutSampler)
19
+ // - box_project(worldPosition, direction, boxCenter, boxExtents)
20
+ // - sampleReflectionProbeSpecular(..., probeMap, probeSampler, ...)
19
21
 
20
- #import forgeax_pbr::ibl_shared::{PI, fresnelSchlickRoughness, inverseRotateEnvironment}
22
+ #import forgeax_pbr::ibl_shared::{fresnelSchlickRoughness, inverseRotateEnvironment}
21
23
 
22
24
  // Sample pre-convolved irradiance from the irradiance cubemap.
23
25
  // Y is negated to compensate for WebGPU's top-left texture origin vs the
@@ -31,7 +33,7 @@ fn sampleIblDiffuse(
31
33
  let rotated = inverseRotateEnvironment(normal, rotation);
32
34
  let dir = vec3<f32>(rotated.x, -rotated.y, rotated.z);
33
35
  let irradianceEOverPi = textureSample(irradianceMap, irradianceSampler, dir).rgb;
34
- return irradianceEOverPi / PI;
36
+ return irradianceEOverPi;
35
37
  }
36
38
 
37
39
  // Split-sum specular IBL: prefiltered env * (F0 * scale + bias).
@@ -56,3 +58,55 @@ fn sampleIblSpecular(
56
58
  let F = fresnelSchlickRoughness(NdotV, F0, roughness);
57
59
  return prefilteredColor * (F * envBRDF.r + envBRDF.g);
58
60
  }
61
+
62
+ // Project a reflection ray from a point inside a probe box onto the box
63
+ // boundary. The helper is binding-free so the scene table and the material
64
+ // resource projection remain owned by the host record stage.
65
+ fn box_project(
66
+ worldPosition: vec3<f32>,
67
+ direction: vec3<f32>,
68
+ boxCenter: vec3<f32>,
69
+ boxExtents: vec3<f32>,
70
+ ) -> vec3<f32> {
71
+ let safeExtents = max(boxExtents, vec3<f32>(0.0001));
72
+ let safeDirection = select(
73
+ vec3<f32>(0.0001),
74
+ direction,
75
+ abs(direction) >= vec3<f32>(0.0001),
76
+ );
77
+ let localPosition = worldPosition - boxCenter;
78
+ let edgeSign = select(vec3<f32>(-1.0), vec3<f32>(1.0), direction >= vec3<f32>(0.0));
79
+ let edge = edgeSign * safeExtents;
80
+ let distances = (edge - localPosition) / safeDirection;
81
+ let travel = min(distances.x, min(distances.y, distances.z));
82
+ return normalize(localPosition + direction * max(travel, 0.0));
83
+ }
84
+
85
+ // Split-sum probe sample. The probe texture is supplied by the caller so a
86
+ // missing scene candidate can keep using sampleIblSpecular with the Skylight
87
+ // resources already present in the Standard material layout.
88
+ fn sampleReflectionProbeSpecular(
89
+ normal: vec3<f32>,
90
+ view: vec3<f32>,
91
+ roughness: f32,
92
+ F0: vec3<f32>,
93
+ worldPosition: vec3<f32>,
94
+ boxCenter: vec3<f32>,
95
+ boxExtents: vec3<f32>,
96
+ rotation: vec4<f32>,
97
+ probeMap: texture_cube<f32>,
98
+ probeSampler: sampler,
99
+ brdfLut: texture_2d<f32>,
100
+ brdfLutSampler: sampler,
101
+ ) -> vec3<f32> {
102
+ let NdotV = max(dot(normal, view), 0.001);
103
+ let reflection = reflect(-view, normal);
104
+ let projected = box_project(worldPosition, reflection, boxCenter, boxExtents);
105
+ let rotated = inverseRotateEnvironment(projected, rotation);
106
+ let probeDirection = vec3<f32>(rotated.x, -rotated.y, rotated.z);
107
+ let mip = roughness * 4.0;
108
+ let prefilteredColor = textureSampleLevel(probeMap, probeSampler, probeDirection, mip).rgb;
109
+ let envBRDF = textureSample(brdfLut, brdfLutSampler, vec2<f32>(NdotV, roughness)).rg;
110
+ let F = fresnelSchlickRoughness(NdotV, F0, roughness);
111
+ return prefilteredColor * (F * envBRDF.r + envBRDF.g);
112
+ }
package/src/index.ts CHANGED
@@ -39,6 +39,16 @@ export {
39
39
  type ShaderErrorDetail,
40
40
  shaderNotFound,
41
41
  } from './errors.js';
42
+ export {
43
+ generateLtcTables,
44
+ hashLtcTable,
45
+ LTC_SOURCE_PROVENANCE,
46
+ LTC_TABLE_HASHES,
47
+ LTC_TABLE_HEIGHT,
48
+ LTC_TABLE_INPUT_HASHES,
49
+ LTC_TABLE_WIDTH,
50
+ LTC_TABLES,
51
+ } from './ltc/tables.js';
42
52
  export {
43
53
  type MaterialArtifactConflictError,
44
54
  type MaterialArtifactInspection,
@@ -46,8 +56,12 @@ export {
46
56
  type MaterialRuntimeArtifact,
47
57
  } from './material/artifact-registry.js';
48
58
  export {
59
+ createStandardPbrArtifactReceipt,
49
60
  isMaterialShaderArtifact,
50
61
  type MaterialShaderArtifact,
62
+ type MaterialShaderArtifactReceipt,
63
+ type MaterialShaderResourceSlot,
64
+ type MaterialShaderVertexInput,
51
65
  } from './material/artifact-types.js';
52
66
  export {
53
67
  DEFAULT_MSDF_TEXT_PARAM_SCHEMA,
@@ -55,12 +69,15 @@ export {
55
69
  DEFAULT_STANDARD_PBR_PARAM_SCHEMA,
56
70
  DEFAULT_UNLIT_PARAM_SCHEMA,
57
71
  STANDARD_PBR_ALPHA_CUTOFF_DEFAULT,
72
+ STANDARD_PBR_ARTIFACT_RECEIPT,
73
+ STANDARD_PBR_SKIN_ARTIFACT_RECEIPT,
58
74
  } from './material-schemas.js';
59
75
  export {
60
76
  registerDefaultSpriteLit,
61
77
  type SpriteLitCaps,
62
78
  } from './register-default-sprite-lit.js';
63
79
  export { registerDefaultStandardPbrSkin } from './register-default-standard-pbr-skin.js';
80
+ export const RECT_AREA_LTC_SHADER_MODULE = 'forgeax::lighting-rect-area' as const;
64
81
  export {
65
82
  FORGEAX_RESERVED_PATH_PREFIX,
66
83
  type MaterialShaderEntry,
@@ -108,6 +125,12 @@ export const ENGINE_MATERIAL_MODULES = [
108
125
  'forgeax_material::sprite-lit',
109
126
  ] as const;
110
127
 
128
+ /** Names of the zero-binding Standard reflection-probe shader helpers. */
129
+ export const REFLECTION_PROBE_SHADER_HELPERS = Object.freeze([
130
+ 'box_project',
131
+ 'sampleReflectionProbeSpecular',
132
+ ] as const);
133
+
111
134
  export function isEngineMaterialModule(module: string): boolean {
112
135
  return (ENGINE_MATERIAL_MODULES as readonly string[]).includes(module);
113
136
  }
@@ -46,128 +46,43 @@
46
46
  // layered BRDFs such as base + clearcoat)
47
47
 
48
48
  #import forgeax_view::common::{view, shadowMap, shadowSampler}
49
- #import forgeax_pbr::brdf::{f_schlick, v_smith, d_ggx}
49
+ #import forgeax_pbr::brdf::{f_schlick, v_smith, d_ggx, threeR184DirectMultiScatter}
50
50
  #import forgeax_pbr::shadow_pcf::{sample_shadow_2d}
51
+ #ifdef DIRECTIONAL_PCSS_AVAILABLE
52
+ #import forgeax_pbr::shadow_pcf::{shadow_biased_receiver_depth, shadow_clamp_texel_to_tile, shadow_load_raw_depth, shadow_sample_compare}
53
+ #endif
51
54
 
52
55
  // feat-20260621-learn-render-5-3-production-shadow-demos M0 / AC-14:
53
56
  // compile-time upper bound on the PCF half-extent so the WGSL tap loops keep
54
57
  // a constant trip count (no dynamic loop bounds / shader variants). half=2
55
- // covers pcfKernelSize in {1,3,5} -> {1,9,25} taps; view.pcfKernelSize selects
56
- // the runtime radius via a per-iteration clip (plan-strategy D-1).
58
+ // covers the PCF1/3/5 profiles -> {1,9,25} taps; the profile carrier selects
59
+ // the runtime radius while keeping the receiver variant-free.
57
60
  const MAX_PCF_HALF : u32 = 2u;
58
61
 
59
- // Source: Three.js r184 src/nodes/functions/BSDF/DFGLUT.js. The full 16x16
60
- // RG16F table is decoded to exact f32 constants here; the lookup below mirrors
61
- // the source DataTexture's linear filtering and clamp-to-edge sampling without
62
- // adding a host binding or coupling direct light to the engine IBL LUT.
63
- const THREE_R184_DFG_LUT_SIZE : u32 = 16u;
64
- const THREE_R184_DFG_LUT : array<vec2<f32>, 256> = array<vec2<f32>, 256>(
65
- vec2<f32>(0.1470947265625, 0.85205078125), vec2<f32>(0.16552734375, 0.78759765625), vec2<f32>(0.244384765625, 0.638671875), vec2<f32>(0.370849609375, 0.51953125),
66
- vec2<f32>(0.496826171875, 0.41552734375), vec2<f32>(0.60205078125, 0.326416015625), vec2<f32>(0.68408203125, 0.25390625), vec2<f32>(0.74609375, 0.197509765625),
67
- vec2<f32>(0.79052734375, 0.154296875), vec2<f32>(0.822265625, 0.12164306640625), vec2<f32>(0.84326171875, 0.09698486328125), vec2<f32>(0.8564453125, 0.07843017578125),
68
- vec2<f32>(0.86328125, 0.06439208984375), vec2<f32>(0.86572265625, 0.0537109375), vec2<f32>(0.8642578125, 0.045440673828125), vec2<f32>(0.85986328125, 0.039031982421875),
69
- vec2<f32>(0.388671875, 0.611328125), vec2<f32>(0.39306640625, 0.6005859375), vec2<f32>(0.412353515625, 0.5458984375), vec2<f32>(0.45654296875, 0.4482421875),
70
- vec2<f32>(0.52783203125, 0.3525390625), vec2<f32>(0.607421875, 0.27392578125), vec2<f32>(0.6787109375, 0.21142578125), vec2<f32>(0.7333984375, 0.16259765625),
71
- vec2<f32>(0.77099609375, 0.1253662109375), vec2<f32>(0.79345703125, 0.0972900390625), vec2<f32>(0.80322265625, 0.07623291015625), vec2<f32>(0.8037109375, 0.06036376953125),
72
- vec2<f32>(0.79638671875, 0.048431396484375), vec2<f32>(0.78564453125, 0.03936767578125), vec2<f32>(0.77197265625, 0.03240966796875), vec2<f32>(0.7548828125, 0.0269775390625),
73
- vec2<f32>(0.572265625, 0.427490234375), vec2<f32>(0.57373046875, 0.424072265625), vec2<f32>(0.57958984375, 0.403564453125), vec2<f32>(0.591796875, 0.3544921875),
74
- vec2<f32>(0.6162109375, 0.2880859375), vec2<f32>(0.6552734375, 0.224853515625), vec2<f32>(0.69873046875, 0.172607421875), vec2<f32>(0.7353515625, 0.1318359375),
75
- vec2<f32>(0.75927734375, 0.10089111328125), vec2<f32>(0.77001953125, 0.07745361328125), vec2<f32>(0.77197265625, 0.0599365234375), vec2<f32>(0.76611328125, 0.046844482421875),
76
- vec2<f32>(0.751953125, 0.0369873046875), vec2<f32>(0.732421875, 0.029541015625), vec2<f32>(0.70947265625, 0.023834228515625), vec2<f32>(0.68359375, 0.019439697265625),
77
- vec2<f32>(0.708984375, 0.291015625), vec2<f32>(0.708984375, 0.289794921875), vec2<f32>(0.7099609375, 0.28125), vec2<f32>(0.7099609375, 0.258544921875),
78
- vec2<f32>(0.71142578125, 0.220458984375), vec2<f32>(0.7197265625, 0.1768798828125), vec2<f32>(0.734375, 0.1370849609375), vec2<f32>(0.748046875, 0.10479736328125),
79
- vec2<f32>(0.755859375, 0.07989501953125), vec2<f32>(0.759765625, 0.061004638671875), vec2<f32>(0.75341796875, 0.046844482421875), vec2<f32>(0.73876953125, 0.036224365234375),
80
- vec2<f32>(0.7177734375, 0.02825927734375), vec2<f32>(0.69189453125, 0.0222625732421875), vec2<f32>(0.6611328125, 0.0177001953125), vec2<f32>(0.62841796875, 0.01419830322265625),
81
- vec2<f32>(0.80810546875, 0.1917724609375), vec2<f32>(0.8076171875, 0.1912841796875), vec2<f32>(0.80615234375, 0.18798828125), vec2<f32>(0.8017578125, 0.1781005859375),
82
- vec2<f32>(0.79296875, 0.15869140625), vec2<f32>(0.78369140625, 0.1322021484375), vec2<f32>(0.775390625, 0.1048583984375), vec2<f32>(0.7685546875, 0.08111572265625),
83
- vec2<f32>(0.7646484375, 0.061981201171875), vec2<f32>(0.75537109375, 0.047271728515625), vec2<f32>(0.740234375, 0.036163330078125), vec2<f32>(0.71875, 0.0277557373046875),
84
- vec2<f32>(0.69091796875, 0.021484375), vec2<f32>(0.658203125, 0.0167388916015625), vec2<f32>(0.6220703125, 0.01316070556640625), vec2<f32>(0.583984375, 0.01041412353515625),
85
- vec2<f32>(0.87841796875, 0.1217041015625), vec2<f32>(0.8779296875, 0.1217041015625), vec2<f32>(0.875, 0.12060546875), vec2<f32>(0.869140625, 0.116943359375),
86
- vec2<f32>(0.85693359375, 0.1080322265625), vec2<f32>(0.837890625, 0.09375), vec2<f32>(0.81494140625, 0.076904296875), vec2<f32>(0.7958984375, 0.060699462890625),
87
- vec2<f32>(0.7763671875, 0.046875), vec2<f32>(0.75634765625, 0.035919189453125), vec2<f32>(0.732421875, 0.0274505615234375), vec2<f32>(0.703125, 0.021026611328125),
88
- vec2<f32>(0.6689453125, 0.01617431640625), vec2<f32>(0.63037109375, 0.01251220703125), vec2<f32>(0.58935546875, 0.00974273681640625), vec2<f32>(0.54638671875, 0.007633209228515625),
89
- vec2<f32>(0.92626953125, 0.07379150390625), vec2<f32>(0.92578125, 0.07391357421875), vec2<f32>(0.9228515625, 0.07391357421875), vec2<f32>(0.9169921875, 0.0731201171875),
90
- vec2<f32>(0.90380859375, 0.06982421875), vec2<f32>(0.88134765625, 0.0631103515625), vec2<f32>(0.85107421875, 0.053802490234375), vec2<f32>(0.8212890625, 0.043701171875),
91
- vec2<f32>(0.791015625, 0.034393310546875), vec2<f32>(0.76123046875, 0.026611328125), vec2<f32>(0.72802734375, 0.02044677734375), vec2<f32>(0.69189453125, 0.015655517578125),
92
- vec2<f32>(0.65087890625, 0.0120086669921875), vec2<f32>(0.607421875, 0.00925445556640625), vec2<f32>(0.56103515625, 0.0071563720703125), vec2<f32>(0.51416015625, 0.005565643310546875),
93
- vec2<f32>(0.95751953125, 0.042327880859375), vec2<f32>(0.95703125, 0.042449951171875), vec2<f32>(0.95458984375, 0.042816162109375), vec2<f32>(0.94921875, 0.043182373046875),
94
- vec2<f32>(0.93701171875, 0.04266357421875), vec2<f32>(0.9130859375, 0.040252685546875), vec2<f32>(0.8818359375, 0.035797119140625), vec2<f32>(0.8447265625, 0.0301361083984375),
95
- vec2<f32>(0.80615234375, 0.0243377685546875), vec2<f32>(0.76708984375, 0.0191497802734375), vec2<f32>(0.7265625, 0.01485443115234375), vec2<f32>(0.6826171875, 0.01142120361328125),
96
- vec2<f32>(0.63623046875, 0.0087738037109375), vec2<f32>(0.5869140625, 0.006740570068359375), vec2<f32>(0.53662109375, 0.005199432373046875), vec2<f32>(0.486083984375, 0.00402069091796875),
97
- vec2<f32>(0.9775390625, 0.0226287841796875), vec2<f32>(0.97705078125, 0.0227508544921875), vec2<f32>(0.97509765625, 0.023193359375), vec2<f32>(0.9697265625, 0.0239105224609375),
98
- vec2<f32>(0.95947265625, 0.0244903564453125), vec2<f32>(0.93603515625, 0.0241241455078125), vec2<f32>(0.9052734375, 0.02252197265625), vec2<f32>(0.865234375, 0.0197601318359375),
99
- vec2<f32>(0.82177734375, 0.016510009765625), vec2<f32>(0.7744140625, 0.0132904052734375), vec2<f32>(0.7265625, 0.01045989990234375), vec2<f32>(0.67626953125, 0.0081329345703125),
100
- vec2<f32>(0.6240234375, 0.00627899169921875), vec2<f32>(0.56982421875, 0.004825592041015625), vec2<f32>(0.51513671875, 0.0037136077880859375), vec2<f32>(0.46142578125, 0.0028629302978515625),
101
- vec2<f32>(0.98876953125, 0.01107025146484375), vec2<f32>(0.98876953125, 0.01116180419921875), vec2<f32>(0.98681640625, 0.01151275634765625), vec2<f32>(0.98291015625, 0.01221466064453125),
102
- vec2<f32>(0.97314453125, 0.01299285888671875), vec2<f32>(0.953125, 0.013519287109375), vec2<f32>(0.9228515625, 0.01328277587890625), vec2<f32>(0.88232421875, 0.01226043701171875),
103
- vec2<f32>(0.8349609375, 0.01065826416015625), vec2<f32>(0.783203125, 0.00885009765625), vec2<f32>(0.728515625, 0.007114410400390625), vec2<f32>(0.671875, 0.00560760498046875),
104
- vec2<f32>(0.61376953125, 0.004360198974609375), vec2<f32>(0.5546875, 0.0033721923828125), vec2<f32>(0.496337890625, 0.0025997161865234375), vec2<f32>(0.439453125, 0.0020046234130859375),
105
- vec2<f32>(0.9951171875, 0.00479888916015625), vec2<f32>(0.9951171875, 0.004863739013671875), vec2<f32>(0.99365234375, 0.005096435546875), vec2<f32>(0.990234375, 0.005603790283203125),
106
- vec2<f32>(0.9814453125, 0.0063323974609375), vec2<f32>(0.96435546875, 0.006977081298828125), vec2<f32>(0.93603515625, 0.007297515869140625), vec2<f32>(0.896484375, 0.0071258544921875),
107
- vec2<f32>(0.8466796875, 0.00649261474609375), vec2<f32>(0.79150390625, 0.005596160888671875), vec2<f32>(0.7314453125, 0.004627227783203125), vec2<f32>(0.6689453125, 0.0037174224853515625),
108
- vec2<f32>(0.60546875, 0.0029296875), vec2<f32>(0.54150390625, 0.00228118896484375), vec2<f32>(0.479248046875, 0.001766204833984375), vec2<f32>(0.419677734375, 0.00136566162109375),
109
- vec2<f32>(0.998046875, 0.0017604827880859375), vec2<f32>(0.998046875, 0.0017938613891601562), vec2<f32>(0.99658203125, 0.0019292831420898438), vec2<f32>(0.994140625, 0.002239227294921875),
110
- vec2<f32>(0.986328125, 0.0027294158935546875), vec2<f32>(0.9716796875, 0.0032501220703125), vec2<f32>(0.94580078125, 0.00366973876953125), vec2<f32>(0.90771484375, 0.003818511962890625),
111
- vec2<f32>(0.8583984375, 0.003681182861328125), vec2<f32>(0.79931640625, 0.0033206939697265625), vec2<f32>(0.7353515625, 0.0028438568115234375), vec2<f32>(0.66748046875, 0.0023441314697265625),
112
- vec2<f32>(0.5986328125, 0.0018796920776367188), vec2<f32>(0.5302734375, 0.0014829635620117188), vec2<f32>(0.464111328125, 0.0011587142944335938), vec2<f32>(0.402099609375, 0.0008993148803710938),
113
- vec2<f32>(0.99951171875, 0.0005011558532714844), vec2<f32>(0.99951171875, 0.0005154609680175781), vec2<f32>(0.998046875, 0.000583648681640625), vec2<f32>(0.99658203125, 0.0007505416870117188),
114
- vec2<f32>(0.9892578125, 0.00101470947265625), vec2<f32>(0.97607421875, 0.001346588134765625), vec2<f32>(0.95263671875, 0.0016527175903320312), vec2<f32>(0.916015625, 0.0018558502197265625),
115
- vec2<f32>(0.8671875, 0.0019054412841796875), vec2<f32>(0.8076171875, 0.0018167495727539062), vec2<f32>(0.7392578125, 0.001621246337890625), vec2<f32>(0.6669921875, 0.0013799667358398438),
116
- vec2<f32>(0.59326171875, 0.0011358261108398438), vec2<f32>(0.52001953125, 0.0009121894836425781), vec2<f32>(0.450439453125, 0.0007219314575195312), vec2<f32>(0.385986328125, 0.0005650520324707031),
117
- vec2<f32>(1.0, 0.00009316205978393555), vec2<f32>(1.0, 0.0000978708267211914), vec2<f32>(0.9990234375, 0.00012540817260742188), vec2<f32>(0.9970703125, 0.00019216537475585938),
118
- vec2<f32>(0.99072265625, 0.0003104209899902344), vec2<f32>(0.97900390625, 0.0004699230194091797), vec2<f32>(0.95751953125, 0.0006470680236816406), vec2<f32>(0.92333984375, 0.0007915496826171875),
119
- vec2<f32>(0.87548828125, 0.0008769035339355469), vec2<f32>(0.81494140625, 0.0008869171142578125), vec2<f32>(0.744140625, 0.0008325576782226562), vec2<f32>(0.66748046875, 0.0007386207580566406),
120
- vec2<f32>(0.58837890625, 0.0006265640258789062), vec2<f32>(0.51123046875, 0.0005168914794921875), vec2<f32>(0.438232421875, 0.0004165172576904297), vec2<f32>(0.37109375, 0.0003311634063720703),
121
- vec2<f32>(1.0, 0.000007271766662597656), vec2<f32>(1.0, 0.000008165836334228516), vec2<f32>(0.9990234375, 0.000016987323760986328), vec2<f32>(0.99755859375, 0.00003790855407714844),
122
- vec2<f32>(0.9921875, 0.00007593631744384766), vec2<f32>(0.9814453125, 0.00013744831085205078), vec2<f32>(0.96142578125, 0.00020754337310791016), vec2<f32>(0.92919921875, 0.00028014183044433594),
123
- vec2<f32>(0.8828125, 0.0003345012664794922), vec2<f32>(0.82177734375, 0.00036263465881347656), vec2<f32>(0.7490234375, 0.00036215782165527344), vec2<f32>(0.66845703125, 0.0003380775451660156),
124
- vec2<f32>(0.58544921875, 0.00029969215393066406), vec2<f32>(0.50390625, 0.00025582313537597656), vec2<f32>(0.427001953125, 0.0002117156982421875), vec2<f32>(0.357666015625, 0.00017201900482177734),
125
- vec2<f32>(1.0, 0.0), vec2<f32>(1.0, 5.960464477539063e-8), vec2<f32>(0.99951171875, 0.0000012516975402832031), vec2<f32>(0.99755859375, 0.000005304813385009766),
126
- vec2<f32>(0.9931640625, 0.000015079975128173828), vec2<f32>(0.98291015625, 0.00002855062484741211), vec2<f32>(0.96435546875, 0.00004744529724121094), vec2<f32>(0.93408203125, 0.00006842613220214844),
127
- vec2<f32>(0.88916015625, 0.00008893013000488281), vec2<f32>(0.828125, 0.0001042485237121582), vec2<f32>(0.75390625, 0.00011217594146728516), vec2<f32>(0.67041015625, 0.00011241436004638672),
128
- vec2<f32>(0.5830078125, 0.00010627508163452148), vec2<f32>(0.4970703125, 0.00009584426879882812), vec2<f32>(0.4169921875, 0.0000833272933959961), vec2<f32>(0.34521484375, 0.00007051229476928711),
129
- );
62
+ #ifdef DIRECTIONAL_PCSS_AVAILABLE
63
+ const PCSS_MEDIUM_RAW_TAPS : u32 = 8u;
64
+ const PCSS_MEDIUM_COMPARE_TAPS : u32 = 16u;
65
+ const PCSS_HIGH_RAW_TAPS : u32 = 16u;
66
+ const PCSS_HIGH_COMPARE_TAPS : u32 = 32u;
130
67
 
131
- fn _sampleThreeR184DfgLut(roughness : f32, dotNV : f32) -> vec2<f32> {
132
- let uv = clamp(vec2<f32>(roughness, dotNV), vec2<f32>(0.0), vec2<f32>(1.0));
133
- let samplePosition = uv * f32(THREE_R184_DFG_LUT_SIZE) - vec2<f32>(0.5);
134
- let base = vec2<i32>(floor(samplePosition));
135
- let weight = fract(samplePosition);
136
- let last = i32(THREE_R184_DFG_LUT_SIZE - 1u);
137
- let lo = clamp(base, vec2<i32>(0), vec2<i32>(last));
138
- let hi = clamp(base + vec2<i32>(1), vec2<i32>(0), vec2<i32>(last));
139
- let rowLo = mix(
140
- THREE_R184_DFG_LUT[u32(lo.y) * THREE_R184_DFG_LUT_SIZE + u32(lo.x)],
141
- THREE_R184_DFG_LUT[u32(lo.y) * THREE_R184_DFG_LUT_SIZE + u32(hi.x)],
142
- weight.x,
143
- );
144
- let rowHi = mix(
145
- THREE_R184_DFG_LUT[u32(hi.y) * THREE_R184_DFG_LUT_SIZE + u32(lo.x)],
146
- THREE_R184_DFG_LUT[u32(hi.y) * THREE_R184_DFG_LUT_SIZE + u32(hi.x)],
147
- weight.x,
148
- );
149
- return mix(rowLo, rowHi, weight.y);
150
- }
151
-
152
- fn _threeR184DirectMultiScatter(
153
- roughness : f32,
154
- nDotV : f32,
155
- nDotL : f32,
156
- F0 : vec3<f32>,
157
- ) -> vec3<f32> {
158
- let dfgV = _sampleThreeR184DfgLut(roughness, nDotV);
159
- let dfgL = _sampleThreeR184DfgLut(roughness, nDotL);
160
- let fssEssV = F0 * dfgV.x + vec3<f32>(dfgV.y);
161
- let fssEssL = F0 * dfgL.x + vec3<f32>(dfgL.y);
162
- let emsV = 1.0 - dfgV.x - dfgV.y;
163
- let emsL = 1.0 - dfgL.x - dfgL.y;
164
- let favg = F0 + (vec3<f32>(1.0) - F0) * 0.047619;
165
- let energyLoss = emsV * emsL;
166
- let fms = fssEssV * fssEssL * favg
167
- / (vec3<f32>(1.0) - energyLoss * favg * favg + vec3<f32>(1e-6));
168
- return fms * energyLoss;
169
- }
68
+ // Cascade-local disk points are fixed, bounded, and shared by raw and compare
69
+ // phases. A rotation derived from the integer receiver texel and cascade keeps
70
+ // the pattern stable without a temporal input.
71
+ const PCSS_DISK_OFFSETS : array<vec2<f32>, 32> = array<vec2<f32>, 32>(
72
+ vec2<f32>(-0.326, -0.945), vec2<f32>(0.236, -0.873), vec2<f32>(0.891, -0.404), vec2<f32>(-0.761, -0.581),
73
+ vec2<f32>(0.612, 0.146), vec2<f32>(-0.148, 0.514), vec2<f32>(-0.527, -0.109), vec2<f32>(0.074, 0.911),
74
+ vec2<f32>(-0.944, 0.238), vec2<f32>(0.444, -0.736), vec2<f32>(0.707, 0.641), vec2<f32>(-0.184, -0.342),
75
+ vec2<f32>(0.318, 0.382), vec2<f32>(-0.638, 0.526), vec2<f32>(0.955, -0.083), vec2<f32>(-0.401, 0.816),
76
+ vec2<f32>(-0.083, -0.632), vec2<f32>(0.539, -0.262), vec2<f32>(-0.735, -0.168), vec2<f32>(0.162, 0.719),
77
+ vec2<f32>(-0.841, 0.003), vec2<f32>(0.791, 0.332), vec2<f32>(-0.291, -0.791), vec2<f32>(0.021, -0.224),
78
+ vec2<f32>(0.386, 0.799), vec2<f32>(-0.558, 0.134), vec2<f32>(0.638, -0.555), vec2<f32>(-0.189, 0.957),
79
+ vec2<f32>(-0.977, -0.117), vec2<f32>(0.271, 0.589), vec2<f32>(0.819, -0.719), vec2<f32>(-0.472, -0.409),
80
+ );
81
+ #endif
170
82
 
83
+ // Direct multiscatter is owned by forgeax_pbr::brdf. Keep directional
84
+ // lighting as a consumer of that shared function so its LUT and energy
85
+ // compensation cannot diverge from punctual and cluster lighting.
171
86
  // Pick the cascade layer for a positive view-space depth -- walks
172
87
  // splitPlanes in order, returns the first split the depth falls below. Last
173
88
  // layer (count - 1) catches everything beyond splits[count-2]. cascadeCount=1
@@ -226,8 +141,128 @@ fn _atlasTileOrigin(layer : u32, count : u32) -> vec2<f32> {
226
141
  return vec2<f32>(tile) / vec2<f32>(grid);
227
142
  }
228
143
 
144
+ #ifdef DIRECTIONAL_PCSS_AVAILABLE
145
+ fn _pcssDiskRotation(texel : vec2<i32>, layer : u32) -> f32 {
146
+ let hash = u32(texel.x) * 1664525u + u32(texel.y) * 1013904223u + (layer + 1u) * 374761393u;
147
+ return f32(hash % 6283u) * 0.001;
148
+ }
149
+
150
+ fn _pcssDiskPoint(index : u32, angle : f32, radius : f32) -> vec2<f32> {
151
+ let point = PCSS_DISK_OFFSETS[index];
152
+ let cs = cos(angle);
153
+ let sn = sin(angle);
154
+ return vec2<f32>(point.x * cs - point.y * sn, point.x * sn + point.y * cs) * radius;
155
+ }
156
+
157
+ fn _pcssProjectedSample(
158
+ shadowMapSize : vec2<u32>,
159
+ tileOrigin : vec2<u32>,
160
+ tileSize : vec2<u32>,
161
+ baseTexel : vec2<i32>,
162
+ offset : vec2<f32>,
163
+ ) -> vec2<f32> {
164
+ let texel = shadow_clamp_texel_to_tile(
165
+ baseTexel + vec2<i32>(round(offset)),
166
+ vec2<i32>(tileOrigin),
167
+ vec2<i32>(tileSize),
168
+ 1,
169
+ );
170
+ return (vec2<f32>(texel) + vec2<f32>(0.5)) / vec2<f32>(shadowMapSize);
171
+ }
172
+
173
+ fn _samplePcssForCascade(
174
+ worldPos : vec3<f32>,
175
+ layer : u32,
176
+ count : u32,
177
+ normal : vec3<f32>,
178
+ l : vec3<f32>,
179
+ profile : u32,
180
+ ) -> f32 {
181
+ let lightClip = _cascadeLightViewProj(layer) * vec4<f32>(worldPos, 1.0);
182
+ if (!(lightClip.w > 0.0 || lightClip.w < 0.0)) {
183
+ return 1.0;
184
+ }
185
+ let projCoords = lightClip.xyz / lightClip.w;
186
+ let tileUv = vec2<f32>(projCoords.x * 0.5 + 0.5, -projCoords.y * 0.5 + 0.5);
187
+ if (!(tileUv.x >= 0.0 && tileUv.x <= 1.0 && tileUv.y >= 0.0 && tileUv.y <= 1.0 && projCoords.z >= 0.0 && projCoords.z <= 1.0)) {
188
+ return 1.0;
189
+ }
190
+ let nDotL = dot(normal, l);
191
+ if (!(nDotL >= -1.0 && nDotL <= 1.0)) {
192
+ return 1.0;
193
+ }
194
+ let shadowMapSize = textureDimensions(shadowMap, 0);
195
+ let grid = _atlasTileGrid(count);
196
+ let tileSize = shadowMapSize / grid;
197
+ let tileOrigin = vec2<u32>(layer % grid.x, layer / grid.x) * tileSize;
198
+ let baseTexel = vec2<i32>(tileOrigin) + vec2<i32>(floor(tileUv * vec2<f32>(tileSize)));
199
+ let angle = _pcssDiskRotation(baseTexel, layer);
200
+ let biasedDepth = shadow_biased_receiver_depth(
201
+ projCoords.z,
202
+ view.normalBias,
203
+ view.depthBias,
204
+ nDotL,
205
+ );
206
+
207
+ let rawRadius = select(2.0, 3.0, profile == 5u);
208
+ var blockerDepth = 0.0;
209
+ var blockerCount = 0u;
210
+ if (profile == 4u) {
211
+ for (var i = 0u; i < PCSS_MEDIUM_RAW_TAPS; i = i + 1u) {
212
+ let rawUv = _pcssProjectedSample(shadowMapSize, tileOrigin, tileSize, baseTexel, _pcssDiskPoint(i, angle, rawRadius));
213
+ let rawTexel = vec2<i32>(rawUv * vec2<f32>(shadowMapSize) - vec2<f32>(0.5));
214
+ let rawDepth = shadow_load_raw_depth(shadowMap, rawTexel);
215
+ if (rawDepth >= 0.0 && rawDepth < biasedDepth) {
216
+ blockerDepth = blockerDepth + rawDepth;
217
+ blockerCount = blockerCount + 1u;
218
+ }
219
+ }
220
+ } else {
221
+ for (var i = 0u; i < PCSS_HIGH_RAW_TAPS; i = i + 1u) {
222
+ let rawUv = _pcssProjectedSample(shadowMapSize, tileOrigin, tileSize, baseTexel, _pcssDiskPoint(i, angle, rawRadius));
223
+ let rawTexel = vec2<i32>(rawUv * vec2<f32>(shadowMapSize) - vec2<f32>(0.5));
224
+ let rawDepth = shadow_load_raw_depth(shadowMap, rawTexel);
225
+ if (rawDepth >= 0.0 && rawDepth < biasedDepth) {
226
+ blockerDepth = blockerDepth + rawDepth;
227
+ blockerCount = blockerCount + 1u;
228
+ }
229
+ }
230
+ }
231
+ if (blockerCount == 0u) {
232
+ return 1.0;
233
+ }
234
+
235
+ let averageBlockerDepth = blockerDepth / f32(blockerCount);
236
+ let lightDepthWorldSpan = max(view.splitPlanes[layer].z, 0.0);
237
+ let worldUnitsPerTexel = max(view.splitPlanes[layer].y, 0.0);
238
+ if (!(lightDepthWorldSpan > 0.0 && worldUnitsPerTexel > 0.0 && view.directionalShadowFilter.y >= 0.0 && view.directionalShadowFilter.z >= 0.0)) {
239
+ return 1.0;
240
+ }
241
+ let worldDistance = max(0.0, biasedDepth - averageBlockerDepth) * lightDepthWorldSpan;
242
+ let penumbraTexels = clamp(
243
+ worldDistance * tan(view.directionalShadowFilter.y) / worldUnitsPerTexel,
244
+ 0.0,
245
+ view.directionalShadowFilter.z,
246
+ );
247
+ let compareRadius = max(0.5, penumbraTexels);
248
+ var litSum = 0.0;
249
+ if (profile == 4u) {
250
+ for (var i = 0u; i < PCSS_MEDIUM_COMPARE_TAPS; i = i + 1u) {
251
+ let compareUv = _pcssProjectedSample(shadowMapSize, tileOrigin, tileSize, baseTexel, _pcssDiskPoint(i, angle, compareRadius));
252
+ litSum = litSum + shadow_sample_compare(shadowMap, shadowSampler, compareUv, biasedDepth);
253
+ }
254
+ return litSum / f32(PCSS_MEDIUM_COMPARE_TAPS);
255
+ }
256
+ for (var i = 0u; i < PCSS_HIGH_COMPARE_TAPS; i = i + 1u) {
257
+ let compareUv = _pcssProjectedSample(shadowMapSize, tileOrigin, tileSize, baseTexel, _pcssDiskPoint(i, angle, compareRadius));
258
+ litSum = litSum + shadow_sample_compare(shadowMap, shadowSampler, compareUv, biasedDepth);
259
+ }
260
+ return litSum / f32(PCSS_HIGH_COMPARE_TAPS);
261
+ }
262
+ #endif
263
+
229
264
  // Sample the shadow atlas with the LO 3.1.3 slope-scaled bias + dynamic PCF
230
- // kernel (driven by view.pcfKernelSize, MAX_PCF_HALF=2),
265
+ // kernel (driven by the directional filter profile, MAX_PCF_HALF=2),
231
266
  // against the lightViewProj for the chosen cascade. The shader maps NDC
232
267
  // xy to that cascade's atlas tile in fragment space (matrix carries
233
268
  // clip-space; tile placement happens here so shadow_caster.gl_Position
@@ -275,21 +310,16 @@ fn _sampleShadowForCascade(
275
310
  // [tileOrigin, tileOrigin+tileScale) (one texel inset) so taps stay in-tile.
276
311
  let tileLo = tileOrigin + texel;
277
312
  let tileHi = tileOrigin + tileScale - texel;
278
- // Variable-width PCF kernel driven by view.pcfKernelSize (feat-20260621
279
- // 5.3-production-shadow-demos AC-14 merged with the DirectionalLightShadow
280
- // merge). Constant trip count to MAX_PCF_HALF with a per-iteration clip to the
281
- // runtime radius keeps the shader variant-free (no dynamic loop bound, legal
282
- // for textureSampleCompareLevel uniform control flow). Host clamps
283
- // view.pcfKernelSize to {1,3,5}; divisor = actual tap count, so pcfKernelSize=3
284
- // -> half=1 -> 9 taps / 9.0 (result-identical to the prior hard-coded 3x3);
285
- // pcfKernelSize=1 -> half=0 -> single centre tap (hard edge); pcfKernelSize=5
286
- // -> half=2 -> 25-tap soft penumbra.
287
- let requestedKernel = clamp(u32(round(view.pcfKernelSize)), 1u, 2u * MAX_PCF_HALF + 1u);
288
- // Preserve the previous radius mapping for malformed even values:
289
- // 1 -> 1x1, 2/3/4 -> 3x3, 5 -> 5x5. The authored component accepts only
290
- // {1,3,5}, but keeping this normalization makes the refactor output-stable
291
- // for raw UBO callers too.
292
- let kernel = select(select(5u, 3u, requestedKernel <= 4u), 1u, requestedKernel == 1u);
313
+ // PCF1/3/5 profiles 1/2/3 retain their existing receiver. PCSS profiles 4/5
314
+ // use the bounded three-stage receiver below without a material or variant
315
+ // axis; the carrier y/z lanes provide angular radius and penumbra limit.
316
+ let filterProfile = clamp(u32(round(view.directionalShadowFilter.x)), 1u, 5u);
317
+ #ifdef DIRECTIONAL_PCSS_AVAILABLE
318
+ if (filterProfile >= 4u) {
319
+ return _samplePcssForCascade(worldPos, layer, count, normal, l, filterProfile);
320
+ }
321
+ #endif
322
+ let kernel = select(select(3u, 5u, filterProfile == 3u), 1u, filterProfile == 1u);
293
323
  if (kernel == 1u) {
294
324
  let lit = textureSampleCompareLevel(shadowMap, shadowSampler, clamp(uv, tileLo, tileHi), adjustedDepth);
295
325
  return lit;
@@ -383,10 +413,9 @@ fn evalDirectionalNoShadow(
383
413
  let nDotV = max(dot(normal, viewDir), 1e-5);
384
414
  let nDotH = max(dot(normal, h), 0.0);
385
415
  let vDotH = max(dot(viewDir, h), 0.0);
386
- let fresnel = exp2((-5.55473 * vDotH - 6.98316) * vDotH);
387
- let f = F0 * (vec3<f32>(1.0) - fresnel) + vec3<f32>(fresnel);
416
+ let f = f_schlick(vDotH, F0);
388
417
  let roughness = sqrt(max(alphaSq, 0.0));
389
- let multiScatter = _threeR184DirectMultiScatter(roughness, nDotV, nDotL, F0);
418
+ let multiScatter = threeR184DirectMultiScatter(roughness, nDotV, nDotL, F0);
390
419
  let specular = d_ggx(nDotH, alphaSq) * v_smith(nDotV, nDotL, alphaSq) * f + multiScatter;
391
420
  let diffuse = (1.0 - metallic) * baseColor / 3.14159265;
392
421
  return (diffuse + specular) * view.lightColor * nDotL;
@@ -0,0 +1,51 @@
1
+ // @forgeax/engine-shader - lighting-probe.wgsl
2
+ // Local diffuse LightProbe composition. The record is supplied by the
3
+ // renderer's per-object retained projection; Skylight remains the sole Sky
4
+ // and specular owner.
5
+
6
+ #define_import_path forgeax_pbr::lighting_probe
7
+
8
+ const PROBE_INV_PI : f32 = 0.3183098861837907;
9
+
10
+ fn probe_sh9(shPreblend : array<vec4<f32>, 9>, normal : vec3<f32>) -> vec3<f32> {
11
+ let x = normal.x;
12
+ let y = normal.y;
13
+ let z = normal.z;
14
+ let basis = array<f32, 9>(
15
+ 0.2820947918,
16
+ 0.4886025119 * y,
17
+ 0.4886025119 * z,
18
+ 0.4886025119 * x,
19
+ 1.0925484306 * x * y,
20
+ 1.0925484306 * y * z,
21
+ 0.3153915653 * (3.0 * z * z - 1.0),
22
+ 1.0925484306 * x * z,
23
+ 0.5462742153 * (x * x - y * y),
24
+ );
25
+ var result = vec3<f32>(0.0);
26
+ for (var band = 0u; band < 9u; band = band + 1u) {
27
+ result += shPreblend[band].xyz * basis[band];
28
+ }
29
+ return result;
30
+ }
31
+
32
+ // One SH9 evaluation, then the real Skylight residual. No Sky q, probe loop,
33
+ // hidden gain, smoothstep, or specular probe path is present here.
34
+ fn evaluateProbeDiffuse(
35
+ shPreblend : array<vec4<f32>, 9>,
36
+ localBlendFraction : f32,
37
+ normal : vec3<f32>,
38
+ e_sky : vec3<f32>,
39
+ k_d : vec3<f32>,
40
+ albedo : vec3<f32>,
41
+ metallic : f32,
42
+ ) -> vec3<f32> {
43
+ // `shPreblend` stores cosine-convolved irradiance. Match the existing
44
+ // Standard-PBR Skylight path and Three's BRDF_Lambert: irradiance becomes
45
+ // reflected diffuse radiance only after the single 1/pi factor. The Sky
46
+ // argument is already the Skylight path's E/pi * color * intensity value,
47
+ // so it must not be divided a second time below.
48
+ let local = max(probe_sh9(shPreblend, normal), vec3<f32>(0.0)) * PROBE_INV_PI;
49
+ let skyResidualFraction = 1.0 - localBlendFraction;
50
+ return (local + skyResidualFraction * e_sky) * k_d * albedo * (1.0 - metallic);
51
+ }