@forgeax/engine-shader 0.1.7 → 0.1.20
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 +34 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/ShaderRegistry.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +53 -2
- package/dist/index.mjs.map +1 -1
- package/dist/material-schemas.d.ts.map +1 -1
- package/package.json +6 -3
- package/src/ShaderRegistry.ts +50 -0
- package/src/__tests__/bloom-fxaa-tonemap.unit.test.ts +86 -12
- package/src/__tests__/default-standard-pbr-transmission.unit.test.ts +185 -0
- package/src/__tests__/fog-oracle.unit.test.ts +110 -114
- package/src/__tests__/fog-orthographic-ray.unit.test.ts +12 -37
- package/src/__tests__/fog-producer-matrix.integration.test.ts +16 -51
- package/src/__tests__/lighting-point-spot-volume.unit.test.ts +48 -0
- package/src/__tests__/lighting-punctual.unit.test.ts +1 -1
- package/src/__tests__/lighting-spot-volume-attenuation.unit.test.ts +26 -0
- package/src/__tests__/lighting-spot-volume.unit.test.ts +25 -0
- package/src/__tests__/material-builtins.unit.test.ts +38 -1
- package/src/__tests__/material-contract.unit.test.ts +123 -0
- package/src/__tests__/motion-blur.unit.test.ts +44 -0
- package/src/__tests__/scene-temporal.unit.test.ts +23 -5
- package/src/__tests__/shader-manifest-transmission.unit.test.ts +75 -0
- package/src/__tests__/shader.unit.test.ts +44 -3
- package/src/__tests__/sprite-variants.unit.test.ts +20 -0
- package/src/__tests__/taa-resolve.unit.test.ts +75 -3
- package/src/__tests__/transmission-thickness-scale.unit.test.ts +19 -0
- package/src/common.wgsl +34 -65
- package/src/default-standard-pbr-skin.wgsl +29 -28
- package/src/default-standard-pbr.wgsl +257 -57
- package/src/fxaa.wgsl +5 -6
- package/src/hdrp-cluster-forward.wgsl +41 -3
- package/src/hdrp-deferred-lighting.wgsl +3 -27
- package/src/index.ts +3 -0
- package/src/lighting-attenuation.wgsl +58 -0
- package/src/lighting-directional.wgsl +31 -3
- package/src/lighting-punctual.wgsl +57 -12
- package/src/material-schemas.ts +7 -0
- package/src/motion-blur.wgsl +77 -0
- package/src/msdf-text.wgsl +4 -23
- package/src/pbr-temporal.wgsl +15 -2
- package/src/scene-temporal.wgsl +54 -0
- package/src/shadow-pcf.wgsl +31 -10
- package/src/shadow_caster.wgsl +27 -2
- package/src/skybox.wgsl +2 -7
- package/src/sprite-lit.wgsl +7 -25
- package/src/sprite.wgsl +7 -25
- package/src/taa-resolve.wgsl +76 -81
- package/src/tonemap.wgsl +7 -9
- package/src/unlit.wgsl +7 -28
- package/src/volume/volume-composite.wgsl +113 -0
- package/src/volume/volume-inject.wgsl +249 -0
- package/src/volume/volume-integrate.wgsl +342 -0
- package/src/volume/volume-temporal.wgsl +70 -0
- package/dist/__tests__/atmosphere-background.test.d.ts +0 -2
- package/dist/__tests__/atmosphere-background.test.d.ts.map +0 -1
- package/dist/__tests__/atmosphere-cubemap-orientation.test.d.ts +0 -2
- package/dist/__tests__/atmosphere-cubemap-orientation.test.d.ts.map +0 -1
- package/dist/__tests__/atmosphere-preetham-numeric.test.d.ts +0 -2
- package/dist/__tests__/atmosphere-preetham-numeric.test.d.ts.map +0 -1
- package/dist/__tests__/atmosphere-preetham-red.test.d.ts +0 -2
- package/dist/__tests__/atmosphere-preetham-red.test.d.ts.map +0 -1
- package/dist/__tests__/atmosphere-preetham.test.d.ts +0 -2
- package/dist/__tests__/atmosphere-preetham.test.d.ts.map +0 -1
- package/dist/__tests__/atmosphere-three-r184-reference.test.d.ts +0 -2
- package/dist/__tests__/atmosphere-three-r184-reference.test.d.ts.map +0 -1
- package/dist/__tests__/bloom-fxaa-tonemap.unit.test.d.ts +0 -2
- package/dist/__tests__/bloom-fxaa-tonemap.unit.test.d.ts.map +0 -1
- package/dist/__tests__/builtin-texture-sampling-contract.test.d.ts +0 -2
- package/dist/__tests__/builtin-texture-sampling-contract.test.d.ts.map +0 -1
- package/dist/__tests__/default-standard-pbr-alpha.unit.test.d.ts +0 -2
- package/dist/__tests__/default-standard-pbr-alpha.unit.test.d.ts.map +0 -1
- package/dist/__tests__/deferred-lighting-ssao.test.d.ts +0 -2
- package/dist/__tests__/deferred-lighting-ssao.test.d.ts.map +0 -1
- package/dist/__tests__/fog-oracle.unit.test.d.ts +0 -2
- package/dist/__tests__/fog-oracle.unit.test.d.ts.map +0 -1
- package/dist/__tests__/fog-orthographic-ray.unit.test.d.ts +0 -2
- package/dist/__tests__/fog-orthographic-ray.unit.test.d.ts.map +0 -1
- package/dist/__tests__/fog-producer-matrix.integration.test.d.ts +0 -2
- package/dist/__tests__/fog-producer-matrix.integration.test.d.ts.map +0 -1
- package/dist/__tests__/hdrp-ssao-compile.test.d.ts +0 -2
- package/dist/__tests__/hdrp-ssao-compile.test.d.ts.map +0 -1
- package/dist/__tests__/ibl-irradiance.unit.test.d.ts +0 -2
- package/dist/__tests__/ibl-irradiance.unit.test.d.ts.map +0 -1
- package/dist/__tests__/ibl-sampling.unit.test.d.ts +0 -2
- package/dist/__tests__/ibl-sampling.unit.test.d.ts.map +0 -1
- package/dist/__tests__/lighting-punctual.unit.test.d.ts +0 -2
- package/dist/__tests__/lighting-punctual.unit.test.d.ts.map +0 -1
- package/dist/__tests__/material-artifact-dedup.unit.test.d.ts +0 -2
- package/dist/__tests__/material-artifact-dedup.unit.test.d.ts.map +0 -1
- package/dist/__tests__/material-builtins.unit.test.d.ts +0 -2
- package/dist/__tests__/material-builtins.unit.test.d.ts.map +0 -1
- package/dist/__tests__/material-contract.unit.test.d.ts +0 -2
- package/dist/__tests__/material-contract.unit.test.d.ts.map +0 -1
- package/dist/__tests__/material-derived-builtins.integration.test.d.ts +0 -2
- package/dist/__tests__/material-derived-builtins.integration.test.d.ts.map +0 -1
- package/dist/__tests__/points-lines-shader.unit.test.d.ts +0 -2
- package/dist/__tests__/points-lines-shader.unit.test.d.ts.map +0 -1
- package/dist/__tests__/public-surface.unit.test.d.ts +0 -2
- package/dist/__tests__/public-surface.unit.test.d.ts.map +0 -1
- package/dist/__tests__/scene-temporal.unit.test.d.ts +0 -2
- package/dist/__tests__/scene-temporal.unit.test.d.ts.map +0 -1
- package/dist/__tests__/shader-registry-transaction.unit.test.d.ts +0 -2
- package/dist/__tests__/shader-registry-transaction.unit.test.d.ts.map +0 -1
- package/dist/__tests__/shader.unit.test.d.ts +0 -2
- package/dist/__tests__/shader.unit.test.d.ts.map +0 -1
- package/dist/__tests__/sprite-lit-shader.test.d.ts +0 -2
- package/dist/__tests__/sprite-lit-shader.test.d.ts.map +0 -1
- package/dist/__tests__/sprite-variants.unit.test.d.ts +0 -2
- package/dist/__tests__/sprite-variants.unit.test.d.ts.map +0 -1
- package/dist/__tests__/taa-resolve.unit.test.d.ts +0 -2
- package/dist/__tests__/taa-resolve.unit.test.d.ts.map +0 -1
- package/dist/__tests__/transparent-pbr.unit.test.d.ts +0 -2
- package/dist/__tests__/transparent-pbr.unit.test.d.ts.map +0 -1
- package/dist/__tests__/vertex-color-variant.unit.test.d.ts +0 -2
- package/dist/__tests__/vertex-color-variant.unit.test.d.ts.map +0 -1
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
#define_import_path forgeax_view::hdrp_deferred_lighting
|
|
2
2
|
|
|
3
3
|
#import forgeax_view::common::FullscreenOutput
|
|
4
|
-
#import forgeax_view::common::
|
|
4
|
+
#import forgeax_view::common::View
|
|
5
5
|
#import forgeax_view::common::fullscreen_triangle
|
|
6
|
-
#import forgeax_view::fog::{apply_fog}
|
|
7
6
|
|
|
8
7
|
// HDRP deferred lighting producer. The lighting pass consumes the material
|
|
9
8
|
// G-buffer and current-frame depth, reconstructs the fragment world position,
|
|
10
|
-
// then
|
|
9
|
+
// then writes linear radiance for the shared volume composite pass.
|
|
11
10
|
|
|
12
11
|
@group(0) @binding(0) var<uniform> view : View;
|
|
13
12
|
|
|
@@ -24,28 +23,6 @@ fn vs_main(@builtin(vertex_index) vertex_index : u32) -> FullscreenOutput {
|
|
|
24
23
|
return fullscreen_triangle(vertex_index);
|
|
25
24
|
}
|
|
26
25
|
|
|
27
|
-
fn reconstructWorldPosition(uv : vec2<f32>, depth : f32) -> vec3<f32> {
|
|
28
|
-
let clip = vec4<f32>(uv * 2.0 - vec2<f32>(1.0), depth, 1.0);
|
|
29
|
-
let worldH = view.inverseViewProj * clip;
|
|
30
|
-
return worldH.xyz / worldH.w;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
fn fogDeferredRadiance(color : vec4<f32>, worldPos : vec3<f32>) -> vec4<f32> {
|
|
34
|
-
var origin = view.cameraPos;
|
|
35
|
-
var direction = normalize(worldPos - origin);
|
|
36
|
-
var rayDistance = length(worldPos - origin);
|
|
37
|
-
if (view.temporalProjection.z >= 0.5) {
|
|
38
|
-
let nearH = view.inverseViewProj * vec4<f32>(0.0, 0.0, 0.0, 1.0);
|
|
39
|
-
let farH = view.inverseViewProj * vec4<f32>(0.0, 0.0, 1.0, 1.0);
|
|
40
|
-
let nearPoint = nearH.xyz / nearH.w;
|
|
41
|
-
let farPoint = farH.xyz / farH.w;
|
|
42
|
-
direction = normalize(farPoint - nearPoint);
|
|
43
|
-
origin = worldPos - direction * dot(worldPos - view.cameraPos, direction);
|
|
44
|
-
rayDistance = max(dot(worldPos - origin, direction), 0.0);
|
|
45
|
-
}
|
|
46
|
-
return apply_fog(view.fog, FogRay(origin, direction, rayDistance), color);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
26
|
@fragment
|
|
50
27
|
fn fs_lighting(in : FullscreenOutput) -> @location(0) vec4<f32> {
|
|
51
28
|
// The lighting pass runs before the forward lane and shares its cleared
|
|
@@ -64,6 +41,5 @@ fn fs_lighting(in : FullscreenOutput) -> @location(0) vec4<f32> {
|
|
|
64
41
|
let light = max(dot(normal, normalize(-view.lightDir)), 0.0);
|
|
65
42
|
let direct = material.rgb * view.lightColor * light * clamp(emissiveSample.a, 0.0, 1.0);
|
|
66
43
|
let radiance = vec4<f32>(direct + emissiveSample.rgb, material.a);
|
|
67
|
-
|
|
68
|
-
return fogDeferredRadiance(radiance, worldPos);
|
|
44
|
+
return radiance;
|
|
69
45
|
}
|
package/src/index.ts
CHANGED
|
@@ -84,6 +84,9 @@ export const BUILTIN_MATERIAL_MODULES = {
|
|
|
84
84
|
sprite: 'forgeax_material::sprite',
|
|
85
85
|
} as const;
|
|
86
86
|
|
|
87
|
+
/** Stable build-time module id for the temporal-v1 accessor owner. */
|
|
88
|
+
export const SCENE_DATA_TEMPORAL_V1_SHADER_MODULE = 'forgeax_scene_temporal' as const;
|
|
89
|
+
|
|
87
90
|
/**
|
|
88
91
|
* Material modules whose shader source and parameter contract are owned by
|
|
89
92
|
* the Engine. They live in the runtime ShaderRegistry, so a Pack containing
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#define_import_path forgeax_pbr::lighting_attenuation
|
|
2
|
+
|
|
3
|
+
// Shared punctual light optics. Range is represented by the host-derived
|
|
4
|
+
// inverse squared range; the shader never reconstructs authoring units.
|
|
5
|
+
fn evalDistanceAttenuation(dSquared : f32, invRangeSquared : f32) -> f32 {
|
|
6
|
+
let safeDistance = max(dSquared, 1e-4);
|
|
7
|
+
let window = clamp(1.0 - (safeDistance * invRangeSquared) * (safeDistance * invRangeSquared), 0.0, 1.0);
|
|
8
|
+
return window * window / safeDistance;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
fn evalSpotAttenuation(
|
|
12
|
+
lightPos : vec3<f32>,
|
|
13
|
+
lightDir : vec3<f32>,
|
|
14
|
+
worldPos : vec3<f32>,
|
|
15
|
+
cosInner : f32,
|
|
16
|
+
cosOuter : f32,
|
|
17
|
+
invRangeSquared : f32,
|
|
18
|
+
) -> f32 {
|
|
19
|
+
let toLight = lightPos - worldPos;
|
|
20
|
+
let dSquared = dot(toLight, toLight);
|
|
21
|
+
let distance = evalDistanceAttenuation(dSquared, invRangeSquared);
|
|
22
|
+
let direction = normalize(select(vec3<f32>(0.0, 0.0, -1.0), toLight, dSquared > 1e-4));
|
|
23
|
+
let cone = smoothstep(cosOuter, cosInner, dot(direction, -normalize(lightDir)));
|
|
24
|
+
return distance * cone;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Isolated volume radiance helpers: this module has no resource bindings, so
|
|
28
|
+
// compute stages can reuse the exact surface attenuation and cone facts.
|
|
29
|
+
fn evalVolumePointRadiance(
|
|
30
|
+
lightPos : vec3<f32>,
|
|
31
|
+
colorTimesIntensity : vec3<f32>,
|
|
32
|
+
invRangeSquared : f32,
|
|
33
|
+
worldPos : vec3<f32>,
|
|
34
|
+
) -> vec3<f32> {
|
|
35
|
+
let toLight = lightPos - worldPos;
|
|
36
|
+
let dSquared = max(dot(toLight, toLight), 1e-4);
|
|
37
|
+
return colorTimesIntensity * evalDistanceAttenuation(dSquared, invRangeSquared);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
fn evalVolumeSpotRadiance(
|
|
41
|
+
lightPos : vec3<f32>,
|
|
42
|
+
lightDir : vec3<f32>,
|
|
43
|
+
colorTimesIntensity : vec3<f32>,
|
|
44
|
+
cosInner : f32,
|
|
45
|
+
cosOuter : f32,
|
|
46
|
+
invRangeSquared : f32,
|
|
47
|
+
worldPos : vec3<f32>,
|
|
48
|
+
) -> vec3<f32> {
|
|
49
|
+
return colorTimesIntensity * evalSpotAttenuation(
|
|
50
|
+
lightPos, lightDir, worldPos, cosInner, cosOuter, invRangeSquared,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
fn projectSpotUv(lightViewProj : mat4x4<f32>, worldPos : vec3<f32>) -> vec2<f32> {
|
|
55
|
+
let clip = lightViewProj * vec4<f32>(worldPos, 1.0);
|
|
56
|
+
let invW = select(1.0 / clip.w, 0.0, abs(clip.w) < 1e-6);
|
|
57
|
+
return vec2<f32>(clip.x * invW * 0.5 + 0.5, clip.y * invW * -0.5 + 0.5);
|
|
58
|
+
}
|
|
@@ -238,6 +238,7 @@ fn _sampleShadowForCascade(
|
|
|
238
238
|
count : u32,
|
|
239
239
|
normal : vec3<f32>,
|
|
240
240
|
l : vec3<f32>,
|
|
241
|
+
useNormalBias : bool,
|
|
241
242
|
) -> f32 {
|
|
242
243
|
let lvp = _cascadeLightViewProj(layer);
|
|
243
244
|
let lightClip = lvp * vec4<f32>(worldPos, 1.0);
|
|
@@ -253,7 +254,7 @@ fn _sampleShadowForCascade(
|
|
|
253
254
|
// shadow fields carried in the View UBO -- normalBias scales the
|
|
254
255
|
// (1 - N.L) slope term, depthBias is the constant floor. Replaces the prior
|
|
255
256
|
// hardcoded max(0.05*(1-N.L), 0.005).
|
|
256
|
-
let bias = max(view.normalBias * (1.0 - dot(normal, l)), view.depthBias);
|
|
257
|
+
let bias = select(view.depthBias, max(view.normalBias * (1.0 - dot(normal, l)), view.depthBias), useNormalBias);
|
|
257
258
|
let adjustedDepth = currentDepth - bias;
|
|
258
259
|
// NaN-safe bounds: relational < and > do not reject NaN (NaN < 0 is
|
|
259
260
|
// false), so a zero / degenerate lightViewProj matrix that produces
|
|
@@ -414,7 +415,7 @@ fn evalDirectionalShadowFactor(
|
|
|
414
415
|
return 1.0;
|
|
415
416
|
}
|
|
416
417
|
let layer = _pickCascadeLayer(viewDepth, count);
|
|
417
|
-
let shadowCurr = _sampleShadowForCascade(worldPos, layer, count, normal, l);
|
|
418
|
+
let shadowCurr = _sampleShadowForCascade(worldPos, layer, count, normal, l, true);
|
|
418
419
|
|
|
419
420
|
var shadow = shadowCurr;
|
|
420
421
|
if (view.cascadeBlend > 0.0 && layer + 1u < count) {
|
|
@@ -424,7 +425,7 @@ fn evalDirectionalShadowFactor(
|
|
|
424
425
|
let dist = spCurr - viewDepth;
|
|
425
426
|
let t = clamp(1.0 - dist / blendWidth, 0.0, 1.0);
|
|
426
427
|
if (t > 0.0) {
|
|
427
|
-
let shadowNext = _sampleShadowForCascade(worldPos, layer + 1u, count, normal, l);
|
|
428
|
+
let shadowNext = _sampleShadowForCascade(worldPos, layer + 1u, count, normal, l, true);
|
|
428
429
|
shadow = mix(shadowCurr, shadowNext, t);
|
|
429
430
|
}
|
|
430
431
|
}
|
|
@@ -432,6 +433,33 @@ fn evalDirectionalShadowFactor(
|
|
|
432
433
|
return shadow;
|
|
433
434
|
}
|
|
434
435
|
|
|
436
|
+
// Volume receivers are samples in a medium, not surfaces. They therefore use
|
|
437
|
+
// the same cascade selection, atlas placement, PCF, and shadow-distance SSOT
|
|
438
|
+
// but only the bounded constant receiver bias; a ray direction is never
|
|
439
|
+
// treated as a geometric normal for normalBias.
|
|
440
|
+
fn evalDirectionalVolumeShadowFactor(
|
|
441
|
+
worldPos : vec3<f32>,
|
|
442
|
+
viewDepth : f32,
|
|
443
|
+
) -> f32 {
|
|
444
|
+
if (view.cascadeCount < 1.0 || viewDepth > view.splitPlanes[u32(max(view.cascadeCount, 1.0)) - 1u].x) {
|
|
445
|
+
return 1.0;
|
|
446
|
+
}
|
|
447
|
+
let count = u32(max(view.cascadeCount, 1.0));
|
|
448
|
+
let layer = _pickCascadeLayer(viewDepth, count);
|
|
449
|
+
let lightDirection = normalize(-view.lightDir);
|
|
450
|
+
let current = _sampleShadowForCascade(worldPos, layer, count, vec3<f32>(0.0), lightDirection, false);
|
|
451
|
+
if (view.cascadeBlend <= 0.0 || layer + 1u >= count) {
|
|
452
|
+
return current;
|
|
453
|
+
}
|
|
454
|
+
let blendWidth = view.splitPlanes[layer].x * view.cascadeBlend;
|
|
455
|
+
if (blendWidth <= 0.0) {
|
|
456
|
+
return current;
|
|
457
|
+
}
|
|
458
|
+
let blend = clamp(1.0 - (view.splitPlanes[layer].x - viewDepth) / blendWidth, 0.0, 1.0);
|
|
459
|
+
let next = _sampleShadowForCascade(worldPos, layer + 1u, count, vec3<f32>(0.0), lightDirection, false);
|
|
460
|
+
return mix(current, next, blend);
|
|
461
|
+
}
|
|
462
|
+
|
|
435
463
|
// `evalDirectional` evaluates the GGX direct-lighting term for the single
|
|
436
464
|
// directional light carried in `view.lightDir / view.lightColor`. CSM
|
|
437
465
|
// pathway: pick cascade layer from viewZ + splitPlanes, sample the atlas
|
|
@@ -32,12 +32,13 @@
|
|
|
32
32
|
// invRangeSquared, ...) -> vec3<f32>
|
|
33
33
|
|
|
34
34
|
#import forgeax_pbr::brdf::{f_schlick, v_smith, d_ggx}
|
|
35
|
+
#import forgeax_pbr::lighting_attenuation::{evalDistanceAttenuation, evalSpotAttenuation}
|
|
35
36
|
// feat-20260625-spot-light-shadow-mapping M3 / w15 (plan-strategy D-3 + D-5):
|
|
36
37
|
// spot shadow sampling reuses the shared 2D 9-tap PCF core (sample_shadow_2d)
|
|
37
38
|
// and the always-on `spotShadowMap` (binding 8) + `shadowSampler` (binding 4).
|
|
38
39
|
// `shadowSampler` is imported UNCONDITIONALLY here (spot is always-on, D-5):
|
|
39
40
|
// the point-shadow #ifdef block below must NOT re-import it (double import).
|
|
40
|
-
#import forgeax_pbr::shadow_pcf::{
|
|
41
|
+
#import forgeax_pbr::shadow_pcf::{sample_shadow_2d_kernel}
|
|
41
42
|
#import forgeax_view::common::{spotShadowMap, shadowSampler}
|
|
42
43
|
#ifdef POINT_SHADOW_AVAILABLE
|
|
43
44
|
#import forgeax_pbr::shadow_pcf::{sample_shadow_cube_hw2x2}
|
|
@@ -48,6 +49,45 @@
|
|
|
48
49
|
#import forgeax_view::common::{shadowAtlas}
|
|
49
50
|
#endif
|
|
50
51
|
|
|
52
|
+
// Volume lighting uses the same host-derived range and cone facts as surface
|
|
53
|
+
// lighting. These wrappers intentionally return radiance factors only; the
|
|
54
|
+
// volume integrator owns density, phase, transmittance, and accumulation.
|
|
55
|
+
fn evalVolumePoint(
|
|
56
|
+
lightPos : vec3<f32>,
|
|
57
|
+
colorTimesIntensity : vec3<f32>,
|
|
58
|
+
invRangeSquared : f32,
|
|
59
|
+
worldPos : vec3<f32>,
|
|
60
|
+
) -> vec3<f32> {
|
|
61
|
+
let toLight = lightPos - worldPos;
|
|
62
|
+
let dSquared = max(dot(toLight, toLight), 1e-4);
|
|
63
|
+
let safeDistance = max(dSquared, 1e-4);
|
|
64
|
+
return colorTimesIntensity * evalDistanceAttenuation(safeDistance, invRangeSquared);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
fn evalVolumeSpot(
|
|
68
|
+
lightPos : vec3<f32>,
|
|
69
|
+
lightDir : vec3<f32>,
|
|
70
|
+
colorTimesIntensity : vec3<f32>,
|
|
71
|
+
cosInner : f32,
|
|
72
|
+
cosOuter : f32,
|
|
73
|
+
invRangeSquared : f32,
|
|
74
|
+
worldPos : vec3<f32>,
|
|
75
|
+
) -> vec3<f32> {
|
|
76
|
+
return colorTimesIntensity * evalSpotAttenuation(
|
|
77
|
+
lightPos, lightDir, worldPos, cosInner, cosOuter, invRangeSquared,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
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
|
+
|
|
51
91
|
// Shared punctual BRDF body returning (diffuse + specular) *
|
|
52
92
|
// colorTimesIntensity * nDotL * attenuation. Cone factor is applied by the
|
|
53
93
|
// caller (evalSpot only).
|
|
@@ -75,8 +115,7 @@ fn evalPunctualBody(
|
|
|
75
115
|
let specular = d_ggx(nDotH, alphaSq) * v_smith(nDotV, nDotL, alphaSq) * f;
|
|
76
116
|
let kd = (vec3<f32>(1.0) - f) * (1.0 - metallic);
|
|
77
117
|
let diffuse = kd * baseColor / 3.14159265;
|
|
78
|
-
let
|
|
79
|
-
let attenuation = factor * factor / dSquared;
|
|
118
|
+
let attenuation = evalDistanceAttenuation(dSquared, invRangeSquared);
|
|
80
119
|
return (diffuse + specular) * colorTimesIntensity * nDotL * attenuation;
|
|
81
120
|
}
|
|
82
121
|
|
|
@@ -185,10 +224,9 @@ fn evalSpot(
|
|
|
185
224
|
lightPos, colorTimesIntensity, invRangeSquared,
|
|
186
225
|
worldPos, normal, viewDir, baseColor, metallic, alphaSq, F0,
|
|
187
226
|
);
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
return body * cone;
|
|
227
|
+
return body * evalSpotAttenuation(
|
|
228
|
+
lightPos, lightDir, worldPos, cosInner, cosOuter, invRangeSquared,
|
|
229
|
+
) / max(evalDistanceAttenuation(dot(lightPos - worldPos, lightPos - worldPos), invRangeSquared), 1e-4);
|
|
192
230
|
}
|
|
193
231
|
|
|
194
232
|
// feat-20260625-spot-light-shadow-mapping M3 / w15 (plan-strategy D-3 + D-4 +
|
|
@@ -234,6 +272,8 @@ fn evalSpotShadowed(
|
|
|
234
272
|
shadowAtlasTile : i32,
|
|
235
273
|
depthBias : f32,
|
|
236
274
|
normalBias : f32,
|
|
275
|
+
pcfKernelSize : f32,
|
|
276
|
+
shadowIntensity : f32,
|
|
237
277
|
) -> vec3<f32> {
|
|
238
278
|
let body = evalSpot(
|
|
239
279
|
lightPos, lightDir, colorTimesIntensity, cosInner, cosOuter, invRangeSquared,
|
|
@@ -245,10 +285,11 @@ fn evalSpotShadowed(
|
|
|
245
285
|
// Perspective divide; guard a zero/near-zero w (fragment behind the light or
|
|
246
286
|
// a degenerate matrix) so the OOB gate below catches it as fully lit.
|
|
247
287
|
let invW = select(1.0 / splane.w, 0.0, abs(splane.w) < 1e-6);
|
|
248
|
-
let
|
|
288
|
+
let clipUv = projectSpotUv(lightViewProj, worldPos);
|
|
249
289
|
let depthRef = splane.z * invW;
|
|
250
|
-
//
|
|
251
|
-
|
|
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;
|
|
252
293
|
|
|
253
294
|
// OOB / NaN gate: outside the light frustum (or NaN from a degenerate matrix)
|
|
254
295
|
// returns fully lit. Mirrors the directional `>= 0 && <= 1` NaN-safe form.
|
|
@@ -267,8 +308,12 @@ fn evalSpotShadowed(
|
|
|
267
308
|
let texel = vec2<f32>(1.0, 1.0) / atlasDims;
|
|
268
309
|
|
|
269
310
|
let nDotL = max(dot(normal, normalize(lightPos - worldPos)), 0.0);
|
|
270
|
-
let shadowFactor =
|
|
311
|
+
let shadowFactor = sample_shadow_2d_kernel(
|
|
271
312
|
spotShadowMap, shadowSampler, atlasUv, texel, depthRef, normalBias, depthBias, nDotL,
|
|
313
|
+
pcfKernelSize,
|
|
272
314
|
);
|
|
273
|
-
|
|
315
|
+
// Three's SpotLight.shadow.intensity is the opacity of the shadow, not a
|
|
316
|
+
// multiplier on the light's candela radiance. A value of 1 keeps the
|
|
317
|
+
// sampled visibility fully opaque; 0 leaves the unshadowed body intact.
|
|
318
|
+
return body * mix(1.0, shadowFactor, clamp(shadowIntensity, 0.0, 1.0));
|
|
274
319
|
}
|
package/src/material-schemas.ts
CHANGED
|
@@ -19,12 +19,19 @@ export const DEFAULT_STANDARD_PBR_PARAM_SCHEMA: readonly ParamSchemaEntry[] = [
|
|
|
19
19
|
{ name: 'clearcoatRoughness', type: 'f32', default: 0.5 },
|
|
20
20
|
{ name: 'specularTint', type: 'vec3', colorSpace: 'srgb', default: [1, 1, 1] },
|
|
21
21
|
{ name: 'normalScale', type: 'f32', default: 1 },
|
|
22
|
+
{ name: 'transmission', type: 'f32', default: 0 },
|
|
23
|
+
{ name: 'ior', type: 'f32', default: 1.5 },
|
|
24
|
+
{ name: 'thickness', type: 'f32', default: 0 },
|
|
25
|
+
{ name: 'attenuationColor', type: 'vec3', colorSpace: 'linear', default: [1, 1, 1] },
|
|
26
|
+
{ name: 'attenuationDistance', type: 'f32' },
|
|
22
27
|
{ name: 'baseColorTexture', type: 'texture2d' },
|
|
23
28
|
{ name: 'metallicRoughnessTexture', type: 'texture2d' },
|
|
24
29
|
{ name: 'normalTexture', type: 'texture2d' },
|
|
25
30
|
{ name: 'specularTintTexture', type: 'texture2d' },
|
|
26
31
|
{ name: 'emissiveTexture', type: 'texture2d' },
|
|
27
32
|
{ name: 'occlusionTexture', type: 'texture2d' },
|
|
33
|
+
{ name: 'transmissionTexture', type: 'texture2d' },
|
|
34
|
+
{ name: 'thicknessTexture', type: 'texture2d' },
|
|
28
35
|
];
|
|
29
36
|
|
|
30
37
|
export const DEFAULT_UNLIT_PARAM_SCHEMA: readonly ParamSchemaEntry[] = [
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#define_import_path forgeax_view::motion_blur
|
|
2
|
+
|
|
3
|
+
#import forgeax_view::common::FullscreenOutput
|
|
4
|
+
#import forgeax_view::common::fullscreen_triangle
|
|
5
|
+
#import forgeax_scene_temporal::{unpackSceneTemporalV1}
|
|
6
|
+
|
|
7
|
+
struct MotionBlurParams {
|
|
8
|
+
shutterAngle : f32,
|
|
9
|
+
maxRadiusPixels : f32,
|
|
10
|
+
sampleCount : u32,
|
|
11
|
+
reset : u32,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
@group(1) @binding(0) var currentColor : texture_2d<f32>;
|
|
15
|
+
@group(1) @binding(1) var linearSampler : sampler;
|
|
16
|
+
@group(1) @binding(2) var<uniform> params : MotionBlurParams;
|
|
17
|
+
@group(1) @binding(3) var sceneTemporal : texture_2d<f32>;
|
|
18
|
+
|
|
19
|
+
fn depthReject(center : f32, sample : f32) -> bool {
|
|
20
|
+
return abs(center - sample) > max(0.01, center * 0.01);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
fn symmetricOffset(index : u32, count : u32, motion : vec2<f32>) -> vec2<f32> {
|
|
24
|
+
let denominator = max(f32(count), 1.0);
|
|
25
|
+
let signedIndex = f32(index) - (denominator - 1.0) * 0.5;
|
|
26
|
+
return motion * (signedIndex / denominator);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@vertex
|
|
30
|
+
fn vs_main(@builtin(vertex_index) vertexIndex : u32) -> FullscreenOutput {
|
|
31
|
+
return fullscreen_triangle(vertexIndex);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@fragment
|
|
35
|
+
fn fs_main(in : FullscreenOutput) -> @location(0) vec4<f32> {
|
|
36
|
+
let current = textureSampleLevel(currentColor, linearSampler, in.uv, 0.0);
|
|
37
|
+
let packed = textureSampleLevel(sceneTemporal, linearSampler, in.uv, 0.0);
|
|
38
|
+
let temporal = unpackSceneTemporalV1(packed);
|
|
39
|
+
let dimensions = vec2<f32>(textureDimensions(currentColor));
|
|
40
|
+
let pixelMotion = temporal.motionUv * dimensions;
|
|
41
|
+
let shutter = clamp(params.shutterAngle / 360.0, 0.0, 1.0);
|
|
42
|
+
let radius = min(length(pixelMotion) * shutter, params.maxRadiusPixels);
|
|
43
|
+
let motion = normalize(select(vec2<f32>(0.0), pixelMotion, radius > 1e-5));
|
|
44
|
+
let invalidDepth = !temporal.validDepth;
|
|
45
|
+
let reactive = clamp(temporal.reactive, 0.0, 1.0);
|
|
46
|
+
let subpixel = radius <= 1e-5;
|
|
47
|
+
let reset = params.reset != 0u;
|
|
48
|
+
var output = current;
|
|
49
|
+
output.a = current.a;
|
|
50
|
+
if (invalidDepth || subpixel || reset || params.sampleCount < 4u) {
|
|
51
|
+
return output;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
var accum = vec3<f32>(0.0);
|
|
55
|
+
var weight = 0.0;
|
|
56
|
+
let temporalDimensions = vec2<i32>(textureDimensions(sceneTemporal));
|
|
57
|
+
for (var index = 0u; index < 16u; index += 1u) {
|
|
58
|
+
if (index >= params.sampleCount) { break; }
|
|
59
|
+
let offset = symmetricOffset(index, params.sampleCount, motion * radius) / dimensions;
|
|
60
|
+
let sampleUv = clamp(in.uv + offset, vec2<f32>(0.0), vec2<f32>(1.0));
|
|
61
|
+
let samplePixel = clamp(
|
|
62
|
+
vec2<i32>(sampleUv * dimensions),
|
|
63
|
+
vec2<i32>(0),
|
|
64
|
+
temporalDimensions - vec2<i32>(1),
|
|
65
|
+
);
|
|
66
|
+
let sampleTemporal = unpackSceneTemporalV1(textureLoad(sceneTemporal, samplePixel, 0));
|
|
67
|
+
if (sampleTemporal.validDepth && !depthReject(temporal.viewDepth, sampleTemporal.viewDepth)) {
|
|
68
|
+
accum += textureSampleLevel(currentColor, linearSampler, sampleUv, 0.0).rgb;
|
|
69
|
+
weight += 1.0;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
let gathered = accum / max(weight, 1.0);
|
|
73
|
+
let blurWeight = (1.0 - reactive) * select(0.0, 1.0, weight > 0.0);
|
|
74
|
+
output = vec4<f32>(mix(current.rgb, gathered, blurWeight), current.a);
|
|
75
|
+
output.a = current.a;
|
|
76
|
+
return output;
|
|
77
|
+
}
|
package/src/msdf-text.wgsl
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#pragma variant_axis STORAGE_BUFFER_AVAILABLE
|
|
2
2
|
|
|
3
|
-
#import forgeax_view::common::{View,
|
|
4
|
-
#import forgeax_view::fog::{apply_fog}
|
|
3
|
+
#import forgeax_view::common::{View, Mesh, view, meshes, sampleMaterialTexture}
|
|
5
4
|
|
|
6
5
|
// @forgeax/engine-shader - msdf-text.wgsl
|
|
7
6
|
// (feat-20260531-world-space-msdf-text-rendering M5 / w20).
|
|
@@ -146,22 +145,6 @@ fn screen_px_range(uv : vec2<f32>) -> f32 {
|
|
|
146
145
|
return max(0.5 * dot(unit_range, screen_tex_size), 1.0);
|
|
147
146
|
}
|
|
148
147
|
|
|
149
|
-
fn applySceneFog(viewParams : View, color : vec3<f32>, alpha : f32, worldPos : vec3<f32>) -> vec4<f32> {
|
|
150
|
-
var origin = viewParams.cameraPos;
|
|
151
|
-
var direction = normalize(worldPos - origin);
|
|
152
|
-
var rayDistance = length(worldPos - origin);
|
|
153
|
-
if (viewParams.temporalProjection.z >= 0.5) {
|
|
154
|
-
let nearH = viewParams.inverseViewProj * vec4<f32>(0.0, 0.0, 0.0, 1.0);
|
|
155
|
-
let farH = viewParams.inverseViewProj * vec4<f32>(0.0, 0.0, 1.0, 1.0);
|
|
156
|
-
let nearPoint = nearH.xyz / nearH.w;
|
|
157
|
-
let farPoint = farH.xyz / farH.w;
|
|
158
|
-
direction = normalize(farPoint - nearPoint);
|
|
159
|
-
origin = worldPos - direction * dot(worldPos - viewParams.cameraPos, direction);
|
|
160
|
-
rayDistance = max(dot(worldPos - origin, direction), 0.0);
|
|
161
|
-
}
|
|
162
|
-
return apply_fog(viewParams.fog, FogRay(origin, direction, rayDistance), vec4<f32>(color, alpha));
|
|
163
|
-
}
|
|
164
|
-
|
|
165
148
|
// fs_main_hdr: outputs linear premultiplied alpha for the rgba16float
|
|
166
149
|
// offscreen target (D-7 / R-7). The tonemap fullscreen pass handles sRGB
|
|
167
150
|
// encoding; writing hdrColor lets the bloom bright-pass catch the text.
|
|
@@ -176,8 +159,7 @@ fn fs_main_hdr(in : VsOut) -> @location(0) vec4<f32> {
|
|
|
176
159
|
let alpha = clamp(dist + 0.5, 0.0, 1.0) * material.tintColor.a;
|
|
177
160
|
// Premultiplied output: rgb already multiplied by alpha for srcFactor=ONE /
|
|
178
161
|
// dstFactor=ONE_MINUS_SRC_ALPHA (wiki section 6 SSOT).
|
|
179
|
-
|
|
180
|
-
return vec4<f32>(fogged.rgb * fogged.a, fogged.a);
|
|
162
|
+
return vec4<f32>(material.tintColor.rgb * alpha, alpha);
|
|
181
163
|
}
|
|
182
164
|
|
|
183
165
|
// linear_to_srgb: per-channel IEC 61966-2-1 transfer function for the LDR
|
|
@@ -197,12 +179,11 @@ fn fs_main(in : VsOut) -> @location(0) vec4<f32> {
|
|
|
197
179
|
let sd = median(msd.r, msd.g, msd.b);
|
|
198
180
|
let dist = (sd - 0.5) * screen_px_range(in.uv);
|
|
199
181
|
let alpha = clamp(dist + 0.5, 0.0, 1.0) * material.tintColor.a;
|
|
200
|
-
let
|
|
201
|
-
let premult = fogged.rgb * fogged.a;
|
|
182
|
+
let premult = material.tintColor.rgb * alpha;
|
|
202
183
|
return vec4<f32>(
|
|
203
184
|
linear_to_srgb(premult.r),
|
|
204
185
|
linear_to_srgb(premult.g),
|
|
205
186
|
linear_to_srgb(premult.b),
|
|
206
|
-
|
|
187
|
+
alpha,
|
|
207
188
|
);
|
|
208
189
|
}
|
package/src/pbr-temporal.wgsl
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#define_import_path forgeax_pbr::temporal
|
|
2
2
|
|
|
3
|
-
#import forgeax_view::common::{sampleMaterialTexture
|
|
3
|
+
#import forgeax_view::common::{sampleMaterialTexture}
|
|
4
|
+
#import forgeax_scene_temporal::{packSceneTemporalV1}
|
|
4
5
|
|
|
5
6
|
fn transformedPbrTemporalUv(
|
|
6
7
|
transform : vec4<f32>,
|
|
@@ -29,6 +30,16 @@ fn transformedPbrTemporalUv(
|
|
|
29
30
|
return vec2<f32>(scaled.x * c - scaled.y * s, scaled.x * s + scaled.y * c) + transform.xy;
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
fn resolvePbrTemporalReactive(
|
|
34
|
+
reactive : f32,
|
|
35
|
+
baseColorAlpha : f32,
|
|
36
|
+
sampledAlpha : f32,
|
|
37
|
+
) -> f32 {
|
|
38
|
+
let coverage = clamp(baseColorAlpha * sampledAlpha, 0.0, 1.0);
|
|
39
|
+
let coverageReactive = 1.0 - coverage;
|
|
40
|
+
return max(clamp(reactive, 0.0, 1.0), coverageReactive);
|
|
41
|
+
}
|
|
42
|
+
|
|
32
43
|
fn projectPbrSceneTemporal(
|
|
33
44
|
baseColorAlpha : f32,
|
|
34
45
|
alphaCutoff : f32,
|
|
@@ -38,6 +49,7 @@ fn projectPbrSceneTemporal(
|
|
|
38
49
|
metadata : vec4<f32>,
|
|
39
50
|
currentClip : vec4<f32>,
|
|
40
51
|
previousClip : vec4<f32>,
|
|
52
|
+
temporalProjection : vec4<f32>,
|
|
41
53
|
reactive : f32,
|
|
42
54
|
uv0 : vec2<f32>,
|
|
43
55
|
uv1 : vec2<f32>,
|
|
@@ -69,5 +81,6 @@ fn projectPbrSceneTemporal(
|
|
|
69
81
|
if (alphaCutoff > 0.0 && baseColorAlpha * baseSample.a <= alphaCutoff) {
|
|
70
82
|
discard;
|
|
71
83
|
}
|
|
72
|
-
|
|
84
|
+
let reactiveCoverage = resolvePbrTemporalReactive(reactive, baseColorAlpha, baseSample.a);
|
|
85
|
+
return packSceneTemporalV1(currentClip, previousClip, temporalProjection, reactiveCoverage);
|
|
73
86
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#define_import_path forgeax_scene_temporal
|
|
2
|
+
|
|
3
|
+
// The sole shader interpretation of forgeax::scene-data::temporal-v1.
|
|
4
|
+
// The packed target is rgba16float at render resolution and is sampled only.
|
|
5
|
+
|
|
6
|
+
const SCENE_DATA_TEMPORAL_V1_CLEAR : vec4<f32> = vec4<f32>(0.0, 0.0, -1.0, 1.0);
|
|
7
|
+
|
|
8
|
+
struct SceneTemporalV1 {
|
|
9
|
+
motionUv : vec2<f32>,
|
|
10
|
+
viewDepth : f32,
|
|
11
|
+
reactive : f32,
|
|
12
|
+
validDepth : bool,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
fn unpackSceneTemporalV1(packed : vec4<f32>) -> SceneTemporalV1 {
|
|
16
|
+
let invalidDepth = packed.z < 0.0;
|
|
17
|
+
let validDepth = !invalidDepth;
|
|
18
|
+
let viewDepth = select(0.0, exp2(packed.z) - 1.0, validDepth);
|
|
19
|
+
let motionUv = packed.xy;
|
|
20
|
+
let reactive = clamp(packed.w, 0.0, 1.0);
|
|
21
|
+
return SceneTemporalV1(motionUv, viewDepth, reactive, validDepth);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
fn sceneTemporalUv(clip : vec4<f32>) -> vec2<f32> {
|
|
25
|
+
let safeW = select(1e-6, clip.w, abs(clip.w) >= 1e-6);
|
|
26
|
+
let ndc = clip.xy / safeW;
|
|
27
|
+
return vec2<f32>(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
fn sceneTemporalViewDepth(clip : vec4<f32>, temporalProjection : vec4<f32>) -> f32 {
|
|
31
|
+
let perspectiveDepth = max(clip.w, 0.0);
|
|
32
|
+
let ndcDepth = clip.z / max(abs(clip.w), 1e-6);
|
|
33
|
+
let orthographicDepth = temporalProjection.x +
|
|
34
|
+
ndcDepth * (temporalProjection.y - temporalProjection.x);
|
|
35
|
+
let viewDepth = select(
|
|
36
|
+
perspectiveDepth,
|
|
37
|
+
max(orthographicDepth, 0.0),
|
|
38
|
+
temporalProjection.z >= 0.5,
|
|
39
|
+
);
|
|
40
|
+
return log2(1.0 + viewDepth);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
fn packSceneTemporalV1(
|
|
44
|
+
currentClip : vec4<f32>,
|
|
45
|
+
previousClip : vec4<f32>,
|
|
46
|
+
temporalProjection : vec4<f32>,
|
|
47
|
+
reactive : f32,
|
|
48
|
+
) -> vec4<f32> {
|
|
49
|
+
return vec4<f32>(
|
|
50
|
+
sceneTemporalUv(currentClip) - sceneTemporalUv(previousClip),
|
|
51
|
+
sceneTemporalViewDepth(currentClip, temporalProjection),
|
|
52
|
+
clamp(reactive, 0.0, 1.0),
|
|
53
|
+
);
|
|
54
|
+
}
|
package/src/shadow-pcf.wgsl
CHANGED
|
@@ -49,6 +49,22 @@ fn sample_shadow_2d(
|
|
|
49
49
|
normalBias : f32,
|
|
50
50
|
depthBias : f32,
|
|
51
51
|
nDotL : f32,
|
|
52
|
+
) -> f32 {
|
|
53
|
+
return sample_shadow_2d_kernel(
|
|
54
|
+
shadowMap, shadowSampler, uv, texel, depthRef, normalBias, depthBias, nDotL, 3.0,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
fn sample_shadow_2d_kernel(
|
|
59
|
+
shadowMap : texture_depth_2d,
|
|
60
|
+
shadowSampler: sampler_comparison,
|
|
61
|
+
uv : vec2<f32>,
|
|
62
|
+
texel : vec2<f32>,
|
|
63
|
+
depthRef : f32,
|
|
64
|
+
normalBias : f32,
|
|
65
|
+
depthBias : f32,
|
|
66
|
+
nDotL : f32,
|
|
67
|
+
kernelSize : f32,
|
|
52
68
|
) -> f32 {
|
|
53
69
|
// feat-20260621-merge-directionallightshadow-into-directionallight M3 / m3-t5:
|
|
54
70
|
// param names aligned to the D-1 directional convention -- the slope
|
|
@@ -60,17 +76,22 @@ fn sample_shadow_2d(
|
|
|
60
76
|
let bias = max(normalBias * (1.0 - nDotL), depthBias / 1000.0);
|
|
61
77
|
let adjustedDepth = depthRef - bias;
|
|
62
78
|
|
|
63
|
-
|
|
64
|
-
//
|
|
65
|
-
//
|
|
79
|
+
let halfWidth = clamp((i32(round(kernelSize)) - 1) / 2, 0, 2);
|
|
80
|
+
// Variable odd-kernel PCF. textureSampleCompareLevel returns 1.0 when
|
|
81
|
+
// adjustedDepth < sampledDepth; sampler clamp-to-edge handles OOB texels.
|
|
66
82
|
var blocked = 0.0;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
83
|
+
var samples = 0.0;
|
|
84
|
+
for (var y = -2; y <= 2; y = y + 1) {
|
|
85
|
+
for (var x = -2; x <= 2; x = x + 1) {
|
|
86
|
+
if (abs(x) <= halfWidth && abs(y) <= halfWidth) {
|
|
87
|
+
let offsetUv = uv + vec2<f32>(f32(x), f32(y)) * texel;
|
|
88
|
+
let lit = textureSampleCompareLevel(shadowMap, shadowSampler, offsetUv, adjustedDepth);
|
|
89
|
+
blocked = blocked + (1.0 - lit);
|
|
90
|
+
samples = samples + 1.0;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
72
93
|
}
|
|
73
|
-
return 1.0 - blocked /
|
|
94
|
+
return 1.0 - blocked / max(samples, 1.0);
|
|
74
95
|
}
|
|
75
96
|
|
|
76
97
|
// === Cube point-light-shadow wrapper =========================================
|
|
@@ -117,4 +138,4 @@ fn sample_shadow_cube_hw2x2(
|
|
|
117
138
|
// handles face-boundary texel fetches. Returns 1.0 = fully lit, 0.0 = fully
|
|
118
139
|
// shadowed.
|
|
119
140
|
return textureSampleCompareLevel(shadowAtlas, shadowSampler, lightLocal, layer, adjustedDepth);
|
|
120
|
-
}
|
|
141
|
+
}
|