@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
|
@@ -1,45 +1,25 @@
|
|
|
1
1
|
#define_import_path forgeax_material::standard
|
|
2
|
-
#import forgeax_view::common::{View, Mesh, InstanceData, view, meshes, instances,
|
|
3
|
-
#
|
|
4
|
-
#import forgeax_view::common::{projectorTexture, projectorSampler}
|
|
5
|
-
#endif
|
|
2
|
+
#import forgeax_view::common::{View, Mesh, InstanceData, view, meshes, instances, shadowMap, shadowSampler, sampleMaterialTexture}
|
|
3
|
+
#import forgeax_scene_temporal::{sceneViewZ}
|
|
6
4
|
#import forgeax_pbr::temporal::{projectPbrSceneTemporal}
|
|
7
5
|
#import forgeax_pbr::brdf::{f_schlick, v_smith, d_ggx}
|
|
8
|
-
#import forgeax_pbr::ibl_sampling::{sampleIblDiffuse, sampleIblSpecular}
|
|
6
|
+
#import forgeax_pbr::ibl_sampling::{sampleIblDiffuse, sampleIblSpecular, sampleReflectionProbeSpecular}
|
|
7
|
+
#import forgeax_pbr::lighting_probe::{evaluateProbeDiffuse}
|
|
9
8
|
#import forgeax_pbr::tbn::{decodeTangentSpaceNormalRg, scaleTangentSpaceNormal, applyTBN}
|
|
10
9
|
#import forgeax_pbr::lighting_directional::{evalDirectionalNoShadow, evalDirectionalShadowFactor}
|
|
11
|
-
#import forgeax_pbr::lighting_punctual::{evalPoint, evalSpot, evalSpotShadowed, projectSpotUv}
|
|
12
|
-
#ifdef POINT_SHADOW_AVAILABLE
|
|
13
|
-
#import forgeax_pbr::lighting_punctual::{evalPointShadowed}
|
|
14
|
-
#import forgeax_view::common::{shadowParams}
|
|
15
|
-
#endif
|
|
16
|
-
|
|
17
|
-
fn sampleSpotProjector(lightViewProj : mat4x4<f32>, worldPos : vec3<f32>) -> vec3<f32> {
|
|
18
|
-
#ifdef PROJECTOR_AVAILABLE
|
|
19
|
-
let uv = projectSpotUv(lightViewProj, worldPos);
|
|
20
|
-
if (!(uv.x >= 0.0 && uv.x <= 1.0 && uv.y >= 0.0 && uv.y <= 1.0)) {
|
|
21
|
-
// Three's SpotLight.map is a modulation inside the projected light
|
|
22
|
-
// frustum, not a second cone mask. Outside the map the original spot
|
|
23
|
-
// radiance remains intact; the cone/visibility terms already own whether
|
|
24
|
-
// the light reaches the fragment.
|
|
25
|
-
return vec3<f32>(1.0);
|
|
26
|
-
}
|
|
27
|
-
return textureSampleLevel(projectorTexture, projectorSampler, uv, 0.0).rgb;
|
|
28
|
-
#else
|
|
29
|
-
// Keep the analytic SpotLight contribution intact on adapters whose
|
|
30
|
-
// fragment-stage sampled-texture budget cannot carry the optional cookie.
|
|
31
|
-
return vec3<f32>(1.0);
|
|
32
|
-
#endif
|
|
33
|
-
}
|
|
34
10
|
#ifdef CLUSTER_FORWARD_AVAILABLE
|
|
35
|
-
#import
|
|
11
|
+
#import forgeax_standard::cluster::{evaluateStandardClusterLights, get_ssao_intensity}
|
|
36
12
|
#endif
|
|
37
13
|
|
|
38
14
|
#pragma variant_axis STORAGE_BUFFER_AVAILABLE
|
|
39
15
|
#pragma variant_axis CLUSTER_FORWARD_AVAILABLE
|
|
40
16
|
#pragma variant_axis VERTEX_COLOR_AVAILABLE
|
|
17
|
+
#pragma variant_axis PROBE_BLEND_AVAILABLE
|
|
18
|
+
#pragma variant_axis EXTENDED_LIGHTING_AVAILABLE
|
|
41
19
|
#pragma variant_axis TRANSMISSION_AVAILABLE
|
|
20
|
+
#pragma variant_axis DIRECTIONAL_PCSS_AVAILABLE
|
|
42
21
|
#pragma variant_axis PROJECTOR_AVAILABLE
|
|
22
|
+
#pragma variant_axis REFLECTION_FALLBACK_AVAILABLE
|
|
43
23
|
|
|
44
24
|
// @forgeax/engine-shader - default-standard-pbr.wgsl
|
|
45
25
|
// (feat-20260523-shader-template-instance-split M5 / T04).
|
|
@@ -235,6 +215,12 @@ struct SkylightUniforms {
|
|
|
235
215
|
@group(1) @binding(22) var brdfLutSampler : sampler;
|
|
236
216
|
@group(1) @binding(23) var<uniform> skylight : SkylightUniforms;
|
|
237
217
|
|
|
218
|
+
#ifdef PROBE_BLEND_AVAILABLE
|
|
219
|
+
// Optional consumer lane. The host must provide the retained 160B record
|
|
220
|
+
// buffer and matching BGL before enabling this capability.
|
|
221
|
+
@group(3) @binding(1) var<storage, read> probeBlendRecords : array<vec4<f32>>;
|
|
222
|
+
#endif
|
|
223
|
+
|
|
238
224
|
// feat-20260612-hdrp-ssao M7 (round 2) D-B + D-C, scope-amend-webgl2-ubo:
|
|
239
225
|
// SSAO sampling lives on the HDRP unified BGL @group(2) alongside the
|
|
240
226
|
// cluster bindings (binding 7 = ssao texture, binding 8 = sampler). The
|
|
@@ -274,7 +260,11 @@ struct VsOut {
|
|
|
274
260
|
@location(1) worldNormal : vec3<f32>,
|
|
275
261
|
@location(2) uv : vec2<f32>,
|
|
276
262
|
@location(3) worldTangent : vec4<f32>,
|
|
277
|
-
|
|
263
|
+
#ifdef TRANSMISSION_AVAILABLE
|
|
264
|
+
// Keep mesh and instance storage vertex-only; transmission receives the
|
|
265
|
+
// transform basis from the vertex stage below.
|
|
266
|
+
@location(4) @interpolate(flat) transmissionBasis0 : vec4<f32>,
|
|
267
|
+
#endif
|
|
278
268
|
// feat-city-glb multi-UV tiling: second UV set inter-stage varying. Uses the
|
|
279
269
|
// previously-vacant @location(5) so @location(6)/(7) stay byte-stable with
|
|
280
270
|
// the prior layout (CSM M5/w19).
|
|
@@ -288,7 +278,7 @@ struct VsOut {
|
|
|
288
278
|
#ifdef VERTEX_COLOR_AVAILABLE
|
|
289
279
|
@location(14) color : vec4<f32>,
|
|
290
280
|
#endif
|
|
291
|
-
@location(6) ndc :
|
|
281
|
+
@location(6) ndc : vec4<f32>, // NDC for HDRP cluster lookup; .w = transmission basis tail
|
|
292
282
|
// feat-20260609-hdrp-cluster-fragment-ggx M4.5-followup: view-space z is
|
|
293
283
|
// needed by ndc_position_to_cluster (slice index uses log-z mapping that
|
|
294
284
|
// takes negative view_z, NOT NDC z which is [0,1]). Earlier `in.ndc.z`
|
|
@@ -297,6 +287,9 @@ struct VsOut {
|
|
|
297
287
|
// hot zone -- received zero light. Forward view_z explicitly. M5 / w19:
|
|
298
288
|
// also feeds CSM cascade selection in evalDirectional.
|
|
299
289
|
@location(7) viewZ : f32,
|
|
290
|
+
#ifdef TRANSMISSION_AVAILABLE
|
|
291
|
+
@location(15) @interpolate(flat) transmissionBasis1 : vec4<f32>,
|
|
292
|
+
#endif
|
|
300
293
|
};
|
|
301
294
|
|
|
302
295
|
fn pick_channel(rgba : vec4<f32>, channelIndex : u32) -> f32 {
|
|
@@ -310,18 +303,12 @@ fn pick_channel(rgba : vec4<f32>, channelIndex : u32) -> f32 {
|
|
|
310
303
|
}
|
|
311
304
|
}
|
|
312
305
|
|
|
313
|
-
//
|
|
314
|
-
//
|
|
315
|
-
//
|
|
316
|
-
//
|
|
317
|
-
// / evalSpot share evalPunctualBody (GGX specular + Lambertian diffuse +
|
|
318
|
-
// KHR_lights_punctual quartic range attenuation;
|
|
319
|
-
// feat-20260519-light-casters-point-spot-pbr M4 / w22 byte-equivalent).
|
|
320
|
-
// Charter P4: spot light is a thin cone-multiplier on top of the punctual
|
|
321
|
-
// body, point light is the body unchanged -- no magic-value collapse.
|
|
306
|
+
// Directional lighting lives in forgeax_pbr::lighting_directional. Point,
|
|
307
|
+
// Spot, and Rect local lights are evaluated by the Standard Cluster module
|
|
308
|
+
// below from one indexed light_data corpus; no per-kind fixed-slot loop is
|
|
309
|
+
// present in this material shader.
|
|
322
310
|
|
|
323
|
-
|
|
324
|
-
fn vs_main(in : VsIn, @builtin(instance_index) idx : u32) -> VsOut {
|
|
311
|
+
fn vs_main_impl(in : VsIn, meshIndex : u32, instanceIndex : u32) -> VsOut {
|
|
325
312
|
// feat-20260604-instances-per-instance-transform-shader-group3-bin M1 / w4:
|
|
326
313
|
// Entity world from meshes[0] — the @group(2) dynamic-offset window has
|
|
327
314
|
// already been aimed at this entity's slot (render-system-record.ts:2996
|
|
@@ -329,8 +316,8 @@ fn vs_main(in : VsIn, @builtin(instance_index) idx : u32) -> VsOut {
|
|
|
329
316
|
// local from instances[idx] — @group(3) is a flat per-instance buffer indexed
|
|
330
317
|
// directly by instance_index (firstInstance=0, render-system-record.ts:2898).
|
|
331
318
|
// Combine: entity_world * per_instance_local.
|
|
332
|
-
let instanceLocal = instances[
|
|
333
|
-
let entityWorld = meshes[
|
|
319
|
+
let instanceLocal = instances[instanceIndex].localFromInstance;
|
|
320
|
+
let entityWorld = meshes[meshIndex].worldFromLocal;
|
|
334
321
|
let localToWorld = entityWorld * instanceLocal;
|
|
335
322
|
let world = localToWorld * vec4<f32>(in.pos, 1.0);
|
|
336
323
|
var out : VsOut;
|
|
@@ -358,6 +345,20 @@ fn vs_main(in : VsIn, @builtin(instance_index) idx : u32) -> VsOut {
|
|
|
358
345
|
// (w=0); .w handedness preserved for bitangent reconstruction in fragment.
|
|
359
346
|
let worldTangentXyz = normalize((entityWorld * instanceLocal * vec4<f32>(in.tangent.xyz, 0.0)).xyz);
|
|
360
347
|
out.worldTangent = vec4<f32>(worldTangentXyz, in.tangent.w);
|
|
348
|
+
#ifdef TRANSMISSION_AVAILABLE
|
|
349
|
+
out.transmissionBasis0 = vec4<f32>(
|
|
350
|
+
localToWorld[0].x,
|
|
351
|
+
localToWorld[0].y,
|
|
352
|
+
localToWorld[0].z,
|
|
353
|
+
localToWorld[1].x,
|
|
354
|
+
);
|
|
355
|
+
out.transmissionBasis1 = vec4<f32>(
|
|
356
|
+
localToWorld[1].y,
|
|
357
|
+
localToWorld[1].z,
|
|
358
|
+
localToWorld[2].x,
|
|
359
|
+
localToWorld[2].y,
|
|
360
|
+
);
|
|
361
|
+
#endif
|
|
361
362
|
out.uv = in.uv;
|
|
362
363
|
out.uv1 = in.uv1;
|
|
363
364
|
out.uv2 = in.uv2;
|
|
@@ -369,7 +370,6 @@ fn vs_main(in : VsIn, @builtin(instance_index) idx : u32) -> VsOut {
|
|
|
369
370
|
#ifdef VERTEX_COLOR_AVAILABLE
|
|
370
371
|
out.color = in.color;
|
|
371
372
|
#endif
|
|
372
|
-
out.instanceIdx = idx;
|
|
373
373
|
// feat-20260613-csm-cascaded-shadow-maps M5 / w19: the per-fragment
|
|
374
374
|
// light-space position varying is gone; evalDirectional computes
|
|
375
375
|
// per-cascade lightViewProj * worldPos in the fragment stage from
|
|
@@ -377,15 +377,30 @@ fn vs_main(in : VsIn, @builtin(instance_index) idx : u32) -> VsOut {
|
|
|
377
377
|
// NDC for HDRP cluster lookup (feat-20260609-hdrp-cluster-fragment-ggx M2 / w10).
|
|
378
378
|
// Perspective divide on clip-space position; ndc.z retains depth-buffer value.
|
|
379
379
|
let clipPos = out.clip;
|
|
380
|
-
out.ndc =
|
|
380
|
+
out.ndc = vec4(clipPos.xy / clipPos.w, clipPos.z / clipPos.w, localToWorld[2].z);
|
|
381
381
|
// Standard WebGPU/GL perspective projection has clip.w = -view.z, so
|
|
382
382
|
// view_z = -clip.w (negative in front of the camera). M4.5-followup:
|
|
383
383
|
// ndc_position_to_cluster needs this for log-z slice mapping; passing
|
|
384
384
|
// ndc.z (in [0,1]) instead made every fragment land in slice 0.
|
|
385
|
-
|
|
385
|
+
// Keep the cluster depth exactly aligned with the CPU binner for both
|
|
386
|
+
// perspective and off-axis orthographic projections.
|
|
387
|
+
out.viewZ = sceneViewZ(clipPos, view.temporalProjection);
|
|
386
388
|
return out;
|
|
387
389
|
}
|
|
388
390
|
|
|
391
|
+
@vertex
|
|
392
|
+
fn vs_main(in : VsIn, @builtin(instance_index) idx : u32) -> VsOut {
|
|
393
|
+
return vs_main_impl(in, 0u, idx);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
@vertex
|
|
397
|
+
fn vs_scene_index(in : VsIn, @builtin(instance_index) idx : u32) -> VsOut {
|
|
398
|
+
// GPU-driven scene projection packs one Mesh row per visible candidate and
|
|
399
|
+
// uses the identity instance row. The scene index therefore selects the
|
|
400
|
+
// projected Mesh directly while retaining the Standard PBR varyings.
|
|
401
|
+
return vs_main_impl(in, idx, 0u);
|
|
402
|
+
}
|
|
403
|
+
|
|
389
404
|
fn transformedMaterialUv(transform : vec4<f32>, metadata : vec4<f32>, in : VsOut) -> vec2<f32> {
|
|
390
405
|
var source = in.uv;
|
|
391
406
|
if (metadata.x >= 1.0) { source = in.uv1; }
|
|
@@ -447,8 +462,17 @@ fn alphaTest(alpha : f32) {
|
|
|
447
462
|
}
|
|
448
463
|
}
|
|
449
464
|
|
|
465
|
+
struct StandardPbrOutput {
|
|
466
|
+
@location(0) color : vec4<f32>,
|
|
467
|
+
#ifdef REFLECTION_FALLBACK_AVAILABLE
|
|
468
|
+
// Linear HDR environment contribution from this same Standard BRDF callsite.
|
|
469
|
+
// The detached producer names this second target S_fallback.
|
|
470
|
+
@location(1) reflectionFallback : vec4<f32>,
|
|
471
|
+
#endif
|
|
472
|
+
};
|
|
473
|
+
|
|
450
474
|
@fragment
|
|
451
|
-
fn fs_main(in : VsOut) ->
|
|
475
|
+
fn fs_main(in : VsOut) -> StandardPbrOutput {
|
|
452
476
|
let baseUv = transformedMaterialUv(material.baseColorTextureCoordinatesTransform, material.baseColorTextureCoordinatesMetadata, in);
|
|
453
477
|
let baseSample = sampleMaterialTexture(baseColorTexture, baseColorSampler, baseUv, material.baseColorTextureCoordinatesMetadata.zw);
|
|
454
478
|
let vertexColor = materialVertexColor(in);
|
|
@@ -561,7 +585,7 @@ fn fs_main(in : VsOut) -> @location(0) vec4<f32> {
|
|
|
561
585
|
// feat-20260520-directional-light-shadow-mapping +
|
|
562
586
|
// feat-20260518-pbr-direct-lighting-mvp Finding 4):
|
|
563
587
|
//
|
|
564
|
-
// color = ambient(IBL) + directional(shadowed) +
|
|
588
|
+
// color = ambient(IBL) + directional(shadowed) + clustered local lights
|
|
565
589
|
//
|
|
566
590
|
// When the host Skylight bind group provides default-zero resources the IBL
|
|
567
591
|
// helpers sample to vec3(0) and ambient = 0, so the shader falls through to
|
|
@@ -577,22 +601,33 @@ fn fs_main(in : VsOut) -> @location(0) vec4<f32> {
|
|
|
577
601
|
// split-sum mip lookup, so we re-derive the post-shader-clamp roughness
|
|
578
602
|
// here (matches sampleIblSpecular's `mip = roughness * 4.0` expectation).
|
|
579
603
|
//
|
|
580
|
-
//
|
|
581
|
-
//
|
|
582
|
-
//
|
|
583
|
-
// forgeax_pbr::brdf) run inside each evalDirectional / evalPoint / evalSpot
|
|
584
|
-
// so all three light types share the same microfacet specular + Lambertian
|
|
585
|
-
// diffuse form. The directional path additionally projects worldPos
|
|
586
|
-
// through the per-cascade light-space matrix in the fragment stage for
|
|
587
|
-
// shadow-map PCF lookup (CSM, feat-20260613).
|
|
604
|
+
// Directional lighting is evaluated here. All local lights (Point, Spot,
|
|
605
|
+
// Rect) are evaluated by the Standard Cluster module below, which reads
|
|
606
|
+
// one indexed light_data corpus and never performs a fixed-slot loop.
|
|
588
607
|
let kD = (vec3<f32>(1.0) - f_schlick(max(dot(n, v), 0.0), f0)) * (1.0 - metallic);
|
|
589
608
|
let iblRoughness = clamp(max(finiteScalar(material.roughness, 0.5), 0.04) * roughnessTex, 0.04, 1.0);
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
609
|
+
var irradiance = vec3<f32>(0.0);
|
|
610
|
+
var specularIbl = vec3<f32>(0.0);
|
|
611
|
+
if (skylight.intensity < 0.0) {
|
|
612
|
+
// A negative intensity is the renderer-owned probe sentinel. The producer
|
|
613
|
+
// stores -(probeIntensity + 1), so intensity=0 remains distinguishable
|
|
614
|
+
// from the ordinary Skylight zero payload. The four color/rotation lanes
|
|
615
|
+
// carry probe center and half extents in this mode; Skylight resources
|
|
616
|
+
// remain unchanged and therefore preserve the ABI.
|
|
617
|
+
specularIbl = sampleReflectionProbeSpecular(
|
|
618
|
+
n, v, iblRoughness, f0, in.worldPos,
|
|
619
|
+
vec3<f32>(skylight.colorR, skylight.colorG, skylight.colorB),
|
|
620
|
+
skylight.rotation.xyz, vec4<f32>(0.0, 0.0, 0.0, 1.0),
|
|
621
|
+
prefilterMap, prefilterSampler, brdfLut, brdfLutSampler,
|
|
622
|
+
);
|
|
623
|
+
} else {
|
|
624
|
+
irradiance = sampleIblDiffuse(n, skylight.rotation, irradianceMap, irradianceSampler);
|
|
625
|
+
specularIbl = sampleIblSpecular(
|
|
626
|
+
n, v, iblRoughness, f0,
|
|
627
|
+
skylight.rotation,
|
|
628
|
+
prefilterMap, prefilterSampler, brdfLut, brdfLutSampler,
|
|
629
|
+
);
|
|
630
|
+
}
|
|
596
631
|
let occlusionUv = transformedMaterialUv(material.occlusionTextureCoordinatesTransform, material.occlusionTextureCoordinatesMetadata, in);
|
|
597
632
|
let aoSample = sampleMaterialTexture(occlusionTexture, occlusionSampler, occlusionUv, material.occlusionTextureCoordinatesMetadata.zw);
|
|
598
633
|
let ao = mix(1.0, aoSample.r, material.occlusionStrength);
|
|
@@ -600,16 +635,67 @@ fn fs_main(in : VsOut) -> @location(0) vec4<f32> {
|
|
|
600
635
|
// CLUSTER_FORWARD_AVAILABLE branch below can `ambient *=` the SSAO
|
|
601
636
|
// factor. The non-HDRP path leaves ambient untouched.
|
|
602
637
|
let skyColor = vec3<f32>(skylight.colorR, skylight.colorG, skylight.colorB);
|
|
638
|
+
let skyFactor = skyColor * skylight.intensity;
|
|
639
|
+
var reflectionFallback = specularIbl * (vec3<f32>(1.0) - coatF);
|
|
603
640
|
var ambient = (kD * irradiance * diffuseAlbedo + specularIbl) * (vec3<f32>(1.0) - coatF);
|
|
641
|
+
#ifdef PROBE_BLEND_AVAILABLE
|
|
642
|
+
// group(3) is the per-object bind group; record zero is the object selected
|
|
643
|
+
// by this draw, independent of instance count.
|
|
644
|
+
let probeShPreblend = array<vec4<f32>, 9>(
|
|
645
|
+
probeBlendRecords[1], probeBlendRecords[2], probeBlendRecords[3],
|
|
646
|
+
probeBlendRecords[4], probeBlendRecords[5], probeBlendRecords[6],
|
|
647
|
+
probeBlendRecords[7], probeBlendRecords[8], probeBlendRecords[9],
|
|
648
|
+
);
|
|
649
|
+
let probeLocalBlendFraction = probeBlendRecords[0].z;
|
|
650
|
+
let probeDiffuseK = kD / max(vec3<f32>(1.0 - metallic), vec3<f32>(0.0001));
|
|
651
|
+
let probeDiffuse = evaluateProbeDiffuse(probeShPreblend, probeLocalBlendFraction, n, irradiance * skyFactor, probeDiffuseK, albedo, metallic);
|
|
652
|
+
ambient = (probeDiffuse + specularIbl * skyFactor) * (vec3<f32>(1.0) - coatF);
|
|
653
|
+
#endif
|
|
604
654
|
if (material.clearcoat != 0.0) {
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
);
|
|
610
|
-
|
|
655
|
+
// Keep the clearcoat lobe on the same selected environment as the base
|
|
656
|
+
// specular lobe. Probe mode reuses the packed center/extents lanes in the
|
|
657
|
+
// per-probe uniform; routing it through global Skylight IBL would make the
|
|
658
|
+
// fallback a mixed-source projection and break c=1 source equivalence.
|
|
659
|
+
var clearcoatIbl = vec3<f32>(0.0);
|
|
660
|
+
if (skylight.intensity < 0.0) {
|
|
661
|
+
clearcoatIbl = sampleReflectionProbeSpecular(
|
|
662
|
+
n, v, coatRoughness, vec3<f32>(0.04), in.worldPos,
|
|
663
|
+
vec3<f32>(skylight.colorR, skylight.colorG, skylight.colorB),
|
|
664
|
+
skylight.rotation.xyz, vec4<f32>(0.0, 0.0, 0.0, 1.0),
|
|
665
|
+
prefilterMap, prefilterSampler, brdfLut, brdfLutSampler,
|
|
666
|
+
);
|
|
667
|
+
} else {
|
|
668
|
+
clearcoatIbl = sampleIblSpecular(
|
|
669
|
+
n, v, coatRoughness, vec3<f32>(0.04),
|
|
670
|
+
skylight.rotation,
|
|
671
|
+
prefilterMap, prefilterSampler, brdfLut, brdfLutSampler,
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
let clearcoatContribution = clearcoatIbl * material.clearcoat;
|
|
675
|
+
#ifdef PROBE_BLEND_AVAILABLE
|
|
676
|
+
if (skylight.intensity < 0.0) {
|
|
677
|
+
ambient = ambient + clearcoatContribution;
|
|
678
|
+
} else {
|
|
679
|
+
ambient = ambient + clearcoatIbl * material.clearcoat * skyFactor;
|
|
680
|
+
}
|
|
681
|
+
#else
|
|
682
|
+
ambient = ambient + clearcoatContribution;
|
|
683
|
+
#endif
|
|
684
|
+
reflectionFallback = reflectionFallback + clearcoatContribution;
|
|
611
685
|
}
|
|
612
|
-
|
|
686
|
+
#ifdef PROBE_BLEND_AVAILABLE
|
|
687
|
+
ambient = ambient * ao;
|
|
688
|
+
#else
|
|
689
|
+
if (skylight.intensity < 0.0) {
|
|
690
|
+
let environmentScale = max(-skylight.intensity - 1.0, 0.0) * ao;
|
|
691
|
+
ambient = ambient * environmentScale;
|
|
692
|
+
reflectionFallback = reflectionFallback * environmentScale;
|
|
693
|
+
} else {
|
|
694
|
+
let environmentScale = skyColor * skylight.intensity * ao;
|
|
695
|
+
ambient = ambient * environmentScale;
|
|
696
|
+
reflectionFallback = reflectionFallback * environmentScale;
|
|
697
|
+
}
|
|
698
|
+
#endif
|
|
613
699
|
#ifdef CLUSTER_FORWARD_AVAILABLE
|
|
614
700
|
// feat-20260612-hdrp-ssao M2 round-1 + M7 round-2 (plan-strategy D-7 + D-B + D-C):
|
|
615
701
|
// SSAO ambient synthesis. Reads the half-res R8 `ssaoBlurredTexture` from
|
|
@@ -625,7 +711,9 @@ fn fs_main(in : VsOut) -> @location(0) vec4<f32> {
|
|
|
625
711
|
let ssaoFactor = textureSample(ssaoBlurredTexture, ssaoBlurredSampler, ssaoUv).r;
|
|
626
712
|
// scope-amend-webgl2-ubo: intensity packed into cluster_uniform.near_far_log.w.
|
|
627
713
|
let ssaoIntensity = get_ssao_intensity();
|
|
714
|
+
let ssaoScale = mix(1.0, ssaoFactor * ao, ssaoIntensity);
|
|
628
715
|
ambient *= mix(1.0, ssaoFactor * ao, ssaoIntensity);
|
|
716
|
+
reflectionFallback *= ssaoScale;
|
|
629
717
|
#endif
|
|
630
718
|
var color = ambient;
|
|
631
719
|
#ifdef TRANSMISSION_AVAILABLE
|
|
@@ -636,10 +724,17 @@ fn fs_main(in : VsOut) -> @location(0) vec4<f32> {
|
|
|
636
724
|
// the fragment stage preserves that contract and keeps thickness in the
|
|
637
725
|
// glTF unit-scale mesh space before measuring the refracted segment in world
|
|
638
726
|
// metres.
|
|
639
|
-
let
|
|
640
|
-
let
|
|
641
|
-
|
|
642
|
-
|
|
727
|
+
let localToWorld0 = in.transmissionBasis0.xyz;
|
|
728
|
+
let localToWorld1 = vec3<f32>(
|
|
729
|
+
in.transmissionBasis0.w,
|
|
730
|
+
in.transmissionBasis1.x,
|
|
731
|
+
in.transmissionBasis1.y,
|
|
732
|
+
);
|
|
733
|
+
let localToWorld2 = vec3<f32>(
|
|
734
|
+
in.transmissionBasis1.z,
|
|
735
|
+
in.transmissionBasis1.w,
|
|
736
|
+
in.ndc.w,
|
|
737
|
+
);
|
|
643
738
|
let inverseCofactor0 = cross(localToWorld1, localToWorld2);
|
|
644
739
|
let inverseCofactor1 = cross(localToWorld2, localToWorld0);
|
|
645
740
|
let inverseCofactor2 = cross(localToWorld0, localToWorld1);
|
|
@@ -723,127 +818,24 @@ fn fs_main(in : VsOut) -> @location(0) vec4<f32> {
|
|
|
723
818
|
#ifdef CLUSTER_FORWARD_AVAILABLE
|
|
724
819
|
// NDC from vertex shader (perspective-divided clip-space, interpolated).
|
|
725
820
|
// view_z: NDC depth for cluster Z-slice lookup.
|
|
726
|
-
color = color +
|
|
821
|
+
color = color + evaluateStandardClusterLights(
|
|
822
|
+
in.ndc.xyz, in.viewZ, in.worldPos, n, v, diffuseAlbedo, metallic, a, f0, false,
|
|
823
|
+
);
|
|
727
824
|
if (material.clearcoat != 0.0) {
|
|
728
|
-
color = color + material.clearcoat *
|
|
729
|
-
in.ndc, in.viewZ, in.worldPos, n, v, vec3<f32>(0.0), 1.0, coatAlpha, vec3<f32>(0.04),
|
|
825
|
+
color = color + material.clearcoat * evaluateStandardClusterLights(
|
|
826
|
+
in.ndc.xyz, in.viewZ, in.worldPos, n, v, vec3<f32>(0.0), 1.0, coatAlpha, vec3<f32>(0.04), false,
|
|
730
827
|
);
|
|
731
828
|
}
|
|
732
|
-
#else
|
|
733
|
-
let pointCount = pointLightsBuffer.count;
|
|
734
|
-
for (var i: u32 = 0u; i < pointCount; i = i + 1u) {
|
|
735
|
-
let p = pointLightsBuffer.slots[i];
|
|
736
|
-
#ifdef POINT_SHADOW_AVAILABLE
|
|
737
|
-
// feat-20260612-point-light-shadows-urp-hdrp Round-2 F-1: gate between
|
|
738
|
-
// shadowed and unshadowed evaluation on the host-assigned
|
|
739
|
-
// shadowAtlasLayer (>= 0 means PointLightShadow companion exists; -1
|
|
740
|
-
// sentinel means no shadow). shadowParams[layer] = (near, far,
|
|
741
|
-
// 1/(far-near), 0) is bound at @group(0) @binding(6) (shadowParams
|
|
742
|
-
// declared in common.wgsl).
|
|
743
|
-
if (p.shadowAtlasLayer >= 0) {
|
|
744
|
-
let lane = shadowParams[p.shadowAtlasLayer];
|
|
745
|
-
color = color + evalPointShadowed(
|
|
746
|
-
p.position, p.colorTimesIntensity, p.invRangeSquared,
|
|
747
|
-
in.worldPos, n, v, diffuseAlbedo, metallic, a, f0,
|
|
748
|
-
p.shadowAtlasLayer, lane.x, lane.y, 0.005, 0.05,
|
|
749
|
-
);
|
|
750
|
-
if (material.clearcoat != 0.0) {
|
|
751
|
-
color = color + material.clearcoat * evalPointShadowed(
|
|
752
|
-
p.position, p.colorTimesIntensity, p.invRangeSquared,
|
|
753
|
-
in.worldPos, n, v, vec3<f32>(0.0), 1.0, coatAlpha, vec3<f32>(0.04),
|
|
754
|
-
p.shadowAtlasLayer, lane.x, lane.y, 0.005, 0.05,
|
|
755
|
-
);
|
|
756
|
-
}
|
|
757
|
-
} else {
|
|
758
|
-
color = color + evalPoint(
|
|
759
|
-
p.position, p.colorTimesIntensity, p.invRangeSquared,
|
|
760
|
-
in.worldPos, n, v, diffuseAlbedo, metallic, a, f0,
|
|
761
|
-
);
|
|
762
|
-
if (material.clearcoat != 0.0) {
|
|
763
|
-
color = color + material.clearcoat * evalPoint(
|
|
764
|
-
p.position, p.colorTimesIntensity, p.invRangeSquared,
|
|
765
|
-
in.worldPos, n, v, vec3<f32>(0.0), 1.0, coatAlpha, vec3<f32>(0.04),
|
|
766
|
-
);
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
#else
|
|
770
|
-
color = color + evalPoint(
|
|
771
|
-
p.position, p.colorTimesIntensity, p.invRangeSquared,
|
|
772
|
-
in.worldPos, n, v, diffuseAlbedo, metallic, a, f0,
|
|
773
|
-
);
|
|
774
|
-
if (material.clearcoat != 0.0) {
|
|
775
|
-
color = color + material.clearcoat * evalPoint(
|
|
776
|
-
p.position, p.colorTimesIntensity, p.invRangeSquared,
|
|
777
|
-
in.worldPos, n, v, vec3<f32>(0.0), 1.0, coatAlpha, vec3<f32>(0.04),
|
|
778
|
-
);
|
|
779
|
-
}
|
|
780
|
-
#endif
|
|
781
|
-
}
|
|
782
|
-
let spotCount = spotLightsBuffer.count;
|
|
783
|
-
// Surface and volume consume the same projectSpotUv / projectorRevision
|
|
784
|
-
// tuple; an accepted projector is sampled with textureSampleLevel.
|
|
785
|
-
const PROJECTOR_ONLY_TILE : i32 = -2;
|
|
786
|
-
for (var i: u32 = 0u; i < spotCount; i = i + 1u) {
|
|
787
|
-
let s = spotLightsBuffer.slots[i];
|
|
788
|
-
// feat-20260625-spot-light-shadow-mapping M3 / w15 (plan-strategy D-1
|
|
789
|
-
// fragment side + D-3 + D-4): gate on the host-assigned shadowAtlasTile
|
|
790
|
-
// (>= 0 means a spot atlas tile was allocated; -1 sentinel means no
|
|
791
|
-
// shadow / clipped / direction-degenerate). The shadowed path reads the
|
|
792
|
-
// per-spot perspective matrix from the View UBO `view.spotLightViewProj`
|
|
793
|
-
// array (lane N = shadowAtlasTile N; folded from the standalone binding 9
|
|
794
|
-
// into the View UBO in feat-20260625 w25 for WebGL2 uniform-buffer budget);
|
|
795
|
-
// the unshadowed path stays on evalSpot. Receiver controls come from the
|
|
796
|
-
// same packed SpotLight snapshot used by the volume path.
|
|
797
|
-
if (s.shadowAtlasTile >= 0) {
|
|
798
|
-
let projector = sampleSpotProjector(view.spotLightViewProj[s.shadowAtlasTile], in.worldPos);
|
|
799
|
-
color = color + evalSpotShadowed(
|
|
800
|
-
s.position, s.direction, s.colorTimesIntensity,
|
|
801
|
-
s.cosInner, s.cosOuter, s.invRangeSquared,
|
|
802
|
-
in.worldPos, n, v, albedo, metallic, a, f0,
|
|
803
|
-
view.spotLightViewProj[s.shadowAtlasTile], s.shadowAtlasTile, s.depthBias, s.normalBias, s.pcfKernelSize, s.shadowIntensity,
|
|
804
|
-
) * projector;
|
|
805
|
-
if (material.clearcoat != 0.0) {
|
|
806
|
-
color = color + material.clearcoat * evalSpotShadowed(
|
|
807
|
-
s.position, s.direction, s.colorTimesIntensity,
|
|
808
|
-
s.cosInner, s.cosOuter, s.invRangeSquared,
|
|
809
|
-
in.worldPos, n, v, vec3<f32>(0.0), 1.0, coatAlpha, vec3<f32>(0.04),
|
|
810
|
-
view.spotLightViewProj[s.shadowAtlasTile], s.shadowAtlasTile, s.depthBias, s.normalBias, s.pcfKernelSize, s.shadowIntensity,
|
|
811
|
-
) * projector;
|
|
812
|
-
}
|
|
813
|
-
} else if (s.shadowAtlasTile == PROJECTOR_ONLY_TILE) {
|
|
814
|
-
let projector = sampleSpotProjector(view.spotLightViewProj[0], in.worldPos);
|
|
815
|
-
color = color + evalSpot(
|
|
816
|
-
s.position, s.direction, s.colorTimesIntensity,
|
|
817
|
-
s.cosInner, s.cosOuter, s.invRangeSquared,
|
|
818
|
-
in.worldPos, n, v, albedo, metallic, a, f0,
|
|
819
|
-
) * projector;
|
|
820
|
-
if (material.clearcoat != 0.0) {
|
|
821
|
-
color = color + material.clearcoat * evalSpot(
|
|
822
|
-
s.position, s.direction, s.colorTimesIntensity,
|
|
823
|
-
s.cosInner, s.cosOuter, s.invRangeSquared,
|
|
824
|
-
in.worldPos, n, v, vec3<f32>(0.0), 1.0, coatAlpha, vec3<f32>(0.04),
|
|
825
|
-
) * projector;
|
|
826
|
-
}
|
|
827
|
-
} else {
|
|
828
|
-
color = color + evalSpot(
|
|
829
|
-
s.position, s.direction, s.colorTimesIntensity,
|
|
830
|
-
s.cosInner, s.cosOuter, s.invRangeSquared,
|
|
831
|
-
in.worldPos, n, v, diffuseAlbedo, metallic, a, f0,
|
|
832
|
-
);
|
|
833
|
-
if (material.clearcoat != 0.0) {
|
|
834
|
-
color = color + material.clearcoat * evalSpot(
|
|
835
|
-
s.position, s.direction, s.colorTimesIntensity,
|
|
836
|
-
s.cosInner, s.cosOuter, s.invRangeSquared,
|
|
837
|
-
in.worldPos, n, v, vec3<f32>(0.0), 1.0, coatAlpha, vec3<f32>(0.04),
|
|
838
|
-
);
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
829
|
#endif // CLUSTER_FORWARD_AVAILABLE
|
|
843
830
|
let emissiveUv = transformedMaterialUv(material.emissiveTextureCoordinatesTransform, material.emissiveTextureCoordinatesMetadata, in);
|
|
844
831
|
let emissiveSample = sampleMaterialTexture(emissiveTexture, emissiveSampler, emissiveUv, material.emissiveTextureCoordinatesMetadata.zw).rgb;
|
|
845
832
|
color = color + material.emissive * material.emissiveIntensity * emissiveSample;
|
|
846
|
-
|
|
833
|
+
var output : StandardPbrOutput;
|
|
834
|
+
output.color = vec4<f32>(color, alpha);
|
|
835
|
+
#ifdef REFLECTION_FALLBACK_AVAILABLE
|
|
836
|
+
output.reflectionFallback = vec4<f32>(reflectionFallback, 1.0);
|
|
837
|
+
#endif
|
|
838
|
+
return output;
|
|
847
839
|
}
|
|
848
840
|
|
|
849
841
|
// ── G-buffer output struct (feat-20260612-hdrp-deferred-shading M2 / w12) ──
|
|
@@ -931,6 +923,7 @@ struct TemporalVsOut {
|
|
|
931
923
|
@location(7) uv7 : vec2<f32>,
|
|
932
924
|
@location(8) @interpolate(perspective) currentClip : vec4<f32>,
|
|
933
925
|
@location(9) @interpolate(perspective) previousClip : vec4<f32>,
|
|
926
|
+
@location(10) @interpolate(flat) reactive : f32,
|
|
934
927
|
#ifdef VERTEX_COLOR_AVAILABLE
|
|
935
928
|
@location(14) color : vec4<f32>,
|
|
936
929
|
#endif
|
|
@@ -941,7 +934,7 @@ fn vs_temporal(in : VsIn, @builtin(instance_index) idx : u32) -> TemporalVsOut {
|
|
|
941
934
|
let currentWorld = meshes[0].worldFromLocal *
|
|
942
935
|
instances[idx].localFromInstance * vec4<f32>(in.pos, 1.0);
|
|
943
936
|
var previousWorld = currentWorld;
|
|
944
|
-
#
|
|
937
|
+
#ifdef STORAGE_BUFFER_AVAILABLE
|
|
945
938
|
previousWorld = meshes[0].previousWorldFromLocal *
|
|
946
939
|
instances[idx].previousLocalFromInstance * vec4<f32>(in.pos, 1.0);
|
|
947
940
|
#endif
|
|
@@ -949,6 +942,11 @@ fn vs_temporal(in : VsIn, @builtin(instance_index) idx : u32) -> TemporalVsOut {
|
|
|
949
942
|
out.currentClip = view.temporalCurrentViewProj * currentWorld;
|
|
950
943
|
out.clip = out.currentClip;
|
|
951
944
|
out.previousClip = view.temporalPreviousViewProj * previousWorld;
|
|
945
|
+
#if STORAGE_BUFFER_AVAILABLE == true
|
|
946
|
+
out.reactive = meshes[0].temporal.x;
|
|
947
|
+
#else
|
|
948
|
+
out.reactive = 1.0;
|
|
949
|
+
#endif
|
|
952
950
|
out.uv = in.uv;
|
|
953
951
|
out.uv1 = in.uv1;
|
|
954
952
|
out.uv2 = in.uv2;
|
|
@@ -974,7 +972,7 @@ fn temporalVertexAlpha(in : TemporalVsOut) -> f32 {
|
|
|
974
972
|
@fragment
|
|
975
973
|
fn fs_temporal(in : TemporalVsOut) -> @location(0) vec4<f32> {
|
|
976
974
|
var reactive = 0.0;
|
|
977
|
-
#
|
|
975
|
+
#ifdef STORAGE_BUFFER_AVAILABLE
|
|
978
976
|
reactive = meshes[0].temporal.x;
|
|
979
977
|
#endif
|
|
980
978
|
return projectPbrSceneTemporal(
|
|
@@ -987,7 +985,7 @@ fn fs_temporal(in : TemporalVsOut) -> @location(0) vec4<f32> {
|
|
|
987
985
|
in.currentClip,
|
|
988
986
|
in.previousClip,
|
|
989
987
|
view.temporalProjection,
|
|
990
|
-
reactive,
|
|
988
|
+
in.reactive,
|
|
991
989
|
in.uv, in.uv1, in.uv2, in.uv3,
|
|
992
990
|
in.uv4, in.uv5, in.uv6, in.uv7,
|
|
993
991
|
);
|
package/src/fxaa.wgsl
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
// FXAA 3.11 fragment shader -- faithful port of the canonical Simon
|
|
8
8
|
// Rodriguez reference (same lineage as Bevy's WGSL port), adapted to
|
|
9
9
|
// forgeax naga_oil conventions. Hardcoded "High" quality preset: 12-step
|
|
10
|
-
// edge search with compile-time step multipliers (
|
|
11
|
-
// branches).
|
|
10
|
+
// edge search with compile-time step multipliers (the only UBO field is the
|
|
11
|
+
// final-output dither switch; there are no quality #ifdef branches).
|
|
12
12
|
//
|
|
13
13
|
// Vertex stage: imports the SSOT fullscreen_triangle() from
|
|
14
14
|
// forgeax_view::common (single large triangle, research Finding 1).
|
|
@@ -35,10 +35,13 @@
|
|
|
35
35
|
// Bindings (group 0):
|
|
36
36
|
// @binding(0) screenTexture : texture_2d<f32> -- sampled display-encoded input
|
|
37
37
|
// @binding(1) samp : sampler -- linear filterable sampler
|
|
38
|
+
// @binding(2) params : FxaaParams -- final-output policy
|
|
38
39
|
// display-encoded: FXAA samples and writes display-encoded values; the Output
|
|
39
|
-
// Transform owns the sole linear-to-sRGB conversion before this pass.
|
|
40
|
+
// Transform owns the sole linear-to-sRGB conversion before this pass. FXAA is
|
|
41
|
+
// the final Standard surface writer, so the shared bounded dither is applied
|
|
42
|
+
// here, immediately before the surface's UNORM8 conversion.
|
|
40
43
|
|
|
41
|
-
#import forgeax_view::common::FullscreenOutput
|
|
44
|
+
#import forgeax_view::common::{FullscreenOutput, ditherUnorm8}
|
|
42
45
|
#import forgeax_view::common::fullscreen_triangle
|
|
43
46
|
|
|
44
47
|
const EDGE_THRESHOLD_MIN: f32 = 0.0312;
|
|
@@ -49,6 +52,18 @@ const ITERATIONS: i32 = 12;
|
|
|
49
52
|
@group(0) @binding(0) var screenTexture: texture_2d<f32>;
|
|
50
53
|
@group(0) @binding(1) var samp: sampler;
|
|
51
54
|
|
|
55
|
+
struct FxaaParams {
|
|
56
|
+
ditherEnabled: f32,
|
|
57
|
+
// Keep the uniform block at one 16-byte slot. WebGL2/GLES uniform-buffer
|
|
58
|
+
// validation uses the std140-aligned block size, while the host uploads a
|
|
59
|
+
// four-f32 (16-byte) payload.
|
|
60
|
+
_pad0: f32,
|
|
61
|
+
_pad1: f32,
|
|
62
|
+
_pad2: f32,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
@group(0) @binding(2) var<uniform> params: FxaaParams;
|
|
66
|
+
|
|
52
67
|
fn rgb2luma(rgb: vec3<f32>) -> f32 {
|
|
53
68
|
return sqrt(dot(rgb, vec3<f32>(0.299, 0.587, 0.114)));
|
|
54
69
|
}
|
|
@@ -105,7 +120,10 @@ fn fs_main(in: FullscreenOutput) -> @location(0) vec4<f32> {
|
|
|
105
120
|
|
|
106
121
|
// Not on an edge (or in a near-uniform region): pass the centre through.
|
|
107
122
|
if lumaRange < max(EDGE_THRESHOLD_MIN, lumaMax * EDGE_THRESHOLD_MAX) {
|
|
108
|
-
return vec4<f32>(
|
|
123
|
+
return vec4<f32>(
|
|
124
|
+
select(centerColor, ditherUnorm8(centerColor, in.position.xy), params.ditherEnabled > 0.5),
|
|
125
|
+
1.0,
|
|
126
|
+
);
|
|
109
127
|
}
|
|
110
128
|
|
|
111
129
|
// Lumas of the 4 corners.
|
|
@@ -254,5 +272,8 @@ fn fs_main(in: FullscreenOutput) -> @location(0) vec4<f32> {
|
|
|
254
272
|
}
|
|
255
273
|
|
|
256
274
|
let finalColor = sampleColor(finalUv);
|
|
257
|
-
return vec4<f32>(
|
|
275
|
+
return vec4<f32>(
|
|
276
|
+
select(finalColor, ditherUnorm8(finalColor, in.position.xy), params.ditherEnabled > 0.5),
|
|
277
|
+
1.0,
|
|
278
|
+
);
|
|
258
279
|
}
|
package/src/ibl-irradiance.wgsl
CHANGED
|
@@ -83,7 +83,10 @@ fn irradianceConvolve_fs(in0: CubemapVsOut) -> @location(0) vec4<f32> {
|
|
|
83
83
|
phi += IRRADIANCE_SAMPLE_DELTA;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
//
|
|
86
|
+
// IRRADIANCE_PAYLOAD_E_OVER_PI: PI * the uniform theta/phi sample average
|
|
87
|
+
// produces the Lambert-normalized diffuse radiance E / PI. Runtime sampling
|
|
88
|
+
// consumes this payload directly; applying another Lambert divide would
|
|
89
|
+
// darken diffuse IBL by PI.
|
|
87
90
|
irradiance = PI * irradiance / max(nrSamples, 1.0);
|
|
88
91
|
return vec4<f32>(irradiance, 1.0);
|
|
89
92
|
}
|