@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
package/src/shadow_caster.wgsl
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#pragma variant_axis STORAGE_BUFFER_AVAILABLE
|
|
2
|
+
#pragma variant_axis SKINNING_DISABLED
|
|
2
3
|
|
|
3
4
|
// @forgeax/engine-shader shadow_caster.wgsl
|
|
4
5
|
// feat-20260520-directional-light-shadow-mapping M1c / w9 (D-9 / AC-09):
|
|
@@ -20,15 +21,28 @@
|
|
|
20
21
|
// @group(2) binding(0) meshes : array<Mesh> -- common.wgsl
|
|
21
22
|
// @group(3) binding(0) instances : array<InstanceData> -- common.wgsl
|
|
22
23
|
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
24
|
+
// SKINNING_DISABLED=true consumes the ordinary 12F mesh layout; the explicit
|
|
25
|
+
// false variant consumes JOINTS_0 / WEIGHTS_0 from the same 18F layout as pbr-skin and
|
|
26
|
+
// projects the animated world-space palette result into the shadow view.
|
|
25
27
|
|
|
26
28
|
#import forgeax_view::common::{View, Mesh, InstanceData, ShadowCasterCascade, view, shadowCasterCascade, meshes, instances}
|
|
27
29
|
|
|
28
30
|
struct VsInput {
|
|
29
31
|
@location(0) position : vec3<f32>,
|
|
32
|
+
#if SKINNING_DISABLED == false
|
|
33
|
+
@location(4) skinIndex : vec4<u32>,
|
|
34
|
+
@location(5) skinWeight : vec4<f32>,
|
|
35
|
+
#endif
|
|
30
36
|
};
|
|
31
37
|
|
|
38
|
+
#if SKINNING_DISABLED == false
|
|
39
|
+
#if STORAGE_BUFFER_AVAILABLE == true
|
|
40
|
+
@group(2) @binding(1) var<storage, read> palette : array<mat4x4<f32>>;
|
|
41
|
+
#else
|
|
42
|
+
@group(2) @binding(1) var<uniform> palette : array<mat4x4<f32>, 255>;
|
|
43
|
+
#endif
|
|
44
|
+
#endif
|
|
45
|
+
|
|
32
46
|
fn _cascadeLightViewProj(layer : u32) -> mat4x4<f32> {
|
|
33
47
|
switch (layer) {
|
|
34
48
|
case 0u: { return view.lightViewProj_A; }
|
|
@@ -40,7 +54,18 @@ fn _cascadeLightViewProj(layer : u32) -> mat4x4<f32> {
|
|
|
40
54
|
|
|
41
55
|
@vertex
|
|
42
56
|
fn vs_main(in : VsInput, @builtin(instance_index) idx : u32) -> @builtin(position) vec4<f32> {
|
|
57
|
+
#if SKINNING_DISABLED == false
|
|
58
|
+
let skinMatrix = palette[in.skinIndex.x] * in.skinWeight.x +
|
|
59
|
+
palette[in.skinIndex.y] * in.skinWeight.y +
|
|
60
|
+
palette[in.skinIndex.z] * in.skinWeight.z +
|
|
61
|
+
palette[in.skinIndex.w] * in.skinWeight.w;
|
|
62
|
+
// Palette entries are jointWorld * inverseBind and therefore already
|
|
63
|
+
// produce world-space positions. Applying meshes[0].worldFromLocal again
|
|
64
|
+
// would double-transform a parented skinned entity.
|
|
65
|
+
let worldPos = skinMatrix * vec4<f32>(in.position, 1.0);
|
|
66
|
+
#else
|
|
43
67
|
let worldPos = meshes[0].worldFromLocal * instances[idx].localFromInstance * vec4<f32>(in.position, 1.0);
|
|
68
|
+
#endif
|
|
44
69
|
// feat-20260625-spot-light-shadow-mapping M2 / w10 (D-1): spot shadow passes
|
|
45
70
|
// set `isSpot = 1u` and write their perspective matrix into
|
|
46
71
|
// `spotLightViewProj`; directional cascade passes keep `isSpot = 0u` and read
|
package/src/skybox.wgsl
CHANGED
|
@@ -23,9 +23,8 @@
|
|
|
23
23
|
// @binding(3) rotation : SkyboxRotation (UBO)
|
|
24
24
|
|
|
25
25
|
#import forgeax_view::common::FullscreenOutput
|
|
26
|
-
#import forgeax_view::common::
|
|
26
|
+
#import forgeax_view::common::View
|
|
27
27
|
#import forgeax_view::common::fullscreen_triangle
|
|
28
|
-
#import forgeax_view::fog::{apply_fog}
|
|
29
28
|
#import forgeax_pbr::ibl_shared::{inverseRotateEnvironment}
|
|
30
29
|
|
|
31
30
|
@group(0) @binding(0) var cubemap : texture_cube<f32>;
|
|
@@ -67,9 +66,5 @@ fn skyboxDirection(uv : vec2<f32>) -> vec3<f32> {
|
|
|
67
66
|
fn skybox_fs(in : FullscreenOutput) -> @location(0) vec4<f32> {
|
|
68
67
|
let dir = skyboxDirection(in.uv);
|
|
69
68
|
let color = textureSample(cubemap, cubemapSampler, dir).rgb;
|
|
70
|
-
return
|
|
71
|
-
view.fog,
|
|
72
|
-
FogRay(view.cameraPos, dir, max(view.temporalProjection.y, 0.0)),
|
|
73
|
-
vec4<f32>(color, 1.0),
|
|
74
|
-
);
|
|
69
|
+
return vec4<f32>(color, 1.0);
|
|
75
70
|
}
|
package/src/sprite-lit.wgsl
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#pragma variant_axis STORAGE_BUFFER_AVAILABLE
|
|
2
2
|
#define_import_path forgeax_material::sprite-lit
|
|
3
3
|
|
|
4
|
-
#import forgeax_view::common::{View,
|
|
5
|
-
#import
|
|
4
|
+
#import forgeax_view::common::{View, Mesh, InstanceData, PointLight, SpotLight, view, meshes, instances, pointLightsBuffer, spotLightsBuffer, sampleMaterialTexture}
|
|
5
|
+
#import forgeax_scene_temporal::{packSceneTemporalV1}
|
|
6
6
|
|
|
7
7
|
// @forgeax/engine-shader - sprite-lit.wgsl
|
|
8
8
|
// (tweak-20260701-sprite-lit-flat-default-drop-ndotl-for-2d).
|
|
@@ -221,22 +221,6 @@ fn spriteLitShadeAccum(albedo : vec3<f32>, worldPos : vec3<f32>) -> vec3<f32> {
|
|
|
221
221
|
return lit;
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
-
fn applySceneFog(viewParams : View, color : vec3<f32>, alpha : f32, worldPos : vec3<f32>) -> vec4<f32> {
|
|
225
|
-
var origin = viewParams.cameraPos;
|
|
226
|
-
var direction = normalize(worldPos - origin);
|
|
227
|
-
var rayDistance = length(worldPos - origin);
|
|
228
|
-
if (viewParams.temporalProjection.z >= 0.5) {
|
|
229
|
-
let nearH = viewParams.inverseViewProj * vec4<f32>(0.0, 0.0, 0.0, 1.0);
|
|
230
|
-
let farH = viewParams.inverseViewProj * vec4<f32>(0.0, 0.0, 1.0, 1.0);
|
|
231
|
-
let nearPoint = nearH.xyz / nearH.w;
|
|
232
|
-
let farPoint = farH.xyz / farH.w;
|
|
233
|
-
direction = normalize(farPoint - nearPoint);
|
|
234
|
-
origin = worldPos - direction * dot(worldPos - viewParams.cameraPos, direction);
|
|
235
|
-
rayDistance = max(dot(worldPos - origin, direction), 0.0);
|
|
236
|
-
}
|
|
237
|
-
return apply_fog(viewParams.fog, FogRay(origin, direction, rayDistance), vec4<f32>(color, alpha));
|
|
238
|
-
}
|
|
239
|
-
|
|
240
224
|
@fragment
|
|
241
225
|
fn fs_main(in : VsOut) -> @location(0) vec4<f32> {
|
|
242
226
|
let texel = sampleMaterialTexture(baseColorTexture, baseColorSampler, in.uv_atlas, material.baseColorTextureCoordinatesMetadata.zw);
|
|
@@ -245,8 +229,7 @@ fn fs_main(in : VsOut) -> @location(0) vec4<f32> {
|
|
|
245
229
|
// Strict clamp 0..1 before premultiplied alpha multiply keeps the LDR
|
|
246
230
|
// path bounded even when a scene stacks many lights.
|
|
247
231
|
let lit_rgba = clamp(vec4<f32>(lit, albedo4.a), vec4<f32>(0.0), vec4<f32>(1.0));
|
|
248
|
-
let
|
|
249
|
-
let premult = vec4<f32>(fogged.rgb * fogged.a, fogged.a);
|
|
232
|
+
let premult = vec4<f32>(lit_rgba.rgb * lit_rgba.a, lit_rgba.a);
|
|
250
233
|
// LDR target is bgra8unorm; encode rgb via the sRGB transfer in-shader.
|
|
251
234
|
return vec4<f32>(
|
|
252
235
|
linear_to_srgb(premult.r),
|
|
@@ -265,15 +248,14 @@ fn fs_main_hdr(in : VsOut) -> @location(0) vec4<f32> {
|
|
|
265
248
|
// pass absorb HDR values > 1. Alpha stays clamped because the premult
|
|
266
249
|
// math requires alpha in [0, 1].
|
|
267
250
|
let alpha = clamp(albedo4.a, 0.0, 1.0);
|
|
268
|
-
|
|
269
|
-
return vec4<f32>(fogged.rgb * fogged.a, fogged.a);
|
|
251
|
+
return vec4<f32>(lit * alpha, alpha);
|
|
270
252
|
}
|
|
271
253
|
|
|
272
254
|
struct TemporalVsOut {
|
|
273
255
|
@builtin(position) clip : vec4<f32>,
|
|
274
256
|
@location(0) uvAtlas : vec2<f32>,
|
|
275
|
-
@location(1) @interpolate(
|
|
276
|
-
@location(2) @interpolate(
|
|
257
|
+
@location(1) @interpolate(perspective) currentClip : vec4<f32>,
|
|
258
|
+
@location(2) @interpolate(perspective) previousClip : vec4<f32>,
|
|
277
259
|
};
|
|
278
260
|
|
|
279
261
|
@vertex
|
|
@@ -301,7 +283,7 @@ fn fs_temporal(in : TemporalVsOut) -> @location(0) vec4<f32> {
|
|
|
301
283
|
if (alpha <= 0.0) {
|
|
302
284
|
discard;
|
|
303
285
|
}
|
|
304
|
-
return
|
|
286
|
+
return packSceneTemporalV1(in.currentClip, in.previousClip, view.temporalProjection, 1.0);
|
|
305
287
|
}
|
|
306
288
|
// sprite-lit needs at least one light in the scene to be visible; for unlit
|
|
307
289
|
// sprites use forgeax::sprite (1 string change in MaterialAsset).
|
package/src/sprite.wgsl
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
#pragma variant_axis STORAGE_BUFFER_AVAILABLE
|
|
3
3
|
#pragma variant_axis PER_INSTANCE_REGION
|
|
4
4
|
|
|
5
|
-
#import forgeax_view::common::{View,
|
|
6
|
-
#import
|
|
5
|
+
#import forgeax_view::common::{View, Mesh, InstanceData, view, meshes, instances, sampleMaterialTexture}
|
|
6
|
+
#import forgeax_scene_temporal::{packSceneTemporalV1}
|
|
7
7
|
|
|
8
8
|
// @forgeax/engine-shader - sprite.wgsl (feat-20260520-2d-sprite-layer-mvp /
|
|
9
9
|
// M-3 / w19). 2D sprite material — third variant of the MaterialAsset
|
|
@@ -270,22 +270,6 @@ fn vs_main(in : VsIn, @builtin(instance_index) idx : u32, @builtin(vertex_index)
|
|
|
270
270
|
return out;
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
-
fn applySceneFog(viewParams : View, color : vec3<f32>, alpha : f32, worldPos : vec3<f32>) -> vec4<f32> {
|
|
274
|
-
var origin = viewParams.cameraPos;
|
|
275
|
-
var direction = normalize(worldPos - origin);
|
|
276
|
-
var rayDistance = length(worldPos - origin);
|
|
277
|
-
if (viewParams.temporalProjection.z >= 0.5) {
|
|
278
|
-
let nearH = viewParams.inverseViewProj * vec4<f32>(0.0, 0.0, 0.0, 1.0);
|
|
279
|
-
let farH = viewParams.inverseViewProj * vec4<f32>(0.0, 0.0, 1.0, 1.0);
|
|
280
|
-
let nearPoint = nearH.xyz / nearH.w;
|
|
281
|
-
let farPoint = farH.xyz / farH.w;
|
|
282
|
-
direction = normalize(farPoint - nearPoint);
|
|
283
|
-
origin = worldPos - direction * dot(worldPos - viewParams.cameraPos, direction);
|
|
284
|
-
rayDistance = max(dot(worldPos - origin, direction), 0.0);
|
|
285
|
-
}
|
|
286
|
-
return apply_fog(viewParams.fog, FogRay(origin, direction, rayDistance), vec4<f32>(color, alpha));
|
|
287
|
-
}
|
|
288
|
-
|
|
289
273
|
// linear_to_srgb: per-channel IEC 61966-2-1 transfer function used by the
|
|
290
274
|
// LDR fragment entry to encode linear premultiplied RGB into the bgra8unorm
|
|
291
275
|
// swap-chain storage. The bgra8unorm target is not hardware-sRGB-encoded
|
|
@@ -303,8 +287,7 @@ fn fs_main(in : VsOut) -> @location(0) vec4<f32> {
|
|
|
303
287
|
let rgba = clamp(texel * material.colorTint, vec4<f32>(0.0), vec4<f32>(1.0));
|
|
304
288
|
// Premultiplied alpha: rgb pre-multiplied by alpha for srcFactor='one' /
|
|
305
289
|
// dstFactor='one-minus-src-alpha' blend.
|
|
306
|
-
let
|
|
307
|
-
let premult = vec4<f32>(fogged.rgb * fogged.a, fogged.a);
|
|
290
|
+
let premult = vec4<f32>(rgba.rgb * rgba.a, rgba.a);
|
|
308
291
|
// LDR target is bgra8unorm (blendable per WebGPU spec); hardware does not
|
|
309
292
|
// sRGB-encode bgra8unorm, so apply the transfer function in the shader.
|
|
310
293
|
// Only RGB channels are encoded; alpha is linear throughout the blend.
|
|
@@ -323,15 +306,14 @@ fn fs_main_hdr(in : VsOut) -> @location(0) vec4<f32> {
|
|
|
323
306
|
let texel = sampleMaterialTexture(baseColorTexture, baseColorSampler, in.uv_atlas, material.baseColorTextureCoordinatesMetadata.zw);
|
|
324
307
|
// R6 mitigation: strict clamp 0..1 before premultiplied alpha multiply.
|
|
325
308
|
let rgba = clamp(texel * material.colorTint, vec4<f32>(0.0), vec4<f32>(1.0));
|
|
326
|
-
|
|
327
|
-
return vec4<f32>(fogged.rgb * fogged.a, fogged.a);
|
|
309
|
+
return vec4<f32>(rgba.rgb * rgba.a, rgba.a);
|
|
328
310
|
}
|
|
329
311
|
|
|
330
312
|
struct TemporalVsOut {
|
|
331
313
|
@builtin(position) clip : vec4<f32>,
|
|
332
314
|
@location(0) uvAtlas : vec2<f32>,
|
|
333
|
-
@location(1) @interpolate(
|
|
334
|
-
@location(2) @interpolate(
|
|
315
|
+
@location(1) @interpolate(perspective) currentClip : vec4<f32>,
|
|
316
|
+
@location(2) @interpolate(perspective) previousClip : vec4<f32>,
|
|
335
317
|
};
|
|
336
318
|
|
|
337
319
|
@vertex
|
|
@@ -359,7 +341,7 @@ fn fs_temporal(in : TemporalVsOut) -> @location(0) vec4<f32> {
|
|
|
359
341
|
if (alpha <= 0.0) {
|
|
360
342
|
discard;
|
|
361
343
|
}
|
|
362
|
-
return
|
|
344
|
+
return packSceneTemporalV1(in.currentClip, in.previousClip, view.temporalProjection, 1.0);
|
|
363
345
|
}
|
|
364
346
|
// sprite is unlit; do NOT read the directional-light fields of the View
|
|
365
347
|
// UBO -- same SSOT hard rule as the header comment above; R5 mitigation.
|
package/src/taa-resolve.wgsl
CHANGED
|
@@ -1,7 +1,28 @@
|
|
|
1
1
|
#define_import_path forgeax_view::taa_resolve
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
// Keep the fragment input shape explicit for compilers that tree-shake imported
|
|
4
|
+
// type declarations before resolving the vertex entry point.
|
|
5
|
+
struct FullscreenOutput {
|
|
6
|
+
@builtin(position) position : vec4<f32>,
|
|
7
|
+
@location(0) uv : vec2<f32>,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
fn fullscreen_triangle(vertex_index : u32) -> FullscreenOutput {
|
|
11
|
+
var x : f32 = -1.0;
|
|
12
|
+
var y : f32 = -1.0;
|
|
13
|
+
if (vertex_index == 1u) {
|
|
14
|
+
x = 3.0;
|
|
15
|
+
}
|
|
16
|
+
if (vertex_index == 2u) {
|
|
17
|
+
y = 3.0;
|
|
18
|
+
}
|
|
19
|
+
let u : f32 = (x + 1.0) * 0.5;
|
|
20
|
+
let v : f32 = 1.0 - (y + 1.0) * 0.5;
|
|
21
|
+
var out : FullscreenOutput;
|
|
22
|
+
out.position = vec4<f32>(x, y, 0.0, 1.0);
|
|
23
|
+
out.uv = vec2<f32>(u, v);
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
5
26
|
|
|
6
27
|
struct TaaResolveParams {
|
|
7
28
|
currentJitterUv : vec2<f32>,
|
|
@@ -14,12 +35,15 @@ struct TaaResolveOutput {
|
|
|
14
35
|
@location(1) temporal : vec4<f32>,
|
|
15
36
|
};
|
|
16
37
|
|
|
17
|
-
@group(
|
|
18
|
-
@group(
|
|
19
|
-
@group(
|
|
20
|
-
@group(
|
|
21
|
-
@group(
|
|
22
|
-
@group(
|
|
38
|
+
@group(1) @binding(0) var currentColor : texture_2d<f32>;
|
|
39
|
+
@group(1) @binding(1) var currentSampler : sampler;
|
|
40
|
+
@group(1) @binding(2) var historyColor : texture_2d<f32>;
|
|
41
|
+
@group(1) @binding(3) var historySampler : sampler;
|
|
42
|
+
@group(1) @binding(4) var historyTemporal : texture_2d<f32>;
|
|
43
|
+
@group(1) @binding(5) var temporalSampler : sampler;
|
|
44
|
+
@group(1) @binding(6) var currentTemporal : texture_2d<f32>;
|
|
45
|
+
@group(1) @binding(7) var currentTemporalSampler : sampler;
|
|
46
|
+
@group(1) @binding(8) var<uniform> params : TaaResolveParams;
|
|
23
47
|
|
|
24
48
|
fn rgbToYCoCg(rgb : vec3<f32>) -> vec3<f32> {
|
|
25
49
|
return vec3<f32>(
|
|
@@ -37,93 +61,64 @@ fn luminance(rgb : vec3<f32>) -> f32 {
|
|
|
37
61
|
return dot(rgb, vec3<f32>(0.2126, 0.7152, 0.0722));
|
|
38
62
|
}
|
|
39
63
|
|
|
40
|
-
fn
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
// packed motion is already unjittered currentUv - previousUv.
|
|
44
|
-
return textureSampleLevel(currentColor, historySampler, uv + params.currentJitterUv, 0.0);
|
|
64
|
+
fn closestCurrentTemporal(pixel : vec2<i32>, dimensions : vec2<i32>) -> vec4<f32> {
|
|
65
|
+
let clamped = clamp(pixel, vec2<i32>(0), dimensions - vec2<i32>(1));
|
|
66
|
+
return textureLoad(currentTemporal, clamped, 0);
|
|
45
67
|
}
|
|
46
68
|
|
|
47
|
-
fn
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
let candidate = textureLoad(currentTemporal, samplePixel, 0);
|
|
54
|
-
if candidate.z >= 0.0 && candidate.z < closestDepth {
|
|
55
|
-
closest = candidate;
|
|
56
|
-
closestDepth = candidate.z;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
return closest;
|
|
69
|
+
fn neighborhoodSquareMean(uv : vec2<f32>, texel : vec2<f32>) -> vec3<f32> {
|
|
70
|
+
let north = textureSampleLevel(currentColor, currentSampler, uv + vec2<f32>(0.0, texel.y), 0.0).rgb;
|
|
71
|
+
let south = textureSampleLevel(currentColor, currentSampler, uv - vec2<f32>(0.0, texel.y), 0.0).rgb;
|
|
72
|
+
let east = textureSampleLevel(currentColor, currentSampler, uv + vec2<f32>(texel.x, 0.0), 0.0).rgb;
|
|
73
|
+
let west = textureSampleLevel(currentColor, currentSampler, uv - vec2<f32>(texel.x, 0.0), 0.0).rgb;
|
|
74
|
+
return (north + south + east + west) * 0.25;
|
|
61
75
|
}
|
|
62
76
|
|
|
63
77
|
@vertex
|
|
64
|
-
fn
|
|
78
|
+
fn vs_main(@builtin(vertex_index) vertexIndex : u32) -> FullscreenOutput {
|
|
65
79
|
return fullscreen_triangle(vertexIndex);
|
|
66
80
|
}
|
|
67
81
|
|
|
68
|
-
@fragment
|
|
69
82
|
fn fs_taa_resolve(in : FullscreenOutput) -> TaaResolveOutput {
|
|
70
|
-
let dimensions = vec2<i32>(textureDimensions(currentColor));
|
|
71
|
-
let
|
|
72
|
-
let current =
|
|
83
|
+
let dimensions = vec2<i32>(textureDimensions(currentColor, 0));
|
|
84
|
+
let texel = 1.0 / vec2<f32>(dimensions);
|
|
85
|
+
let current = textureSampleLevel(currentColor, currentSampler, in.uv + params.currentJitterUv, 0.0);
|
|
86
|
+
let pixel = vec2<i32>(in.uv * vec2<f32>(dimensions));
|
|
73
87
|
let temporal = closestCurrentTemporal(pixel, dimensions);
|
|
74
|
-
|
|
75
|
-
var output : TaaResolveOutput;
|
|
76
|
-
output.temporal = temporal;
|
|
77
|
-
output.color = current;
|
|
78
|
-
|
|
79
88
|
let historyUv = in.uv - temporal.xy;
|
|
80
|
-
let historyInBounds = all(historyUv >= vec2<f32>(0.0)) && all(historyUv
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
let previousTemporal = textureSampleLevel(historyTemporal, historySampler, historyUv, 0.0);
|
|
89
|
+
let historyInBounds = all(historyUv >= vec2<f32>(0.0)) && all(historyUv <= vec2<f32>(1.0));
|
|
90
|
+
let history = textureSampleLevel(historyColor, historySampler, historyUv, 0.0);
|
|
91
|
+
let previousTemporal = textureSampleLevel(historyTemporal, temporalSampler, historyUv, 0.0);
|
|
86
92
|
let depthDelta = abs(previousTemporal.z - temporal.z);
|
|
87
|
-
let depthThreshold =
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
neighborhoodMin = min(neighborhoodMin, sampleValue);
|
|
101
|
-
neighborhoodMax = max(neighborhoodMax, sampleValue);
|
|
102
|
-
neighborhoodMean += sampleValue;
|
|
103
|
-
neighborhoodSquareMean += sampleValue * sampleValue;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
neighborhoodMean /= 9.0;
|
|
107
|
-
neighborhoodSquareMean /= 9.0;
|
|
108
|
-
let sigma = sqrt(max(neighborhoodSquareMean - neighborhoodMean * neighborhoodMean, vec3<f32>(0.0)));
|
|
109
|
-
let clipMin = max(neighborhoodMin, neighborhoodMean - sigma * 1.25);
|
|
110
|
-
let clipMax = min(neighborhoodMax, neighborhoodMean + sigma * 1.25);
|
|
111
|
-
|
|
112
|
-
let sampledHistory = textureSampleLevel(historyColor, historySampler, historyUv, 0.0);
|
|
113
|
-
let clippedHistoryRgb = yCoCgToRgb(clamp(rgbToYCoCg(sampledHistory.rgb), clipMin, clipMax));
|
|
114
|
-
let velocityFactor = 1.0 - clamp(length(temporal.xy) * 64.0, 0.0, 1.0);
|
|
93
|
+
let depthThreshold = 0.0025 + temporal.z * 0.01;
|
|
94
|
+
let rejected =
|
|
95
|
+
params.historyValid == 0u ||
|
|
96
|
+
!historyInBounds ||
|
|
97
|
+
temporal.z < 0.0 ||
|
|
98
|
+
previousTemporal.z < 0.0 ||
|
|
99
|
+
depthDelta > depthThreshold;
|
|
100
|
+
let neighborhood = neighborhoodSquareMean(in.uv, texel);
|
|
101
|
+
let neighborhoodDelta = abs(luminance(current.rgb) - luminance(neighborhood));
|
|
102
|
+
let clipPadding = vec3<f32>(0.5);
|
|
103
|
+
let clipMin = rgbToYCoCg(min(current.rgb, neighborhood - vec3<f32>(neighborhoodDelta) - clipPadding));
|
|
104
|
+
let clipMax = rgbToYCoCg(max(current.rgb, neighborhood + vec3<f32>(neighborhoodDelta) + clipPadding));
|
|
105
|
+
let clippedHistoryRgb = yCoCgToRgb(clamp(rgbToYCoCg(history.rgb), clipMin, clipMax));
|
|
115
106
|
let reactiveFactor = 1.0 - clamp(temporal.w, 0.0, 1.0);
|
|
107
|
+
let velocityFactor = 1.0 - clamp(length(temporal.xy) * 64.0, 0.0, 1.0);
|
|
116
108
|
let depthFactor = 1.0 - clamp(depthDelta / depthThreshold, 0.0, 1.0);
|
|
117
|
-
let
|
|
118
|
-
let historyLuma = luminance(clippedHistoryRgb);
|
|
119
|
-
let lumaDelta = abs(historyLuma - currentLuma);
|
|
120
|
-
let unbiasedLumaDelta = clamp(lumaDelta / max(max(currentLuma, historyLuma), 0.2), 0.0, 1.0);
|
|
109
|
+
let unbiasedLumaDelta = clamp(abs(luminance(current.rgb) - luminance(history.rgb)), 0.0, 1.0);
|
|
121
110
|
let lumaFactor = (1.0 - unbiasedLumaDelta) * (1.0 - unbiasedLumaDelta);
|
|
122
111
|
let fixedPresetWeight = mix(0.88, 0.97, lumaFactor);
|
|
123
|
-
let
|
|
124
|
-
let
|
|
125
|
-
let
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
112
|
+
let progressiveWeight = select(fixedPresetWeight, 0.0, params.temporalFrameIndex < 8u);
|
|
113
|
+
let historyWeight = progressiveWeight * reactiveFactor * velocityFactor * depthFactor;
|
|
114
|
+
let resolved = select(mix(current.rgb, clippedHistoryRgb, historyWeight), current.rgb, rejected);
|
|
115
|
+
return TaaResolveOutput(
|
|
116
|
+
vec4<f32>(resolved, current.a),
|
|
117
|
+
temporal,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
@fragment
|
|
122
|
+
fn fs_main(in : FullscreenOutput) -> TaaResolveOutput {
|
|
123
|
+
return fs_taa_resolve(in);
|
|
129
124
|
}
|
package/src/tonemap.wgsl
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#define_import_path forgeax_view::tonemap
|
|
2
2
|
|
|
3
|
-
// @forgeax/engine-shader - Three r184
|
|
3
|
+
// @forgeax/engine-shader - Three r184 Output Transform module.
|
|
4
4
|
//
|
|
5
5
|
// The public modes and formulas are the Three.js r184 oracle. The numeric
|
|
6
6
|
// values are the binding contract mirrored by TONEMAP_SHADER_MODE in tonemap.ts.
|
|
@@ -153,8 +153,7 @@ fn tonemapNeutral(color : vec3<f32>) -> vec3<f32> {
|
|
|
153
153
|
|
|
154
154
|
@fragment
|
|
155
155
|
fn fs_main(in : FullscreenOutput) -> @location(0) vec4<f32> {
|
|
156
|
-
//
|
|
157
|
-
// linearLdrColorDomain: the output pass emits the display-target domain.
|
|
156
|
+
// Output Transform consumes linear HDR and emits display-encoded color.
|
|
158
157
|
let source = textureSample(hdr, samp, in.uv);
|
|
159
158
|
let sample : vec3<f32> = source.rgb;
|
|
160
159
|
let exposed : vec3<f32> = sample * params.exposure;
|
|
@@ -167,11 +166,10 @@ fn fs_main(in : FullscreenOutput) -> @location(0) vec4<f32> {
|
|
|
167
166
|
case 5u: { mapped = tonemapAgx(exposed); }
|
|
168
167
|
case 6u: { mapped = tonemapNeutral(exposed); }
|
|
169
168
|
case 7u: { mapped = tonemapReinhard(exposed); }
|
|
170
|
-
// Mode 0
|
|
171
|
-
|
|
172
|
-
default: { mapped = linearToSrgbOetf(exposed); }
|
|
169
|
+
// Mode 0 skips the tone curve but still performs the output conversion.
|
|
170
|
+
default: { mapped = sample; }
|
|
173
171
|
}
|
|
174
|
-
//
|
|
175
|
-
|
|
176
|
-
return vec4<f32>(
|
|
172
|
+
// Every public mode reaches exactly one shared sRGB OETF.
|
|
173
|
+
let encoded = linearToSrgbOetf(mapped);
|
|
174
|
+
return vec4<f32>(encoded, source.a);
|
|
177
175
|
}
|
package/src/unlit.wgsl
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#define_import_path forgeax_material::unlit
|
|
2
|
-
#import forgeax_view::common::{View,
|
|
3
|
-
#import
|
|
2
|
+
#import forgeax_view::common::{View, Mesh, InstanceData, view, meshes, instances, sampleMaterialTextureLinear}
|
|
3
|
+
#import forgeax_scene_temporal::{packSceneTemporalV1}
|
|
4
4
|
|
|
5
5
|
#pragma variant_axis STORAGE_BUFFER_AVAILABLE
|
|
6
6
|
#pragma variant_axis VERTEX_COLOR_AVAILABLE
|
|
@@ -100,22 +100,6 @@ fn materialVertexColor(in : VsOut) -> vec4<f32> {
|
|
|
100
100
|
#endif
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
fn applySceneFog(viewParams : View, color : vec3<f32>, alpha : f32, worldPos : vec3<f32>) -> vec4<f32> {
|
|
104
|
-
var origin = viewParams.cameraPos;
|
|
105
|
-
var direction = normalize(worldPos - origin);
|
|
106
|
-
var rayDistance = length(worldPos - origin);
|
|
107
|
-
if (viewParams.temporalProjection.z >= 0.5) {
|
|
108
|
-
let nearH = viewParams.inverseViewProj * vec4<f32>(0.0, 0.0, 0.0, 1.0);
|
|
109
|
-
let farH = viewParams.inverseViewProj * vec4<f32>(0.0, 0.0, 1.0, 1.0);
|
|
110
|
-
let nearPoint = nearH.xyz / nearH.w;
|
|
111
|
-
let farPoint = farH.xyz / farH.w;
|
|
112
|
-
direction = normalize(farPoint - nearPoint);
|
|
113
|
-
origin = worldPos - direction * dot(worldPos - viewParams.cameraPos, direction);
|
|
114
|
-
rayDistance = max(dot(worldPos - origin, direction), 0.0);
|
|
115
|
-
}
|
|
116
|
-
return apply_fog(viewParams.fog, FogRay(origin, direction, rayDistance), vec4<f32>(color, alpha));
|
|
117
|
-
}
|
|
118
|
-
|
|
119
103
|
@fragment
|
|
120
104
|
fn fs_main(in : VsOut) -> @location(0) vec4<f32> {
|
|
121
105
|
let texSample = sampleMaterialTextureLinear(baseColorTexture, baseColorSampler, in.uv, material.baseColorTextureCoordinatesMetadata.zw);
|
|
@@ -124,19 +108,14 @@ fn fs_main(in : VsOut) -> @location(0) vec4<f32> {
|
|
|
124
108
|
if (material.alphaCutoff > 0.0 && alpha < material.alphaCutoff) {
|
|
125
109
|
discard;
|
|
126
110
|
}
|
|
127
|
-
return
|
|
128
|
-
view,
|
|
129
|
-
material.baseColor.rgb * texSample.rgb * vertexColor.rgb,
|
|
130
|
-
alpha,
|
|
131
|
-
in.worldPos,
|
|
132
|
-
);
|
|
111
|
+
return vec4<f32>(material.baseColor.rgb * texSample.rgb * vertexColor.rgb, alpha);
|
|
133
112
|
}
|
|
134
113
|
|
|
135
114
|
struct TemporalVsOut {
|
|
136
115
|
@builtin(position) clip : vec4<f32>,
|
|
137
116
|
@location(0) uv : vec2<f32>,
|
|
138
|
-
@location(1) @interpolate(
|
|
139
|
-
@location(2) @interpolate(
|
|
117
|
+
@location(1) @interpolate(perspective) currentClip : vec4<f32>,
|
|
118
|
+
@location(2) @interpolate(perspective) previousClip : vec4<f32>,
|
|
140
119
|
#ifdef VERTEX_COLOR_AVAILABLE
|
|
141
120
|
@location(14) color : vec4<f32>,
|
|
142
121
|
#endif
|
|
@@ -178,9 +157,9 @@ fn fs_temporal(in : TemporalVsOut) -> @location(0) vec4<f32> {
|
|
|
178
157
|
if (material.alphaCutoff > 0.0 && alpha < material.alphaCutoff) {
|
|
179
158
|
discard;
|
|
180
159
|
}
|
|
181
|
-
var reactive =
|
|
160
|
+
var reactive = 0.0;
|
|
182
161
|
#if STORAGE_BUFFER_AVAILABLE == true
|
|
183
162
|
reactive = meshes[0].temporal.x;
|
|
184
163
|
#endif
|
|
185
|
-
return
|
|
164
|
+
return packSceneTemporalV1(in.currentClip, in.previousClip, view.temporalProjection, reactive);
|
|
186
165
|
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#define_import_path forgeax_view::volume_composite
|
|
2
|
+
// This is a view-UBO projection, not a second matrix source. The fields and
|
|
3
|
+
// padding preserve common::View camera/inverse-projection byte offsets.
|
|
4
|
+
struct VolumeDepthView {
|
|
5
|
+
_prefix : array<vec4<f32>, 6>,
|
|
6
|
+
cameraPos : vec4<f32>,
|
|
7
|
+
_lightViewProjA : mat4x4<f32>,
|
|
8
|
+
inverseViewProj : mat4x4<f32>,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
struct VolumeOutput {
|
|
12
|
+
@builtin(position) position : vec4<f32>,
|
|
13
|
+
@location(0) uv : vec2<f32>,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
@group(0) @binding(0) var resolved_volume : texture_2d<f32>;
|
|
17
|
+
@group(0) @binding(1) var volume_sampler : sampler;
|
|
18
|
+
@group(0) @binding(2) var scene_depth : texture_depth_2d;
|
|
19
|
+
@group(0) @binding(3) var<uniform> volume_view : VolumeDepthView;
|
|
20
|
+
|
|
21
|
+
@vertex
|
|
22
|
+
fn volume_vs(@builtin(vertex_index) index : u32) -> VolumeOutput {
|
|
23
|
+
var positions = array<vec2<f32>, 3>(
|
|
24
|
+
vec2<f32>(-1.0, -1.0),
|
|
25
|
+
vec2<f32>(3.0, -1.0),
|
|
26
|
+
vec2<f32>(-1.0, 3.0),
|
|
27
|
+
);
|
|
28
|
+
var output : VolumeOutput;
|
|
29
|
+
output.position = vec4<f32>(positions[index], 0.0, 1.0);
|
|
30
|
+
output.uv = vec2<f32>(
|
|
31
|
+
positions[index].x * 0.5 + 0.5,
|
|
32
|
+
0.5 - positions[index].y * 0.5,
|
|
33
|
+
);
|
|
34
|
+
return output;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
fn composite_resolved_volume(sample : vec4<f32>) -> vec4<f32> {
|
|
38
|
+
let transmittance = clamp(sample.a, 0.0, 1.0);
|
|
39
|
+
return vec4<f32>(sample.rgb, 1.0 - transmittance);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
fn linear_scene_depth(uv : vec2<f32>) -> f32 {
|
|
43
|
+
let dimensions = max(vec2<f32>(textureDimensions(scene_depth)), vec2<f32>(1.0));
|
|
44
|
+
let pixel = vec2<i32>(clamp(uv * dimensions, vec2<f32>(0.0), dimensions - vec2<f32>(1.0)));
|
|
45
|
+
let device_depth = textureLoad(scene_depth, pixel, 0);
|
|
46
|
+
// WebGPU's framebuffer Y is opposite the view UBO's NDC Y.
|
|
47
|
+
let clip = vec4<f32>(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0, device_depth, 1.0);
|
|
48
|
+
let world = volume_view.inverseViewProj * clip;
|
|
49
|
+
let world_position = world.xyz / max(abs(world.w), 1e-5);
|
|
50
|
+
return distance(world_position, volume_view.cameraPos.xyz);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
fn radiance_luma(sample : vec4<f32>) -> f32 {
|
|
54
|
+
return max(dot(sample.rgb, vec3<f32>(0.2126, 0.7152, 0.0722)), 0.0);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
fn radiance_luma_weight(center : vec4<f32>, neighbor : vec4<f32>) -> f32 {
|
|
58
|
+
let center_luma = radiance_luma(center);
|
|
59
|
+
let neighbor_luma = radiance_luma(neighbor);
|
|
60
|
+
let relative_luma = abs(neighbor_luma - center_luma) /
|
|
61
|
+
max(max(neighbor_luma, center_luma), 1e-4);
|
|
62
|
+
return exp(-relative_luma * 8.0);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
fn edge_aware_resolved_volume(uv : vec2<f32>) -> vec4<f32> {
|
|
66
|
+
let dimensions = max(vec2<f32>(textureDimensions(resolved_volume)), vec2<f32>(1.0));
|
|
67
|
+
let texel = 1.0 / dimensions;
|
|
68
|
+
let center = textureSampleLevel(resolved_volume, volume_sampler, uv, 0.0);
|
|
69
|
+
let center_depth = linear_scene_depth(uv);
|
|
70
|
+
var accumulated = center * 4.0;
|
|
71
|
+
var total_weight = 4.0;
|
|
72
|
+
let offsets = array<vec2<f32>, 8>(
|
|
73
|
+
vec2<f32>(texel.x, 0.0),
|
|
74
|
+
vec2<f32>(-texel.x, 0.0),
|
|
75
|
+
vec2<f32>(0.0, texel.y),
|
|
76
|
+
vec2<f32>(0.0, -texel.y),
|
|
77
|
+
vec2<f32>(texel.x, texel.y),
|
|
78
|
+
vec2<f32>(-texel.x, texel.y),
|
|
79
|
+
vec2<f32>(texel.x, -texel.y),
|
|
80
|
+
vec2<f32>(-texel.x, -texel.y),
|
|
81
|
+
);
|
|
82
|
+
let spatial_weights = array<f32, 8>(2.0, 2.0, 2.0, 2.0, 1.0, 1.0, 1.0, 1.0);
|
|
83
|
+
for (var index = 0u; index < 8u; index += 1u) {
|
|
84
|
+
let neighbor_uv = clamp(uv + offsets[index], vec2<f32>(0.0), vec2<f32>(1.0));
|
|
85
|
+
let neighbor = textureSampleLevel(
|
|
86
|
+
resolved_volume,
|
|
87
|
+
volume_sampler,
|
|
88
|
+
neighbor_uv,
|
|
89
|
+
0.0,
|
|
90
|
+
);
|
|
91
|
+
let neighbor_depth = linear_scene_depth(neighbor_uv);
|
|
92
|
+
// Relative view-depth rejection keeps the silhouette transition local;
|
|
93
|
+
// a fixed world-unit threshold creates halos at far occluders.
|
|
94
|
+
let depth_weight = select(0.0, exp(-abs(neighbor_depth - center_depth) * 0.08),
|
|
95
|
+
abs(neighbor_depth - center_depth) <= max(center_depth * 0.08, 0.25));
|
|
96
|
+
// Transmittance is the available edge signal: retain hard depth/occluder
|
|
97
|
+
// boundaries while reconstructing smooth low-frequency scattering.
|
|
98
|
+
let edge_weight = spatial_weights[index] * depth_weight * exp(-abs(neighbor.a - center.a) * 6.0) *
|
|
99
|
+
radiance_luma_weight(center, neighbor);
|
|
100
|
+
accumulated += neighbor * edge_weight;
|
|
101
|
+
total_weight += edge_weight;
|
|
102
|
+
}
|
|
103
|
+
return accumulated / max(total_weight, 1e-5);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@fragment
|
|
107
|
+
fn volume_fs(input : VolumeOutput) -> @location(0) vec4<f32> {
|
|
108
|
+
// Injection zeroes samples behind the observed scene depth. Sampling the
|
|
109
|
+
// cumulative far slice therefore applies the full ray integral while the
|
|
110
|
+
// depth test remains owned by the injection path.
|
|
111
|
+
let sample = edge_aware_resolved_volume(input.uv);
|
|
112
|
+
return composite_resolved_volume(sample);
|
|
113
|
+
}
|