@aethervtt/shard-render 0.0.1 → 0.2.0
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/dist/ablation.d.ts +74 -0
- package/dist/ablation.js +117 -0
- package/dist/assets.d.ts +3 -0
- package/dist/assets.js +43 -2
- package/dist/atmosphere-nodes.d.ts +9 -0
- package/dist/atmosphere-nodes.js +157 -30
- package/dist/atmosphere-plugin.js +8 -2
- package/dist/atmosphere-shaders.js +17 -5
- package/dist/baseline/atmosphere.d.ts +16 -0
- package/dist/baseline/atmosphere.js +301 -0
- package/dist/baseline/data-texture.d.ts +36 -0
- package/dist/baseline/data-texture.js +181 -0
- package/dist/baseline/environment.d.ts +14 -0
- package/dist/baseline/environment.js +368 -0
- package/dist/baseline/exposure.d.ts +25 -0
- package/dist/baseline/exposure.js +133 -0
- package/dist/baseline/lights.d.ts +33 -0
- package/dist/baseline/lights.js +133 -0
- package/dist/baseline/textures.d.ts +97 -0
- package/dist/baseline/textures.js +203 -0
- package/dist/camera.d.ts +2 -0
- package/dist/camera.js +4 -0
- package/dist/crowd.d.ts +21 -0
- package/dist/crowd.js +121 -0
- package/dist/culling-shaders.js +21 -2
- package/dist/culling.d.ts +11 -2
- package/dist/culling.js +44 -18
- package/dist/cutaway-plugin.d.ts +48 -0
- package/dist/cutaway-plugin.js +364 -0
- package/dist/cutaway.d.ts +27 -0
- package/dist/cutaway.js +49 -0
- package/dist/data-store.d.ts +69 -0
- package/dist/data-store.js +105 -0
- package/dist/debug-shaders.js +17 -3
- package/dist/debug-views.js +4 -2
- package/dist/decals-plugin.d.ts +5 -0
- package/dist/decals-plugin.js +54 -0
- package/dist/decals.d.ts +69 -0
- package/dist/decals.js +326 -0
- package/dist/deferred-shaders.js +2 -0
- package/dist/deferred.js +28 -4
- package/dist/deform.d.ts +5 -4
- package/dist/deform.js +4 -9
- package/dist/describe-plugin.d.ts +4 -2
- package/dist/describe-plugin.js +75 -3
- package/dist/display-nodes.js +37 -13
- package/dist/dynamic-resolution.d.ts +2 -0
- package/dist/dynamic-resolution.js +6 -1
- package/dist/environment-plugin.js +17 -2
- package/dist/environment-state.d.ts +7 -3
- package/dist/environment-state.js +20 -9
- package/dist/environment.js +7 -1
- package/dist/features.d.ts +49 -0
- package/dist/features.js +106 -0
- package/dist/foliage-budget.d.ts +64 -0
- package/dist/foliage-budget.js +108 -0
- package/dist/foliage-path.d.ts +19 -0
- package/dist/foliage-path.js +7 -0
- package/dist/foliage-shaders.d.ts +11 -0
- package/dist/foliage-shaders.js +504 -0
- package/dist/foliage.d.ts +194 -0
- package/dist/foliage.js +1027 -0
- package/dist/forward.d.ts +38 -2
- package/dist/forward.js +608 -186
- package/dist/fxaa.js +12 -2
- package/dist/gizmos-plugin.js +7 -0
- package/dist/gizmos.d.ts +3 -3
- package/dist/gizmos.js +11 -13
- package/dist/gpu-assets.d.ts +58 -4
- package/dist/gpu-assets.js +191 -8
- package/dist/graph.d.ts +45 -0
- package/dist/graph.js +169 -19
- package/dist/health.d.ts +58 -0
- package/dist/health.js +112 -0
- package/dist/index.d.ts +40 -11
- package/dist/index.js +343 -9
- package/dist/instances.d.ts +119 -8
- package/dist/instances.js +387 -78
- package/dist/interior-plugin.d.ts +121 -0
- package/dist/interior-plugin.js +465 -0
- package/dist/interior.d.ts +24 -0
- package/dist/interior.js +13 -0
- package/dist/layers.d.ts +35 -0
- package/dist/layers.js +49 -0
- package/dist/lens-plugin.d.ts +43 -0
- package/dist/lens-plugin.js +304 -0
- package/dist/lens.d.ts +41 -0
- package/dist/lens.js +99 -0
- package/dist/lights.d.ts +50 -5
- package/dist/lights.js +101 -22
- package/dist/material-pipelines.d.ts +37 -4
- package/dist/material-pipelines.js +102 -5
- package/dist/materials.d.ts +23 -0
- package/dist/materials.js +44 -1
- package/dist/outline-plugin.d.ts +48 -0
- package/dist/outline-plugin.js +529 -0
- package/dist/outline.d.ts +15 -0
- package/dist/outline.js +42 -0
- package/dist/overlay-registry.d.ts +3 -1
- package/dist/overlay-registry.js +3 -1
- package/dist/overlays.js +6 -2
- package/dist/perf-overlay.d.ts +9 -0
- package/dist/perf-overlay.js +125 -0
- package/dist/picking.d.ts +17 -0
- package/dist/picking.js +40 -11
- package/dist/pixel-perfect-plugin.js +10 -2
- package/dist/plugin.d.ts +69 -7
- package/dist/plugin.js +223 -25
- package/dist/post-common.d.ts +22 -2
- package/dist/post-common.js +82 -2
- package/dist/post-nodes.d.ts +7 -1
- package/dist/post-nodes.js +235 -111
- package/dist/post-plugin.js +5 -3
- package/dist/post-scene.d.ts +130 -0
- package/dist/post-scene.js +164 -0
- package/dist/post-shaders.js +85 -62
- package/dist/post.js +11 -1
- package/dist/projection.d.ts +24 -0
- package/dist/projection.js +87 -0
- package/dist/readback.d.ts +1 -0
- package/dist/readback.js +1 -0
- package/dist/render-scale.js +1 -0
- package/dist/ribbons-plugin.d.ts +5 -0
- package/dist/ribbons-plugin.js +59 -0
- package/dist/ribbons.d.ts +44 -0
- package/dist/ribbons.js +288 -0
- package/dist/scenario.d.ts +70 -0
- package/dist/scenario.js +125 -0
- package/dist/screen-effects.d.ts +97 -0
- package/dist/screen-effects.js +192 -0
- package/dist/shaders.d.ts +13 -0
- package/dist/shaders.js +189 -34
- package/dist/shadow-catcher.d.ts +9 -0
- package/dist/shadow-catcher.js +133 -0
- package/dist/shadows.d.ts +27 -2
- package/dist/shadows.js +100 -20
- package/dist/standard-fields.d.ts +1 -0
- package/dist/standard-fields.js +5 -0
- package/dist/standard.js +18 -0
- package/dist/stats.d.ts +45 -1
- package/dist/stats.js +91 -3
- package/dist/surface-model.d.ts +57 -0
- package/dist/surface-model.js +289 -0
- package/dist/surface-shaders.d.ts +3 -0
- package/dist/surface-shaders.js +160 -0
- package/dist/surface.d.ts +40 -0
- package/dist/surface.js +86 -0
- package/dist/target.d.ts +1 -13
- package/dist/target.js +1 -26
- package/dist/testing.d.ts +11 -0
- package/dist/testing.js +39 -1
- package/dist/tier.d.ts +13 -0
- package/dist/tier.js +14 -0
- package/dist/timer.d.ts +18 -1
- package/dist/timer.js +73 -20
- package/dist/variants.d.ts +37 -0
- package/dist/variants.js +104 -0
- package/dist/vfx-draw.d.ts +37 -0
- package/dist/vfx-draw.js +291 -0
- package/dist/vfx-state.d.ts +42 -0
- package/dist/vfx-state.js +81 -0
- package/dist/vfx.d.ts +69 -0
- package/dist/vfx.js +103 -0
- package/dist/view-visibility-plugin.d.ts +61 -0
- package/dist/view-visibility-plugin.js +267 -0
- package/dist/view-visibility.d.ts +28 -0
- package/dist/view-visibility.js +46 -0
- package/dist/view.d.ts +29 -1
- package/dist/view.js +57 -34
- package/package.json +23 -11
package/dist/shaders.js
CHANGED
|
@@ -26,7 +26,13 @@ const ViewUniform = defineComponent("render/ViewUniform", {
|
|
|
26
26
|
* Per directional light (column i): the atmosphere's transmittance (rgb) between the camera and
|
|
27
27
|
* that sun, applied to its light (spec 0044). All ones without an atmosphere.
|
|
28
28
|
*/
|
|
29
|
-
sunTransmittance: t.mat4
|
|
29
|
+
sunTransmittance: t.mat4,
|
|
30
|
+
/**
|
|
31
|
+
* Screen scale (0057): render pixels per CSS pixel (x); world units per render pixel at one
|
|
32
|
+
* unit of view depth, or everywhere when orthographic (y); 1 when orthographic (z). A CSS-pixel
|
|
33
|
+
* width w at view depth d is w · x · y · d world units (w · x · y orthographic).
|
|
34
|
+
*/
|
|
35
|
+
pixelScale: t.vec4
|
|
30
36
|
});
|
|
31
37
|
const viewLayout = wgslLayout(ViewUniform);
|
|
32
38
|
const materialLayout = wgslLayout(StandardMaterial);
|
|
@@ -77,9 +83,9 @@ const FLAG_VISIBLE: u32 = 1u;
|
|
|
77
83
|
const FLAG_CASTER: u32 = 2u;
|
|
78
84
|
const FLAG_RECEIVER: u32 = 4u;
|
|
79
85
|
|
|
80
|
-
@group(2) @binding(0) var<storage, read> instances: array<Instance>;
|
|
86
|
+
@data @group(2) @binding(0) var<storage, read> instances: array<Instance>;
|
|
81
87
|
/** The view's culled slots; a draw's instance_index indexes this. */
|
|
82
|
-
@group(2) @binding(1) var<storage, read> visible: array<u32>;
|
|
88
|
+
@data @group(2) @binding(1) var<storage, read> visible: array<u32>;
|
|
83
89
|
|
|
84
90
|
/** Visible-list entries: slot in the low 28 bits, LOD level in the top 4. */
|
|
85
91
|
const SLOT_MASK: u32 = 0x0fffffffu;
|
|
@@ -98,7 +104,7 @@ fn instance_world(inst: Instance, position: vec3f) -> vec3f {
|
|
|
98
104
|
}
|
|
99
105
|
|
|
100
106
|
/** Last frame's transform of each slot (three affine rows), for motion vectors. */
|
|
101
|
-
@group(2) @binding(2) var<storage, read> previous: array<vec4f>;
|
|
107
|
+
@data @group(2) @binding(2) var<storage, read> previous: array<vec4f>;
|
|
102
108
|
|
|
103
109
|
/** Where an object-space position was last frame, in world space. */
|
|
104
110
|
fn previous_world(instance_index: u32, position: vec3f) -> vec3f {
|
|
@@ -171,9 +177,9 @@ struct Deform {
|
|
|
171
177
|
const FLAG_SKINNED: u32 = 32u;
|
|
172
178
|
const FLAG_MORPH: u32 = 64u;
|
|
173
179
|
|
|
174
|
-
@group(2) @binding(3) var<storage, read> deforms: array<Deform>;
|
|
175
|
-
@group(2) @binding(4) var<storage, read> poses: array<vec4f>;
|
|
176
|
-
@group(2) @binding(5) var<storage, read> deform_data: array<u32>;
|
|
180
|
+
@data @group(2) @binding(3) var<storage, read> deforms: array<Deform>;
|
|
181
|
+
@data @group(2) @binding(4) var<storage, read> poses: array<vec4f>;
|
|
182
|
+
@data @group(2) @binding(5) var<storage, read> deform_data: array<u32>;
|
|
177
183
|
|
|
178
184
|
struct Deformed {
|
|
179
185
|
position: vec3f,
|
|
@@ -360,6 +366,7 @@ import shard::view::view;
|
|
|
360
366
|
import shard::pbr::types::VertexOutput;
|
|
361
367
|
import shard::mesh::mesh_vertex_at;
|
|
362
368
|
import shard::unlit::shading::shade;
|
|
369
|
+
@if(CUTAWAY) import shard::cutaway::cutaway_clip;
|
|
363
370
|
|
|
364
371
|
@vertex fn vs(
|
|
365
372
|
@builtin(instance_index) instance_index: u32,
|
|
@@ -376,9 +383,11 @@ import shard::unlit::shading::shade;
|
|
|
376
383
|
}
|
|
377
384
|
|
|
378
385
|
@fragment fn fs(in: VertexOutput) -> @location(0) vec4f {
|
|
386
|
+
@if(CUTAWAY) cutaway_clip(in.world_position, in.clip.xy, in.flags);
|
|
379
387
|
let c = shade(in);
|
|
380
388
|
@if(MASK) if (c.a < 0.5) { discard; }
|
|
381
|
-
return vec4f(c.rgb * view.exposure,
|
|
389
|
+
@if(OPAQUE) return vec4f(c.rgb * view.exposure, 1.0);
|
|
390
|
+
@if(!OPAQUE) return vec4f(c.rgb * view.exposure, c.a);
|
|
382
391
|
}`,
|
|
383
392
|
"shard::pbr::brdf": `
|
|
384
393
|
const PI: f32 = 3.14159265359;
|
|
@@ -416,8 +425,24 @@ struct Light {
|
|
|
416
425
|
shadow_bias: f32,
|
|
417
426
|
shadow_normal_bias: f32,
|
|
418
427
|
shadow_softness: f32,
|
|
419
|
-
|
|
420
|
-
|
|
428
|
+
/** 0: physical; 1: tabletop (0057). */
|
|
429
|
+
falloff: f32,
|
|
430
|
+
/** Tabletop: full intensity inside this radius. */
|
|
431
|
+
bright: f32,
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Distance attenuation of a point or spot light: physical inverse square windowed to zero at
|
|
436
|
+
* range, or tabletop (0057): full intensity inside bright, linear to zero at range.
|
|
437
|
+
*/
|
|
438
|
+
fn light_falloff(light: Light, d: f32, d2: f32) -> f32 {
|
|
439
|
+
if (light.falloff > 0.5) {
|
|
440
|
+
return clamp((light.range - d) / max(light.range - light.bright, 1e-4), 0.0, 1.0);
|
|
441
|
+
}
|
|
442
|
+
let ratio = d / light.range;
|
|
443
|
+
let r4 = ratio * ratio * ratio * ratio;
|
|
444
|
+
let window = clamp(1.0 - r4, 0.0, 1.0);
|
|
445
|
+
return window * window / d2;
|
|
421
446
|
}
|
|
422
447
|
|
|
423
448
|
struct DirectionalLight {
|
|
@@ -446,10 +471,29 @@ const CLUSTER_Z: u32 = 24u;
|
|
|
446
471
|
const CLUSTER_COUNT: u32 = 3456u;
|
|
447
472
|
const MAX_PER_CLUSTER: u32 = 128u;
|
|
448
473
|
|
|
449
|
-
@group(0) @binding(1) var<storage, read> lights: array<Light>;
|
|
474
|
+
@data(uniform, 128) @group(0) @binding(1) var<storage, read> lights: array<Light>;
|
|
450
475
|
/** counts[CLUSTER_COUNT], then MAX_PER_CLUSTER light indices per cluster. */
|
|
451
|
-
@group(0) @binding(2) var<storage, read> clusters: array<u32>;
|
|
452
|
-
|
|
476
|
+
@if(!BASELINE) @group(0) @binding(2) var<storage, read> clusters: array<u32>;
|
|
477
|
+
/** Baseline (0064): a bit per packed light, 128 per cluster. */
|
|
478
|
+
@if(BASELINE) @data @group(0) @binding(2) var<storage, read> cluster_bits: array<vec4u>;
|
|
479
|
+
@data(uniform) @group(0) @binding(3) var<storage, read> directional: DirectionalLights;
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Baseline (0064): takes the lowest light left in \`mask\` (a cluster's bits) into \`index\`;
|
|
483
|
+
* false once none are left.
|
|
484
|
+
*/
|
|
485
|
+
@if(BASELINE) fn next_light(mask: ptr<function, vec4u>, index: ptr<function, u32>) -> bool {
|
|
486
|
+
for (var w = 0u; w < 4u; w++) {
|
|
487
|
+
let word = (*mask)[w];
|
|
488
|
+
if (word != 0u) {
|
|
489
|
+
let lowest = word & (~word + 1u);
|
|
490
|
+
(*mask)[w] = word ^ lowest;
|
|
491
|
+
*index = w * 32u + u32(round(log2(f32(lowest))));
|
|
492
|
+
return true;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return false;
|
|
496
|
+
}`,
|
|
453
497
|
"shard::pbr::shadows": `
|
|
454
498
|
import shard::color::ign;
|
|
455
499
|
|
|
@@ -473,7 +517,7 @@ struct ShadowData {
|
|
|
473
517
|
spot_params: array<vec4f, 8>,
|
|
474
518
|
}
|
|
475
519
|
|
|
476
|
-
@group(0) @binding(4) var<storage, read> shadows: ShadowData;
|
|
520
|
+
@data(uniform) @group(0) @binding(4) var<storage, read> shadows: ShadowData;
|
|
477
521
|
@group(0) @binding(5) var cascade_maps: texture_depth_2d_array;
|
|
478
522
|
@group(0) @binding(6) var spot_maps: texture_depth_2d_array;
|
|
479
523
|
@group(0) @binding(7) var point_maps: texture_depth_2d_array;
|
|
@@ -576,9 +620,11 @@ fn point_shadow(index: u32, world: vec3f, n: vec3f, bias: f32, normal_bias: f32,
|
|
|
576
620
|
import shard::view::view;
|
|
577
621
|
import shard::pbr::types::PbrInput;
|
|
578
622
|
import shard::pbr::brdf::{ PI, d_ggx, v_smith_ggx_correlated, f_schlick };
|
|
579
|
-
import shard::pbr::lights::{ lights, clusters, directional, LIGHT_SPOT, NO_SHADOW, CLUSTER_X, CLUSTER_Y, CLUSTER_Z, CLUSTER_COUNT, MAX_PER_CLUSTER };
|
|
623
|
+
import shard::pbr::lights::{ lights, clusters, cluster_bits, next_light, directional, light_falloff, LIGHT_SPOT, NO_SHADOW, CLUSTER_X, CLUSTER_Y, CLUSTER_Z, CLUSTER_COUNT, MAX_PER_CLUSTER };
|
|
580
624
|
import shard::pbr::shadows::{ directional_shadow, spot_shadow, point_shadow, cascade_index };
|
|
581
625
|
import shard::pbr::environment::environment_light;
|
|
626
|
+
@if(SKY_VISIBILITY) import shard::interior::interior_ambient;
|
|
627
|
+
@if(BLOCKED_LIGHTS) import shard::interior::{ wall_visibility, interior_receiver };
|
|
582
628
|
|
|
583
629
|
const FLAG_RECEIVER: u32 = 4u;
|
|
584
630
|
|
|
@@ -618,6 +664,26 @@ fn heat(count: u32) -> vec3f {
|
|
|
618
664
|
* Lighting stage: the only place lights are evaluated. Returns scene-referred radiance.
|
|
619
665
|
* Directional lights (with cascaded shadows), then clustered point and spot lights, then ambient.
|
|
620
666
|
*/
|
|
667
|
+
/**
|
|
668
|
+
* SSAO at a full-resolution pixel, from the half-resolution AO and view depth (rg): the four
|
|
669
|
+
* nearest texels, weighted by how well their depth matches this pixel's. Without SSAO a white
|
|
670
|
+
* 1\xD71 texture is bound, which gives 1.
|
|
671
|
+
*/
|
|
672
|
+
fn ssao_at(frag: vec2f, z: f32) -> f32 {
|
|
673
|
+
let half_size = vec2i(textureDimensions(ao_texture));
|
|
674
|
+
let base = vec2i(floor((frag - 1.0) * 0.5));
|
|
675
|
+
var sum = 0.0;
|
|
676
|
+
var weight = 0.0;
|
|
677
|
+
for (var k = 0; k < 4; k++) {
|
|
678
|
+
let q = clamp(base + vec2i(k & 1, k >> 1), vec2i(0), half_size - 1);
|
|
679
|
+
let s = textureLoad(ao_texture, q, 0);
|
|
680
|
+
let w = 1.0 / (1e-3 + abs(s.y - z) / max(z, 1e-3));
|
|
681
|
+
sum += s.x * w;
|
|
682
|
+
weight += w;
|
|
683
|
+
}
|
|
684
|
+
return sum / max(weight, 1e-6);
|
|
685
|
+
}
|
|
686
|
+
|
|
621
687
|
fn apply_lighting(p: PbrInput, world_position: vec3f, frag_coord: vec4f, flags: u32) -> vec3f {
|
|
622
688
|
let n = p.normal;
|
|
623
689
|
let v = normalize(view.cameraPosition - world_position);
|
|
@@ -642,7 +708,39 @@ fn apply_lighting(p: PbrInput, world_position: vec3f, frag_coord: vec4f, flags:
|
|
|
642
708
|
// Point and spot lights from this fragment's cluster: intensity (cd) / d\xB2 \xD7 window.
|
|
643
709
|
let cluster = cluster_of(frag_coord, view_depth);
|
|
644
710
|
var count = 0u;
|
|
645
|
-
if (
|
|
711
|
+
@if(BLOCKED_LIGHTS) let receiver = interior_receiver(world_position, n);
|
|
712
|
+
@if(BASELINE) if (cluster >= 0) {
|
|
713
|
+
// The cluster's lights, lowest first; keep the body in step with the loop below.
|
|
714
|
+
var mask = cluster_bits[cluster];
|
|
715
|
+
var index = 0u;
|
|
716
|
+
while (next_light(&mask, &index)) {
|
|
717
|
+
count++;
|
|
718
|
+
let light = lights[index];
|
|
719
|
+
let to_light = light.position - world_position;
|
|
720
|
+
let d2 = max(dot(to_light, to_light), 1e-4);
|
|
721
|
+
let d = sqrt(d2);
|
|
722
|
+
let l = to_light / d;
|
|
723
|
+
var attenuation = light_falloff(light, d, d2);
|
|
724
|
+
if (light.kind == LIGHT_SPOT) {
|
|
725
|
+
let cd = dot(-l, light.direction);
|
|
726
|
+
let spot = clamp(cd * light.spot_scale + light.spot_offset, 0.0, 1.0);
|
|
727
|
+
attenuation *= spot * spot;
|
|
728
|
+
}
|
|
729
|
+
if (attenuation <= 0.0) { continue; }
|
|
730
|
+
@if(BLOCKED_LIGHTS) attenuation *= wall_visibility(light, receiver);
|
|
731
|
+
if (receives && light.shadow != NO_SHADOW) {
|
|
732
|
+
if (light.kind == LIGHT_SPOT) {
|
|
733
|
+
attenuation *= spot_shadow(light.shadow, world_position, n, light.position, light.shadow_bias, light.shadow_normal_bias, light.shadow_softness, frag_coord.xy);
|
|
734
|
+
} else {
|
|
735
|
+
attenuation *= point_shadow(light.shadow, world_position, n, light.shadow_bias, light.shadow_normal_bias, light.shadow_softness, frag_coord.xy);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
let a_light = clamp(a + light.radius / (2.0 * d), 0.0, 1.0);
|
|
739
|
+
let norm = (a / a_light) * (a / a_light);
|
|
740
|
+
color += brdf(n, v, l, a_light, diffuse_color, f0, norm) * light.color * attenuation;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
@if(!BASELINE) if (cluster >= 0) {
|
|
646
744
|
count = clusters[cluster];
|
|
647
745
|
let base = CLUSTER_COUNT + u32(cluster) * MAX_PER_CLUSTER;
|
|
648
746
|
for (var k = 0u; k < count; k++) {
|
|
@@ -651,16 +749,14 @@ fn apply_lighting(p: PbrInput, world_position: vec3f, frag_coord: vec4f, flags:
|
|
|
651
749
|
let d2 = max(dot(to_light, to_light), 1e-4);
|
|
652
750
|
let d = sqrt(d2);
|
|
653
751
|
let l = to_light / d;
|
|
654
|
-
|
|
655
|
-
let r4 = ratio * ratio * ratio * ratio;
|
|
656
|
-
let window = clamp(1.0 - r4, 0.0, 1.0);
|
|
657
|
-
var attenuation = window * window / d2;
|
|
752
|
+
var attenuation = light_falloff(light, d, d2);
|
|
658
753
|
if (light.kind == LIGHT_SPOT) {
|
|
659
754
|
let cd = dot(-l, light.direction);
|
|
660
755
|
let spot = clamp(cd * light.spot_scale + light.spot_offset, 0.0, 1.0);
|
|
661
756
|
attenuation *= spot * spot;
|
|
662
757
|
}
|
|
663
758
|
if (attenuation <= 0.0) { continue; }
|
|
759
|
+
@if(BLOCKED_LIGHTS) attenuation *= wall_visibility(light, receiver);
|
|
664
760
|
if (receives && light.shadow != NO_SHADOW) {
|
|
665
761
|
if (light.kind == LIGHT_SPOT) {
|
|
666
762
|
attenuation *= spot_shadow(light.shadow, world_position, n, light.position, light.shadow_bias, light.shadow_normal_bias, light.shadow_softness, frag_coord.xy);
|
|
@@ -678,14 +774,13 @@ fn apply_lighting(p: PbrInput, world_position: vec3f, frag_coord: vec4f, flags:
|
|
|
678
774
|
// Ambient: image-based lighting from the environment, or the uniform AmbientLight (cd/m\xB2).
|
|
679
775
|
// SSAO darkens it on opaque surfaces (it measures the depth buffer, which blended ones aren't in).
|
|
680
776
|
var surface = p;
|
|
681
|
-
if (p.alpha >= 0.999) {
|
|
682
|
-
let dims = vec2i(textureDimensions(ao_texture));
|
|
683
|
-
surface.occlusion *= textureLoad(ao_texture, min(vec2i(frag_coord.xy), dims - 1), 0).r;
|
|
684
|
-
}
|
|
777
|
+
if (p.alpha >= 0.999) { surface.occlusion *= ssao_at(frag_coord.xy, view_depth); }
|
|
685
778
|
if (view.envParams.w > 0.5) {
|
|
686
|
-
color += environment_light(surface, n, v);
|
|
779
|
+
@if(!SKY_VISIBILITY) color += environment_light(surface, n, v);
|
|
780
|
+
@if(SKY_VISIBILITY) color += interior_ambient(environment_light(surface, n, v), diffuse_color * surface.occlusion, world_position, n);
|
|
687
781
|
} else {
|
|
688
|
-
color += diffuse_color * view.ambient * surface.occlusion;
|
|
782
|
+
@if(!SKY_VISIBILITY) color += diffuse_color * view.ambient * surface.occlusion;
|
|
783
|
+
@if(SKY_VISIBILITY) color += interior_ambient(diffuse_color * view.ambient * surface.occlusion, diffuse_color * surface.occlusion, world_position, n);
|
|
689
784
|
}
|
|
690
785
|
|
|
691
786
|
let debug = u32(view.clusterParams.w);
|
|
@@ -912,6 +1007,7 @@ import shard::mesh::mesh_vertex_at;
|
|
|
912
1007
|
import shard::pbr::material::{ pbr_input, fragment_output };
|
|
913
1008
|
import shard::pbr::standard::material;
|
|
914
1009
|
import shard::pbr::lighting::apply_lighting;
|
|
1010
|
+
@if(CUTAWAY) import shard::cutaway::cutaway_clip;
|
|
915
1011
|
|
|
916
1012
|
@vertex fn vs(
|
|
917
1013
|
@builtin(instance_index) instance_index: u32,
|
|
@@ -928,12 +1024,15 @@ import shard::pbr::lighting::apply_lighting;
|
|
|
928
1024
|
}
|
|
929
1025
|
|
|
930
1026
|
@fragment fn fs(in: VertexOutput) -> @location(0) vec4f {
|
|
1027
|
+
@if(CUTAWAY) cutaway_clip(in.world_position, in.clip.xy, in.flags);
|
|
931
1028
|
let p = pbr_input(in);
|
|
932
1029
|
if (material.alphaMode == 1u && p.alpha < material.alphaCutoff) { discard; }
|
|
933
1030
|
let color = (apply_lighting(p, in.world_position, in.clip, in.flags) + p.emissive) * view.exposure;
|
|
934
1031
|
// Premultiplied blending expects color \xD7 alpha; additive and alpha blend multiply in hardware.
|
|
935
1032
|
@if(PREMULTIPLY) return fragment_output(vec4f(color * p.alpha, p.alpha));
|
|
936
|
-
|
|
1033
|
+
// Opaque and masked surfaces cover their pixel, whatever their base color's alpha (0052).
|
|
1034
|
+
@if(OPAQUE) return vec4f(fragment_output(vec4f(color, p.alpha)).rgb, 1.0);
|
|
1035
|
+
@if(!PREMULTIPLY && !OPAQUE) return fragment_output(vec4f(color, p.alpha));
|
|
937
1036
|
}`,
|
|
938
1037
|
"shard::post::depth_resolve": `
|
|
939
1038
|
@group(0) @binding(0) var depth_ms: texture_depth_multisampled_2d;
|
|
@@ -950,7 +1049,7 @@ import shard::pbr::types::PbrInput;
|
|
|
950
1049
|
@group(0) @binding(10) var env_lut: texture_2d<f32>;
|
|
951
1050
|
@group(0) @binding(11) var env_sampler: sampler;
|
|
952
1051
|
/** SH9, convolved with the cosine lobe and divided by \u03C0: dot with the basis = irradiance / \u03C0. */
|
|
953
|
-
@group(0) @binding(12) var<storage, read> env_sh: array<vec4f, 9>;
|
|
1052
|
+
@data(uniform) @group(0) @binding(12) var<storage, read> env_sh: array<vec4f, 9>;
|
|
954
1053
|
@group(0) @binding(13) var env_source: texture_cube<f32>;
|
|
955
1054
|
|
|
956
1055
|
const ENV_SPECULAR_MIPS: f32 = 5.0;
|
|
@@ -1001,10 +1100,21 @@ fn environment_light(p: PbrInput, n: vec3f, v: vec3f) -> vec3f {
|
|
|
1001
1100
|
/** The environment's own radiance toward d (the background), in cd/m\xB2. */
|
|
1002
1101
|
fn environment_background(d: vec3f) -> vec3f {
|
|
1003
1102
|
return textureSampleLevel(env_source, env_sampler, env_rotate(d), 0.0).rgb * view.envParams.x;
|
|
1103
|
+
}`,
|
|
1104
|
+
"shard::post::glow": `
|
|
1105
|
+
/**
|
|
1106
|
+
* Bloom's first level over the image: mixed, conserving energy, without a threshold; added with
|
|
1107
|
+
* one. \`glow\`: intensity, the chain's level count, mix (1) or add (0), 0. The tonemap applies it
|
|
1108
|
+
* as it reads the image, and auto exposure meters the same mix.
|
|
1109
|
+
*/
|
|
1110
|
+
fn add_glow(c: vec3f, level0: vec3f, glow: vec4f) -> vec3f {
|
|
1111
|
+
let g = level0 / glow.y;
|
|
1112
|
+
return select(c + g * glow.x, mix(c, g, glow.x), glow.z > 0.5);
|
|
1004
1113
|
}`,
|
|
1005
1114
|
"shard::post::tonemap": `
|
|
1006
1115
|
import shard::color::{ linear_to_srgb, srgb_to_linear, ign, luminance };
|
|
1007
1116
|
import shard::tonemap::tonemap;
|
|
1117
|
+
@if(BLOOM) import shard::post::glow::add_glow;
|
|
1008
1118
|
|
|
1009
1119
|
struct TonemapParams {
|
|
1010
1120
|
/** curve, dither, flags (1 grading, 2 vignette, 4 LUT, 8 sRGB LUT), 0. */
|
|
@@ -1020,12 +1130,16 @@ struct TonemapParams {
|
|
|
1020
1130
|
vignette: vec4f,
|
|
1021
1131
|
/** 1 / width, 1 / height of the view. */
|
|
1022
1132
|
texel: vec4f,
|
|
1133
|
+
/** Bloom: intensity, level count, mix (1) or add (0), 0. */
|
|
1134
|
+
glow: vec4f,
|
|
1023
1135
|
}
|
|
1024
1136
|
|
|
1025
1137
|
@group(0) @binding(0) var hdr: texture_2d<f32>;
|
|
1026
1138
|
@group(0) @binding(1) var<uniform> params: TonemapParams;
|
|
1027
1139
|
@group(0) @binding(2) var lut: texture_2d<f32>;
|
|
1028
1140
|
@group(0) @binding(3) var lut_sampler: sampler;
|
|
1141
|
+
/** Bloom's first level, composited here instead of in a pass of its own. */
|
|
1142
|
+
@if(BLOOM) @group(0) @binding(4) var bloom: texture_2d<f32>;
|
|
1029
1143
|
|
|
1030
1144
|
const LIN_TO_LMS = mat3x3f(
|
|
1031
1145
|
vec3f(3.90405e-1, 7.08416e-2, 2.31082e-2),
|
|
@@ -1063,8 +1177,19 @@ fn apply_lut(c: vec3f) -> vec3f {
|
|
|
1063
1177
|
}
|
|
1064
1178
|
|
|
1065
1179
|
@fragment fn fs(@builtin(position) p: vec4f) -> @location(0) vec4f {
|
|
1066
|
-
|
|
1180
|
+
var texel = textureLoad(hdr, vec2i(p.xy), 0);
|
|
1181
|
+
@if(BLOOM) {
|
|
1182
|
+
let level0 = textureSampleLevel(bloom, lut_sampler, p.xy * params.texel.xy, 0.0).rgb;
|
|
1183
|
+
texel = vec4f(add_glow(texel.rgb, level0, params.glow), texel.a);
|
|
1184
|
+
}
|
|
1067
1185
|
var hdr_color = max(texel.rgb, vec3f(0.0));
|
|
1186
|
+
@if(TRANSPARENT) var alpha = 1.0;
|
|
1187
|
+
@if(TRANSPARENT) {
|
|
1188
|
+
// Premultiplied (0052): light over less coverage raises alpha, so a glow shows over the page;
|
|
1189
|
+
// then the color is un-premultiplied for grading and the curve, and premultiplied again below.
|
|
1190
|
+
alpha = clamp(max(texel.a, max(hdr_color.r, max(hdr_color.g, hdr_color.b))), 0.0, 1.0);
|
|
1191
|
+
hdr_color = hdr_color / max(alpha, 1e-6);
|
|
1192
|
+
}
|
|
1068
1193
|
let flags = params.mode.z;
|
|
1069
1194
|
if ((flags & 1u) != 0u) { hdr_color = grade(hdr_color); }
|
|
1070
1195
|
if ((flags & 2u) != 0u) {
|
|
@@ -1080,9 +1205,12 @@ fn apply_lut(c: vec3f) -> vec3f {
|
|
|
1080
1205
|
let n = ign(p.xy) + ign(p.xy + vec2f(71.0, 13.0)) - 1.0;
|
|
1081
1206
|
c = c + vec3f(n / 255.0);
|
|
1082
1207
|
}
|
|
1208
|
+
// The page composites in display space, so premultiply there; it keeps rgb <= alpha.
|
|
1209
|
+
@if(TRANSPARENT) c = clamp(c, vec3f(0.0), vec3f(1.0)) * alpha;
|
|
1083
1210
|
// sRGB targets encode in hardware: hand them linear values.
|
|
1084
1211
|
@if(SRGB_TARGET) c = srgb_to_linear(clamp(c, vec3f(0.0), vec3f(1.0)));
|
|
1085
|
-
return vec4f(c,
|
|
1212
|
+
@if(TRANSPARENT) return vec4f(c, max(alpha, max(c.r, max(c.g, c.b))));
|
|
1213
|
+
@if(!TRANSPARENT) return vec4f(c, 1.0);
|
|
1086
1214
|
}`,
|
|
1087
1215
|
"shard::post::upscale": `
|
|
1088
1216
|
struct Upscale {
|
|
@@ -1094,8 +1222,18 @@ struct Upscale {
|
|
|
1094
1222
|
@group(0) @binding(1) var input_sampler: sampler;
|
|
1095
1223
|
@group(0) @binding(2) var<uniform> upscale: Upscale;
|
|
1096
1224
|
|
|
1097
|
-
|
|
1098
|
-
|
|
1225
|
+
@if(!TRANSPARENT) alias Texel = vec3f;
|
|
1226
|
+
@if(TRANSPARENT) alias Texel = vec4f;
|
|
1227
|
+
|
|
1228
|
+
fn at(uv: vec2f) -> Texel {
|
|
1229
|
+
@if(!TRANSPARENT) return textureSampleLevel(input, input_sampler, uv, 0.0).rgb;
|
|
1230
|
+
@if(TRANSPARENT) return textureSampleLevel(input, input_sampler, uv, 0.0);
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
/** Opaque views write alpha 1; premultiplied ones keep rgb <= alpha after sharpening (0052). */
|
|
1234
|
+
fn out(c: Texel) -> vec4f {
|
|
1235
|
+
@if(!TRANSPARENT) return vec4f(c, 1.0);
|
|
1236
|
+
@if(TRANSPARENT) return vec4f(c.rgb, max(c.a, max(c.r, max(c.g, c.b))));
|
|
1099
1237
|
}
|
|
1100
1238
|
|
|
1101
1239
|
/**
|
|
@@ -1106,7 +1244,7 @@ fn at(uv: vec2f) -> vec3f {
|
|
|
1106
1244
|
let uv = frag.xy * upscale.params.xy;
|
|
1107
1245
|
let c = at(uv);
|
|
1108
1246
|
let k = upscale.params.z;
|
|
1109
|
-
if (k <= 0.0) { return
|
|
1247
|
+
if (k <= 0.0) { return out(c); }
|
|
1110
1248
|
let texel = 1.0 / vec2f(textureDimensions(input));
|
|
1111
1249
|
let n = at(uv + vec2f(0.0, -texel.y));
|
|
1112
1250
|
let s = at(uv + vec2f(0.0, texel.y));
|
|
@@ -1115,7 +1253,7 @@ fn at(uv: vec2f) -> vec3f {
|
|
|
1115
1253
|
let lo = min(c, min(min(n, s), min(e, w)));
|
|
1116
1254
|
let hi = max(c, max(max(n, s), max(e, w)));
|
|
1117
1255
|
let sharp = c + (4.0 * c - (n + s + e + w)) * (0.25 * k);
|
|
1118
|
-
return
|
|
1256
|
+
return out(clamp(sharp, lo, hi));
|
|
1119
1257
|
}`,
|
|
1120
1258
|
"shard::post::common": `
|
|
1121
1259
|
import shard::view::view;
|
|
@@ -1132,6 +1270,23 @@ fn world_at(uv: vec2f, depth: f32) -> vec3f {
|
|
|
1132
1270
|
return w.xyz / w.w;
|
|
1133
1271
|
}
|
|
1134
1272
|
|
|
1273
|
+
/**
|
|
1274
|
+
* Rounds to nearest on a device that truncates when it stores rg11b10ufloat (Metal does): half a
|
|
1275
|
+
* step of each channel's own exponent (6 mantissa bits in red and green, 5 in blue) added before
|
|
1276
|
+
* the store. Without it every pass through the format loses half a step on average, blue twice
|
|
1277
|
+
* as much, and the image drifts warm.
|
|
1278
|
+
*/
|
|
1279
|
+
fn rg11_round(c: vec3f) -> vec3f {
|
|
1280
|
+
let e = exp2(floor(log2(max(c, vec3f(6.2e-5)))));
|
|
1281
|
+
return c + e * vec3f(1.0 / 128.0, 1.0 / 128.0, 1.0 / 64.0);
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
/** A post pass's output into the chain: rounded where it's rg11b10ufloat on a truncating device. */
|
|
1285
|
+
fn post_out(c: vec4f) -> vec4f {
|
|
1286
|
+
@if(RG11_ROUND) return vec4f(rg11_round(c.rgb), c.a);
|
|
1287
|
+
@if(!RG11_ROUND) return c;
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1135
1290
|
/** Distance along the view axis, in meters. */
|
|
1136
1291
|
fn view_depth(world: vec3f) -> f32 {
|
|
1137
1292
|
return -(view.view * vec4f(world, 1.0)).z;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const SHADOW_CATCHER_SHADERS: Record<string, string>;
|
|
2
|
+
/**
|
|
3
|
+
* Invisible except for the shadows it receives (0052): color 0, alpha `opacity × (1 − visibility)`
|
|
4
|
+
* over every shadow-casting light. Premultiplied, so it darkens what's behind it by that alpha.
|
|
5
|
+
* Needs `shadowCatcherPlugin` (in `forwardPlugin`).
|
|
6
|
+
*/
|
|
7
|
+
export declare const ShadowCatcher: import("./materials.js").MaterialType;
|
|
8
|
+
/** The ShadowCatcher material's shader. */
|
|
9
|
+
export declare const shadowCatcherPlugin: import("@aethervtt/shard-runtime").Plugin;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { t } from "@aethervtt/shard-core";
|
|
2
|
+
import { definePlugin } from "@aethervtt/shard-runtime";
|
|
3
|
+
import { defineMaterial } from "./materials.js";
|
|
4
|
+
import { Shaders } from "./plugin.js";
|
|
5
|
+
import { registerShaders } from "./shaders.js";
|
|
6
|
+
const SHADOW_CATCHER_SHADERS = {
|
|
7
|
+
"shard::pbr::catcher": `
|
|
8
|
+
import shard::view::view;
|
|
9
|
+
import shard::pbr::types::VertexOutput;
|
|
10
|
+
import shard::pbr::lights::{ lights, clusters, cluster_bits, next_light, directional, light_falloff, LIGHT_SPOT, NO_SHADOW, CLUSTER_COUNT, MAX_PER_CLUSTER };
|
|
11
|
+
import shard::pbr::shadows::{ directional_shadow, spot_shadow, point_shadow };
|
|
12
|
+
import shard::pbr::lighting::cluster_of;
|
|
13
|
+
|
|
14
|
+
const FLAG_RECEIVER: u32 = 4u;
|
|
15
|
+
|
|
16
|
+
fn brightest(c: vec3f) -> f32 { return max(c.r, max(c.g, c.b)); }
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* How much of the shadow-casting light reaches the surface: the light of every shadow-casting
|
|
20
|
+
* light with shadows, over that light without them. 1 when none cast shadows, or when the mesh
|
|
21
|
+
* doesn't receive them. Shadow catchers (and the dice tray, 0054) turn 1 \u2212 this into alpha.
|
|
22
|
+
*/
|
|
23
|
+
fn shadow_visibility(in: VertexOutput) -> f32 {
|
|
24
|
+
if ((in.flags & FLAG_RECEIVER) == 0u) { return 1.0; }
|
|
25
|
+
let n = normalize(in.world_normal);
|
|
26
|
+
let p = in.world_position;
|
|
27
|
+
let view_depth = -(view.view * vec4f(p, 1.0)).z;
|
|
28
|
+
var lit = 0.0;
|
|
29
|
+
var total = 0.0;
|
|
30
|
+
for (var i = 0u; i < directional.count; i++) {
|
|
31
|
+
let light = directional.lights[i];
|
|
32
|
+
if (light.shadowed == 0u) { continue; }
|
|
33
|
+
let e = max(dot(n, normalize(light.direction)), 0.0) * brightest(light.color);
|
|
34
|
+
total += e;
|
|
35
|
+
lit += e * directional_shadow(p, n, view_depth, in.clip.xy);
|
|
36
|
+
}
|
|
37
|
+
let cluster = cluster_of(in.clip, view_depth);
|
|
38
|
+
// Baseline (0064): the cluster's bits; keep the body in step with the loop below.
|
|
39
|
+
@if(BASELINE) if (cluster >= 0) {
|
|
40
|
+
var mask = cluster_bits[cluster];
|
|
41
|
+
var index = 0u;
|
|
42
|
+
while (next_light(&mask, &index)) {
|
|
43
|
+
let light = lights[index];
|
|
44
|
+
if (light.shadow == NO_SHADOW) { continue; }
|
|
45
|
+
let to_light = light.position - p;
|
|
46
|
+
let d2 = max(dot(to_light, to_light), 1e-4);
|
|
47
|
+
let d = sqrt(d2);
|
|
48
|
+
let l = to_light / d;
|
|
49
|
+
var attenuation = light_falloff(light, d, d2);
|
|
50
|
+
if (light.kind == LIGHT_SPOT) {
|
|
51
|
+
let spot = clamp(dot(-l, light.direction) * light.spot_scale + light.spot_offset, 0.0, 1.0);
|
|
52
|
+
attenuation *= spot * spot;
|
|
53
|
+
}
|
|
54
|
+
let e = max(dot(n, l), 0.0) * attenuation * brightest(light.color);
|
|
55
|
+
if (e <= 0.0) { continue; }
|
|
56
|
+
var shadow = 1.0;
|
|
57
|
+
if (light.kind == LIGHT_SPOT) {
|
|
58
|
+
shadow = spot_shadow(light.shadow, p, n, light.position, light.shadow_bias, light.shadow_normal_bias, light.shadow_softness, in.clip.xy);
|
|
59
|
+
} else {
|
|
60
|
+
shadow = point_shadow(light.shadow, p, n, light.shadow_bias, light.shadow_normal_bias, light.shadow_softness, in.clip.xy);
|
|
61
|
+
}
|
|
62
|
+
total += e;
|
|
63
|
+
lit += e * shadow;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
@if(!BASELINE) if (cluster >= 0) {
|
|
67
|
+
let count = clusters[cluster];
|
|
68
|
+
let base = CLUSTER_COUNT + u32(cluster) * MAX_PER_CLUSTER;
|
|
69
|
+
for (var k = 0u; k < count; k++) {
|
|
70
|
+
let light = lights[clusters[base + k]];
|
|
71
|
+
if (light.shadow == NO_SHADOW) { continue; }
|
|
72
|
+
let to_light = light.position - p;
|
|
73
|
+
let d2 = max(dot(to_light, to_light), 1e-4);
|
|
74
|
+
let d = sqrt(d2);
|
|
75
|
+
let l = to_light / d;
|
|
76
|
+
var attenuation = light_falloff(light, d, d2);
|
|
77
|
+
if (light.kind == LIGHT_SPOT) {
|
|
78
|
+
let spot = clamp(dot(-l, light.direction) * light.spot_scale + light.spot_offset, 0.0, 1.0);
|
|
79
|
+
attenuation *= spot * spot;
|
|
80
|
+
}
|
|
81
|
+
let e = max(dot(n, l), 0.0) * attenuation * brightest(light.color);
|
|
82
|
+
if (e <= 0.0) { continue; }
|
|
83
|
+
var shadow = 1.0;
|
|
84
|
+
if (light.kind == LIGHT_SPOT) {
|
|
85
|
+
shadow = spot_shadow(light.shadow, p, n, light.position, light.shadow_bias, light.shadow_normal_bias, light.shadow_softness, in.clip.xy);
|
|
86
|
+
} else {
|
|
87
|
+
shadow = point_shadow(light.shadow, p, n, light.shadow_bias, light.shadow_normal_bias, light.shadow_softness, in.clip.xy);
|
|
88
|
+
}
|
|
89
|
+
total += e;
|
|
90
|
+
lit += e * shadow;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return select(1.0, lit / total, total > 0.0);
|
|
94
|
+
}`,
|
|
95
|
+
"shard::material::shadow_catcher": `
|
|
96
|
+
import shard::pbr::types::VertexOutput;
|
|
97
|
+
import shard::pbr::catcher::shadow_visibility;
|
|
98
|
+
import material::shadow_catcher::ShadowCatcher;
|
|
99
|
+
|
|
100
|
+
/** No color; alpha opacity \xD7 (1 \u2212 visibility) over every shadow-casting light. */
|
|
101
|
+
override fn shade(in: VertexOutput) -> vec4f {
|
|
102
|
+
return vec4f(0.0, 0.0, 0.0, ShadowCatcher.opacity * (1.0 - shadow_visibility(in)));
|
|
103
|
+
}`
|
|
104
|
+
};
|
|
105
|
+
const ShadowCatcher = defineMaterial("render/ShadowCatcher", {
|
|
106
|
+
extends: "none",
|
|
107
|
+
blend: "premultiplied",
|
|
108
|
+
fields: {
|
|
109
|
+
opacity: t.f32({
|
|
110
|
+
default: 0.6,
|
|
111
|
+
min: 0,
|
|
112
|
+
max: 1,
|
|
113
|
+
description: "Alpha of full shadow. Partial shadow (penumbra, one of two lights) is less."
|
|
114
|
+
})
|
|
115
|
+
},
|
|
116
|
+
shader: "shard::material::shadow_catcher",
|
|
117
|
+
description: "Invisible except for the shadows it receives: under a camera clearing to alpha 0 they fall on the page (a dice tray floor). Give its mesh NotShadowCaster."
|
|
118
|
+
});
|
|
119
|
+
const shadowCatcherPlugin = definePlugin({
|
|
120
|
+
name: "render/shadow-catcher",
|
|
121
|
+
dependencies: ["render/forward"],
|
|
122
|
+
provides: [ShadowCatcher],
|
|
123
|
+
build() {
|
|
124
|
+
},
|
|
125
|
+
ready(app) {
|
|
126
|
+
registerShaders(app.world.resource(Shaders), SHADOW_CATCHER_SHADERS);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
export {
|
|
130
|
+
SHADOW_CATCHER_SHADERS,
|
|
131
|
+
ShadowCatcher,
|
|
132
|
+
shadowCatcherPlugin
|
|
133
|
+
};
|
package/dist/shadows.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { type World } from '@aethervtt/shard-core';
|
|
2
2
|
import { GpuBuffer, type GpuContext } from '@aethervtt/shard-gpu';
|
|
3
|
+
import type { FoliageSupport } from './foliage-path.js';
|
|
3
4
|
import type { NodeContext } from './graph.js';
|
|
4
|
-
import { type DrawList } from './instances.js';
|
|
5
|
+
import { type DrawList, type InstanceStore } from './instances.js';
|
|
5
6
|
import { type LightRecord, type LightStore } from './lights.js';
|
|
6
7
|
import { type MaterialPipelines } from './material-pipelines.js';
|
|
7
8
|
import type { CameraData } from './view.js';
|
|
@@ -15,10 +16,28 @@ export declare function pointTanHalf(size: number): number;
|
|
|
15
16
|
export interface ShadowViewDraw {
|
|
16
17
|
viewProj: Float32Array;
|
|
17
18
|
frustum: Float32Array;
|
|
19
|
+
/** What casters are culled against: the frustum, but open toward the light for cascades. */
|
|
20
|
+
cullPlanes: Float32Array;
|
|
18
21
|
draws: DrawList;
|
|
19
22
|
/** Dynamic offset of this view's matrix in the frame's shadow uniforms. */
|
|
20
23
|
offset: number;
|
|
24
|
+
/** Whether its layer holds a complete draw (0055: cached shadows keep it). */
|
|
25
|
+
drawn: boolean;
|
|
26
|
+
/** The matrix and `InstanceStore.shadowEpoch` it was drawn with. */
|
|
27
|
+
drawnViewProj: Float32Array;
|
|
28
|
+
drawnEpoch: number;
|
|
29
|
+
/** Set by the queue: draw it this frame. */
|
|
30
|
+
redraw: boolean;
|
|
21
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Whether a shadow view must draw this frame (0055). Views of `shadowUpdate: 'always'` lights
|
|
34
|
+
* always do. A cached view draws when its layer isn't complete, its matrix changed (the light or
|
|
35
|
+
* the cascade's fit moved), what casts shadows changed (`shadowEpoch`), poses deform, or a
|
|
36
|
+
* caster inside its cull planes moved.
|
|
37
|
+
*/
|
|
38
|
+
export declare function shadowRedraw(view: ShadowViewDraw, cached: boolean, store: InstanceStore): boolean;
|
|
39
|
+
/** Records that a shadow view's layer now holds a complete draw of this frame's state. */
|
|
40
|
+
export declare function markShadowDrawn(view: ShadowViewDraw, store: InstanceStore): void;
|
|
22
41
|
/** A camera's cascades: fitted each frame, one depth layer each. */
|
|
23
42
|
export declare class Cascades {
|
|
24
43
|
count: number;
|
|
@@ -98,11 +117,17 @@ export declare class ShadowPassUniforms {
|
|
|
98
117
|
bindGroup: GPUBindGroup | undefined;
|
|
99
118
|
private bound;
|
|
100
119
|
private readonly gpu;
|
|
120
|
+
/** What the buffer holds, so unchanged shadow views write nothing (0055). */
|
|
121
|
+
private sent;
|
|
122
|
+
private sentCount;
|
|
123
|
+
private sentVersion;
|
|
124
|
+
private sentGeneration;
|
|
101
125
|
constructor(gpu: GpuContext);
|
|
102
126
|
reset(): void;
|
|
103
127
|
/** Adds a view matrix; returns its dynamic offset. */
|
|
104
128
|
push(viewProj: Float32Array): number;
|
|
105
129
|
upload(layout: GPUBindGroupLayout, globals: GpuBuffer): void;
|
|
130
|
+
private unchanged;
|
|
106
131
|
}
|
|
107
132
|
export declare function shadowViewLayout(gpu: GpuContext): GPUBindGroupLayout;
|
|
108
133
|
/**
|
|
@@ -110,7 +135,7 @@ export declare function shadowViewLayout(gpu: GpuContext): GPUBindGroupLayout;
|
|
|
110
135
|
* `vertex_position` displacement moves shadows too). Opaque materials are vertex-only; masked
|
|
111
136
|
* standard materials run the surface stage to discard.
|
|
112
137
|
*/
|
|
113
|
-
export declare function drawShadowCasters(ctx: NodeContext, target: GPUTextureView, view: ShadowViewDraw, offset: number, uniforms: ShadowPassUniforms, pipelines: MaterialPipelines, shadowView: GPUBindGroupLayout, label: string): void;
|
|
138
|
+
export declare function drawShadowCasters(ctx: NodeContext, target: GPUTextureView, view: ShadowViewDraw, offset: number, uniforms: ShadowPassUniforms, pipelines: MaterialPipelines, shadowView: GPUBindGroupLayout, label: string, foliage?: FoliageSupport, cam?: CameraData, cascade?: number): void;
|
|
114
139
|
/** Culls instances that cast shadows into a shadow view, and unions their world bounds. */
|
|
115
140
|
export declare function cullCasters(world: World, view: ShadowViewDraw, planes: Float32Array, box?: Float32Array): boolean;
|
|
116
141
|
export declare const ShadowsResource: import("@aethervtt/shard-core").ResourceDef<{
|