@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
package/dist/post-nodes.d.ts
CHANGED
|
@@ -32,7 +32,8 @@ export declare const adaptExposure: import("@aethervtt/shard-core").SystemDef<{
|
|
|
32
32
|
/** Graph node names of the post chain, in order (for describe and tests). */
|
|
33
33
|
export declare const POST_NODES: readonly [readonly ['post/fog', 1], readonly ['post/taa', 2], readonly ['post/motion-blur', 4], readonly ['post/dof', 8], readonly ['post/bloom', 16], readonly ['post/exposure', 32], readonly ['post/fxaa', 64]];
|
|
34
34
|
/** Adds the prepass, SSAO, and the post chain to the graph. */
|
|
35
|
-
|
|
35
|
+
/** The post chain's nodes. `baseline`: the baseline tier's exposure meter (0064), when on one. */
|
|
36
|
+
export declare function addPostNodes(world: World, baseline?: typeof import('./baseline/exposure.js')): void;
|
|
36
37
|
/** The post section of `render.describe`: per view, active effects in order with GPU time. */
|
|
37
38
|
export declare function describePost(world: World): {
|
|
38
39
|
views: Record<string, unknown>;
|
package/dist/post-nodes.js
CHANGED
|
@@ -6,12 +6,14 @@ import {
|
|
|
6
6
|
} from "@aethervtt/shard-core";
|
|
7
7
|
import { Time } from "@aethervtt/shard-runtime";
|
|
8
8
|
import { Exposure } from "./camera.js";
|
|
9
|
+
import { addRenderFeatures } from "./features.js";
|
|
9
10
|
import { drawMaterials, ForwardStateResource, PASS_PREPASS, viewBindGroup } from "./forward.js";
|
|
10
11
|
import { RenderPhase } from "./graph.js";
|
|
11
12
|
import { Graph, Views } from "./plugin.js";
|
|
12
13
|
import { AutoExposure, cocParams, hasEffect, needsPrepass, PostEffect } from "./post.js";
|
|
13
14
|
import {
|
|
14
15
|
beginPass,
|
|
16
|
+
depthTex,
|
|
15
17
|
forwardView,
|
|
16
18
|
HDR,
|
|
17
19
|
idOf,
|
|
@@ -42,6 +44,7 @@ const prepassNode = {
|
|
|
42
44
|
if (v.cam.deferred) drawMaterials(ctx, state, v.pv, v.cam, v.cam.forwardOnly, PASS_PREPASS);
|
|
43
45
|
}
|
|
44
46
|
};
|
|
47
|
+
const FOG_KEYS = ["post/fog", "post/fog/alpha", "post/fog/interior", "post/fog/alpha/interior"];
|
|
45
48
|
const SSAO_STEPS = [
|
|
46
49
|
[1, 4],
|
|
47
50
|
[2, 4],
|
|
@@ -70,11 +73,11 @@ function ssaoNode(phase, deferred) {
|
|
|
70
73
|
layouts = {
|
|
71
74
|
gtao: gpu.layouts.bindGroupLayout({
|
|
72
75
|
label: "ssao/gtao",
|
|
73
|
-
entries: [uniform(0),
|
|
76
|
+
entries: [uniform(0), depthTex(gpu, 1), tex(2, "unfilterable-float"), uniform(3)]
|
|
74
77
|
}),
|
|
75
78
|
up: gpu.layouts.bindGroupLayout({
|
|
76
79
|
label: "ssao/upsample",
|
|
77
|
-
entries: [uniform(0),
|
|
80
|
+
entries: [uniform(0), depthTex(gpu, 1), uniform(3), tex(4, "unfilterable-float")]
|
|
78
81
|
})
|
|
79
82
|
};
|
|
80
83
|
}
|
|
@@ -170,16 +173,18 @@ function fogNode() {
|
|
|
170
173
|
layoutGen = gpu.generation;
|
|
171
174
|
layout = gpu.layouts.bindGroupLayout({
|
|
172
175
|
label: "fog",
|
|
173
|
-
entries: [tex(0, "unfilterable-float"),
|
|
176
|
+
entries: [tex(0, "unfilterable-float"), depthTex(gpu, 1), uniform(2)]
|
|
174
177
|
});
|
|
175
178
|
}
|
|
179
|
+
const alpha = v.cam.alphaOutput;
|
|
176
180
|
const pipeline = cache.render(
|
|
177
181
|
ctx,
|
|
178
|
-
|
|
182
|
+
FOG_KEYS[(alpha ? 1 : 0) + (state.interiorMode !== 0 ? 2 : 0)],
|
|
179
183
|
"shard::post::fog",
|
|
180
184
|
"fs",
|
|
181
185
|
[state.layouts.view, layout],
|
|
182
|
-
HDR
|
|
186
|
+
HDR,
|
|
187
|
+
{ TRANSPARENT: alpha }
|
|
183
188
|
);
|
|
184
189
|
if (!pipeline) return;
|
|
185
190
|
const f = v.cam.post.fog;
|
|
@@ -233,7 +238,7 @@ function taaNode() {
|
|
|
233
238
|
entries: [
|
|
234
239
|
uniform(0),
|
|
235
240
|
tex(1, "unfilterable-float"),
|
|
236
|
-
|
|
241
|
+
depthTex(gpu, 2),
|
|
237
242
|
tex(3, "unfilterable-float"),
|
|
238
243
|
tex(4),
|
|
239
244
|
sampler(5),
|
|
@@ -242,13 +247,15 @@ function taaNode() {
|
|
|
242
247
|
});
|
|
243
248
|
histories.clear();
|
|
244
249
|
}
|
|
250
|
+
const alpha = cameraOf(ctx.view)?.alphaOutput === true;
|
|
245
251
|
const pipeline = cache.render(
|
|
246
252
|
ctx,
|
|
247
|
-
"post/taa",
|
|
253
|
+
alpha ? "post/taa/alpha" : "post/taa",
|
|
248
254
|
"shard::post::taa",
|
|
249
255
|
"fs",
|
|
250
256
|
[layout],
|
|
251
|
-
[{ format: "rgba16float" }, { format: "rgba16float" }]
|
|
257
|
+
[{ format: "rgba16float" }, { format: "rgba16float" }],
|
|
258
|
+
{ TRANSPARENT: alpha }
|
|
252
259
|
);
|
|
253
260
|
if (!pipeline) return;
|
|
254
261
|
const input = ctx.texture("taa-in");
|
|
@@ -330,7 +337,7 @@ function motionBlurNode() {
|
|
|
330
337
|
entries: [
|
|
331
338
|
uniform(0),
|
|
332
339
|
tex(1),
|
|
333
|
-
|
|
340
|
+
depthTex(gpu, 2),
|
|
334
341
|
tex(3, "unfilterable-float"),
|
|
335
342
|
uniform(4),
|
|
336
343
|
sampler(5)
|
|
@@ -396,7 +403,7 @@ function dofNode() {
|
|
|
396
403
|
layoutGen = gpu.generation;
|
|
397
404
|
layout = gpu.layouts.bindGroupLayout({
|
|
398
405
|
label: "dof",
|
|
399
|
-
entries: [uniform(0), tex(1),
|
|
406
|
+
entries: [uniform(0), tex(1), depthTex(gpu, 2), uniform(3), tex(4), sampler(5)]
|
|
400
407
|
});
|
|
401
408
|
}
|
|
402
409
|
const prepare = cache.render(ctx, "dof/prepare", "shard::post::dof", "prepare", [layout], HDR);
|
|
@@ -586,12 +593,22 @@ function histogramEv(bins) {
|
|
|
586
593
|
}
|
|
587
594
|
return weight > 0 ? sum / weight : void 0;
|
|
588
595
|
}
|
|
589
|
-
function
|
|
596
|
+
function landReading(meters, entity, index, ev) {
|
|
597
|
+
const m = meters.get(entity);
|
|
598
|
+
if (m && ev !== void 0 && index > m.reading) {
|
|
599
|
+
m.metered = ev;
|
|
600
|
+
m.reading = index;
|
|
601
|
+
m.readings++;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
function autoExposureNode(baseline) {
|
|
590
605
|
const cache = new PostCache();
|
|
591
606
|
let layout;
|
|
592
607
|
let layoutGen = -1;
|
|
593
608
|
const perView = /* @__PURE__ */ new Map();
|
|
609
|
+
const meterViews = /* @__PURE__ */ new Map();
|
|
594
610
|
const zero = new Uint32Array(256);
|
|
611
|
+
const bins = new Uint32Array(256);
|
|
595
612
|
return {
|
|
596
613
|
kind: "raw",
|
|
597
614
|
phase: RenderPhase.Post + 50,
|
|
@@ -602,6 +619,63 @@ function autoExposureNode() {
|
|
|
602
619
|
const v = forwardView(ctx);
|
|
603
620
|
if (!v) return;
|
|
604
621
|
const gpu = ctx.gpu;
|
|
622
|
+
if (baseline) {
|
|
623
|
+
const layout2 = baseline.meterLayout(gpu);
|
|
624
|
+
const pipeline2 = cache.render(
|
|
625
|
+
ctx,
|
|
626
|
+
"post/exposure-meter",
|
|
627
|
+
"shard::post::baseline::meter",
|
|
628
|
+
"main",
|
|
629
|
+
[layout2],
|
|
630
|
+
[{ format: "rg16float" }]
|
|
631
|
+
);
|
|
632
|
+
if (!pipeline2) return;
|
|
633
|
+
let m = meterViews.get(ctx.view.name);
|
|
634
|
+
if (!m || m.generation !== gpu.generation) {
|
|
635
|
+
m = baseline.meterView(gpu, ctx.view.name, METER_READBACKS);
|
|
636
|
+
meterViews.set(ctx.view.name, m);
|
|
637
|
+
}
|
|
638
|
+
const input2 = ctx.texture("post-hdr");
|
|
639
|
+
const key = `${idOf(input2)}/${v.pv.uniform.version}`;
|
|
640
|
+
if (!m.group || m.groupKey !== key) {
|
|
641
|
+
m.groupKey = key;
|
|
642
|
+
m.group = gpu.device.createBindGroup({
|
|
643
|
+
label: `${ctx.view.name}/exposure`,
|
|
644
|
+
layout: layout2,
|
|
645
|
+
entries: [
|
|
646
|
+
{ binding: 0, resource: { buffer: v.pv.uniform.buffer } },
|
|
647
|
+
{ binding: 1, resource: input2.createView() },
|
|
648
|
+
{ binding: 2, resource: { buffer: m.params } }
|
|
649
|
+
]
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
scratch[0] = METER_MIN_EV;
|
|
653
|
+
scratch[1] = METER_BINS_PER_EV;
|
|
654
|
+
scratch[2] = v.cam.post.exposure.metering;
|
|
655
|
+
scratch[3] = 0;
|
|
656
|
+
const k2 = baseline.renderMeter(ctx, m, pipeline2, m.group, scratch.subarray(0, 4));
|
|
657
|
+
if (k2 === void 0) return;
|
|
658
|
+
const meters2 = ctx.world.resource(ExposureMeters);
|
|
659
|
+
const entity2 = v.cam.entity;
|
|
660
|
+
const meter2 = meters2.get(entity2);
|
|
661
|
+
const index2 = meter2 ? ++meter2.submitted : 0;
|
|
662
|
+
const readback2 = m.readbacks[k2];
|
|
663
|
+
const busy2 = m.busy;
|
|
664
|
+
ctx.afterSubmit(() => {
|
|
665
|
+
readback2.mapAsync(GPUMapMode.READ).then(
|
|
666
|
+
() => {
|
|
667
|
+
baseline.binMeter(readback2.getMappedRange(), METER_MIN_EV, METER_BINS_PER_EV, bins);
|
|
668
|
+
readback2.unmap();
|
|
669
|
+
busy2[k2] = false;
|
|
670
|
+
landReading(meters2, entity2, index2, histogramEv(bins));
|
|
671
|
+
},
|
|
672
|
+
() => {
|
|
673
|
+
busy2[k2] = false;
|
|
674
|
+
}
|
|
675
|
+
);
|
|
676
|
+
});
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
605
679
|
const C = GPUShaderStage.COMPUTE;
|
|
606
680
|
if (!layout || layoutGen !== gpu.generation) {
|
|
607
681
|
layoutGen = gpu.generation;
|
|
@@ -687,12 +761,7 @@ function autoExposureNode() {
|
|
|
687
761
|
const ev = histogramEv(new Uint32Array(readback.getMappedRange().slice(0)));
|
|
688
762
|
readback.unmap();
|
|
689
763
|
busy[k] = false;
|
|
690
|
-
|
|
691
|
-
if (m && ev !== void 0 && index > m.reading) {
|
|
692
|
-
m.metered = ev;
|
|
693
|
-
m.reading = index;
|
|
694
|
-
m.readings++;
|
|
695
|
-
}
|
|
764
|
+
landReading(meters, entity, index, ev);
|
|
696
765
|
},
|
|
697
766
|
() => {
|
|
698
767
|
busy[k] = false;
|
|
@@ -753,7 +822,7 @@ const POST_NODES = [
|
|
|
753
822
|
["post/fxaa", PostEffect.Fxaa]
|
|
754
823
|
];
|
|
755
824
|
const halfSize = { divide: 2 };
|
|
756
|
-
function addPostNodes(world) {
|
|
825
|
+
function addPostNodes(world, baseline) {
|
|
757
826
|
const graph = world.resource(Graph);
|
|
758
827
|
world.initResource(ExposureMeters);
|
|
759
828
|
graph.declare({ name: "prepass-normal", format: "rgba16float" });
|
|
@@ -774,6 +843,29 @@ function addPostNodes(world) {
|
|
|
774
843
|
return cam ? bloomLevels(cam) : 1;
|
|
775
844
|
}
|
|
776
845
|
});
|
|
846
|
+
addRenderFeatures(world, {
|
|
847
|
+
name: "render/post",
|
|
848
|
+
description: "The prepass, SSAO, fog, TAA, motion blur, depth of field and bloom.",
|
|
849
|
+
nodes: [
|
|
850
|
+
"prepass",
|
|
851
|
+
"ssao",
|
|
852
|
+
"ssao-deferred",
|
|
853
|
+
"post/fog",
|
|
854
|
+
"post/taa",
|
|
855
|
+
"post/motion-blur",
|
|
856
|
+
"post/dof",
|
|
857
|
+
"post/bloom"
|
|
858
|
+
],
|
|
859
|
+
baseline: { strategy: "The same fragment passes" }
|
|
860
|
+
});
|
|
861
|
+
addRenderFeatures(world, {
|
|
862
|
+
name: "render/auto-exposure",
|
|
863
|
+
description: "Automatic exposure from a luminance histogram.",
|
|
864
|
+
nodes: ["post/exposure"],
|
|
865
|
+
baseline: {
|
|
866
|
+
strategy: "A 64\xD736 meter image read back and binned into the same histogram on the CPU"
|
|
867
|
+
}
|
|
868
|
+
});
|
|
777
869
|
graph.addNode("prepass", prepassNode);
|
|
778
870
|
graph.addNode("ssao", ssaoNode(RenderPhase.Prepass + 50, false));
|
|
779
871
|
graph.addNode("ssao-deferred", ssaoNode(RenderPhase.Opaque + 20, true));
|
|
@@ -782,7 +874,7 @@ function addPostNodes(world) {
|
|
|
782
874
|
graph.addNode("post/motion-blur", motionBlurNode());
|
|
783
875
|
graph.addNode("post/dof", dofNode());
|
|
784
876
|
graph.addNode("post/bloom", bloomNode());
|
|
785
|
-
graph.addNode("post/exposure", autoExposureNode());
|
|
877
|
+
graph.addNode("post/exposure", autoExposureNode(baseline));
|
|
786
878
|
}
|
|
787
879
|
const DESCRIBED = [
|
|
788
880
|
["ssao", "ssao", PostEffect.Ssao],
|
package/dist/post-plugin.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { PostUpdate } from "@aethervtt/shard-core";
|
|
2
2
|
import { definePlugin } from "@aethervtt/shard-runtime";
|
|
3
3
|
import { applyPhysicalCameras } from "./camera.js";
|
|
4
|
-
import { RenderDescribers, Shaders } from "./plugin.js";
|
|
4
|
+
import { Gpu, RenderDescribers, Shaders } from "./plugin.js";
|
|
5
5
|
import { PostEffect, PostFeatures } from "./post.js";
|
|
6
6
|
import { adaptExposure, addPostNodes, describePost, ExposureMeters } from "./post-nodes.js";
|
|
7
7
|
import { POST_SHADERS } from "./post-shaders.js";
|
|
@@ -15,10 +15,12 @@ const postPlugin = definePlugin({
|
|
|
15
15
|
app.world.initResource(PostFeatures).effects |= POST_EFFECTS;
|
|
16
16
|
app.addSystems(PostUpdate, adaptExposure.after(applyPhysicalCameras));
|
|
17
17
|
},
|
|
18
|
-
ready(app) {
|
|
18
|
+
async ready(app) {
|
|
19
|
+
const baseline = app.world.resource(Gpu).tier === "baseline" ? await import("./baseline/exposure.js") : void 0;
|
|
19
20
|
registerShaders(app.world.resource(Shaders), POST_SHADERS);
|
|
21
|
+
if (baseline) registerShaders(app.world.resource(Shaders), baseline.BASELINE_EXPOSURE_SHADERS);
|
|
20
22
|
app.world.initResource(RenderDescribers).set("post", (world) => describePost(world));
|
|
21
|
-
addPostNodes(app.world);
|
|
23
|
+
addPostNodes(app.world, baseline);
|
|
22
24
|
}
|
|
23
25
|
});
|
|
24
26
|
export {
|
package/dist/post-shaders.js
CHANGED
|
@@ -69,6 +69,7 @@ fn prepass_output(in: PrepassVertex, n: vec3f) -> PrepassOutput {
|
|
|
69
69
|
import shard::prepass::common::{ PrepassVertex, PrepassOutput, prepass_vertex, prepass_output, surface_of };
|
|
70
70
|
import shard::pbr::material::pbr_input;
|
|
71
71
|
import shard::pbr::standard::material;
|
|
72
|
+
@if(CUTAWAY) import shard::cutaway::cutaway_clip;
|
|
72
73
|
|
|
73
74
|
@vertex fn vs(
|
|
74
75
|
@builtin(instance_index) instance_index: u32,
|
|
@@ -84,12 +85,20 @@ import shard::pbr::standard::material;
|
|
|
84
85
|
|
|
85
86
|
/** Depth, the shaded normal (normal maps included), and velocity. */
|
|
86
87
|
@fragment fn fs(in: PrepassVertex) -> PrepassOutput {
|
|
88
|
+
@if(CUTAWAY) cutaway_clip(in.world_position, in.clip.xy, in.flags);
|
|
87
89
|
let p = pbr_input(surface_of(in));
|
|
88
90
|
@if(MASK) if (p.alpha < material.alphaCutoff) { discard; }
|
|
89
91
|
return prepass_output(in, p.normal);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Baseline (0064): depth alone, for what reads depth when the scene is multisampled. */
|
|
95
|
+
@if(BASELINE) @fragment fn fs_depth(in: PrepassVertex) {
|
|
96
|
+
@if(CUTAWAY) cutaway_clip(in.world_position, in.clip.xy, in.flags);
|
|
97
|
+
@if(MASK) if (pbr_input(surface_of(in)).alpha < material.alphaCutoff) { discard; }
|
|
90
98
|
}`,
|
|
91
99
|
"shard::prepass::plain": `
|
|
92
100
|
import shard::prepass::common::{ PrepassVertex, PrepassOutput, prepass_vertex, prepass_output };
|
|
101
|
+
@if(CUTAWAY) import shard::cutaway::cutaway_clip;
|
|
93
102
|
|
|
94
103
|
@vertex fn vs(
|
|
95
104
|
@builtin(instance_index) instance_index: u32,
|
|
@@ -105,7 +114,13 @@ import shard::prepass::common::{ PrepassVertex, PrepassOutput, prepass_vertex, p
|
|
|
105
114
|
|
|
106
115
|
/** Materials with their own shading: the geometric normal. */
|
|
107
116
|
@fragment fn fs(in: PrepassVertex) -> PrepassOutput {
|
|
117
|
+
@if(CUTAWAY) cutaway_clip(in.world_position, in.clip.xy, in.flags);
|
|
108
118
|
return prepass_output(in, normalize(in.world_normal));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Baseline (0064): depth alone. */
|
|
122
|
+
@if(BASELINE) @fragment fn fs_depth(in: PrepassVertex) {
|
|
123
|
+
@if(CUTAWAY) cutaway_clip(in.world_position, in.clip.xy, in.flags);
|
|
109
124
|
}`,
|
|
110
125
|
"shard::post::fog": `
|
|
111
126
|
import shard::view::view;
|
|
@@ -159,7 +174,10 @@ fn optical_depth(y0: f32, dir_y: f32, t0: f32, t1: f32) -> f32 {
|
|
|
159
174
|
sun = light.color * view.sunTransmittance[0].rgb * mix(1.0 / (4.0 * PI), hg, fog.sun);
|
|
160
175
|
}
|
|
161
176
|
let inscatter = fog.color.rgb * (sky + sun) * view.exposure;
|
|
162
|
-
|
|
177
|
+
let color = c.rgb * transmittance + inscatter * (1.0 - transmittance);
|
|
178
|
+
@if(!TRANSPARENT) return vec4f(color, c.a);
|
|
179
|
+
// Premultiplied (0052): the fog is a layer of its own opacity over what's behind it.
|
|
180
|
+
@if(TRANSPARENT) return vec4f(color, c.a * transmittance + (1.0 - transmittance));
|
|
163
181
|
}`,
|
|
164
182
|
"shard::post::bloom": `
|
|
165
183
|
import shard::color::luminance;
|
|
@@ -461,12 +479,19 @@ fn sample_history(uv: vec2f) -> vec3f {
|
|
|
461
479
|
var hi = vec3f(-1e20);
|
|
462
480
|
var nearest = 0.0;
|
|
463
481
|
var nearest_px = px;
|
|
482
|
+
@if(TRANSPARENT) var alpha_lo = 1.0;
|
|
483
|
+
@if(TRANSPARENT) var alpha_hi = 0.0;
|
|
464
484
|
for (var y = -1; y <= 1; y++) {
|
|
465
485
|
for (var x = -1; x <= 1; x++) {
|
|
466
486
|
let q = clamp(px + vec2i(x, y), vec2i(0), size - 1);
|
|
467
|
-
let
|
|
487
|
+
let texel = textureLoad(input, q, 0);
|
|
488
|
+
let c = rgb_to_ycocg(compress(texel.rgb));
|
|
468
489
|
lo = min(lo, c);
|
|
469
490
|
hi = max(hi, c);
|
|
491
|
+
@if(TRANSPARENT) {
|
|
492
|
+
alpha_lo = min(alpha_lo, texel.a);
|
|
493
|
+
alpha_hi = max(alpha_hi, texel.a);
|
|
494
|
+
}
|
|
470
495
|
let d = textureLoad(depth_texture, q, 0);
|
|
471
496
|
if (d > nearest) { nearest = d; nearest_px = q; }
|
|
472
497
|
}
|
|
@@ -487,8 +512,17 @@ fn sample_history(uv: vec2f) -> vec3f {
|
|
|
487
512
|
let blended = mix(ycocg_to_rgb(h), current, alpha);
|
|
488
513
|
let color = expand(max(blended, vec3f(0.0)));
|
|
489
514
|
var out: TaaOutput;
|
|
490
|
-
|
|
491
|
-
|
|
515
|
+
@if(!TRANSPARENT) {
|
|
516
|
+
out.color = vec4f(color, 1.0);
|
|
517
|
+
out.history = vec4f(color, 1.0);
|
|
518
|
+
}
|
|
519
|
+
@if(TRANSPARENT) {
|
|
520
|
+
// Coverage resolves like color: history clamped to the neighborhood, blended at the same rate.
|
|
521
|
+
let past = clamp(textureSampleLevel(history, linear_sampler, prev_uv, 0.0).a, alpha_lo, alpha_hi);
|
|
522
|
+
let coverage = mix(past, textureLoad(input, px, 0).a, alpha);
|
|
523
|
+
out.color = vec4f(color, coverage);
|
|
524
|
+
out.history = vec4f(color, coverage);
|
|
525
|
+
}
|
|
492
526
|
return out;
|
|
493
527
|
}`,
|
|
494
528
|
"shard::post::ssao": `
|
|
@@ -614,6 +648,13 @@ fn at(uv: vec2f, o: vec2f, texel: vec2f) -> vec3f {
|
|
|
614
648
|
return textureSampleLevel(input, input_sampler, uv + o * texel, 0.0).rgb;
|
|
615
649
|
}
|
|
616
650
|
|
|
651
|
+
/** The output at uv: opaque views write alpha 1; premultiplied ones carry it, rgb <= alpha (0052). */
|
|
652
|
+
fn result(uv: vec2f) -> vec4f {
|
|
653
|
+
let c = textureSampleLevel(input, input_sampler, uv, 0.0);
|
|
654
|
+
@if(!TRANSPARENT) return vec4f(c.rgb, 1.0);
|
|
655
|
+
@if(TRANSPARENT) return vec4f(c.rgb, max(c.a, max(c.r, max(c.g, c.b))));
|
|
656
|
+
}
|
|
657
|
+
|
|
617
658
|
/** FXAA 3.11 (Lottes), quality preset 12: local contrast, edge direction, end search, blend. */
|
|
618
659
|
@fragment fn fs(@builtin(position) frag: vec4f) -> @location(0) vec4f {
|
|
619
660
|
let size = vec2f(textureDimensions(input));
|
|
@@ -628,7 +669,10 @@ fn at(uv: vec2f, o: vec2f, texel: vec2f) -> vec3f {
|
|
|
628
669
|
let lo = min(m, min(min(n, s), min(e, w)));
|
|
629
670
|
let hi = max(m, max(max(n, s), max(e, w)));
|
|
630
671
|
let range = hi - lo;
|
|
631
|
-
if (range < max(0.0312, hi * 0.125)) {
|
|
672
|
+
if (range < max(0.0312, hi * 0.125)) {
|
|
673
|
+
@if(!TRANSPARENT) return vec4f(center, 1.0);
|
|
674
|
+
@if(TRANSPARENT) return result(uv);
|
|
675
|
+
}
|
|
632
676
|
let nw = luma(at(uv, vec2f(-1.0, -1.0), texel));
|
|
633
677
|
let ne = luma(at(uv, vec2f(1.0, -1.0), texel));
|
|
634
678
|
let sw = luma(at(uv, vec2f(-1.0, 1.0), texel));
|
|
@@ -689,7 +733,8 @@ fn at(uv: vec2f, o: vec2f, texel: vec2f) -> vec3f {
|
|
|
689
733
|
let blend = max(edge_blend, sub_blend);
|
|
690
734
|
var final_uv = uv;
|
|
691
735
|
if (horizontal) { final_uv.y += blend * step_len; } else { final_uv.x += blend * step_len; }
|
|
692
|
-
return vec4f(textureSampleLevel(input, input_sampler, final_uv, 0.0).rgb, 1.0);
|
|
736
|
+
@if(!TRANSPARENT) return vec4f(textureSampleLevel(input, input_sampler, final_uv, 0.0).rgb, 1.0);
|
|
737
|
+
@if(TRANSPARENT) return result(final_uv);
|
|
693
738
|
}`
|
|
694
739
|
};
|
|
695
740
|
export {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type Entity, type World } from '@aethervtt/shard-core';
|
|
2
|
+
/**
|
|
3
|
+
* Projects a world point to CSS pixels on `camera`'s target (origin top-left). Returns false when
|
|
4
|
+
* the point is behind the camera (`out` is left unchanged), or the camera hasn't rendered yet.
|
|
5
|
+
*/
|
|
6
|
+
export declare function worldToScreen(world: World, camera: Entity, point: ArrayLike<number>, out: {
|
|
7
|
+
[index: number]: number;
|
|
8
|
+
}): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* The world ray under CSS pixel (x, y) of `camera`'s target: its origin on the near plane and its
|
|
11
|
+
* unit direction. Orthographic rays are parallel, perspective ones start at the near plane.
|
|
12
|
+
*/
|
|
13
|
+
export declare function screenToRay(world: World, camera: Entity, x: number, y: number, outOrigin: {
|
|
14
|
+
[index: number]: number;
|
|
15
|
+
}, outDir: {
|
|
16
|
+
[index: number]: number;
|
|
17
|
+
}): void;
|
|
18
|
+
/**
|
|
19
|
+
* Where the ray under CSS pixel (x, y) meets the horizontal plane `y = planeY` (a floor, a ground
|
|
20
|
+
* band). Returns false when it doesn't (parallel, or behind the camera).
|
|
21
|
+
*/
|
|
22
|
+
export declare function screenToPlane(world: World, camera: Entity, x: number, y: number, planeY: number, out: {
|
|
23
|
+
[index: number]: number;
|
|
24
|
+
}): boolean;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { mat4 } from "@aethervtt/shard-core";
|
|
2
|
+
import { Cameras } from "./view.js";
|
|
3
|
+
const scratchInv = mat4.create();
|
|
4
|
+
const scratchKey = mat4.create();
|
|
5
|
+
let invValid = false;
|
|
6
|
+
function inverse(viewProj) {
|
|
7
|
+
let same = invValid;
|
|
8
|
+
for (let i = 0; same && i < 16; i++) if (scratchKey[i] !== viewProj[i]) same = false;
|
|
9
|
+
if (!same) {
|
|
10
|
+
mat4.copy(scratchKey, viewProj);
|
|
11
|
+
invValid = mat4.invert(scratchInv, viewProj) !== null;
|
|
12
|
+
}
|
|
13
|
+
return scratchInv;
|
|
14
|
+
}
|
|
15
|
+
function worldToScreen(world, camera, point, out) {
|
|
16
|
+
const cam = world.tryResource(Cameras)?.get(camera);
|
|
17
|
+
if (!cam) return false;
|
|
18
|
+
const m = cam.viewProjNoJitter;
|
|
19
|
+
const x = point[0];
|
|
20
|
+
const y = point[1];
|
|
21
|
+
const z = point[2];
|
|
22
|
+
const cx = m[0] * x + m[4] * y + m[8] * z + m[12];
|
|
23
|
+
const cy = m[1] * x + m[5] * y + m[9] * z + m[13];
|
|
24
|
+
const cw = m[3] * x + m[7] * y + m[11] * z + m[15];
|
|
25
|
+
if (cw <= 1e-9) return false;
|
|
26
|
+
const width = cam.displayWidth / cam.pixelRatio;
|
|
27
|
+
const height = cam.displayHeight / cam.pixelRatio;
|
|
28
|
+
out[0] = (cx / cw * 0.5 + 0.5) * width;
|
|
29
|
+
out[1] = (0.5 - cy / cw * 0.5) * height;
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
function screenToRay(world, camera, x, y, outOrigin, outDir) {
|
|
33
|
+
const cam = world.tryResource(Cameras)?.get(camera);
|
|
34
|
+
if (!cam) {
|
|
35
|
+
outOrigin[0] = outOrigin[1] = outOrigin[2] = 0;
|
|
36
|
+
outDir[0] = outDir[1] = 0;
|
|
37
|
+
outDir[2] = -1;
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const inv = inverse(cam.viewProjNoJitter);
|
|
41
|
+
const nx = x / (cam.displayWidth / cam.pixelRatio) * 2 - 1;
|
|
42
|
+
const ny = 1 - y / (cam.displayHeight / cam.pixelRatio) * 2;
|
|
43
|
+
let px = 0;
|
|
44
|
+
let py = 0;
|
|
45
|
+
let pz = 0;
|
|
46
|
+
for (let k = 0; k < 2; k++) {
|
|
47
|
+
const d = k === 0 ? 1 : 0.5;
|
|
48
|
+
const wx = inv[0] * nx + inv[4] * ny + inv[8] * d + inv[12];
|
|
49
|
+
const wy = inv[1] * nx + inv[5] * ny + inv[9] * d + inv[13];
|
|
50
|
+
const wz = inv[2] * nx + inv[6] * ny + inv[10] * d + inv[14];
|
|
51
|
+
const ww = inv[3] * nx + inv[7] * ny + inv[11] * d + inv[15];
|
|
52
|
+
if (k === 0) {
|
|
53
|
+
px = wx / ww;
|
|
54
|
+
py = wy / ww;
|
|
55
|
+
pz = wz / ww;
|
|
56
|
+
} else {
|
|
57
|
+
const dx = wx / ww - px;
|
|
58
|
+
const dy = wy / ww - py;
|
|
59
|
+
const dz = wz / ww - pz;
|
|
60
|
+
const len = Math.sqrt(dx * dx + dy * dy + dz * dz) || 1;
|
|
61
|
+
outDir[0] = dx / len;
|
|
62
|
+
outDir[1] = dy / len;
|
|
63
|
+
outDir[2] = dz / len;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
outOrigin[0] = px;
|
|
67
|
+
outOrigin[1] = py;
|
|
68
|
+
outOrigin[2] = pz;
|
|
69
|
+
}
|
|
70
|
+
const rayOrigin = new Float64Array(3);
|
|
71
|
+
const rayDir = new Float64Array(3);
|
|
72
|
+
function screenToPlane(world, camera, x, y, planeY, out) {
|
|
73
|
+
screenToRay(world, camera, x, y, rayOrigin, rayDir);
|
|
74
|
+
const dy = rayDir[1];
|
|
75
|
+
if (Math.abs(dy) < 1e-9) return false;
|
|
76
|
+
const t = (planeY - rayOrigin[1]) / dy;
|
|
77
|
+
if (t < 0) return false;
|
|
78
|
+
out[0] = rayOrigin[0] + rayDir[0] * t;
|
|
79
|
+
out[1] = planeY;
|
|
80
|
+
out[2] = rayOrigin[2] + rayDir[2] * t;
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
export {
|
|
84
|
+
screenToPlane,
|
|
85
|
+
screenToRay,
|
|
86
|
+
worldToScreen
|
|
87
|
+
};
|
package/dist/readback.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** Readback helpers: bytes per texel and conversion of any readable format to floats. */
|
|
2
2
|
export declare const BYTES_PER_TEXEL: Partial<Record<GPUTextureFormat, number>>;
|
|
3
|
+
export declare function halfToFloat(h: number): number;
|
|
3
4
|
/** Unpacks a readback row of any supported format into 4 floats per pixel. */
|
|
4
5
|
export declare function toFloats(src: ArrayBuffer, format: GPUTextureFormat, count: number, out: Float32Array, o: number): void;
|
package/dist/readback.js
CHANGED
package/dist/render-scale.js
CHANGED
|
@@ -2,6 +2,7 @@ import { defineResource } from "@aethervtt/shard-core";
|
|
|
2
2
|
import { DisplayRate } from "@aethervtt/shard-runtime";
|
|
3
3
|
import { Views } from "./plugin.js";
|
|
4
4
|
const RenderScale = defineResource("render/RenderScale", {
|
|
5
|
+
hostWritable: true,
|
|
5
6
|
description: "Render resolution of window cameras (0051): the scene renders at scale \xD7 the window size and is upscaled, UI stays native. auto moves the scale between min and max to hold the frame budget: targetMs, or one display refresh (DisplayRate) when it is 0.",
|
|
6
7
|
init: () => ({
|
|
7
8
|
mode: "auto",
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { type Entity, type World } from '@aethervtt/shard-core';
|
|
2
|
+
/** Effects `ScreenEffects` holds at once. */
|
|
3
|
+
export declare const MAX_SCREEN_EFFECTS = 8;
|
|
4
|
+
/** Keys an effect's params may have. */
|
|
5
|
+
export declare const MAX_SCREEN_EFFECT_PARAMS = 8;
|
|
6
|
+
export type ScreenEffectParams = Record<string, number | string | boolean>;
|
|
7
|
+
/** An effect another app's view should draw at a screen point (0065). */
|
|
8
|
+
export interface ScreenEffect {
|
|
9
|
+
/** What to draw: `fire`, or a kind the consuming app registered. */
|
|
10
|
+
kind: string;
|
|
11
|
+
/** Center, in the target's CSS pixels. */
|
|
12
|
+
screen: [number, number];
|
|
13
|
+
/** In CSS pixels. */
|
|
14
|
+
radius: number;
|
|
15
|
+
/** Ms it has left: counts down each frame, and refreshing sets it again. At 0 it drops. */
|
|
16
|
+
ttlMs: number;
|
|
17
|
+
/** What published it (a die); one effect per (source, kind). */
|
|
18
|
+
source: Entity;
|
|
19
|
+
params: ScreenEffectParams;
|
|
20
|
+
}
|
|
21
|
+
export declare const ScreenEffects: import("@aethervtt/shard-core").ResourceDef<{
|
|
22
|
+
effects: ScreenEffect[];
|
|
23
|
+
}>;
|
|
24
|
+
/** What an app draws for one kind of screen effect. */
|
|
25
|
+
export interface ScreenEffectHandler {
|
|
26
|
+
/** An effect of this kind appeared (a new source): spawn what draws it. */
|
|
27
|
+
start(world: World, effect: ScreenEffect): Entity[] | undefined;
|
|
28
|
+
/** Each frame it stays live (refreshed), with the frame's seconds. */
|
|
29
|
+
update?(world: World, effect: ScreenEffect, entities: readonly Entity[], dt: number): void;
|
|
30
|
+
/** It expired or was cleared. Default: despawn what `start` returned. */
|
|
31
|
+
end?(world: World, effect: ScreenEffect, entities: readonly Entity[]): void;
|
|
32
|
+
}
|
|
33
|
+
interface LiveEffect {
|
|
34
|
+
source: Entity;
|
|
35
|
+
kind: string;
|
|
36
|
+
handler: ScreenEffectHandler;
|
|
37
|
+
entities: Entity[];
|
|
38
|
+
/** The effect as last seen, for `end`. */
|
|
39
|
+
last: ScreenEffect;
|
|
40
|
+
seen: boolean;
|
|
41
|
+
}
|
|
42
|
+
/** This app's handlers, the effects they're drawing, and the unhandled kinds already reported. */
|
|
43
|
+
export declare const ScreenEffectHandlers: import("@aethervtt/shard-core").ResourceDef<{
|
|
44
|
+
kinds: Map<string, ScreenEffectHandler>;
|
|
45
|
+
live: LiveEffect[];
|
|
46
|
+
reported: Set<string>;
|
|
47
|
+
}>;
|
|
48
|
+
/**
|
|
49
|
+
* Publishes or refreshes `source`'s effect of this kind. Returns false when `MAX_SCREEN_EFFECTS`
|
|
50
|
+
* others are live. Throws `render/invalid-screen-effect` for an effect without a kind, a negative
|
|
51
|
+
* radius, or more than 8 params.
|
|
52
|
+
*/
|
|
53
|
+
export declare function publishScreenEffect(world: World, effect: ScreenEffect): boolean;
|
|
54
|
+
/** Drops `source`'s effects, or every effect. Publishers call it on dismissal and teardown. */
|
|
55
|
+
export declare function clearScreenEffects(world: World, source?: Entity): void;
|
|
56
|
+
/**
|
|
57
|
+
* Copies `from`'s effects into `to` (another app on the device, 0052), for the host to call each
|
|
58
|
+
* frame, as `forwardLensFields`: copies, each app counting its own down; `offset` is where `to`'s
|
|
59
|
+
* target sits in `from`'s CSS pixels, subtracted from each center.
|
|
60
|
+
*/
|
|
61
|
+
export declare function forwardScreenEffects(from: World, to: World, offset?: readonly [number, number]): void;
|
|
62
|
+
/**
|
|
63
|
+
* Registers what this app draws for a kind of screen effect: `start` when one appears, `update`
|
|
64
|
+
* while it's refreshed, `end` when it expires (default: despawn what `start` returned).
|
|
65
|
+
*/
|
|
66
|
+
export declare function onScreenEffect(world: World, kind: string, handler: ScreenEffectHandler): void;
|
|
67
|
+
/** The frame demand live effects hold, so they expire on time and the app idles after (0052). */
|
|
68
|
+
export declare const SCREEN_EFFECTS_DEMAND = "render/screen-effects";
|
|
69
|
+
export declare const expireScreenEffects: import("@aethervtt/shard-core").SystemDef<undefined>;
|
|
70
|
+
/**
|
|
71
|
+
* Starts, updates and ends this app's handlers as effects appear, stay and go. An effect of a kind
|
|
72
|
+
* nobody registered is dropped and logged once per kind (`render/unhandled-screen-effect`).
|
|
73
|
+
* Allocates only when an effect starts.
|
|
74
|
+
*/
|
|
75
|
+
export declare const runScreenEffects: import("@aethervtt/shard-core").SystemDef<undefined>;
|
|
76
|
+
/** For render.describe: live effects, and which kinds this app draws. */
|
|
77
|
+
export declare function describeScreenEffects(world: World): {
|
|
78
|
+
effects: {
|
|
79
|
+
/** What to draw: `fire`, or a kind the consuming app registered. */
|
|
80
|
+
kind: string;
|
|
81
|
+
/** In CSS pixels. */
|
|
82
|
+
radius: number;
|
|
83
|
+
/** Ms it has left: counts down each frame, and refreshing sets it again. At 0 it drops. */
|
|
84
|
+
ttlMs: number;
|
|
85
|
+
/** What published it (a die); one effect per (source, kind). */
|
|
86
|
+
source: Entity;
|
|
87
|
+
params: ScreenEffectParams;
|
|
88
|
+
screen: number[];
|
|
89
|
+
}[];
|
|
90
|
+
kinds: string[];
|
|
91
|
+
drawing: {
|
|
92
|
+
kind: string;
|
|
93
|
+
source: number;
|
|
94
|
+
entities: number;
|
|
95
|
+
}[];
|
|
96
|
+
};
|
|
97
|
+
export {};
|