@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.
- package/README.md +10 -0
- package/dist/ShaderRegistry.d.ts.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +33070 -8
- package/dist/index.mjs.map +1 -1
- package/dist/ltc/provenance.d.ts +17 -0
- package/dist/ltc/provenance.d.ts.map +1 -0
- package/dist/ltc/tables.d.ts +22 -0
- package/dist/ltc/tables.d.ts.map +1 -0
- package/dist/material/artifact-types.d.ts +52 -0
- package/dist/material/artifact-types.d.ts.map +1 -1
- package/dist/material-schemas.d.ts +5 -0
- package/dist/material-schemas.d.ts.map +1 -1
- package/dist/register-default-sprite-lit.d.ts +3 -6
- package/dist/register-default-sprite-lit.d.ts.map +1 -1
- package/dist/types.d.ts +2 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/ShaderRegistry.ts +65 -0
- package/src/__tests__/bloom-fxaa-tonemap.unit.test.ts +30 -4
- package/src/__tests__/default-standard-pbr-alpha.unit.test.ts +7 -0
- package/src/__tests__/default-standard-pbr-transmission.unit.test.ts +138 -30
- package/src/__tests__/direct-light-layout.unit.test.ts +40 -0
- package/src/__tests__/ibl-irradiance.unit.test.ts +7 -4
- package/src/__tests__/ibl-sampling.unit.test.ts +3 -4
- package/src/__tests__/lighting-point-spot-volume.unit.test.ts +16 -2
- package/src/__tests__/lighting-punctual.unit.test.ts +61 -8
- package/src/__tests__/lighting-spot-volume-attenuation.unit.test.ts +1 -1
- package/src/__tests__/ltc-provenance.unit.test.ts +33 -0
- package/src/__tests__/manifest.fixture.json +33 -0
- package/src/__tests__/material-contract.unit.test.ts +61 -1
- package/src/__tests__/material-derived-builtins.integration.test.ts +27 -0
- package/src/__tests__/probe-lighting-composition.unit.test.ts +38 -0
- package/src/__tests__/rect-area-brdf.unit.test.ts +36 -0
- package/src/__tests__/reflection-probe-sampling.unit.test.ts +45 -0
- package/src/__tests__/scene-temporal.unit.test.ts +20 -0
- package/src/__tests__/shader.unit.test.ts +40 -15
- package/src/__tests__/shadow-pcss.unit.test.ts +131 -0
- package/src/__tests__/spot-cookie-composition.unit.test.ts +57 -0
- package/src/__tests__/spot-modifier-composition.unit.test.ts +19 -0
- package/src/__tests__/sprite-lit-shader.test.ts +24 -11
- package/src/__tests__/sprite-variants.unit.test.ts +5 -1
- package/src/__tests__/transmission-thickness-scale.unit.test.ts +7 -1
- package/src/__tests__/transparent-pbr.unit.test.ts +7 -0
- package/src/brdf.wgsl +115 -1
- package/src/common.wgsl +95 -91
- package/src/default-standard-pbr-skin.wgsl +108 -92
- package/src/default-standard-pbr.wgsl +189 -191
- package/src/fxaa.wgsl +27 -6
- package/src/ibl-irradiance.wgsl +4 -1
- package/src/ibl-sampling.wgsl +56 -2
- package/src/index.ts +23 -0
- package/src/lighting-directional.wgsl +161 -132
- package/src/lighting-probe.wgsl +51 -0
- package/src/lighting-punctual.wgsl +69 -29
- package/src/lighting-rect-area.wgsl +162 -0
- package/src/lighting-spot-modifiers.wgsl +93 -0
- package/src/ltc/provenance.ts +19 -0
- package/src/ltc/tables.ts +2831 -0
- package/src/material/artifact-types.ts +137 -0
- package/src/material-schemas.ts +12 -0
- package/src/register-default-sprite-lit.ts +3 -6
- package/src/scene-temporal.wgsl +14 -8
- package/src/shadow-pcf.wgsl +44 -1
- package/src/sprite-lit.wgsl +37 -53
- package/src/sprite.wgsl +1 -1
- package/src/standard-cluster.wgsl +274 -0
- package/src/tonemap.wgsl +8 -3
- package/src/types.ts +2 -1
- package/src/volume/volume-inject.wgsl +48 -38
- package/src/volume/volume-integrate.wgsl +47 -42
- package/dist/.tsbuildinfo +0 -1
- package/src/hdrp-cluster-forward.wgsl +0 -224
|
@@ -31,8 +31,12 @@
|
|
|
31
31
|
// - evalSpot(lightPos, lightDir, colorTimesIntensity, cosInner, cosOuter,
|
|
32
32
|
// invRangeSquared, ...) -> vec3<f32>
|
|
33
33
|
|
|
34
|
-
#import forgeax_pbr::brdf::{f_schlick, v_smith, d_ggx}
|
|
35
|
-
#import forgeax_pbr::
|
|
34
|
+
#import forgeax_pbr::brdf::{f_schlick, v_smith, d_ggx, threeR184DirectMultiScatter}
|
|
35
|
+
#import forgeax_pbr::lighting_spot_modifiers::{spotModifierProduct}
|
|
36
|
+
|
|
37
|
+
// extendedLighting is one closed resource topology for IES, Cookie, and
|
|
38
|
+
// probe factors. The ordinary path supplies identity factors.
|
|
39
|
+
#import forgeax_pbr::lighting_attenuation::{evalDistanceAttenuation, evalSpotAttenuation, projectSpotUv}
|
|
36
40
|
// feat-20260625-spot-light-shadow-mapping M3 / w15 (plan-strategy D-3 + D-5):
|
|
37
41
|
// spot shadow sampling reuses the shared 2D 9-tap PCF core (sample_shadow_2d)
|
|
38
42
|
// and the always-on `spotShadowMap` (binding 8) + `shadowSampler` (binding 4).
|
|
@@ -78,16 +82,6 @@ fn evalVolumeSpot(
|
|
|
78
82
|
);
|
|
79
83
|
}
|
|
80
84
|
|
|
81
|
-
// One projector UV helper is shared by surface and volume. The accepted
|
|
82
|
-
// projectorRevision travels in the host tuple; it is deliberately not a
|
|
83
|
-
// second shader registry or a device-side identity.
|
|
84
|
-
fn projectSpotUv(lightViewProj : mat4x4<f32>, worldPos : vec3<f32>) -> vec2<f32> {
|
|
85
|
-
let clip = lightViewProj * vec4<f32>(worldPos, 1.0);
|
|
86
|
-
let invW = select(1.0 / clip.w, 0.0, abs(clip.w) < 1e-6);
|
|
87
|
-
let projectorUv = vec2<f32>(clip.x * invW * 0.5 + 0.5, clip.y * invW * -0.5 + 0.5);
|
|
88
|
-
return projectorUv;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
85
|
// Shared punctual BRDF body returning (diffuse + specular) *
|
|
92
86
|
// colorTimesIntensity * nDotL * attenuation. Cone factor is applied by the
|
|
93
87
|
// caller (evalSpot only).
|
|
@@ -112,9 +106,13 @@ fn evalPunctualBody(
|
|
|
112
106
|
let nDotH = max(dot(normal, h), 0.0);
|
|
113
107
|
let vDotH = max(dot(viewDir, h), 0.0);
|
|
114
108
|
let f = f_schlick(vDotH, F0);
|
|
115
|
-
let
|
|
116
|
-
let
|
|
117
|
-
let
|
|
109
|
+
let roughness = sqrt(max(alphaSq, 0.0));
|
|
110
|
+
let multiScatter = threeR184DirectMultiScatter(roughness, nDotV, nDotL, F0);
|
|
111
|
+
let specular = d_ggx(nDotH, alphaSq) * v_smith(nDotV, nDotL, alphaSq) * f + multiScatter;
|
|
112
|
+
// Three r184 direct-light parity keeps diffuse independent of the
|
|
113
|
+
// view-dependent Schlick term; the metallic factor is the only diffuse
|
|
114
|
+
// energy gate in this punctual path.
|
|
115
|
+
let diffuse = (1.0 - metallic) * baseColor / 3.14159265;
|
|
118
116
|
let attenuation = evalDistanceAttenuation(dSquared, invRangeSquared);
|
|
119
117
|
return (diffuse + specular) * colorTimesIntensity * nDotL * attenuation;
|
|
120
118
|
}
|
|
@@ -138,6 +136,50 @@ fn evalPoint(
|
|
|
138
136
|
);
|
|
139
137
|
}
|
|
140
138
|
|
|
139
|
+
// Flat 2D punctual evaluators share the range/cone math with the clustered
|
|
140
|
+
// path, but intentionally skip the 3D BRDF normal term.
|
|
141
|
+
// Sprite-lit treats every quad as an omnidirectional receiver; keeping this
|
|
142
|
+
// owner here prevents URP and Cluster from drifting when the light lies in the
|
|
143
|
+
// sprite plane (the common 2D flashlight setup).
|
|
144
|
+
fn evalFlatRangeAttenuation(dSquared : f32, invRangeSquared : f32) -> f32 {
|
|
145
|
+
let factor = max(min(1.0 - (dSquared * invRangeSquared) * (dSquared * invRangeSquared), 1.0), 0.0);
|
|
146
|
+
return factor * factor / dSquared;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
fn evalPointFlat(
|
|
150
|
+
lightPos : vec3<f32>,
|
|
151
|
+
colorTimesIntensity : vec3<f32>,
|
|
152
|
+
invRangeSquared : f32,
|
|
153
|
+
worldPos : vec3<f32>,
|
|
154
|
+
baseColor : vec3<f32>,
|
|
155
|
+
) -> vec3<f32> {
|
|
156
|
+
// The flat 2D path shares the same finite-range attenuation owner as the
|
|
157
|
+
// clustered and direct Standard paths; its only intentional difference is
|
|
158
|
+
// that a sprite is an omnidirectional receiver.
|
|
159
|
+
let toLight = lightPos - worldPos;
|
|
160
|
+
let dSquared = max(dot(toLight, toLight), 1e-4);
|
|
161
|
+
return baseColor * colorTimesIntensity * evalFlatRangeAttenuation(dSquared, invRangeSquared);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
fn evalSpotFlat(
|
|
165
|
+
lightPos : vec3<f32>,
|
|
166
|
+
lightDir : vec3<f32>,
|
|
167
|
+
colorTimesIntensity : vec3<f32>,
|
|
168
|
+
cosInner : f32,
|
|
169
|
+
cosOuter : f32,
|
|
170
|
+
invRangeSquared : f32,
|
|
171
|
+
worldPos : vec3<f32>,
|
|
172
|
+
baseColor : vec3<f32>,
|
|
173
|
+
) -> vec3<f32> {
|
|
174
|
+
let toLight = lightPos - worldPos;
|
|
175
|
+
let dSquared = max(dot(toLight, toLight), 1e-4);
|
|
176
|
+
let l = toLight / sqrt(dSquared);
|
|
177
|
+
let cone = smoothstep(cosOuter, cosInner, dot(l, -lightDir));
|
|
178
|
+
return evalPointFlat(
|
|
179
|
+
lightPos, colorTimesIntensity, invRangeSquared, worldPos, baseColor,
|
|
180
|
+
) * cone;
|
|
181
|
+
}
|
|
182
|
+
|
|
141
183
|
#ifdef POINT_SHADOW_AVAILABLE
|
|
142
184
|
// Shadow-modulated omnidirectional point light: same BRDF body * shadow factor.
|
|
143
185
|
//
|
|
@@ -148,7 +190,8 @@ fn evalPoint(
|
|
|
148
190
|
// projection (research L0.5 Bevy pattern); the caller passes `near` / `far`
|
|
149
191
|
// directly so both pipelines route the same constants without owning the
|
|
150
192
|
// upstream binding (URP reads them from `shadowParams[layer]` at @group(0)
|
|
151
|
-
// binding 6;
|
|
193
|
+
// binding 6; the shared DirectLightSlot metadata remains the identity owner
|
|
194
|
+
// while this shadow-parameter buffer carries the reconstruction constants per
|
|
152
195
|
// plan-strategy §D-8).
|
|
153
196
|
//
|
|
154
197
|
// Caller responsibility: gate this on `shadowAtlasLayer >= 0` so the
|
|
@@ -175,13 +218,13 @@ fn evalPointShadowed(
|
|
|
175
218
|
lightPos, colorTimesIntensity, invRangeSquared,
|
|
176
219
|
worldPos, normal, viewDir, baseColor, metallic, alphaSq, F0,
|
|
177
220
|
);
|
|
178
|
-
//
|
|
179
|
-
//
|
|
180
|
-
//
|
|
221
|
+
// Keep the fragment-to-light vector for BRDF and nDotL. Shadow matrices are
|
|
222
|
+
// authored light-to-fragment (`lookAt(lightPos, fragment)`), so the cube
|
|
223
|
+
// lookup must use the opposite direction. For a right-handed world, the
|
|
224
|
+
// cubemap convention flips Z so the +Z face look direction matches.
|
|
181
225
|
let toLight = lightPos - worldPos;
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
let lightLocal = vec3<f32>(toLight.x, toLight.y, -toLight.z);
|
|
226
|
+
let fromLight = worldPos - lightPos;
|
|
227
|
+
let lightLocal = vec3<f32>(fromLight.x, fromLight.y, -fromLight.z);
|
|
185
228
|
// Reconstruct [0,1] NDC depth from world-space distance: largest-axis
|
|
186
229
|
// projection (research L0.5). Match the per-face perspective near / far
|
|
187
230
|
// configured by buildPointShadowMatrices (PointLightShadow.nearPlane /
|
|
@@ -224,9 +267,10 @@ fn evalSpot(
|
|
|
224
267
|
lightPos, colorTimesIntensity, invRangeSquared,
|
|
225
268
|
worldPos, normal, viewDir, baseColor, metallic, alphaSq, F0,
|
|
226
269
|
);
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
270
|
+
let toLight = lightPos - worldPos;
|
|
271
|
+
let l = normalize(toLight);
|
|
272
|
+
let cone = smoothstep(cosOuter, cosInner, dot(l, -lightDir));
|
|
273
|
+
return body * spotModifierProduct(1.0, 1.0, cone, 1.0, 1.0, 1.0);
|
|
230
274
|
}
|
|
231
275
|
|
|
232
276
|
// feat-20260625-spot-light-shadow-mapping M3 / w15 (plan-strategy D-3 + D-4 +
|
|
@@ -287,10 +331,6 @@ fn evalSpotShadowed(
|
|
|
287
331
|
let invW = select(1.0 / splane.w, 0.0, abs(splane.w) < 1e-6);
|
|
288
332
|
let clipUv = projectSpotUv(lightViewProj, worldPos);
|
|
289
333
|
let depthRef = splane.z * invW;
|
|
290
|
-
// The accepted projectorRevision selects the same texture tuple for
|
|
291
|
-
// surface and volume; a bound projector uses textureSampleLevel here.
|
|
292
|
-
let projectorRevision = 0.0;
|
|
293
|
-
|
|
294
334
|
// OOB / NaN gate: outside the light frustum (or NaN from a degenerate matrix)
|
|
295
335
|
// returns fully lit. Mirrors the directional `>= 0 && <= 1` NaN-safe form.
|
|
296
336
|
if (!(clipUv.x >= 0.0 && clipUv.x <= 1.0 && clipUv.y >= 0.0 && clipUv.y <= 1.0 && depthRef <= 1.0)) {
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
#define_import_path forgeax_pbr::lighting_rect_area
|
|
2
|
+
#ifdef EXTENDED_LIGHTING_AVAILABLE
|
|
3
|
+
#import forgeax_view::common::{ltcLambertTexture, ltcGgxTexture, spotModifierSampler}
|
|
4
|
+
#endif
|
|
5
|
+
|
|
6
|
+
// One-sided finite-area direct lighting using the same Heitz LTC formulation
|
|
7
|
+
// and tracked 64x64 tables as Three.js r184. The rectangle corners, rather
|
|
8
|
+
// than a punctual center direction, are transformed into the fitted cosine
|
|
9
|
+
// space before the spherical edge integral is evaluated.
|
|
10
|
+
|
|
11
|
+
fn ltcUv(n : vec3<f32>, v : vec3<f32>, roughness : f32) -> vec2<f32> {
|
|
12
|
+
let lutScale = 63.0 / 64.0;
|
|
13
|
+
let lutBias = 0.5 / 64.0;
|
|
14
|
+
let nDotV = clamp(dot(n, v), 0.0, 1.0);
|
|
15
|
+
return vec2<f32>(clamp(roughness, 0.0, 1.0), sqrt(1.0 - nDotV)) * lutScale + lutBias;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
fn ltcClippedSphereFormFactor(f : vec3<f32>) -> f32 {
|
|
19
|
+
let magnitude = length(f);
|
|
20
|
+
return max((magnitude * magnitude + f.z) / (magnitude + 1.0), 0.0);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
fn ltcEdgeVectorFormFactor(v1 : vec3<f32>, v2 : vec3<f32>) -> vec3<f32> {
|
|
24
|
+
let x = clamp(dot(v1, v2), -1.0, 1.0);
|
|
25
|
+
let y = abs(x);
|
|
26
|
+
let a = 0.8543985 + (0.4965155 + 0.0145206 * y) * y;
|
|
27
|
+
let b = 3.4175940 + (4.1616724 + y) * y;
|
|
28
|
+
let rational = a / b;
|
|
29
|
+
let thetaOverSinTheta = select(
|
|
30
|
+
0.5 * inverseSqrt(max(1.0 - x * x, 1e-7)) - rational,
|
|
31
|
+
rational,
|
|
32
|
+
x > 0.0,
|
|
33
|
+
);
|
|
34
|
+
return cross(v1, v2) * thetaOverSinTheta;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
fn ltcEvaluate(
|
|
38
|
+
n : vec3<f32>,
|
|
39
|
+
v : vec3<f32>,
|
|
40
|
+
worldPos : vec3<f32>,
|
|
41
|
+
mInv : mat3x3<f32>,
|
|
42
|
+
lightPos : vec3<f32>,
|
|
43
|
+
axisX : vec3<f32>,
|
|
44
|
+
axisY : vec3<f32>,
|
|
45
|
+
halfWidth : f32,
|
|
46
|
+
halfHeight : f32,
|
|
47
|
+
) -> f32 {
|
|
48
|
+
let halfX = axisX * halfWidth;
|
|
49
|
+
let halfY = axisY * halfHeight;
|
|
50
|
+
// Counter-clockwise from the authored front (+cross(axisX, axisY)).
|
|
51
|
+
let rect0 = lightPos - halfX - halfY;
|
|
52
|
+
let rect1 = lightPos + halfX - halfY;
|
|
53
|
+
let rect2 = lightPos + halfX + halfY;
|
|
54
|
+
let rect3 = lightPos - halfX + halfY;
|
|
55
|
+
let lightNormal = cross(rect1 - rect0, rect3 - rect0);
|
|
56
|
+
if (dot(lightNormal, worldPos - rect0) <= 0.0) {
|
|
57
|
+
return 0.0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let tangentCandidate = v - n * dot(v, n);
|
|
61
|
+
let tangentLengthSquared = dot(tangentCandidate, tangentCandidate);
|
|
62
|
+
var tangent = vec3<f32>(1.0, 0.0, 0.0);
|
|
63
|
+
if (tangentLengthSquared > 1e-7) {
|
|
64
|
+
tangent = tangentCandidate * inverseSqrt(tangentLengthSquared);
|
|
65
|
+
} else {
|
|
66
|
+
let fallback = select(vec3<f32>(0.0, 1.0, 0.0), vec3<f32>(1.0, 0.0, 0.0), abs(n.x) < 0.9);
|
|
67
|
+
tangent = normalize(cross(fallback, n));
|
|
68
|
+
}
|
|
69
|
+
let bitangent = -cross(n, tangent);
|
|
70
|
+
let transform = mInv * transpose(mat3x3<f32>(tangent, bitangent, n));
|
|
71
|
+
|
|
72
|
+
let c0 = normalize(transform * (rect0 - worldPos));
|
|
73
|
+
let c1 = normalize(transform * (rect1 - worldPos));
|
|
74
|
+
let c2 = normalize(transform * (rect2 - worldPos));
|
|
75
|
+
let c3 = normalize(transform * (rect3 - worldPos));
|
|
76
|
+
let vectorFormFactor =
|
|
77
|
+
ltcEdgeVectorFormFactor(c0, c1) +
|
|
78
|
+
ltcEdgeVectorFormFactor(c1, c2) +
|
|
79
|
+
ltcEdgeVectorFormFactor(c2, c3) +
|
|
80
|
+
ltcEdgeVectorFormFactor(c3, c0);
|
|
81
|
+
return ltcClippedSphereFormFactor(vectorFormFactor);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
fn rectAreaRangeFactor(lightPos : vec3<f32>, worldPos : vec3<f32>, invRangeSquared : f32) -> f32 {
|
|
85
|
+
let toLight = lightPos - worldPos;
|
|
86
|
+
let distanceSquared = dot(toLight, toLight);
|
|
87
|
+
let rangeTerm = distanceSquared * invRangeSquared;
|
|
88
|
+
let factor = clamp(1.0 - rangeTerm * rangeTerm, 0.0, 1.0);
|
|
89
|
+
return factor * factor;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
fn evalRectAreaLtcDiffuse(
|
|
93
|
+
lightPos : vec3<f32>, lightColor : vec3<f32>, axisX : vec3<f32>, axisY : vec3<f32>,
|
|
94
|
+
halfWidth : f32, halfHeight : f32, invRangeSquared : f32, worldPos : vec3<f32>,
|
|
95
|
+
n : vec3<f32>, v : vec3<f32>, baseColor : vec3<f32>, metallic : f32, alphaSq : f32, F0 : vec3<f32>,
|
|
96
|
+
) -> vec3<f32> {
|
|
97
|
+
let identity = mat3x3<f32>(
|
|
98
|
+
vec3<f32>(1.0, 0.0, 0.0),
|
|
99
|
+
vec3<f32>(0.0, 1.0, 0.0),
|
|
100
|
+
vec3<f32>(0.0, 0.0, 1.0),
|
|
101
|
+
);
|
|
102
|
+
let formFactor = ltcEvaluate(
|
|
103
|
+
n, v, worldPos, identity, lightPos, axisX, axisY, halfWidth, halfHeight,
|
|
104
|
+
);
|
|
105
|
+
// The LTC edge fit is already normalized by 1/(2*pi); unlike punctual
|
|
106
|
+
// irradiance this form factor must not receive another Lambert 1/pi.
|
|
107
|
+
let diffuse = baseColor * (1.0 - metallic);
|
|
108
|
+
return lightColor * diffuse * formFactor * rectAreaRangeFactor(lightPos, worldPos, invRangeSquared);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
fn evalRectAreaLtcStandard(
|
|
112
|
+
lightPos : vec3<f32>, lightColor : vec3<f32>, axisX : vec3<f32>, axisY : vec3<f32>,
|
|
113
|
+
halfWidth : f32, halfHeight : f32, invRangeSquared : f32, worldPos : vec3<f32>,
|
|
114
|
+
n : vec3<f32>, v : vec3<f32>, baseColor : vec3<f32>, metallic : f32, alphaSq : f32, F0 : vec3<f32>,
|
|
115
|
+
) -> vec3<f32> {
|
|
116
|
+
#ifdef EXTENDED_LIGHTING_AVAILABLE
|
|
117
|
+
let uv = ltcUv(n, v, sqrt(max(alphaSq, 0.0)));
|
|
118
|
+
let matrixSample = textureSampleLevel(ltcLambertTexture, spotModifierSampler, uv, 0.0);
|
|
119
|
+
let fresnelSample = textureSampleLevel(ltcGgxTexture, spotModifierSampler, uv, 0.0);
|
|
120
|
+
let mInv = mat3x3<f32>(
|
|
121
|
+
vec3<f32>(matrixSample.x, 0.0, matrixSample.y),
|
|
122
|
+
vec3<f32>(0.0, 1.0, 0.0),
|
|
123
|
+
vec3<f32>(matrixSample.z, 0.0, matrixSample.w),
|
|
124
|
+
);
|
|
125
|
+
let fresnel = F0 * fresnelSample.x + (vec3<f32>(1.0) - F0) * fresnelSample.y;
|
|
126
|
+
let specularFormFactor = ltcEvaluate(
|
|
127
|
+
n, v, worldPos, mInv, lightPos, axisX, axisY, halfWidth, halfHeight,
|
|
128
|
+
);
|
|
129
|
+
let diffuse = evalRectAreaLtcDiffuse(
|
|
130
|
+
lightPos, lightColor, axisX, axisY, halfWidth, halfHeight, invRangeSquared,
|
|
131
|
+
worldPos, n, v, baseColor, metallic, alphaSq, F0,
|
|
132
|
+
);
|
|
133
|
+
return diffuse + lightColor * fresnel * specularFormFactor * rectAreaRangeFactor(lightPos, worldPos, invRangeSquared);
|
|
134
|
+
#else
|
|
135
|
+
return vec3<f32>(0.0);
|
|
136
|
+
#endif
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Both renderer routes consume one complete Standard-PBR Rect contribution.
|
|
140
|
+
// The public names retain the direct/cluster shader import contract while the
|
|
141
|
+
// Lambert and fitted-GGX terms stay inseparable in one owner implementation.
|
|
142
|
+
fn evalRectAreaLtcLambert(
|
|
143
|
+
lightPos : vec3<f32>, lightColor : vec3<f32>, axisX : vec3<f32>, axisY : vec3<f32>,
|
|
144
|
+
halfWidth : f32, halfHeight : f32, invRangeSquared : f32, worldPos : vec3<f32>,
|
|
145
|
+
n : vec3<f32>, v : vec3<f32>, baseColor : vec3<f32>, metallic : f32, alphaSq : f32, F0 : vec3<f32>,
|
|
146
|
+
) -> vec3<f32> {
|
|
147
|
+
return evalRectAreaLtcStandard(
|
|
148
|
+
lightPos, lightColor, axisX, axisY, halfWidth, halfHeight, invRangeSquared,
|
|
149
|
+
worldPos, n, v, baseColor, metallic, alphaSq, F0,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
fn evalRectAreaLtcGgx(
|
|
154
|
+
lightPos : vec3<f32>, lightColor : vec3<f32>, axisX : vec3<f32>, axisY : vec3<f32>,
|
|
155
|
+
halfWidth : f32, halfHeight : f32, invRangeSquared : f32, worldPos : vec3<f32>,
|
|
156
|
+
n : vec3<f32>, v : vec3<f32>, baseColor : vec3<f32>, metallic : f32, alphaSq : f32, F0 : vec3<f32>,
|
|
157
|
+
) -> vec3<f32> {
|
|
158
|
+
return evalRectAreaLtcStandard(
|
|
159
|
+
lightPos, lightColor, axisX, axisY, halfWidth, halfHeight, invRangeSquared,
|
|
160
|
+
worldPos, n, v, baseColor, metallic, alphaSq, F0,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#define_import_path forgeax_pbr::lighting_spot_modifiers
|
|
2
|
+
|
|
3
|
+
#ifdef EXTENDED_LIGHTING_AVAILABLE
|
|
4
|
+
#import forgeax_view::common::{spotModifierSampler, iesProfileTexture, cookieTexture, cookieMatrices}
|
|
5
|
+
#endif
|
|
6
|
+
|
|
7
|
+
// DirectLightSlot.metadata.y carries this marker for the SpotLight projector
|
|
8
|
+
// path. A marked spot samples the shared array with its lightViewProj in the
|
|
9
|
+
// punctual owner, so the Cookie projection
|
|
10
|
+
// below must not modulate it a second time.
|
|
11
|
+
const PROJECTOR_FLAG: u32 = 0x40000000u;
|
|
12
|
+
|
|
13
|
+
// Shared Spot modifier composition. Identity defaults preserve the ordinary
|
|
14
|
+
// punctual path when an extension resource is absent.
|
|
15
|
+
fn spotModifierProduct(
|
|
16
|
+
brdf : f32,
|
|
17
|
+
range : f32,
|
|
18
|
+
cone : f32,
|
|
19
|
+
ies : f32,
|
|
20
|
+
cookie : f32,
|
|
21
|
+
shadow : f32,
|
|
22
|
+
) -> f32 {
|
|
23
|
+
return brdf * range * cone * ies * cookie * shadow;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Shared IES/Cookie projection owner. The light direction is the authored
|
|
27
|
+
// outgoing axis (light -> scene), matching evalSpot's `dot(L, -lightDir)`.
|
|
28
|
+
// Cookie matrices carry only source-texture aspect correction; keeping the
|
|
29
|
+
// cone term here allows a Cookie slice to be shared by Spots with different
|
|
30
|
+
// outer cones.
|
|
31
|
+
fn spotModifierFactors(
|
|
32
|
+
lightPos : vec3<f32>,
|
|
33
|
+
lightDir : vec3<f32>,
|
|
34
|
+
cosOuter : f32,
|
|
35
|
+
worldPos : vec3<f32>,
|
|
36
|
+
rollDeg : f32,
|
|
37
|
+
metadata : vec4<u32>,
|
|
38
|
+
) -> vec4<f32> {
|
|
39
|
+
#ifdef EXTENDED_LIGHTING_AVAILABLE
|
|
40
|
+
let toSurface = worldPos - lightPos;
|
|
41
|
+
let distanceToSurface = length(toSurface);
|
|
42
|
+
var ies = 1.0;
|
|
43
|
+
var cookie = vec3<f32>(1.0);
|
|
44
|
+
if (distanceToSurface <= 0.000001) {
|
|
45
|
+
return vec4<f32>(ies, cookie);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let outgoing = toSurface / distanceToSurface;
|
|
49
|
+
let forward = normalize(lightDir);
|
|
50
|
+
var referenceUp = vec3<f32>(0.0, 1.0, 0.0);
|
|
51
|
+
if (abs(forward.y) > 0.999) {
|
|
52
|
+
referenceUp = vec3<f32>(0.0, 0.0, 1.0);
|
|
53
|
+
}
|
|
54
|
+
let right = normalize(cross(forward, referenceUp));
|
|
55
|
+
let up = normalize(cross(right, forward));
|
|
56
|
+
let localX = dot(outgoing, right);
|
|
57
|
+
let localY = dot(outgoing, up);
|
|
58
|
+
let depth = dot(outgoing, forward);
|
|
59
|
+
let roll = rollDeg * 0.01745329252;
|
|
60
|
+
let rolledX = localX * cos(roll) - localY * sin(roll);
|
|
61
|
+
let rolledY = localX * sin(roll) + localY * cos(roll);
|
|
62
|
+
|
|
63
|
+
if (metadata.z != 0xffffffffu) {
|
|
64
|
+
let azimuth = (atan2(rolledY, rolledX) + 6.283185307) % 6.283185307;
|
|
65
|
+
let elevation = acos(clamp(depth, -1.0, 1.0)) / 3.14159265;
|
|
66
|
+
let iesUv = vec2<f32>(azimuth / 6.283185307, elevation);
|
|
67
|
+
ies = max(textureSampleLevel(iesProfileTexture, spotModifierSampler, iesUv, metadata.z, 0.0).r, 0.0);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (metadata.w != 0xffffffffu && (metadata.y & PROJECTOR_FLAG) == 0u) {
|
|
71
|
+
if (depth <= 0.0) {
|
|
72
|
+
cookie = vec3<f32>(0.0);
|
|
73
|
+
} else {
|
|
74
|
+
let sinOuter = sqrt(max(1.0 - cosOuter * cosOuter, 0.0));
|
|
75
|
+
let tanOuter = sinOuter / max(cosOuter, 0.0001);
|
|
76
|
+
let matrixPlane = cookieMatrices[metadata.w] * vec4<f32>(rolledX / depth, rolledY / depth, 0.0, 1.0);
|
|
77
|
+
let cookieUv = vec2<f32>(0.5) + matrixPlane.xy / tanOuter * 0.5;
|
|
78
|
+
if (
|
|
79
|
+
cookieUv.x < 0.0 || cookieUv.x > 1.0 ||
|
|
80
|
+
cookieUv.y < 0.0 || cookieUv.y > 1.0
|
|
81
|
+
) {
|
|
82
|
+
cookie = vec3<f32>(0.0);
|
|
83
|
+
} else {
|
|
84
|
+
let cookieSample = textureSampleLevel(cookieTexture, spotModifierSampler, cookieUv, metadata.w, 0.0);
|
|
85
|
+
cookie = max(cookieSample.rgb * cookieSample.a, vec3<f32>(0.0));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return vec4<f32>(ies, cookie);
|
|
90
|
+
#else
|
|
91
|
+
return vec4<f32>(1.0);
|
|
92
|
+
#endif
|
|
93
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// @forgeax/engine-shader - provenance for the tracked RectArea LTC tables.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The table source is the Three.js r184 RectAreaLight implementation. The
|
|
5
|
+
* source arrays are converted to IEEE-754 binary16 offline and checked into
|
|
6
|
+
* `tables.ts`; the runtime never fetches or parses a source file.
|
|
7
|
+
*/
|
|
8
|
+
export const LTC_SOURCE_PROVENANCE = {
|
|
9
|
+
source: 'Three.js',
|
|
10
|
+
revision: 'r184',
|
|
11
|
+
sourceUrl:
|
|
12
|
+
'https://github.com/mrdoob/three.js/blob/r184/examples/jsm/lights/RectAreaLightTexturesLib.js',
|
|
13
|
+
upstreamData: 'selfshadow/ltc_code fit/results/ltc.js',
|
|
14
|
+
generator: 'deterministic Three.js source-array to rgba16float conversion',
|
|
15
|
+
inputSha256: {
|
|
16
|
+
lambert: 'cf5cf21e5c112d2095c7e2418cb0a1ac54636e275d73e42f3453646c67f26814',
|
|
17
|
+
ggx: '3b1b09080b26104498db277c14fc1733786465c6958e7a8403d688b1e24c2ff5',
|
|
18
|
+
},
|
|
19
|
+
} as const;
|