@aethervtt/shard-render 0.0.1 → 0.1.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/assets.d.ts +3 -0
- package/dist/assets.js +43 -2
- package/dist/atmosphere-nodes.d.ts +9 -0
- package/dist/atmosphere-nodes.js +57 -18
- package/dist/atmosphere-plugin.js +8 -2
- 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 +120 -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/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/deferred-shaders.js +2 -0
- package/dist/deferred.js +24 -3
- 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 +15 -4
- 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/forward.d.ts +38 -2
- package/dist/forward.js +582 -184
- 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 +11 -0
- package/dist/graph.js +30 -5
- package/dist/health.d.ts +58 -0
- package/dist/health.js +112 -0
- package/dist/index.d.ts +30 -11
- package/dist/index.js +286 -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/overlays.js +1 -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 +58 -6
- package/dist/plugin.js +191 -24
- package/dist/post-common.d.ts +2 -0
- package/dist/post-common.js +3 -0
- package/dist/post-nodes.d.ts +2 -1
- package/dist/post-nodes.js +111 -19
- package/dist/post-plugin.js +5 -3
- package/dist/post-shaders.js +51 -6
- 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/screen-effects.d.ts +97 -0
- package/dist/screen-effects.js +192 -0
- package/dist/shaders.d.ts +13 -0
- package/dist/shaders.js +131 -29
- package/dist/shadow-catcher.d.ts +9 -0
- package/dist/shadow-catcher.js +133 -0
- package/dist/shadows.d.ts +25 -1
- package/dist/shadows.js +97 -19
- package/dist/standard-fields.d.ts +1 -0
- package/dist/standard-fields.js +5 -0
- package/dist/standard.js +12 -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 +10 -0
- package/dist/testing.js +33 -1
- package/dist/tier.d.ts +13 -0
- package/dist/tier.js +14 -0
- package/dist/timer.d.ts +2 -0
- package/dist/timer.js +7 -1
- package/dist/variants.d.ts +37 -0
- package/dist/variants.js +104 -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 +15 -11
|
@@ -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,7 @@
|
|
|
1
1
|
import { type World } from '@aethervtt/shard-core';
|
|
2
2
|
import { GpuBuffer, type GpuContext } from '@aethervtt/shard-gpu';
|
|
3
3
|
import type { NodeContext } from './graph.js';
|
|
4
|
-
import { type DrawList } from './instances.js';
|
|
4
|
+
import { type DrawList, type InstanceStore } from './instances.js';
|
|
5
5
|
import { type LightRecord, type LightStore } from './lights.js';
|
|
6
6
|
import { type MaterialPipelines } from './material-pipelines.js';
|
|
7
7
|
import type { CameraData } from './view.js';
|
|
@@ -15,10 +15,28 @@ export declare function pointTanHalf(size: number): number;
|
|
|
15
15
|
export interface ShadowViewDraw {
|
|
16
16
|
viewProj: Float32Array;
|
|
17
17
|
frustum: Float32Array;
|
|
18
|
+
/** What casters are culled against: the frustum, but open toward the light for cascades. */
|
|
19
|
+
cullPlanes: Float32Array;
|
|
18
20
|
draws: DrawList;
|
|
19
21
|
/** Dynamic offset of this view's matrix in the frame's shadow uniforms. */
|
|
20
22
|
offset: number;
|
|
23
|
+
/** Whether its layer holds a complete draw (0055: cached shadows keep it). */
|
|
24
|
+
drawn: boolean;
|
|
25
|
+
/** The matrix and `InstanceStore.shadowEpoch` it was drawn with. */
|
|
26
|
+
drawnViewProj: Float32Array;
|
|
27
|
+
drawnEpoch: number;
|
|
28
|
+
/** Set by the queue: draw it this frame. */
|
|
29
|
+
redraw: boolean;
|
|
21
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Whether a shadow view must draw this frame (0055). Views of `shadowUpdate: 'always'` lights
|
|
33
|
+
* always do. A cached view draws when its layer isn't complete, its matrix changed (the light or
|
|
34
|
+
* the cascade's fit moved), what casts shadows changed (`shadowEpoch`), poses deform, or a
|
|
35
|
+
* caster inside its cull planes moved.
|
|
36
|
+
*/
|
|
37
|
+
export declare function shadowRedraw(view: ShadowViewDraw, cached: boolean, store: InstanceStore): boolean;
|
|
38
|
+
/** Records that a shadow view's layer now holds a complete draw of this frame's state. */
|
|
39
|
+
export declare function markShadowDrawn(view: ShadowViewDraw, store: InstanceStore): void;
|
|
22
40
|
/** A camera's cascades: fitted each frame, one depth layer each. */
|
|
23
41
|
export declare class Cascades {
|
|
24
42
|
count: number;
|
|
@@ -98,11 +116,17 @@ export declare class ShadowPassUniforms {
|
|
|
98
116
|
bindGroup: GPUBindGroup | undefined;
|
|
99
117
|
private bound;
|
|
100
118
|
private readonly gpu;
|
|
119
|
+
/** What the buffer holds, so unchanged shadow views write nothing (0055). */
|
|
120
|
+
private sent;
|
|
121
|
+
private sentCount;
|
|
122
|
+
private sentVersion;
|
|
123
|
+
private sentGeneration;
|
|
101
124
|
constructor(gpu: GpuContext);
|
|
102
125
|
reset(): void;
|
|
103
126
|
/** Adds a view matrix; returns its dynamic offset. */
|
|
104
127
|
push(viewProj: Float32Array): number;
|
|
105
128
|
upload(layout: GPUBindGroupLayout, globals: GpuBuffer): void;
|
|
129
|
+
private unchanged;
|
|
106
130
|
}
|
|
107
131
|
export declare function shadowViewLayout(gpu: GpuContext): GPUBindGroupLayout;
|
|
108
132
|
/**
|
package/dist/shadows.js
CHANGED
|
@@ -2,7 +2,11 @@ import { defineResource, frustum, mat4 } from "@aethervtt/shard-core";
|
|
|
2
2
|
import { GpuBuffer } from "@aethervtt/shard-gpu";
|
|
3
3
|
import { Culler } from "./culling.js";
|
|
4
4
|
import { GpuAssetsResource } from "./gpu-assets.js";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
createDrawList,
|
|
7
|
+
InstanceFlags,
|
|
8
|
+
Instances
|
|
9
|
+
} from "./instances.js";
|
|
6
10
|
import { setShadowIndex } from "./lights.js";
|
|
7
11
|
import {
|
|
8
12
|
materialVariant,
|
|
@@ -10,6 +14,7 @@ import {
|
|
|
10
14
|
variantBlend,
|
|
11
15
|
variantCull
|
|
12
16
|
} from "./material-pipelines.js";
|
|
17
|
+
import { bindingDimension } from "./tier.js";
|
|
13
18
|
const MAX_CASCADES = 4;
|
|
14
19
|
const MAX_SHADOWED_SPOTS = 8;
|
|
15
20
|
const MAX_SHADOWED_POINTS = 4;
|
|
@@ -31,7 +36,30 @@ function pointTanHalf(size) {
|
|
|
31
36
|
}
|
|
32
37
|
const VIEW_STRIDE = 256;
|
|
33
38
|
function shadowView() {
|
|
34
|
-
return {
|
|
39
|
+
return {
|
|
40
|
+
viewProj: mat4.create(),
|
|
41
|
+
frustum: frustum.create(),
|
|
42
|
+
cullPlanes: frustum.create(),
|
|
43
|
+
draws: createDrawList(),
|
|
44
|
+
offset: 0,
|
|
45
|
+
drawn: false,
|
|
46
|
+
drawnViewProj: mat4.create(),
|
|
47
|
+
drawnEpoch: -1,
|
|
48
|
+
redraw: true
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function shadowRedraw(view, cached, store) {
|
|
52
|
+
if (!cached || !view.drawn || view.drawnEpoch !== store.shadowEpoch || store.deforming)
|
|
53
|
+
return true;
|
|
54
|
+
const a = view.viewProj;
|
|
55
|
+
const b = view.drawnViewProj;
|
|
56
|
+
for (let i = 0; i < 16; i++) if (a[i] !== b[i]) return true;
|
|
57
|
+
return store.movedCasterIn(view.cullPlanes);
|
|
58
|
+
}
|
|
59
|
+
function markShadowDrawn(view, store) {
|
|
60
|
+
view.drawn = true;
|
|
61
|
+
view.drawnViewProj.set(view.viewProj);
|
|
62
|
+
view.drawnEpoch = store.shadowEpoch;
|
|
35
63
|
}
|
|
36
64
|
class Cascades {
|
|
37
65
|
count = 0;
|
|
@@ -46,11 +74,13 @@ class Cascades {
|
|
|
46
74
|
ensureTexture(gpu, size, label) {
|
|
47
75
|
if (!this.texture || this.size !== size || this.generation !== gpu.generation) {
|
|
48
76
|
this.texture?.destroy();
|
|
77
|
+
for (const view of this.views) view.drawn = false;
|
|
49
78
|
this.texture = gpu.device.createTexture({
|
|
50
79
|
label: `${label}/cascades`,
|
|
51
80
|
size: [size, size, MAX_CASCADES],
|
|
52
81
|
format: "depth32float",
|
|
53
|
-
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC
|
|
82
|
+
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC,
|
|
83
|
+
...bindingDimension(gpu, "2d-array")
|
|
54
84
|
});
|
|
55
85
|
this.size = size;
|
|
56
86
|
this.generation = gpu.generation;
|
|
@@ -160,6 +190,7 @@ function fitCascades(cascades, cam, sun, size, cull) {
|
|
|
160
190
|
set(4, -basis[6], -basis[7], -basis[8], lz + radius);
|
|
161
191
|
set(5, 0, 0, 0, 1);
|
|
162
192
|
const view = cascades.views[i];
|
|
193
|
+
view.cullPlanes.set(p);
|
|
163
194
|
const any = cull(view, p, scratchBox);
|
|
164
195
|
let zNear = lz - radius;
|
|
165
196
|
if (any) {
|
|
@@ -228,15 +259,18 @@ class LocalShadows {
|
|
|
228
259
|
label,
|
|
229
260
|
size: [size, size, layers],
|
|
230
261
|
format: "depth32float",
|
|
231
|
-
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC
|
|
262
|
+
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC,
|
|
263
|
+
...bindingDimension(gpu, "2d-array")
|
|
232
264
|
});
|
|
233
265
|
if (fresh || !this.spotTexture || spotLayers > this.spotLayers) {
|
|
234
266
|
this.spotTexture?.destroy();
|
|
267
|
+
for (const view of this.spotViews) view.drawn = false;
|
|
235
268
|
this.spotLayers = Math.max(spotLayers, fresh ? 1 : this.spotLayers);
|
|
236
269
|
this.spotTexture = make("shadows/spots", this.spotLayers);
|
|
237
270
|
}
|
|
238
271
|
if (fresh || !this.pointTexture || pointLayers > this.pointLayers) {
|
|
239
272
|
this.pointTexture?.destroy();
|
|
273
|
+
for (const view of this.pointViews) view.drawn = false;
|
|
240
274
|
this.pointLayers = Math.max(pointLayers, fresh ? 6 : this.pointLayers);
|
|
241
275
|
this.pointTexture = make("shadows/points", this.pointLayers);
|
|
242
276
|
}
|
|
@@ -317,6 +351,7 @@ function fitLocalShadows(shadows, size) {
|
|
|
317
351
|
const view = shadows.spotViews[i];
|
|
318
352
|
mat4.multiply(view.viewProj, scratchProj, scratchView);
|
|
319
353
|
frustum.fromViewProjection(view.frustum, view.viewProj);
|
|
354
|
+
view.cullPlanes.set(view.frustum);
|
|
320
355
|
shadows.spotParams[i * 4] = tanHalf2;
|
|
321
356
|
shadows.spotParams[i * 4 + 1] = near;
|
|
322
357
|
}
|
|
@@ -335,6 +370,7 @@ function fitLocalShadows(shadows, size) {
|
|
|
335
370
|
const view = shadows.pointViews[i * 6 + f];
|
|
336
371
|
mat4.multiply(view.viewProj, scratchProj, scratchView);
|
|
337
372
|
frustum.fromViewProjection(view.frustum, view.viewProj);
|
|
373
|
+
view.cullPlanes.set(view.frustum);
|
|
338
374
|
}
|
|
339
375
|
}
|
|
340
376
|
}
|
|
@@ -377,6 +413,11 @@ class ShadowPassUniforms {
|
|
|
377
413
|
bindGroup;
|
|
378
414
|
bound = "";
|
|
379
415
|
gpu;
|
|
416
|
+
/** What the buffer holds, so unchanged shadow views write nothing (0055). */
|
|
417
|
+
sent = new Float32Array(0);
|
|
418
|
+
sentCount = 0;
|
|
419
|
+
sentVersion = -1;
|
|
420
|
+
sentGeneration = -1;
|
|
380
421
|
constructor(gpu) {
|
|
381
422
|
this.gpu = gpu;
|
|
382
423
|
this.buffer = new GpuBuffer(gpu, {
|
|
@@ -400,7 +441,15 @@ class ShadowPassUniforms {
|
|
|
400
441
|
return this.count++ * VIEW_STRIDE;
|
|
401
442
|
}
|
|
402
443
|
upload(layout, globals) {
|
|
403
|
-
|
|
444
|
+
const floats = this.count * VIEW_STRIDE / 4;
|
|
445
|
+
if (this.count > 0 && !this.unchanged(floats)) {
|
|
446
|
+
this.buffer.write(this.data, 0, 0, floats);
|
|
447
|
+
if (this.sent.length < this.data.length) this.sent = new Float32Array(this.data.length);
|
|
448
|
+
this.sent.set(this.data.subarray(0, floats));
|
|
449
|
+
this.sentCount = this.count;
|
|
450
|
+
this.sentVersion = this.buffer.version;
|
|
451
|
+
this.sentGeneration = this.gpu.generation;
|
|
452
|
+
}
|
|
404
453
|
const key = `${this.buffer.version}/${globals.version}/${this.gpu.generation}`;
|
|
405
454
|
if (!this.bindGroup || this.bound !== key) {
|
|
406
455
|
this.bindGroup = this.gpu.device.createBindGroup({
|
|
@@ -414,6 +463,15 @@ class ShadowPassUniforms {
|
|
|
414
463
|
this.bound = key;
|
|
415
464
|
}
|
|
416
465
|
}
|
|
466
|
+
unchanged(floats) {
|
|
467
|
+
if (this.sentCount < this.count) return false;
|
|
468
|
+
if (this.sentVersion !== this.buffer.version || this.sentGeneration !== this.gpu.generation)
|
|
469
|
+
return false;
|
|
470
|
+
const data = this.data;
|
|
471
|
+
const sent = this.sent;
|
|
472
|
+
for (let i = 0; i < floats; i++) if (data[i] !== sent[i]) return false;
|
|
473
|
+
return true;
|
|
474
|
+
}
|
|
417
475
|
}
|
|
418
476
|
function shadowViewLayout(gpu) {
|
|
419
477
|
return gpu.layouts.bindGroupLayout({
|
|
@@ -472,7 +530,8 @@ function drawShadowCasters(ctx, target, view, offset, uniforms, pipelines, shado
|
|
|
472
530
|
const draws = view.draws;
|
|
473
531
|
for (let d = 0; d < draws.length; d++) {
|
|
474
532
|
const item = draws.items[d];
|
|
475
|
-
const
|
|
533
|
+
const fallback = pipelines.failing(item.batch.material.type);
|
|
534
|
+
const material = fallback ? pipelines.proxy(item.batch.material) : item.batch.material;
|
|
476
535
|
const type = material.type;
|
|
477
536
|
const variant = materialVariant(material);
|
|
478
537
|
const masked = type.standard && variantBlend(variant) === "mask";
|
|
@@ -481,21 +540,38 @@ function drawShadowCasters(ctx, target, view, offset, uniforms, pipelines, shado
|
|
|
481
540
|
if (!pipeline) {
|
|
482
541
|
const module = pipelines.module(world, gpu, type, 8, "shard::pbr::shadow", void 0);
|
|
483
542
|
if (!module) {
|
|
484
|
-
|
|
543
|
+
if (!fallback && pipelines.failing(type)) d--;
|
|
544
|
+
else gpu.pipelines.skipped++;
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
pipeline = pipelines.create(
|
|
548
|
+
gpu,
|
|
549
|
+
key,
|
|
550
|
+
{
|
|
551
|
+
label: `shadow/${type.name}/${masked ? "mask" : "opaque"}/${variantCull(variant)}`,
|
|
552
|
+
layout: gpu.layouts.pipelineLayout({
|
|
553
|
+
label: `shadow/${type.name}`,
|
|
554
|
+
bindGroupLayouts: [shadowView2, assets.layoutOf(type), store.layout]
|
|
555
|
+
}),
|
|
556
|
+
vertex: { module, entryPoint: "vs", buffers: SHADOW_BUFFERS },
|
|
557
|
+
fragment: masked ? { module, entryPoint: "fs_mask", targets: [] } : void 0,
|
|
558
|
+
primitive: {
|
|
559
|
+
topology: "triangle-list",
|
|
560
|
+
cullMode: variantCull(variant),
|
|
561
|
+
frontFace: "ccw"
|
|
562
|
+
},
|
|
563
|
+
depthStencil: {
|
|
564
|
+
format: "depth32float",
|
|
565
|
+
depthWriteEnabled: true,
|
|
566
|
+
depthCompare: "greater"
|
|
567
|
+
}
|
|
568
|
+
},
|
|
569
|
+
type
|
|
570
|
+
);
|
|
571
|
+
if (!pipeline) {
|
|
572
|
+
if (!fallback && pipelines.failing(type)) d--;
|
|
485
573
|
continue;
|
|
486
574
|
}
|
|
487
|
-
pipeline = pipelines.create(gpu, key, {
|
|
488
|
-
label: `shadow/${type.name}/${masked ? "mask" : "opaque"}/${variantCull(variant)}`,
|
|
489
|
-
layout: gpu.layouts.pipelineLayout({
|
|
490
|
-
label: `shadow/${type.name}`,
|
|
491
|
-
bindGroupLayouts: [shadowView2, assets.layoutOf(type), store.layout]
|
|
492
|
-
}),
|
|
493
|
-
vertex: { module, entryPoint: "vs", buffers: SHADOW_BUFFERS },
|
|
494
|
-
fragment: masked ? { module, entryPoint: "fs_mask", targets: [] } : void 0,
|
|
495
|
-
primitive: { topology: "triangle-list", cullMode: variantCull(variant), frontFace: "ccw" },
|
|
496
|
-
depthStencil: { format: "depth32float", depthWriteEnabled: true, depthCompare: "greater" }
|
|
497
|
-
});
|
|
498
|
-
if (!pipeline) continue;
|
|
499
575
|
}
|
|
500
576
|
const mat = assets.material(world, material);
|
|
501
577
|
if (!mat?.bindGroup) continue;
|
|
@@ -555,8 +631,10 @@ export {
|
|
|
555
631
|
drawShadowCasters,
|
|
556
632
|
fitCascades,
|
|
557
633
|
fitLocalShadows,
|
|
634
|
+
markShadowDrawn,
|
|
558
635
|
packShadowData,
|
|
559
636
|
pointTanHalf,
|
|
637
|
+
shadowRedraw,
|
|
560
638
|
shadowViewLayout,
|
|
561
639
|
sliceSphere
|
|
562
640
|
};
|
|
@@ -15,6 +15,7 @@ export declare const STANDARD_FIELDS: {
|
|
|
15
15
|
emissive: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Color, "f32">;
|
|
16
16
|
emissiveLuminance: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
17
17
|
doubleSided: import("@aethervtt/shard-core").FieldType<boolean, "u8">;
|
|
18
|
+
deferUntilReady: import("@aethervtt/shard-core").FieldType<boolean, "u8">;
|
|
18
19
|
alphaMode: import("@aethervtt/shard-core").FieldType<"additive" | "alpha" | "mask" | "opaque" | "premultiplied", "u8">;
|
|
19
20
|
alphaCutoff: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
20
21
|
normalScale: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
package/dist/standard-fields.js
CHANGED
|
@@ -42,6 +42,11 @@ const STANDARD_FIELDS = {
|
|
|
42
42
|
description: "Emitted luminance. 0 = not emissive."
|
|
43
43
|
}),
|
|
44
44
|
doubleSided: t.bool({ description: "Draw back faces too (no culling)." }),
|
|
45
|
+
deferUntilReady: t.bool({
|
|
46
|
+
// The asset layer reads it; shaders don't.
|
|
47
|
+
gpu: false,
|
|
48
|
+
description: "Baseline tier (0064): while a texture it binds waits for a copy in its slot's color space, skip its draws instead of showing the slot's loading fallback."
|
|
49
|
+
}),
|
|
45
50
|
alphaMode: t.enum(["opaque", "mask", "alpha", "additive", "premultiplied"], {
|
|
46
51
|
description: "opaque ignores alpha; mask discards pixels below alphaCutoff; alpha blends (transparent, drawn after opaque, sorted back to front); additive adds light (glows); premultiplied expects color already multiplied by alpha."
|
|
47
52
|
}),
|
package/dist/standard.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { definePlugin } from "@aethervtt/shard-runtime";
|
|
2
2
|
import { atmospherePlugin } from "./atmosphere-plugin.js";
|
|
3
|
+
import { cutawayPlugin } from "./cutaway-plugin.js";
|
|
3
4
|
import { deferredPlugin } from "./deferred-plugin.js";
|
|
4
5
|
import { renderDescribePlugin } from "./describe-plugin.js";
|
|
5
6
|
import { dynamicResolutionPlugin } from "./dynamic-resolution.js";
|
|
@@ -7,10 +8,15 @@ import { environmentPlugin } from "./environment-plugin.js";
|
|
|
7
8
|
import { forwardCorePlugin } from "./forward.js";
|
|
8
9
|
import { fxaaPlugin } from "./fxaa.js";
|
|
9
10
|
import { gizmosPlugin } from "./gizmos-plugin.js";
|
|
11
|
+
import { interiorPlugin } from "./interior-plugin.js";
|
|
12
|
+
import { lensPlugin } from "./lens-plugin.js";
|
|
13
|
+
import { outlinePlugin } from "./outline-plugin.js";
|
|
10
14
|
import { pickingPlugin } from "./picking-plugin.js";
|
|
11
15
|
import { pixelPerfectPlugin } from "./pixel-perfect-plugin.js";
|
|
12
16
|
import { postPlugin } from "./post-plugin.js";
|
|
17
|
+
import { shadowCatcherPlugin } from "./shadow-catcher.js";
|
|
13
18
|
import { skinningPlugin } from "./skinning-plugin.js";
|
|
19
|
+
import { viewVisibilityPlugin } from "./view-visibility-plugin.js";
|
|
14
20
|
function forwardPlugin(options = {}) {
|
|
15
21
|
return definePlugin({
|
|
16
22
|
name: "render/standard",
|
|
@@ -26,6 +32,12 @@ function forwardPlugin(options = {}) {
|
|
|
26
32
|
deferredPlugin,
|
|
27
33
|
skinningPlugin,
|
|
28
34
|
pixelPerfectPlugin,
|
|
35
|
+
lensPlugin,
|
|
36
|
+
outlinePlugin,
|
|
37
|
+
cutawayPlugin,
|
|
38
|
+
viewVisibilityPlugin,
|
|
39
|
+
interiorPlugin,
|
|
40
|
+
shadowCatcherPlugin,
|
|
29
41
|
dynamicResolutionPlugin,
|
|
30
42
|
renderDescribePlugin
|
|
31
43
|
);
|
package/dist/stats.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type GpuUploads, type UploadCategory } from '@aethervtt/shard-gpu';
|
|
1
2
|
export interface ViewStats {
|
|
2
3
|
visible: number;
|
|
3
4
|
culled: number;
|
|
@@ -8,7 +9,50 @@ export interface ViewStats {
|
|
|
8
9
|
/** Pipeline changes in the opaque pass (sorting keeps these near the number of material types). */
|
|
9
10
|
pipelineSwitches?: number;
|
|
10
11
|
}
|
|
11
|
-
|
|
12
|
+
/** What one frame (or a window of frames) wrote to the GPU and rebuilt (0055). */
|
|
13
|
+
export interface FrameRecord {
|
|
14
|
+
/** Bytes written, by what they were for. */
|
|
15
|
+
bytes: Record<UploadCategory, number>;
|
|
16
|
+
/** Scene data: every byte but `view` (camera and per-frame uniforms). */
|
|
17
|
+
sceneBytes: number;
|
|
18
|
+
/** Buffers and textures created. */
|
|
19
|
+
created: number;
|
|
20
|
+
/** Structure chunks rebuilt (0055). */
|
|
21
|
+
chunksRebuilt: number;
|
|
22
|
+
/** Meshes uploaded to the GPU: new ones, and ones whose data changed. */
|
|
23
|
+
meshesRebuilt: number;
|
|
24
|
+
/** Shadow maps (cascades and local shadow views) drawn. */
|
|
25
|
+
shadowMapsRendered: number;
|
|
26
|
+
}
|
|
27
|
+
/** Counts a system adds to during a frame; they land in `lastFrame` when the frame is submitted. */
|
|
28
|
+
export interface FrameCounts {
|
|
29
|
+
chunksRebuilt: number;
|
|
30
|
+
meshesRebuilt: number;
|
|
31
|
+
shadowMapsRendered: number;
|
|
32
|
+
}
|
|
33
|
+
/** Frames `RenderStatsMap.recent` sums over. */
|
|
34
|
+
export declare const RECENT_FRAMES = 60;
|
|
35
|
+
/**
|
|
36
|
+
* Per-view counts from the last frame (a Map by view name), plus what the last frame wrote to the
|
|
37
|
+
* GPU and rebuilt (`lastFrame`) and the sum over the last 60 rendered frames (`recent`).
|
|
38
|
+
*/
|
|
39
|
+
export declare class RenderStatsMap extends Map<string, ViewStats> {
|
|
40
|
+
readonly lastFrame: FrameRecord;
|
|
41
|
+
readonly recent: FrameRecord;
|
|
42
|
+
/** Counted during the frame being built. */
|
|
43
|
+
readonly current: FrameCounts;
|
|
44
|
+
/** Frames recorded. */
|
|
45
|
+
frames: number;
|
|
46
|
+
private readonly ring;
|
|
47
|
+
private readonly row;
|
|
48
|
+
private readonly seen;
|
|
49
|
+
/**
|
|
50
|
+
* Closes a frame: `uploads` is the owner's running total (`gpu.uploads(owner)`); the difference
|
|
51
|
+
* since the last call is this frame's. Allocates nothing.
|
|
52
|
+
*/
|
|
53
|
+
endFrame(uploads: GpuUploads | undefined): void;
|
|
54
|
+
}
|
|
55
|
+
export declare const RenderStats: import("@aethervtt/shard-core").ResourceDef<RenderStatsMap>;
|
|
12
56
|
export interface GpuMemoryData {
|
|
13
57
|
/** Textures uploaded by the forward renderer. */
|
|
14
58
|
textures: number;
|
package/dist/stats.js
CHANGED
|
@@ -1,7 +1,93 @@
|
|
|
1
1
|
import { defineResource } from "@aethervtt/shard-core";
|
|
2
|
+
import {
|
|
3
|
+
UPLOAD_CATEGORIES,
|
|
4
|
+
Upload
|
|
5
|
+
} from "@aethervtt/shard-gpu";
|
|
6
|
+
const RECENT_FRAMES = 60;
|
|
7
|
+
const FIELDS = UPLOAD_CATEGORIES.length + 4;
|
|
8
|
+
function frameRecord() {
|
|
9
|
+
const bytes = {};
|
|
10
|
+
for (const c of UPLOAD_CATEGORIES) bytes[c] = 0;
|
|
11
|
+
return {
|
|
12
|
+
bytes,
|
|
13
|
+
sceneBytes: 0,
|
|
14
|
+
created: 0,
|
|
15
|
+
chunksRebuilt: 0,
|
|
16
|
+
meshesRebuilt: 0,
|
|
17
|
+
shadowMapsRendered: 0
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
class RenderStatsMap extends Map {
|
|
21
|
+
lastFrame = frameRecord();
|
|
22
|
+
recent = frameRecord();
|
|
23
|
+
/** Counted during the frame being built. */
|
|
24
|
+
current = { chunksRebuilt: 0, meshesRebuilt: 0, shadowMapsRendered: 0 };
|
|
25
|
+
/** Frames recorded. */
|
|
26
|
+
frames = 0;
|
|
27
|
+
ring = new Float64Array(RECENT_FRAMES * FIELDS);
|
|
28
|
+
row = new Float64Array(FIELDS);
|
|
29
|
+
seen = new Float64Array(UPLOAD_CATEGORIES.length + 1);
|
|
30
|
+
/**
|
|
31
|
+
* Closes a frame: `uploads` is the owner's running total (`gpu.uploads(owner)`); the difference
|
|
32
|
+
* since the last call is this frame's. Allocates nothing.
|
|
33
|
+
*/
|
|
34
|
+
endFrame(uploads) {
|
|
35
|
+
const row = this.row;
|
|
36
|
+
const seen = this.seen;
|
|
37
|
+
const n = UPLOAD_CATEGORIES.length;
|
|
38
|
+
for (let i = 0; i < n; i++) {
|
|
39
|
+
const total = uploads ? uploads.bytes[i] : seen[i];
|
|
40
|
+
row[i] = total - seen[i];
|
|
41
|
+
seen[i] = total;
|
|
42
|
+
}
|
|
43
|
+
const created = uploads ? uploads.created : seen[n];
|
|
44
|
+
row[n] = created - seen[n];
|
|
45
|
+
seen[n] = created;
|
|
46
|
+
const cur = this.current;
|
|
47
|
+
row[n + 1] = cur.chunksRebuilt;
|
|
48
|
+
row[n + 2] = cur.meshesRebuilt;
|
|
49
|
+
row[n + 3] = cur.shadowMapsRendered;
|
|
50
|
+
cur.chunksRebuilt = cur.meshesRebuilt = cur.shadowMapsRendered = 0;
|
|
51
|
+
const ring = this.ring;
|
|
52
|
+
const slot = this.frames % RECENT_FRAMES * FIELDS;
|
|
53
|
+
this.frames++;
|
|
54
|
+
const last = this.lastFrame;
|
|
55
|
+
const recent = this.recent;
|
|
56
|
+
let lastScene = 0;
|
|
57
|
+
let recentScene = 0;
|
|
58
|
+
for (let i = 0; i < FIELDS; i++) {
|
|
59
|
+
const sum = recentField(recent, i) - ring[slot + i] + row[i];
|
|
60
|
+
ring[slot + i] = row[i];
|
|
61
|
+
setField(last, i, row[i]);
|
|
62
|
+
setField(recent, i, sum);
|
|
63
|
+
if (i < n && i !== Upload.view) {
|
|
64
|
+
lastScene += row[i];
|
|
65
|
+
recentScene += sum;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
last.sceneBytes = lastScene;
|
|
69
|
+
recent.sceneBytes = recentScene;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function recentField(r, i) {
|
|
73
|
+
const n = UPLOAD_CATEGORIES.length;
|
|
74
|
+
if (i < n) return r.bytes[UPLOAD_CATEGORIES[i]];
|
|
75
|
+
if (i === n) return r.created;
|
|
76
|
+
if (i === n + 1) return r.chunksRebuilt;
|
|
77
|
+
if (i === n + 2) return r.meshesRebuilt;
|
|
78
|
+
return r.shadowMapsRendered;
|
|
79
|
+
}
|
|
80
|
+
function setField(r, i, v) {
|
|
81
|
+
const n = UPLOAD_CATEGORIES.length;
|
|
82
|
+
if (i < n) r.bytes[UPLOAD_CATEGORIES[i]] = v;
|
|
83
|
+
else if (i === n) r.created = v;
|
|
84
|
+
else if (i === n + 1) r.chunksRebuilt = v;
|
|
85
|
+
else if (i === n + 2) r.meshesRebuilt = v;
|
|
86
|
+
else r.shadowMapsRendered = v;
|
|
87
|
+
}
|
|
2
88
|
const RenderStats = defineResource("render/Stats", {
|
|
3
|
-
description: "Per-view counts from the last frame
|
|
4
|
-
init: () =>
|
|
89
|
+
description: "Per-view counts from the last frame (visible, culled, hidden, pending, draw calls), and what the last frame and the last 60 wrote to the GPU and rebuilt.",
|
|
90
|
+
init: () => new RenderStatsMap()
|
|
5
91
|
});
|
|
6
92
|
const GpuMemory = defineResource("render/GpuMemory", {
|
|
7
93
|
description: "GPU memory used by uploaded textures.",
|
|
@@ -13,6 +99,8 @@ const RenderCounters = defineResource("render/Counters", {
|
|
|
13
99
|
});
|
|
14
100
|
export {
|
|
15
101
|
GpuMemory,
|
|
102
|
+
RECENT_FRAMES,
|
|
16
103
|
RenderCounters,
|
|
17
|
-
RenderStats
|
|
104
|
+
RenderStats,
|
|
105
|
+
RenderStatsMap
|
|
18
106
|
};
|