@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,192 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineResource,
|
|
3
|
+
defineSystem,
|
|
4
|
+
ShardError
|
|
5
|
+
} from "@aethervtt/shard-core";
|
|
6
|
+
import { FrameDemand, LogResource, Time } from "@aethervtt/shard-runtime";
|
|
7
|
+
const MAX_SCREEN_EFFECTS = 8;
|
|
8
|
+
const MAX_SCREEN_EFFECT_PARAMS = 8;
|
|
9
|
+
const ScreenEffects = defineResource("render/ScreenEffects", {
|
|
10
|
+
hostWritable: true,
|
|
11
|
+
description: "Typed effects published at a screen point (at most 8): kind, center and radius in CSS pixels, ms to live, source entity, params. The app whose view they are in draws them through handlers registered with onScreenEffect.",
|
|
12
|
+
init: () => ({ effects: [] })
|
|
13
|
+
});
|
|
14
|
+
const ScreenEffectHandlers = defineResource("render/ScreenEffectHandlers", {
|
|
15
|
+
description: "Handlers by kind (onScreenEffect) and the screen effects they are drawing.",
|
|
16
|
+
init: () => ({ kinds: /* @__PURE__ */ new Map(), live: [], reported: /* @__PURE__ */ new Set() })
|
|
17
|
+
});
|
|
18
|
+
function invalid(message, path) {
|
|
19
|
+
return new ShardError("render/invalid-screen-effect", message, {
|
|
20
|
+
path,
|
|
21
|
+
hint: "A screen effect has a kind, a radius of at least 0, and at most 8 params."
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
function publishScreenEffect(world, effect) {
|
|
25
|
+
if (!effect.kind) throw invalid("A screen effect needs a kind", "kind");
|
|
26
|
+
if (!(effect.radius >= 0)) throw invalid(`Radius ${effect.radius} is below 0`, "radius");
|
|
27
|
+
const keys = Object.keys(effect.params);
|
|
28
|
+
if (keys.length > MAX_SCREEN_EFFECT_PARAMS) {
|
|
29
|
+
throw invalid(`${keys.length} params; at most ${MAX_SCREEN_EFFECT_PARAMS}`, "params");
|
|
30
|
+
}
|
|
31
|
+
const effects = world.initResource(ScreenEffects).effects;
|
|
32
|
+
let i = 0;
|
|
33
|
+
while (i < effects.length && (effects[i].source !== effect.source || effects[i].kind !== effect.kind))
|
|
34
|
+
i++;
|
|
35
|
+
if (i === MAX_SCREEN_EFFECTS) return false;
|
|
36
|
+
const e = effects[i];
|
|
37
|
+
if (e) {
|
|
38
|
+
e.screen[0] = effect.screen[0];
|
|
39
|
+
e.screen[1] = effect.screen[1];
|
|
40
|
+
e.radius = effect.radius;
|
|
41
|
+
e.ttlMs = effect.ttlMs;
|
|
42
|
+
e.params = effect.params;
|
|
43
|
+
} else effects.push({ ...effect, screen: [effect.screen[0], effect.screen[1]] });
|
|
44
|
+
world.touchResource(ScreenEffects);
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
function clearScreenEffects(world, source) {
|
|
48
|
+
const value = world.tryResource(ScreenEffects);
|
|
49
|
+
if (!value || value.effects.length === 0) return;
|
|
50
|
+
if (source === void 0) value.effects.length = 0;
|
|
51
|
+
else value.effects = value.effects.filter((e) => e.source !== source);
|
|
52
|
+
world.touchResource(ScreenEffects);
|
|
53
|
+
}
|
|
54
|
+
const NO_OFFSET = [0, 0];
|
|
55
|
+
function forwardScreenEffects(from, to, offset = NO_OFFSET) {
|
|
56
|
+
const source = from.tryResource(ScreenEffects)?.effects ?? [];
|
|
57
|
+
const out = to.initResource(ScreenEffects).effects;
|
|
58
|
+
const n = Math.min(source.length, MAX_SCREEN_EFFECTS);
|
|
59
|
+
for (let i = 0; i < n; i++) {
|
|
60
|
+
const s = source[i];
|
|
61
|
+
const d = out[i];
|
|
62
|
+
if (d) {
|
|
63
|
+
d.kind = s.kind;
|
|
64
|
+
d.radius = s.radius;
|
|
65
|
+
d.ttlMs = s.ttlMs;
|
|
66
|
+
d.source = s.source;
|
|
67
|
+
d.params = s.params;
|
|
68
|
+
} else out.push({ ...s, screen: [0, 0] });
|
|
69
|
+
const e = out[i];
|
|
70
|
+
e.screen[0] = s.screen[0] - offset[0];
|
|
71
|
+
e.screen[1] = s.screen[1] - offset[1];
|
|
72
|
+
}
|
|
73
|
+
if (out.length === 0 && n === 0) return;
|
|
74
|
+
out.length = n;
|
|
75
|
+
to.touchResource(ScreenEffects);
|
|
76
|
+
}
|
|
77
|
+
function onScreenEffect(world, kind, handler) {
|
|
78
|
+
world.initResource(ScreenEffectHandlers).kinds.set(kind, handler);
|
|
79
|
+
}
|
|
80
|
+
const SCREEN_EFFECTS_DEMAND = "render/screen-effects";
|
|
81
|
+
const expireScreenEffects = defineSystem({
|
|
82
|
+
name: "render/expire-screen-effects",
|
|
83
|
+
description: "Counts ScreenEffects down by the frame time and drops expired ones; holds frames while any live.",
|
|
84
|
+
run: (_, world) => {
|
|
85
|
+
const effects = world.tryResource(ScreenEffects)?.effects;
|
|
86
|
+
let n = 0;
|
|
87
|
+
if (effects && effects.length > 0) {
|
|
88
|
+
const ms = world.resource(Time).delta * 1e3;
|
|
89
|
+
for (let i = 0; i < effects.length; i++) {
|
|
90
|
+
const e = effects[i];
|
|
91
|
+
e.ttlMs -= ms;
|
|
92
|
+
if (e.ttlMs > 0 && n < MAX_SCREEN_EFFECTS) effects[n++] = e;
|
|
93
|
+
}
|
|
94
|
+
effects.length = n;
|
|
95
|
+
}
|
|
96
|
+
const live = world.tryResource(ScreenEffectHandlers)?.live.length ?? 0;
|
|
97
|
+
world.tryResource(FrameDemand)?.set(SCREEN_EFFECTS_DEMAND, n > 0 || live > 0);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
function despawnAll(world, entities) {
|
|
101
|
+
for (const e of entities) if (world.isAlive(e)) world.despawn(e);
|
|
102
|
+
}
|
|
103
|
+
const runScreenEffects = defineSystem({
|
|
104
|
+
name: "render/screen-effects",
|
|
105
|
+
description: "Runs the handlers registered with onScreenEffect for live ScreenEffects.",
|
|
106
|
+
run: (_, world) => {
|
|
107
|
+
const handlers = world.tryResource(ScreenEffectHandlers);
|
|
108
|
+
const effects = world.tryResource(ScreenEffects)?.effects;
|
|
109
|
+
if (!handlers || handlers.live.length === 0 && (!effects || effects.length === 0)) return;
|
|
110
|
+
const live = handlers.live;
|
|
111
|
+
for (let i = 0; i < live.length; i++) live[i].seen = false;
|
|
112
|
+
const dt = world.resource(Time).delta;
|
|
113
|
+
if (effects) {
|
|
114
|
+
for (let i = 0; i < effects.length; i++) {
|
|
115
|
+
const e = effects[i];
|
|
116
|
+
let found;
|
|
117
|
+
for (let j = 0; j < live.length; j++) {
|
|
118
|
+
const l = live[j];
|
|
119
|
+
if (l.source === e.source && l.kind === e.kind) {
|
|
120
|
+
found = l;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (found) {
|
|
125
|
+
found.seen = true;
|
|
126
|
+
found.last = e;
|
|
127
|
+
found.handler.update?.(world, e, found.entities, dt);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const handler = handlers.kinds.get(e.kind);
|
|
131
|
+
if (!handler) {
|
|
132
|
+
if (!handlers.reported.has(e.kind)) {
|
|
133
|
+
handlers.reported.add(e.kind);
|
|
134
|
+
world.tryResource(LogResource)?.log("warn", `No handler draws "${e.kind}" effects`, {
|
|
135
|
+
code: "render/unhandled-screen-effect",
|
|
136
|
+
hint: "Register one in this app with onScreenEffect(world, kind, handler)."
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
live.push({
|
|
142
|
+
source: e.source,
|
|
143
|
+
kind: e.kind,
|
|
144
|
+
handler,
|
|
145
|
+
entities: handler.start(world, e) ?? [],
|
|
146
|
+
last: e,
|
|
147
|
+
seen: true
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
let kept = 0;
|
|
152
|
+
for (let i = 0; i < live.length; i++) {
|
|
153
|
+
const l = live[i];
|
|
154
|
+
if (l.seen) {
|
|
155
|
+
live[kept++] = l;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (l.handler.end) l.handler.end(world, l.last, l.entities);
|
|
159
|
+
else despawnAll(world, l.entities);
|
|
160
|
+
}
|
|
161
|
+
live.length = kept;
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
function describeScreenEffects(world) {
|
|
165
|
+
const handlers = world.tryResource(ScreenEffectHandlers);
|
|
166
|
+
return {
|
|
167
|
+
effects: (world.tryResource(ScreenEffects)?.effects ?? []).map((e) => ({
|
|
168
|
+
...e,
|
|
169
|
+
screen: [...e.screen]
|
|
170
|
+
})),
|
|
171
|
+
kinds: handlers ? [...handlers.kinds.keys()].sort() : [],
|
|
172
|
+
drawing: (handlers?.live ?? []).map((l) => ({
|
|
173
|
+
kind: l.kind,
|
|
174
|
+
source: l.source,
|
|
175
|
+
entities: l.entities.length
|
|
176
|
+
}))
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
export {
|
|
180
|
+
MAX_SCREEN_EFFECTS,
|
|
181
|
+
MAX_SCREEN_EFFECT_PARAMS,
|
|
182
|
+
SCREEN_EFFECTS_DEMAND,
|
|
183
|
+
ScreenEffectHandlers,
|
|
184
|
+
ScreenEffects,
|
|
185
|
+
clearScreenEffects,
|
|
186
|
+
describeScreenEffects,
|
|
187
|
+
expireScreenEffects,
|
|
188
|
+
forwardScreenEffects,
|
|
189
|
+
onScreenEffect,
|
|
190
|
+
publishScreenEffect,
|
|
191
|
+
runScreenEffects
|
|
192
|
+
};
|
package/dist/shaders.d.ts
CHANGED
|
@@ -25,6 +25,12 @@ export declare const ViewUniform: import("@aethervtt/shard-core").ComponentDef<{
|
|
|
25
25
|
* that sun, applied to its light (spec 0044). All ones without an atmosphere.
|
|
26
26
|
*/
|
|
27
27
|
readonly sunTransmittance: import("@aethervtt/shard-core").FieldType<[number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], "f32"> & ((options?: import("@aethervtt/shard-core").FieldOptions<[number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number]> | undefined) => import("@aethervtt/shard-core").FieldType<[number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], "f32">);
|
|
28
|
+
/**
|
|
29
|
+
* Screen scale (0057): render pixels per CSS pixel (x); world units per render pixel at one
|
|
30
|
+
* unit of view depth, or everywhere when orthographic (y); 1 when orthographic (z). A CSS-pixel
|
|
31
|
+
* width w at view depth d is w · x · y · d world units (w · x · y orthographic).
|
|
32
|
+
*/
|
|
33
|
+
readonly pixelScale: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec4, "f32"> & ((options?: import("@aethervtt/shard-core").FieldOptions<import("@aethervtt/shard-core").Vec4> | undefined) => import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec4, "f32">);
|
|
28
34
|
}>;
|
|
29
35
|
export declare const viewLayout: import("@aethervtt/shard-shader").WgslLayout<{
|
|
30
36
|
readonly viewProj: import("@aethervtt/shard-core").FieldType<[number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], "f32"> & ((options?: import("@aethervtt/shard-core").FieldOptions<[number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number]> | undefined) => import("@aethervtt/shard-core").FieldType<[number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], "f32">);
|
|
@@ -51,6 +57,12 @@ export declare const viewLayout: import("@aethervtt/shard-shader").WgslLayout<{
|
|
|
51
57
|
* that sun, applied to its light (spec 0044). All ones without an atmosphere.
|
|
52
58
|
*/
|
|
53
59
|
readonly sunTransmittance: import("@aethervtt/shard-core").FieldType<[number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], "f32"> & ((options?: import("@aethervtt/shard-core").FieldOptions<[number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number]> | undefined) => import("@aethervtt/shard-core").FieldType<[number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], "f32">);
|
|
60
|
+
/**
|
|
61
|
+
* Screen scale (0057): render pixels per CSS pixel (x); world units per render pixel at one
|
|
62
|
+
* unit of view depth, or everywhere when orthographic (y); 1 when orthographic (z). A CSS-pixel
|
|
63
|
+
* width w at view depth d is w · x · y · d world units (w · x · y orthographic).
|
|
64
|
+
*/
|
|
65
|
+
readonly pixelScale: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec4, "f32"> & ((options?: import("@aethervtt/shard-core").FieldOptions<import("@aethervtt/shard-core").Vec4> | undefined) => import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec4, "f32">);
|
|
54
66
|
}>;
|
|
55
67
|
export declare const materialLayout: import("@aethervtt/shard-shader").WgslLayout<{
|
|
56
68
|
baseColor: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Color, "f32">;
|
|
@@ -59,6 +71,7 @@ export declare const materialLayout: import("@aethervtt/shard-shader").WgslLayou
|
|
|
59
71
|
emissive: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Color, "f32">;
|
|
60
72
|
emissiveLuminance: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
61
73
|
doubleSided: import("@aethervtt/shard-core").FieldType<boolean, "u8">;
|
|
74
|
+
deferUntilReady: import("@aethervtt/shard-core").FieldType<boolean, "u8">;
|
|
62
75
|
alphaMode: import("@aethervtt/shard-core").FieldType<"additive" | "alpha" | "mask" | "opaque" | "premultiplied", "u8">;
|
|
63
76
|
alphaCutoff: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
64
77
|
normalScale: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
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
|
|
|
@@ -642,7 +688,39 @@ fn apply_lighting(p: PbrInput, world_position: vec3f, frag_coord: vec4f, flags:
|
|
|
642
688
|
// Point and spot lights from this fragment's cluster: intensity (cd) / d\xB2 \xD7 window.
|
|
643
689
|
let cluster = cluster_of(frag_coord, view_depth);
|
|
644
690
|
var count = 0u;
|
|
645
|
-
if (
|
|
691
|
+
@if(BLOCKED_LIGHTS) let receiver = interior_receiver(world_position, n);
|
|
692
|
+
@if(BASELINE) if (cluster >= 0) {
|
|
693
|
+
// The cluster's lights, lowest first; keep the body in step with the loop below.
|
|
694
|
+
var mask = cluster_bits[cluster];
|
|
695
|
+
var index = 0u;
|
|
696
|
+
while (next_light(&mask, &index)) {
|
|
697
|
+
count++;
|
|
698
|
+
let light = lights[index];
|
|
699
|
+
let to_light = light.position - world_position;
|
|
700
|
+
let d2 = max(dot(to_light, to_light), 1e-4);
|
|
701
|
+
let d = sqrt(d2);
|
|
702
|
+
let l = to_light / d;
|
|
703
|
+
var attenuation = light_falloff(light, d, d2);
|
|
704
|
+
if (light.kind == LIGHT_SPOT) {
|
|
705
|
+
let cd = dot(-l, light.direction);
|
|
706
|
+
let spot = clamp(cd * light.spot_scale + light.spot_offset, 0.0, 1.0);
|
|
707
|
+
attenuation *= spot * spot;
|
|
708
|
+
}
|
|
709
|
+
if (attenuation <= 0.0) { continue; }
|
|
710
|
+
@if(BLOCKED_LIGHTS) attenuation *= wall_visibility(light, receiver);
|
|
711
|
+
if (receives && light.shadow != NO_SHADOW) {
|
|
712
|
+
if (light.kind == LIGHT_SPOT) {
|
|
713
|
+
attenuation *= spot_shadow(light.shadow, world_position, n, light.position, light.shadow_bias, light.shadow_normal_bias, light.shadow_softness, frag_coord.xy);
|
|
714
|
+
} else {
|
|
715
|
+
attenuation *= point_shadow(light.shadow, world_position, n, light.shadow_bias, light.shadow_normal_bias, light.shadow_softness, frag_coord.xy);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
let a_light = clamp(a + light.radius / (2.0 * d), 0.0, 1.0);
|
|
719
|
+
let norm = (a / a_light) * (a / a_light);
|
|
720
|
+
color += brdf(n, v, l, a_light, diffuse_color, f0, norm) * light.color * attenuation;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
@if(!BASELINE) if (cluster >= 0) {
|
|
646
724
|
count = clusters[cluster];
|
|
647
725
|
let base = CLUSTER_COUNT + u32(cluster) * MAX_PER_CLUSTER;
|
|
648
726
|
for (var k = 0u; k < count; k++) {
|
|
@@ -651,16 +729,14 @@ fn apply_lighting(p: PbrInput, world_position: vec3f, frag_coord: vec4f, flags:
|
|
|
651
729
|
let d2 = max(dot(to_light, to_light), 1e-4);
|
|
652
730
|
let d = sqrt(d2);
|
|
653
731
|
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;
|
|
732
|
+
var attenuation = light_falloff(light, d, d2);
|
|
658
733
|
if (light.kind == LIGHT_SPOT) {
|
|
659
734
|
let cd = dot(-l, light.direction);
|
|
660
735
|
let spot = clamp(cd * light.spot_scale + light.spot_offset, 0.0, 1.0);
|
|
661
736
|
attenuation *= spot * spot;
|
|
662
737
|
}
|
|
663
738
|
if (attenuation <= 0.0) { continue; }
|
|
739
|
+
@if(BLOCKED_LIGHTS) attenuation *= wall_visibility(light, receiver);
|
|
664
740
|
if (receives && light.shadow != NO_SHADOW) {
|
|
665
741
|
if (light.kind == LIGHT_SPOT) {
|
|
666
742
|
attenuation *= spot_shadow(light.shadow, world_position, n, light.position, light.shadow_bias, light.shadow_normal_bias, light.shadow_softness, frag_coord.xy);
|
|
@@ -683,9 +759,11 @@ fn apply_lighting(p: PbrInput, world_position: vec3f, frag_coord: vec4f, flags:
|
|
|
683
759
|
surface.occlusion *= textureLoad(ao_texture, min(vec2i(frag_coord.xy), dims - 1), 0).r;
|
|
684
760
|
}
|
|
685
761
|
if (view.envParams.w > 0.5) {
|
|
686
|
-
color += environment_light(surface, n, v);
|
|
762
|
+
@if(!SKY_VISIBILITY) color += environment_light(surface, n, v);
|
|
763
|
+
@if(SKY_VISIBILITY) color += interior_ambient(environment_light(surface, n, v), diffuse_color * surface.occlusion, world_position, n);
|
|
687
764
|
} else {
|
|
688
|
-
color += diffuse_color * view.ambient * surface.occlusion;
|
|
765
|
+
@if(!SKY_VISIBILITY) color += diffuse_color * view.ambient * surface.occlusion;
|
|
766
|
+
@if(SKY_VISIBILITY) color += interior_ambient(diffuse_color * view.ambient * surface.occlusion, diffuse_color * surface.occlusion, world_position, n);
|
|
689
767
|
}
|
|
690
768
|
|
|
691
769
|
let debug = u32(view.clusterParams.w);
|
|
@@ -912,6 +990,7 @@ import shard::mesh::mesh_vertex_at;
|
|
|
912
990
|
import shard::pbr::material::{ pbr_input, fragment_output };
|
|
913
991
|
import shard::pbr::standard::material;
|
|
914
992
|
import shard::pbr::lighting::apply_lighting;
|
|
993
|
+
@if(CUTAWAY) import shard::cutaway::cutaway_clip;
|
|
915
994
|
|
|
916
995
|
@vertex fn vs(
|
|
917
996
|
@builtin(instance_index) instance_index: u32,
|
|
@@ -928,12 +1007,15 @@ import shard::pbr::lighting::apply_lighting;
|
|
|
928
1007
|
}
|
|
929
1008
|
|
|
930
1009
|
@fragment fn fs(in: VertexOutput) -> @location(0) vec4f {
|
|
1010
|
+
@if(CUTAWAY) cutaway_clip(in.world_position, in.clip.xy, in.flags);
|
|
931
1011
|
let p = pbr_input(in);
|
|
932
1012
|
if (material.alphaMode == 1u && p.alpha < material.alphaCutoff) { discard; }
|
|
933
1013
|
let color = (apply_lighting(p, in.world_position, in.clip, in.flags) + p.emissive) * view.exposure;
|
|
934
1014
|
// Premultiplied blending expects color \xD7 alpha; additive and alpha blend multiply in hardware.
|
|
935
1015
|
@if(PREMULTIPLY) return fragment_output(vec4f(color * p.alpha, p.alpha));
|
|
936
|
-
|
|
1016
|
+
// Opaque and masked surfaces cover their pixel, whatever their base color's alpha (0052).
|
|
1017
|
+
@if(OPAQUE) return vec4f(fragment_output(vec4f(color, p.alpha)).rgb, 1.0);
|
|
1018
|
+
@if(!PREMULTIPLY && !OPAQUE) return fragment_output(vec4f(color, p.alpha));
|
|
937
1019
|
}`,
|
|
938
1020
|
"shard::post::depth_resolve": `
|
|
939
1021
|
@group(0) @binding(0) var depth_ms: texture_depth_multisampled_2d;
|
|
@@ -950,7 +1032,7 @@ import shard::pbr::types::PbrInput;
|
|
|
950
1032
|
@group(0) @binding(10) var env_lut: texture_2d<f32>;
|
|
951
1033
|
@group(0) @binding(11) var env_sampler: sampler;
|
|
952
1034
|
/** 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>;
|
|
1035
|
+
@data(uniform) @group(0) @binding(12) var<storage, read> env_sh: array<vec4f, 9>;
|
|
954
1036
|
@group(0) @binding(13) var env_source: texture_cube<f32>;
|
|
955
1037
|
|
|
956
1038
|
const ENV_SPECULAR_MIPS: f32 = 5.0;
|
|
@@ -1065,6 +1147,13 @@ fn apply_lut(c: vec3f) -> vec3f {
|
|
|
1065
1147
|
@fragment fn fs(@builtin(position) p: vec4f) -> @location(0) vec4f {
|
|
1066
1148
|
let texel = textureLoad(hdr, vec2i(p.xy), 0);
|
|
1067
1149
|
var hdr_color = max(texel.rgb, vec3f(0.0));
|
|
1150
|
+
@if(TRANSPARENT) var alpha = 1.0;
|
|
1151
|
+
@if(TRANSPARENT) {
|
|
1152
|
+
// Premultiplied (0052): light over less coverage raises alpha, so a glow shows over the page;
|
|
1153
|
+
// then the color is un-premultiplied for grading and the curve, and premultiplied again below.
|
|
1154
|
+
alpha = clamp(max(texel.a, max(hdr_color.r, max(hdr_color.g, hdr_color.b))), 0.0, 1.0);
|
|
1155
|
+
hdr_color = hdr_color / max(alpha, 1e-6);
|
|
1156
|
+
}
|
|
1068
1157
|
let flags = params.mode.z;
|
|
1069
1158
|
if ((flags & 1u) != 0u) { hdr_color = grade(hdr_color); }
|
|
1070
1159
|
if ((flags & 2u) != 0u) {
|
|
@@ -1080,9 +1169,12 @@ fn apply_lut(c: vec3f) -> vec3f {
|
|
|
1080
1169
|
let n = ign(p.xy) + ign(p.xy + vec2f(71.0, 13.0)) - 1.0;
|
|
1081
1170
|
c = c + vec3f(n / 255.0);
|
|
1082
1171
|
}
|
|
1172
|
+
// The page composites in display space, so premultiply there; it keeps rgb <= alpha.
|
|
1173
|
+
@if(TRANSPARENT) c = clamp(c, vec3f(0.0), vec3f(1.0)) * alpha;
|
|
1083
1174
|
// sRGB targets encode in hardware: hand them linear values.
|
|
1084
1175
|
@if(SRGB_TARGET) c = srgb_to_linear(clamp(c, vec3f(0.0), vec3f(1.0)));
|
|
1085
|
-
return vec4f(c,
|
|
1176
|
+
@if(TRANSPARENT) return vec4f(c, max(alpha, max(c.r, max(c.g, c.b))));
|
|
1177
|
+
@if(!TRANSPARENT) return vec4f(c, 1.0);
|
|
1086
1178
|
}`,
|
|
1087
1179
|
"shard::post::upscale": `
|
|
1088
1180
|
struct Upscale {
|
|
@@ -1094,8 +1186,18 @@ struct Upscale {
|
|
|
1094
1186
|
@group(0) @binding(1) var input_sampler: sampler;
|
|
1095
1187
|
@group(0) @binding(2) var<uniform> upscale: Upscale;
|
|
1096
1188
|
|
|
1097
|
-
|
|
1098
|
-
|
|
1189
|
+
@if(!TRANSPARENT) alias Texel = vec3f;
|
|
1190
|
+
@if(TRANSPARENT) alias Texel = vec4f;
|
|
1191
|
+
|
|
1192
|
+
fn at(uv: vec2f) -> Texel {
|
|
1193
|
+
@if(!TRANSPARENT) return textureSampleLevel(input, input_sampler, uv, 0.0).rgb;
|
|
1194
|
+
@if(TRANSPARENT) return textureSampleLevel(input, input_sampler, uv, 0.0);
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
/** Opaque views write alpha 1; premultiplied ones keep rgb <= alpha after sharpening (0052). */
|
|
1198
|
+
fn out(c: Texel) -> vec4f {
|
|
1199
|
+
@if(!TRANSPARENT) return vec4f(c, 1.0);
|
|
1200
|
+
@if(TRANSPARENT) return vec4f(c.rgb, max(c.a, max(c.r, max(c.g, c.b))));
|
|
1099
1201
|
}
|
|
1100
1202
|
|
|
1101
1203
|
/**
|
|
@@ -1106,7 +1208,7 @@ fn at(uv: vec2f) -> vec3f {
|
|
|
1106
1208
|
let uv = frag.xy * upscale.params.xy;
|
|
1107
1209
|
let c = at(uv);
|
|
1108
1210
|
let k = upscale.params.z;
|
|
1109
|
-
if (k <= 0.0) { return
|
|
1211
|
+
if (k <= 0.0) { return out(c); }
|
|
1110
1212
|
let texel = 1.0 / vec2f(textureDimensions(input));
|
|
1111
1213
|
let n = at(uv + vec2f(0.0, -texel.y));
|
|
1112
1214
|
let s = at(uv + vec2f(0.0, texel.y));
|
|
@@ -1115,7 +1217,7 @@ fn at(uv: vec2f) -> vec3f {
|
|
|
1115
1217
|
let lo = min(c, min(min(n, s), min(e, w)));
|
|
1116
1218
|
let hi = max(c, max(max(n, s), max(e, w)));
|
|
1117
1219
|
let sharp = c + (4.0 * c - (n + s + e + w)) * (0.25 * k);
|
|
1118
|
-
return
|
|
1220
|
+
return out(clamp(sharp, lo, hi));
|
|
1119
1221
|
}`,
|
|
1120
1222
|
"shard::post::common": `
|
|
1121
1223
|
import shard::view::view;
|
|
@@ -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;
|